feat(module): Introduce ModuleExecutor and ModuleExecutorImpl for task execution management
This commit is contained in:
@@ -31,11 +31,11 @@ public abstract class AbstractClientModule extends AbstractModule {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void runUiPreEvents() {
|
public void runUiPre() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void runUiPostEvents() {
|
public void runUiPost() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package de.extio.game_engine.module;
|
||||||
|
|
||||||
|
public interface ModuleExecutor {
|
||||||
|
|
||||||
|
void execute();
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
package de.extio.game_engine.module;
|
||||||
|
|
||||||
|
import java.util.concurrent.StructuredTaskScope;
|
||||||
|
import java.util.concurrent.StructuredTaskScope.Joiner;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
public class ModuleExecutorImpl implements ModuleExecutor {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(ModuleExecutorImpl.class);
|
||||||
|
|
||||||
|
private final ModuleManager moduleManager;
|
||||||
|
|
||||||
|
public ModuleExecutorImpl(final ModuleManager moduleManager) {
|
||||||
|
this.moduleManager = moduleManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute() {
|
||||||
|
this.runTasks(module -> () -> {
|
||||||
|
if (module instanceof final AbstractClientModule clientModule && this.moduleManager.getModulesDisplayedClientModules().contains(clientModule)) {
|
||||||
|
this.invokeSafe(clientModule, AbstractClientModule::runUiPre);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.runTasks(module -> () -> {
|
||||||
|
this.invokeSafe(module, AbstractModule::run);
|
||||||
|
});
|
||||||
|
this.runTasks(module -> () -> {
|
||||||
|
if (module instanceof final AbstractClientModule clientModule && this.moduleManager.getModulesDisplayedClientModules().contains(clientModule)) {
|
||||||
|
this.invokeSafe(clientModule, AbstractClientModule::runUiPost);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T extends AbstractModule> void invokeSafe(final T module, final Consumer<T> consumer) {
|
||||||
|
try {
|
||||||
|
consumer.accept(module);
|
||||||
|
}
|
||||||
|
catch (final Exception exc) {
|
||||||
|
LOGGER.error("Error in module " + module.getClass().getName(), exc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void runTasks(final Function<AbstractModule, Runnable> taskSupplier) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
scope.join();
|
||||||
|
}
|
||||||
|
catch (final InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -17,4 +17,10 @@ public class ModuleManagerAutoConfiguration {
|
|||||||
return new ModuleManagerImpl(initialModules);
|
return new ModuleManagerImpl(initialModules);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnMissingBean
|
||||||
|
ModuleExecutor moduleExecutor(final ModuleManager moduleManager) {
|
||||||
|
return new ModuleExecutorImpl(moduleManager);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,13 +6,13 @@ import java.util.Deque;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.slf4j.Logger;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.InitializingBean;
|
import org.springframework.beans.factory.InitializingBean;
|
||||||
|
|
||||||
public class ModuleManagerImpl implements InitializingBean, ModuleManager {
|
public class ModuleManagerImpl implements InitializingBean, ModuleManager {
|
||||||
|
|
||||||
private static final Logger LOGGER = LogManager.getLogger();
|
private static final Logger LOGGER = LoggerFactory.getLogger(ModuleManagerImpl.class);
|
||||||
|
|
||||||
private final List<AbstractModule> modulesInitial;
|
private final List<AbstractModule> modulesInitial;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,125 @@
|
|||||||
|
package de.extio.game_engine.module;
|
||||||
|
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
public class ModuleExecutorImplTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private ModuleManager moduleManager;
|
||||||
|
|
||||||
|
private ModuleExecutorImpl moduleExecutor;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void setUp() {
|
||||||
|
this.moduleExecutor = new ModuleExecutorImpl(this.moduleManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testExecute_CallsAllPhasesForActiveAndDisplayedClientModule() {
|
||||||
|
final AbstractClientModule clientModule = mock(AbstractClientModule.class);
|
||||||
|
when(this.moduleManager.getModulesActive()).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_CallsOnlyRunForActiveNonClientModule() {
|
||||||
|
final AbstractModule module = mock(AbstractModule.class);
|
||||||
|
when(this.moduleManager.getModulesActive()).thenReturn(List.of(module));
|
||||||
|
|
||||||
|
this.moduleExecutor.execute();
|
||||||
|
|
||||||
|
verify(module).run();
|
||||||
|
verify(module, never()).onActivate(); // Just checking it doesn't call other things
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testExecute_CallsOnlyRunForActiveButNotDisplayedClientModule() {
|
||||||
|
final AbstractClientModule clientModule = mock(AbstractClientModule.class);
|
||||||
|
when(this.moduleManager.getModulesActive()).thenReturn(List.of(clientModule));
|
||||||
|
when(this.moduleManager.getModulesDisplayedClientModules()).thenReturn(List.of());
|
||||||
|
|
||||||
|
this.moduleExecutor.execute();
|
||||||
|
|
||||||
|
verify(clientModule, never()).runUiPre();
|
||||||
|
verify(clientModule).run();
|
||||||
|
verify(clientModule, never()).runUiPost();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testExecute_HandlesExceptionInModule() {
|
||||||
|
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));
|
||||||
|
|
||||||
|
this.moduleExecutor.execute();
|
||||||
|
|
||||||
|
verify(module1).run();
|
||||||
|
verify(module2).run();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testExecute_HandlesExceptionInUiPre() {
|
||||||
|
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.getModulesDisplayedClientModules()).thenReturn(List.of(clientModule));
|
||||||
|
|
||||||
|
this.moduleExecutor.execute();
|
||||||
|
|
||||||
|
verify(clientModule).runUiPre();
|
||||||
|
verify(clientModule).run();
|
||||||
|
verify(clientModule).runUiPost();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testExecute_EmptyModules() {
|
||||||
|
when(this.moduleManager.getModulesActive()).thenReturn(List.of());
|
||||||
|
|
||||||
|
this.moduleExecutor.execute();
|
||||||
|
|
||||||
|
verify(this.moduleManager, times(3)).getModulesActive();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testExecute_MultipleModules() {
|
||||||
|
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.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();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user