feat(module): Implement callback subscription mechanism for module execution phases
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
package de.extio.game_engine.module;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -30,6 +32,9 @@ public abstract class AbstractModule {
|
||||
|
||||
}
|
||||
|
||||
public List<ModuleExecutorCallbacks> executorCallbackSubscriptions() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package de.extio.game_engine.module;
|
||||
|
||||
public enum ModuleExecutorCallbacks {
|
||||
|
||||
UI_PRE,
|
||||
|
||||
RUN,
|
||||
|
||||
UI_POST;
|
||||
|
||||
}
|
||||
@@ -20,18 +20,15 @@ public class ModuleExecutorImpl implements ModuleExecutor {
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
|
||||
// TODO Subscriptions for modules to run only on certain conditions
|
||||
|
||||
this.runTasks(module -> () -> {
|
||||
this.runTasks(ModuleExecutorCallbacks.UI_PRE, module -> () -> {
|
||||
if (module instanceof final AbstractClientModule clientModule && this.moduleManager.getModulesDisplayedClientModules().contains(clientModule)) {
|
||||
this.invokeSafe(clientModule, AbstractClientModule::runUiPre);
|
||||
}
|
||||
});
|
||||
this.runTasks(module -> () -> {
|
||||
this.runTasks(ModuleExecutorCallbacks.RUN, module -> () -> {
|
||||
this.invokeSafe(module, AbstractModule::run);
|
||||
});
|
||||
this.runTasks(module -> () -> {
|
||||
this.runTasks(ModuleExecutorCallbacks.UI_POST, module -> () -> {
|
||||
if (module instanceof final AbstractClientModule clientModule && this.moduleManager.getModulesDisplayedClientModules().contains(clientModule)) {
|
||||
this.invokeSafe(clientModule, AbstractClientModule::runUiPost);
|
||||
}
|
||||
@@ -47,12 +44,19 @@ public class ModuleExecutorImpl implements ModuleExecutor {
|
||||
}
|
||||
}
|
||||
|
||||
private void runTasks(final Function<AbstractModule, Runnable> taskSupplier) {
|
||||
private void runTasks(final ModuleExecutorCallbacks callbackType, final Function<AbstractModule, Runnable> taskSupplier) {
|
||||
final var subscribers = this.moduleManager.getSubscribersForCallback(callbackType);
|
||||
if (subscribers.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try (var scope = StructuredTaskScope.open(Joiner.awaitAll())) {
|
||||
for (final var module : this.moduleManager.getModulesActive()) {
|
||||
final var task = taskSupplier.apply(module);
|
||||
if (task != null) {
|
||||
scope.fork(task);
|
||||
synchronized (subscribers) {
|
||||
for (final var module : subscribers) {
|
||||
final var task = taskSupplier.apply(module);
|
||||
if (task != null) {
|
||||
scope.fork(task);
|
||||
}
|
||||
}
|
||||
}
|
||||
scope.join();
|
||||
|
||||
@@ -38,4 +38,6 @@ public interface ModuleManager {
|
||||
|
||||
boolean isModal();
|
||||
|
||||
List<AbstractModule> getSubscribersForCallback(ModuleExecutorCallbacks callback);
|
||||
|
||||
}
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
package de.extio.game_engine.module;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Deque;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Stack;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
@@ -18,7 +22,7 @@ public class ModuleManagerImpl implements InitializingBean, ModuleManager {
|
||||
|
||||
private final List<AbstractModule> modulesInitial;
|
||||
|
||||
private final List<AbstractModule> modulesAll = new ArrayList<>();
|
||||
private final List<AbstractModule> modulesAll = Collections.synchronizedList(new ArrayList<>());
|
||||
|
||||
private final RendererWorkingSet rendererWorkingSet;
|
||||
|
||||
@@ -30,11 +34,13 @@ public class ModuleManagerImpl implements InitializingBean, ModuleManager {
|
||||
|
||||
private List<AbstractClientModule> modulesDisplayedClientModulesView = List.of();
|
||||
|
||||
private final List<AbstractModule> modulesActive = new ArrayList<>();
|
||||
private final List<AbstractModule> modulesActive = Collections.synchronizedList(new ArrayList<>());
|
||||
|
||||
private final List<AbstractClientModule> modulesDisplayed = new ArrayList<>();
|
||||
private final List<AbstractClientModule> modulesDisplayed = Collections.synchronizedList(new ArrayList<>());
|
||||
|
||||
private final Deque<List<AbstractClientModule>> lastVisibleStates = new ArrayDeque<>();
|
||||
private final Stack<List<AbstractClientModule>> lastVisibleStates = new Stack<>();
|
||||
|
||||
private final Map<ModuleExecutorCallbacks, List<AbstractModule>> executorCallbackMap = Collections.synchronizedMap(new EnumMap<>(ModuleExecutorCallbacks.class));
|
||||
|
||||
public ModuleManagerImpl(final List<AbstractModule> initialModules, final RendererWorkingSet rendererWorkingSet) {
|
||||
this.modulesInitial = initialModules;
|
||||
@@ -46,10 +52,7 @@ public class ModuleManagerImpl implements InitializingBean, ModuleManager {
|
||||
if (this.modulesInitial == null || this.modulesInitial.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
this.modulesInitial.forEach(module -> this.invokeSafe(module, AbstractModule::onLoad));
|
||||
this.modulesAll.addAll(this.modulesInitial);
|
||||
this.sortModules(this.modulesAll);
|
||||
this.modulesAllView = List.copyOf(this.modulesAll);
|
||||
this.modulesInitial.forEach(this::load);
|
||||
this.modulesInitial.clear();
|
||||
}
|
||||
|
||||
@@ -74,30 +77,41 @@ public class ModuleManagerImpl implements InitializingBean, ModuleManager {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadModule(final Class<? extends AbstractModule> clazz) {
|
||||
if (this.modulesAll.stream().anyMatch(module -> clazz.isInstance(module))) {
|
||||
LOGGER.debug("AbstractModule already loaded: " + clazz.getName());
|
||||
return;
|
||||
public synchronized void loadModule(final Class<? extends AbstractModule> clazz) {
|
||||
synchronized (this.modulesAll) {
|
||||
if (this.modulesAll.stream().anyMatch(module -> clazz.isInstance(module))) {
|
||||
LOGGER.debug("AbstractModule already loaded: " + clazz.getName());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
LOGGER.debug("Loading module " + clazz.getName());
|
||||
|
||||
try {
|
||||
final AbstractModule module = AbstractModule.class.cast(clazz.getDeclaredConstructor().newInstance());
|
||||
this.invokeSafe(module, m -> m.onLoad());
|
||||
this.modulesAll.add(module);
|
||||
this.sortModules(this.modulesAll);
|
||||
this.modulesAllView = List.copyOf(this.modulesAll);
|
||||
this.load(module);
|
||||
}
|
||||
catch (final Exception e) {
|
||||
LOGGER.error("Error loading module " + clazz.getName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private void load(final AbstractModule module) {
|
||||
this.invokeSafe(module, m -> m.onLoad());
|
||||
this.modulesAll.add(module);
|
||||
this.sortModules(this.modulesAll);
|
||||
synchronized (this.modulesAll) {
|
||||
this.modulesAllView = List.copyOf(this.modulesAll);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unloadModule(final Class<? extends AbstractModule> clazz) {
|
||||
public synchronized void unloadModule(final Class<? extends AbstractModule> clazz) {
|
||||
try {
|
||||
final AbstractModule module = this.modulesAll.stream().filter(mod -> clazz.isInstance(mod)).findAny().orElse(null);
|
||||
AbstractModule module;
|
||||
synchronized (this.modulesAll) {
|
||||
module = this.modulesAll.stream().filter(mod -> clazz.isInstance(mod)).findAny().orElse(null);
|
||||
}
|
||||
|
||||
if (module == null) {
|
||||
LOGGER.debug("AbstractModule not loaded: " + clazz.getName());
|
||||
@@ -107,7 +121,9 @@ public class ModuleManagerImpl implements InitializingBean, ModuleManager {
|
||||
this.changeActiveState(clazz, false);
|
||||
this.modulesAll.remove(module);
|
||||
this.sortModules(this.modulesAll);
|
||||
this.modulesAllView = List.copyOf(this.modulesAll);
|
||||
synchronized (this.modulesAll) {
|
||||
this.modulesAllView = List.copyOf(this.modulesAll);
|
||||
}
|
||||
|
||||
this.invokeSafe(module, m -> m.onUnload());
|
||||
if (module instanceof final AbstractClientModule clientModule) {
|
||||
@@ -133,7 +149,7 @@ public class ModuleManagerImpl implements InitializingBean, ModuleManager {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changeActiveState(final Class<? extends AbstractModule> clazz, final boolean active) {
|
||||
public synchronized void changeActiveState(final Class<? extends AbstractModule> clazz, final boolean active) {
|
||||
this.modulesActiveView.stream()
|
||||
.filter(module -> clazz.isAssignableFrom(module.getClass()))
|
||||
.findAny()
|
||||
@@ -145,39 +161,59 @@ public class ModuleManagerImpl implements InitializingBean, ModuleManager {
|
||||
this.invokeSafe(module, AbstractModule::onDeactivate);
|
||||
this.modulesActive.remove(module);
|
||||
this.sortModules(this.modulesActive);
|
||||
this.modulesActiveView = List.copyOf(this.modulesActive);
|
||||
this.modulesActiveClientModulesView = this.modulesActive.stream()
|
||||
.filter(m -> m instanceof AbstractClientModule)
|
||||
.map(m -> (AbstractClientModule) m)
|
||||
.toList();
|
||||
this.modulesDisplayedClientModulesView = List.copyOf(this.modulesDisplayed);
|
||||
synchronized (this.modulesActive) {
|
||||
this.modulesActiveView = List.copyOf(this.modulesActive);
|
||||
this.modulesActiveClientModulesView = this.modulesActive.stream()
|
||||
.filter(m -> m instanceof AbstractClientModule)
|
||||
.map(m -> (AbstractClientModule) m)
|
||||
.toList();
|
||||
}
|
||||
if (module instanceof final AbstractClientModule clientModule) {
|
||||
this.rendererWorkingSet.clear(clientModule.getClass());
|
||||
}
|
||||
synchronized (this.executorCallbackMap) {
|
||||
final var subscriptions = module.executorCallbackSubscriptions();
|
||||
if (subscriptions != null) {
|
||||
for (final var subscription : subscriptions) {
|
||||
executorCallbackMap.computeIfAbsent(subscription, k -> Collections.synchronizedList(new ArrayList<>())).remove(module);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LOGGER.debug("Deactivated " + clazz.getName());
|
||||
}
|
||||
}, () -> {
|
||||
if (active) {
|
||||
this.modulesAll.stream()
|
||||
.filter(module -> clazz.isAssignableFrom(module.getClass()))
|
||||
.findFirst()
|
||||
.ifPresent(module -> {
|
||||
this.invokeSafe(module, AbstractModule::onActivate);
|
||||
if (module instanceof final AbstractClientModule clientModule && clientModule.isAlwaysDisplay()) {
|
||||
this.changeDisplayState(clientModule.getClass(), true);
|
||||
}
|
||||
this.modulesActive.add(module);
|
||||
this.sortModules(this.modulesActive);
|
||||
this.modulesActiveView = List.copyOf(this.modulesActive);
|
||||
this.modulesActiveClientModulesView = this.modulesActive.stream()
|
||||
.filter(m -> m instanceof AbstractClientModule)
|
||||
.map(m -> (AbstractClientModule) m)
|
||||
.toList();
|
||||
this.modulesDisplayedClientModulesView = List.copyOf(this.modulesDisplayed);
|
||||
synchronized (this.modulesAll) {
|
||||
this.modulesAll.stream()
|
||||
.filter(module -> clazz.isAssignableFrom(module.getClass()))
|
||||
.findFirst()
|
||||
.ifPresent(module -> {
|
||||
this.invokeSafe(module, AbstractModule::onActivate);
|
||||
if (module instanceof final AbstractClientModule clientModule && clientModule.isAlwaysDisplay()) {
|
||||
this.changeDisplayState(clientModule.getClass(), true);
|
||||
}
|
||||
this.modulesActive.add(module);
|
||||
this.sortModules(this.modulesActive);
|
||||
synchronized (this.modulesActive) {
|
||||
this.modulesActiveView = List.copyOf(this.modulesActive);
|
||||
this.modulesActiveClientModulesView = this.modulesActive.stream()
|
||||
.filter(m -> m instanceof AbstractClientModule)
|
||||
.map(m -> (AbstractClientModule) m)
|
||||
.toList();
|
||||
}
|
||||
synchronized (this.executorCallbackMap) {
|
||||
final var subscriptions = module.executorCallbackSubscriptions();
|
||||
if (subscriptions != null) {
|
||||
for (final var subscription : subscriptions) {
|
||||
executorCallbackMap.computeIfAbsent(subscription, k -> Collections.synchronizedList(new ArrayList<>())).add(module);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LOGGER.debug("Activated " + clazz.getName());
|
||||
});
|
||||
LOGGER.debug("Activated " + clazz.getName());
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -202,25 +238,29 @@ public class ModuleManagerImpl implements InitializingBean, ModuleManager {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changeDisplayState(final Class<? extends AbstractClientModule> clazz, final boolean display) {
|
||||
for (final var module : this.modulesAll) {
|
||||
if (module instanceof final AbstractClientModule clientModule && clazz.isAssignableFrom(module.getClass())) {
|
||||
final boolean curDisplayed = this.modulesDisplayed.stream().anyMatch(mod -> clazz.isAssignableFrom(mod.getClass()));
|
||||
public synchronized void changeDisplayState(final Class<? extends AbstractClientModule> clazz, final boolean display) {
|
||||
synchronized (this.modulesAll) {
|
||||
synchronized (this.modulesDisplayed) {
|
||||
for (final var module : this.modulesAll) {
|
||||
if (module instanceof final AbstractClientModule clientModule && clazz.isAssignableFrom(module.getClass())) {
|
||||
final boolean curDisplayed = this.modulesDisplayed.stream().anyMatch(mod -> clazz.isAssignableFrom(mod.getClass()));
|
||||
|
||||
final boolean doDisplay = display || clientModule.isAlwaysDisplay();
|
||||
if (doDisplay && !curDisplayed) {
|
||||
this.modulesDisplayed.add(clientModule);
|
||||
clientModule.setDisplayed(true);
|
||||
this.invokeSafe(clientModule, m -> ((AbstractClientModule) m).onShow());
|
||||
}
|
||||
else if (!doDisplay && curDisplayed) {
|
||||
clientModule.setDisplayed(false);
|
||||
this.modulesDisplayed.removeIf(mod -> clazz.isAssignableFrom(mod.getClass()));
|
||||
this.invokeSafe(clientModule, m -> ((AbstractClientModule) m).onHide());
|
||||
}
|
||||
this.modulesDisplayedClientModulesView = List.copyOf(this.modulesDisplayed);
|
||||
final boolean doDisplay = display || clientModule.isAlwaysDisplay();
|
||||
if (doDisplay && !curDisplayed) {
|
||||
this.modulesDisplayed.add(clientModule);
|
||||
clientModule.setDisplayed(true);
|
||||
this.invokeSafe(clientModule, m -> ((AbstractClientModule) m).onShow());
|
||||
}
|
||||
else if (!doDisplay && curDisplayed) {
|
||||
clientModule.setDisplayed(false);
|
||||
this.modulesDisplayed.removeIf(mod -> clazz.isAssignableFrom(mod.getClass()));
|
||||
this.invokeSafe(clientModule, m -> ((AbstractClientModule) m).onHide());
|
||||
}
|
||||
this.modulesDisplayedClientModulesView = List.copyOf(this.modulesDisplayed);
|
||||
|
||||
break;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -239,9 +279,9 @@ public class ModuleManagerImpl implements InitializingBean, ModuleManager {
|
||||
|
||||
@Override
|
||||
public boolean isDisplayed(final Class<? extends AbstractClientModule> clazz) {
|
||||
final var modules = this.modulesDisplayed;
|
||||
final var modules = this.modulesDisplayedClientModulesView;
|
||||
for (int i = 0; i < modules.size(); i++) {
|
||||
if (clazz.isAssignableFrom(this.modulesDisplayed.get(i).getClass())) {
|
||||
if (clazz.isAssignableFrom(modules.get(i).getClass())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -252,7 +292,8 @@ public class ModuleManagerImpl implements InitializingBean, ModuleManager {
|
||||
public void hideAll() {
|
||||
this.lastVisibleStates.clear();
|
||||
|
||||
for (final var module : List.copyOf(this.modulesDisplayed)) {
|
||||
final var modules = this.modulesDisplayedClientModulesView;
|
||||
for (final var module : modules) {
|
||||
if ((module instanceof final AbstractClientModule clientModule) && !clientModule.isAlwaysDisplay()) {
|
||||
this.changeDisplayState(clientModule.getClass(), false);
|
||||
}
|
||||
@@ -263,9 +304,10 @@ public class ModuleManagerImpl implements InitializingBean, ModuleManager {
|
||||
@SafeVarargs
|
||||
public final void hideExcept(final String... classNames) {
|
||||
final List<AbstractClientModule> state = new ArrayList<>();
|
||||
final var keep = new java.util.HashSet<>(java.util.Arrays.asList(classNames));
|
||||
final var keep = new HashSet<>(Arrays.asList(classNames));
|
||||
|
||||
for (final var module : List.copyOf(this.modulesDisplayed)) {
|
||||
final var modules = this.modulesDisplayedClientModulesView;
|
||||
for (final var module : modules) {
|
||||
if (module instanceof final AbstractClientModule clientModule &&
|
||||
!clientModule.isAlwaysDisplay() &&
|
||||
!keep.contains(clientModule.getClass().getName())) {
|
||||
@@ -285,8 +327,10 @@ public class ModuleManagerImpl implements InitializingBean, ModuleManager {
|
||||
return;
|
||||
}
|
||||
|
||||
for (final AbstractClientModule module : this.lastVisibleStates.pop()) {
|
||||
this.changeDisplayState(module.getClass(), true);
|
||||
synchronized (this.lastVisibleStates) {
|
||||
for (final AbstractClientModule module : this.lastVisibleStates.pop()) {
|
||||
this.changeDisplayState(module.getClass(), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -295,6 +339,11 @@ public class ModuleManagerImpl implements InitializingBean, ModuleManager {
|
||||
return !this.lastVisibleStates.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AbstractModule> getSubscribersForCallback(final ModuleExecutorCallbacks callback) {
|
||||
return this.executorCallbackMap.getOrDefault(callback, List.of());
|
||||
}
|
||||
|
||||
private void invokeSafe(final AbstractModule module, final Consumer<AbstractModule> consumer) {
|
||||
try {
|
||||
consumer.accept(module);
|
||||
|
||||
@@ -24,9 +24,11 @@ public class ModuleExecutorImplTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecute_CallsAllPhasesForActiveAndDisplayedClientModule() {
|
||||
public void testExecute_CallsAllPhasesForDisplayedClientModuleSubscribedToAllPhases() {
|
||||
final AbstractClientModule clientModule = mock(AbstractClientModule.class);
|
||||
when(this.moduleManager.getModulesActive()).thenReturn(List.of(clientModule));
|
||||
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_PRE)).thenReturn(List.of(clientModule));
|
||||
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.RUN)).thenReturn(List.of(clientModule));
|
||||
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_POST)).thenReturn(List.of(clientModule));
|
||||
when(this.moduleManager.getModulesDisplayedClientModules()).thenReturn(List.of(clientModule));
|
||||
|
||||
this.moduleExecutor.execute();
|
||||
@@ -37,20 +39,24 @@ public class ModuleExecutorImplTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecute_CallsOnlyRunForActiveNonClientModule() {
|
||||
public void testExecute_CallsOnlyRunForNonClientModule() {
|
||||
final AbstractModule module = mock(AbstractModule.class);
|
||||
when(this.moduleManager.getModulesActive()).thenReturn(List.of(module));
|
||||
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_PRE)).thenReturn(List.of());
|
||||
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.RUN)).thenReturn(List.of(module));
|
||||
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_POST)).thenReturn(List.of());
|
||||
|
||||
this.moduleExecutor.execute();
|
||||
|
||||
verify(module).run();
|
||||
verify(module, never()).onActivate(); // Just checking it doesn't call other things
|
||||
verify(module, never()).onActivate();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecute_CallsOnlyRunForActiveButNotDisplayedClientModule() {
|
||||
public void testExecute_SkipsUiPhaseForNotDisplayedClientModule() {
|
||||
final AbstractClientModule clientModule = mock(AbstractClientModule.class);
|
||||
when(this.moduleManager.getModulesActive()).thenReturn(List.of(clientModule));
|
||||
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_PRE)).thenReturn(List.of(clientModule));
|
||||
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.RUN)).thenReturn(List.of(clientModule));
|
||||
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_POST)).thenReturn(List.of(clientModule));
|
||||
when(this.moduleManager.getModulesDisplayedClientModules()).thenReturn(List.of());
|
||||
|
||||
this.moduleExecutor.execute();
|
||||
@@ -61,12 +67,14 @@ public class ModuleExecutorImplTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecute_HandlesExceptionInModule() {
|
||||
public void testExecute_HandlesExceptionInRunPhase() {
|
||||
final AbstractModule module1 = mock(AbstractModule.class);
|
||||
final AbstractModule module2 = mock(AbstractModule.class);
|
||||
|
||||
doThrow(new RuntimeException("Test Exception")).when(module1).run();
|
||||
when(this.moduleManager.getModulesActive()).thenReturn(List.of(module1, module2));
|
||||
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_PRE)).thenReturn(List.of());
|
||||
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.RUN)).thenReturn(List.of(module1, module2));
|
||||
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_POST)).thenReturn(List.of());
|
||||
|
||||
this.moduleExecutor.execute();
|
||||
|
||||
@@ -79,7 +87,9 @@ public class ModuleExecutorImplTest {
|
||||
final AbstractClientModule clientModule = mock(AbstractClientModule.class);
|
||||
doThrow(new RuntimeException("UI Pre Exception")).when(clientModule).runUiPre();
|
||||
|
||||
when(this.moduleManager.getModulesActive()).thenReturn(List.of(clientModule));
|
||||
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_PRE)).thenReturn(List.of(clientModule));
|
||||
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.RUN)).thenReturn(List.of(clientModule));
|
||||
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_POST)).thenReturn(List.of(clientModule));
|
||||
when(this.moduleManager.getModulesDisplayedClientModules()).thenReturn(List.of(clientModule));
|
||||
|
||||
this.moduleExecutor.execute();
|
||||
@@ -90,36 +100,226 @@ public class ModuleExecutorImplTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecute_EmptyModules() {
|
||||
when(this.moduleManager.getModulesActive()).thenReturn(List.of());
|
||||
public void testExecute_NoSubscribersForCallback() {
|
||||
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_PRE)).thenReturn(List.of());
|
||||
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.RUN)).thenReturn(List.of());
|
||||
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_POST)).thenReturn(List.of());
|
||||
|
||||
this.moduleExecutor.execute();
|
||||
|
||||
verify(this.moduleManager, times(3)).getModulesActive();
|
||||
verify(this.moduleManager).getSubscribersForCallback(ModuleExecutorCallbacks.UI_PRE);
|
||||
verify(this.moduleManager).getSubscribersForCallback(ModuleExecutorCallbacks.RUN);
|
||||
verify(this.moduleManager).getSubscribersForCallback(ModuleExecutorCallbacks.UI_POST);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecute_MultipleModules() {
|
||||
public void testExecute_MultipleModulesWithMixedSubscriptions() {
|
||||
final AbstractClientModule clientModule1 = mock(AbstractClientModule.class);
|
||||
final AbstractClientModule clientModule2 = mock(AbstractClientModule.class);
|
||||
final AbstractModule module1 = mock(AbstractModule.class);
|
||||
|
||||
when(this.moduleManager.getModulesActive()).thenReturn(List.of(clientModule1, clientModule2, module1));
|
||||
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_PRE)).thenReturn(List.of(clientModule1, clientModule2));
|
||||
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.RUN)).thenReturn(List.of(clientModule1, clientModule2, module1));
|
||||
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_POST)).thenReturn(List.of(clientModule1));
|
||||
when(this.moduleManager.getModulesDisplayedClientModules()).thenReturn(List.of(clientModule1));
|
||||
|
||||
this.moduleExecutor.execute();
|
||||
|
||||
// clientModule1: displayed, so all phases
|
||||
verify(clientModule1).runUiPre();
|
||||
verify(clientModule1).run();
|
||||
verify(clientModule1).runUiPost();
|
||||
|
||||
// clientModule2: active but not displayed, so only run
|
||||
verify(clientModule2, never()).runUiPre();
|
||||
verify(clientModule2).run();
|
||||
verify(clientModule2, never()).runUiPost();
|
||||
|
||||
// module1: only run
|
||||
verify(module1).run();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecute_ModuleSubscribedOnlyToUiPre() {
|
||||
final AbstractClientModule clientModule = mock(AbstractClientModule.class);
|
||||
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_PRE)).thenReturn(List.of(clientModule));
|
||||
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.RUN)).thenReturn(List.of());
|
||||
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_POST)).thenReturn(List.of());
|
||||
when(this.moduleManager.getModulesDisplayedClientModules()).thenReturn(List.of(clientModule));
|
||||
|
||||
this.moduleExecutor.execute();
|
||||
|
||||
verify(clientModule).runUiPre();
|
||||
verify(clientModule, never()).run();
|
||||
verify(clientModule, never()).runUiPost();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecute_ModuleSubscribedOnlyToRun() {
|
||||
final AbstractModule module = mock(AbstractModule.class);
|
||||
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_PRE)).thenReturn(List.of());
|
||||
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.RUN)).thenReturn(List.of(module));
|
||||
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_POST)).thenReturn(List.of());
|
||||
|
||||
this.moduleExecutor.execute();
|
||||
|
||||
verify(module).run();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecute_ClientModuleSubscribedOnlyToUiPost() {
|
||||
final AbstractClientModule clientModule = mock(AbstractClientModule.class);
|
||||
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_PRE)).thenReturn(List.of());
|
||||
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.RUN)).thenReturn(List.of());
|
||||
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_POST)).thenReturn(List.of(clientModule));
|
||||
when(this.moduleManager.getModulesDisplayedClientModules()).thenReturn(List.of(clientModule));
|
||||
|
||||
this.moduleExecutor.execute();
|
||||
|
||||
verify(clientModule, never()).runUiPre();
|
||||
verify(clientModule, never()).run();
|
||||
verify(clientModule).runUiPost();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecute_ClientModuleSubscribedToUiPreAndRun_NotUiPost() {
|
||||
final AbstractClientModule clientModule = mock(AbstractClientModule.class);
|
||||
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_PRE)).thenReturn(List.of(clientModule));
|
||||
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.RUN)).thenReturn(List.of(clientModule));
|
||||
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_POST)).thenReturn(List.of());
|
||||
when(this.moduleManager.getModulesDisplayedClientModules()).thenReturn(List.of(clientModule));
|
||||
|
||||
this.moduleExecutor.execute();
|
||||
|
||||
verify(clientModule).runUiPre();
|
||||
verify(clientModule).run();
|
||||
verify(clientModule, never()).runUiPost();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecute_ClientModuleSubscribedToUiPreAndUiPost_NotRun() {
|
||||
final AbstractClientModule clientModule = mock(AbstractClientModule.class);
|
||||
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_PRE)).thenReturn(List.of(clientModule));
|
||||
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.RUN)).thenReturn(List.of());
|
||||
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_POST)).thenReturn(List.of(clientModule));
|
||||
when(this.moduleManager.getModulesDisplayedClientModules()).thenReturn(List.of(clientModule));
|
||||
|
||||
this.moduleExecutor.execute();
|
||||
|
||||
verify(clientModule).runUiPre();
|
||||
verify(clientModule, never()).run();
|
||||
verify(clientModule).runUiPost();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecute_ClientModuleSubscribedToRunAndUiPost_NotUiPre() {
|
||||
final AbstractClientModule clientModule = mock(AbstractClientModule.class);
|
||||
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_PRE)).thenReturn(List.of());
|
||||
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.RUN)).thenReturn(List.of(clientModule));
|
||||
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_POST)).thenReturn(List.of(clientModule));
|
||||
when(this.moduleManager.getModulesDisplayedClientModules()).thenReturn(List.of(clientModule));
|
||||
|
||||
this.moduleExecutor.execute();
|
||||
|
||||
verify(clientModule, never()).runUiPre();
|
||||
verify(clientModule).run();
|
||||
verify(clientModule).runUiPost();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecute_HandleExceptionInUiPost() {
|
||||
final AbstractClientModule clientModule = mock(AbstractClientModule.class);
|
||||
doThrow(new RuntimeException("UI Post Exception")).when(clientModule).runUiPost();
|
||||
|
||||
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_PRE)).thenReturn(List.of(clientModule));
|
||||
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.RUN)).thenReturn(List.of(clientModule));
|
||||
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_POST)).thenReturn(List.of(clientModule));
|
||||
when(this.moduleManager.getModulesDisplayedClientModules()).thenReturn(List.of(clientModule));
|
||||
|
||||
this.moduleExecutor.execute();
|
||||
|
||||
verify(clientModule).runUiPre();
|
||||
verify(clientModule).run();
|
||||
verify(clientModule).runUiPost();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecute_HandleExceptionInRun() {
|
||||
final AbstractClientModule clientModule = mock(AbstractClientModule.class);
|
||||
doThrow(new RuntimeException("Run Exception")).when(clientModule).run();
|
||||
|
||||
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_PRE)).thenReturn(List.of(clientModule));
|
||||
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.RUN)).thenReturn(List.of(clientModule));
|
||||
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_POST)).thenReturn(List.of(clientModule));
|
||||
when(this.moduleManager.getModulesDisplayedClientModules()).thenReturn(List.of(clientModule));
|
||||
|
||||
this.moduleExecutor.execute();
|
||||
|
||||
verify(clientModule).runUiPre();
|
||||
verify(clientModule).run();
|
||||
verify(clientModule).runUiPost();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecute_MultipleExceptionsInDifferentPhases() {
|
||||
final AbstractModule module1 = mock(AbstractModule.class);
|
||||
final AbstractModule module2 = mock(AbstractModule.class);
|
||||
final AbstractClientModule clientModule = mock(AbstractClientModule.class);
|
||||
|
||||
doThrow(new RuntimeException("Exception in module1")).when(module1).run();
|
||||
doThrow(new RuntimeException("Exception in clientModule")).when(clientModule).runUiPre();
|
||||
|
||||
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_PRE)).thenReturn(List.of(clientModule));
|
||||
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.RUN)).thenReturn(List.of(module1, module2));
|
||||
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_POST)).thenReturn(List.of());
|
||||
when(this.moduleManager.getModulesDisplayedClientModules()).thenReturn(List.of(clientModule));
|
||||
|
||||
this.moduleExecutor.execute();
|
||||
|
||||
verify(clientModule).runUiPre();
|
||||
verify(module1).run();
|
||||
verify(module2).run();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecute_ClientModuleDisplayedButNotSubscribedToUiPhases() {
|
||||
final AbstractClientModule clientModule = mock(AbstractClientModule.class);
|
||||
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_PRE)).thenReturn(List.of());
|
||||
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.RUN)).thenReturn(List.of(clientModule));
|
||||
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_POST)).thenReturn(List.of());
|
||||
|
||||
this.moduleExecutor.execute();
|
||||
|
||||
verify(clientModule, never()).runUiPre();
|
||||
verify(clientModule).run();
|
||||
verify(clientModule, never()).runUiPost();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecute_ClientModuleNotDisplayedButSubscribedToRunOnly() {
|
||||
final AbstractClientModule clientModule = mock(AbstractClientModule.class);
|
||||
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_PRE)).thenReturn(List.of());
|
||||
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.RUN)).thenReturn(List.of(clientModule));
|
||||
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_POST)).thenReturn(List.of());
|
||||
|
||||
this.moduleExecutor.execute();
|
||||
|
||||
verify(clientModule, never()).runUiPre();
|
||||
verify(clientModule).run();
|
||||
verify(clientModule, never()).runUiPost();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecute_MultipleNonClientModulesSubscribedToDifferentCallbacks() {
|
||||
final AbstractModule module1 = mock(AbstractModule.class);
|
||||
final AbstractModule module2 = mock(AbstractModule.class);
|
||||
final AbstractModule module3 = mock(AbstractModule.class);
|
||||
|
||||
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_PRE)).thenReturn(List.of(module1));
|
||||
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.RUN)).thenReturn(List.of(module1, module2, module3));
|
||||
when(this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_POST)).thenReturn(List.of(module3));
|
||||
|
||||
this.moduleExecutor.execute();
|
||||
|
||||
verify(module1).run();
|
||||
verify(module2).run();
|
||||
verify(module3).run();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -408,6 +408,95 @@ public class ModuleManagerTest {
|
||||
assertFalse(this.moduleManager.isModal());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetSubscribersForCallback_NoSubscribers() {
|
||||
final List<AbstractModule> subscribers = this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_PRE);
|
||||
|
||||
assertTrue(subscribers.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetSubscribersForCallback_WithSubscriber() {
|
||||
this.moduleManager.loadModule(CallbackSubscriberModule.class);
|
||||
this.moduleManager.changeActiveState(CallbackSubscriberModule.class, true);
|
||||
|
||||
final List<AbstractModule> subscribers = this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_PRE);
|
||||
|
||||
assertEquals(1, subscribers.size());
|
||||
assertTrue(subscribers.stream().anyMatch(m -> m instanceof CallbackSubscriberModule));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetSubscribersForCallback_MultipleCallbacks() {
|
||||
this.moduleManager.loadModule(MultiCallbackSubscriberModule.class);
|
||||
this.moduleManager.changeActiveState(MultiCallbackSubscriberModule.class, true);
|
||||
|
||||
final List<AbstractModule> uiPreSubscribers = this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_PRE);
|
||||
final List<AbstractModule> runSubscribers = this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.RUN);
|
||||
final List<AbstractModule> uiPostSubscribers = this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_POST);
|
||||
|
||||
assertEquals(1, uiPreSubscribers.size());
|
||||
assertEquals(1, runSubscribers.size());
|
||||
assertEquals(1, uiPostSubscribers.size());
|
||||
|
||||
assertTrue(uiPreSubscribers.get(0) instanceof MultiCallbackSubscriberModule);
|
||||
assertTrue(runSubscribers.get(0) instanceof MultiCallbackSubscriberModule);
|
||||
assertTrue(uiPostSubscribers.get(0) instanceof MultiCallbackSubscriberModule);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetSubscribersForCallback_UnsubscribedCallbackEmpty() {
|
||||
this.moduleManager.loadModule(CallbackSubscriberModule.class);
|
||||
this.moduleManager.changeActiveState(CallbackSubscriberModule.class, true);
|
||||
|
||||
final List<AbstractModule> runSubscribers = this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.RUN);
|
||||
|
||||
assertTrue(runSubscribers.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetSubscribersForCallback_RemovedOnDeactivate() {
|
||||
this.moduleManager.loadModule(CallbackSubscriberModule.class);
|
||||
this.moduleManager.changeActiveState(CallbackSubscriberModule.class, true);
|
||||
|
||||
List<AbstractModule> subscribers = this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_PRE);
|
||||
assertEquals(1, subscribers.size());
|
||||
|
||||
this.moduleManager.changeActiveState(CallbackSubscriberModule.class, false);
|
||||
|
||||
subscribers = this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_PRE);
|
||||
assertTrue(subscribers.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetSubscribersForCallback_MultipleModules() {
|
||||
this.moduleManager.loadModule(CallbackSubscriberModule.class);
|
||||
this.moduleManager.loadModule(MultiCallbackSubscriberModule.class);
|
||||
this.moduleManager.changeActiveState(CallbackSubscriberModule.class, true);
|
||||
this.moduleManager.changeActiveState(MultiCallbackSubscriberModule.class, true);
|
||||
|
||||
final List<AbstractModule> uiPreSubscribers = this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_PRE);
|
||||
|
||||
assertEquals(2, uiPreSubscribers.size());
|
||||
assertTrue(uiPreSubscribers.stream().anyMatch(m -> m instanceof CallbackSubscriberModule));
|
||||
assertTrue(uiPreSubscribers.stream().anyMatch(m -> m instanceof MultiCallbackSubscriberModule));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetSubscribersForCallback_ReactivateModule() {
|
||||
this.moduleManager.loadModule(CallbackSubscriberModule.class);
|
||||
this.moduleManager.changeActiveState(CallbackSubscriberModule.class, true);
|
||||
this.moduleManager.changeActiveState(CallbackSubscriberModule.class, false);
|
||||
|
||||
List<AbstractModule> subscribers = this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_PRE);
|
||||
assertTrue(subscribers.isEmpty());
|
||||
|
||||
this.moduleManager.changeActiveState(CallbackSubscriberModule.class, true);
|
||||
|
||||
subscribers = this.moduleManager.getSubscribersForCallback(ModuleExecutorCallbacks.UI_PRE);
|
||||
assertEquals(1, subscribers.size());
|
||||
}
|
||||
|
||||
static class TestModule extends AbstractModule {
|
||||
|
||||
boolean onLoadCalled = false;
|
||||
@@ -504,4 +593,26 @@ public class ModuleManagerTest {
|
||||
|
||||
}
|
||||
|
||||
static class CallbackSubscriberModule extends AbstractModule {
|
||||
|
||||
@Override
|
||||
public java.util.List<ModuleExecutorCallbacks> executorCallbackSubscriptions() {
|
||||
return java.util.List.of(ModuleExecutorCallbacks.UI_PRE);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class MultiCallbackSubscriberModule extends AbstractModule {
|
||||
|
||||
@Override
|
||||
public java.util.List<ModuleExecutorCallbacks> executorCallbackSubscriptions() {
|
||||
return java.util.List.of(
|
||||
ModuleExecutorCallbacks.UI_PRE,
|
||||
ModuleExecutorCallbacks.RUN,
|
||||
ModuleExecutorCallbacks.UI_POST
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user