diff --git a/docs/src/progress/renderertodo.md b/docs/src/progress/renderertodo.md index 71cd368a..939e2304 100644 --- a/docs/src/progress/renderertodo.md +++ b/docs/src/progress/renderertodo.md @@ -1093,6 +1093,10 @@ Add engine logo to title menu Use STBttf for font loading/remove dependency on java.awt.fonts Fix font height lookups in string carousel, text input, and word Fix invalid audio source ID bug +Up threshold on tests for approximate color matching +Refactor signal service subscription mechanism +Add main thread signal service +Fix backing out to main menu # TODO @@ -1119,10 +1123,6 @@ Implement gadgets - Torch - Throwable potions -Ability to fully reload game engine state without exiting client - - Back out to main menu and load a new level without any values persisting - - Receive a teleport packet from server and flush all game state before requesting state from server again - Bug Fixes - Fix hitbox placement does not scale with entity scale on server - Fix not all grass tiles update when updating a nearby voxel (ie it doesn't go into negative coordinates to scan for foliage updates) diff --git a/src/main/java/electrosphere/client/entity/particle/ParticleService.java b/src/main/java/electrosphere/client/entity/particle/ParticleService.java index 5f36c293..a3d54881 100644 --- a/src/main/java/electrosphere/client/entity/particle/ParticleService.java +++ b/src/main/java/electrosphere/client/entity/particle/ParticleService.java @@ -9,6 +9,7 @@ import electrosphere.client.entity.instance.InstanceTemplate; import electrosphere.client.entity.instance.InstancedEntityUtils; import electrosphere.engine.assetmanager.AssetDataStrings; import electrosphere.engine.signal.Signal; +import electrosphere.engine.signal.Signal.SignalType; import electrosphere.engine.signal.SignalServiceImpl; import electrosphere.entity.DrawableUtils; import electrosphere.entity.Entity; @@ -73,7 +74,12 @@ public class ParticleService extends SignalServiceImpl { * Constructor */ public ParticleService() { - super("ParticleService"); + super( + "ParticleService", + new SignalType[]{ + SignalType.RENDERING_ENGINE_READY, + } + ); } @Override diff --git a/src/main/java/electrosphere/client/ui/menu/ingame/MenuGeneratorsInGame.java b/src/main/java/electrosphere/client/ui/menu/ingame/MenuGeneratorsInGame.java index c6953b80..1e192c11 100644 --- a/src/main/java/electrosphere/client/ui/menu/ingame/MenuGeneratorsInGame.java +++ b/src/main/java/electrosphere/client/ui/menu/ingame/MenuGeneratorsInGame.java @@ -7,8 +7,6 @@ import electrosphere.client.ui.menu.WindowUtils; import electrosphere.controls.ControlHandler.ControlsState; import electrosphere.engine.Globals; import electrosphere.engine.Main; -import electrosphere.engine.loadingthreads.LoadingThread; -import electrosphere.engine.loadingthreads.LoadingThread.LoadingThreadType; import electrosphere.engine.signal.Signal.SignalType; import electrosphere.entity.Entity; import electrosphere.entity.EntityDataStrings; @@ -77,7 +75,7 @@ public class MenuGeneratorsInGame { //Return to main menu div.addChild(Button.createButton("Return To Main Menu", () -> { - Globals.threadManager.start(new LoadingThread(LoadingThreadType.RETURN_TITLE_MENU)); + Globals.signalSystem.post(SignalType.ENGINE_RETURN_TO_TITLE); })); diff --git a/src/main/java/electrosphere/engine/Globals.java b/src/main/java/electrosphere/engine/Globals.java index 548e01d8..7f29fc4c 100644 --- a/src/main/java/electrosphere/engine/Globals.java +++ b/src/main/java/electrosphere/engine/Globals.java @@ -39,7 +39,7 @@ import electrosphere.engine.loadingthreads.InitialAssetLoading; import electrosphere.engine.profiler.Profiler; import electrosphere.engine.service.ServiceManager; import electrosphere.engine.signal.SignalSystem; -import electrosphere.engine.signal.Signal.SignalType; +import electrosphere.engine.signal.sync.MainThreadSignalService; import electrosphere.engine.threads.ThreadManager; import electrosphere.engine.time.Timekeeper; import electrosphere.entity.Entity; @@ -329,6 +329,9 @@ public class Globals { //script engine public static ScriptEngine scriptEngine; + + //services + public static MainThreadSignalService mainThreadSignalService; //client scene management public static Scene clientScene; @@ -534,6 +537,7 @@ public class Globals { Globals.elementService = (ElementService)serviceManager.registerService(new ElementService()); Globals.particleService = (ParticleService)serviceManager.registerService(new ParticleService()); Globals.scriptEngine = (ScriptEngine)serviceManager.registerService(new ScriptEngine()); + Globals.mainThreadSignalService = (MainThreadSignalService)serviceManager.registerService(new MainThreadSignalService()); serviceManager.instantiate(); // //End service manager @@ -541,11 +545,10 @@ public class Globals { // //Register all signals - Globals.signalSystem.registerService(SignalType.YOGA_APPLY, Globals.elementService); - Globals.signalSystem.registerService(SignalType.YOGA_DESTROY, Globals.elementService); - Globals.signalSystem.registerService(SignalType.UI_MODIFICATION, Globals.elementService); - Globals.signalSystem.registerService(SignalType.RENDERING_ENGINE_READY, Globals.particleService); - Globals.signalSystem.registerService(SignalType.SCRIPT_RECOMPILE, Globals.scriptEngine); + Globals.signalSystem.registerService(Globals.elementService); + Globals.signalSystem.registerService(Globals.particleService); + Globals.signalSystem.registerService(Globals.scriptEngine); + Globals.signalSystem.registerService(Globals.mainThreadSignalService); } diff --git a/src/main/java/electrosphere/engine/Main.java b/src/main/java/electrosphere/engine/Main.java index 1dfa99af..ab393679 100644 --- a/src/main/java/electrosphere/engine/Main.java +++ b/src/main/java/electrosphere/engine/Main.java @@ -15,6 +15,7 @@ import electrosphere.controls.ControlHandler.ControlsState; import electrosphere.engine.cli.CLIParser; import electrosphere.engine.loadingthreads.LoadingThread; import electrosphere.engine.loadingthreads.LoadingThread.LoadingThreadType; +import electrosphere.engine.signal.SynchronousSignalHandling; import electrosphere.engine.time.Timekeeper; import electrosphere.game.server.world.MacroData; import electrosphere.logger.LoggerInterface; @@ -298,7 +299,7 @@ public class Main { /// /// S Y N C H R O N O U S S I G N A L H A N D L I N G /// - Globals.scriptEngine.handleAllSignals(); + SynchronousSignalHandling.runMainThreadSignalHandlers(); /// /// S C R I P T E N G I N E diff --git a/src/main/java/electrosphere/engine/loadingthreads/MainMenuLoading.java b/src/main/java/electrosphere/engine/loadingthreads/MainMenuLoading.java index e45944d4..2d99ceed 100644 --- a/src/main/java/electrosphere/engine/loadingthreads/MainMenuLoading.java +++ b/src/main/java/electrosphere/engine/loadingthreads/MainMenuLoading.java @@ -7,7 +7,6 @@ import electrosphere.engine.Globals; import electrosphere.engine.signal.Signal.SignalType; import electrosphere.engine.threads.LabeledThread.ThreadLabel; import electrosphere.renderer.ui.elements.Window; -import electrosphere.server.datacell.RealmManager; /** * Loading thread that returns the client to the main menu @@ -19,7 +18,7 @@ public class MainMenuLoading { * Resets all state to main menu * @param params not necessary */ - protected static void returnToMainMenu(Object[] params){ + public static void returnToMainMenu(Object[] params){ // //stop rendering game Globals.RENDER_FLAG_RENDER_SHADOW_MAP = false; @@ -59,7 +58,8 @@ public class MainMenuLoading { Globals.server.close(); Globals.server = null; Globals.serverSynchronizationManager = null; - Globals.realmManager = new RealmManager(); + Globals.realmManager.reset(); + Globals.realmManager = null; Globals.macroSimulation = null; } diff --git a/src/main/java/electrosphere/engine/signal/Signal.java b/src/main/java/electrosphere/engine/signal/Signal.java index c9593d60..a9e0d04b 100644 --- a/src/main/java/electrosphere/engine/signal/Signal.java +++ b/src/main/java/electrosphere/engine/signal/Signal.java @@ -16,6 +16,7 @@ public class Signal { //CORE ENGINE // ENGINE_SHUTDOWN, + ENGINE_RETURN_TO_TITLE, // //RENDERING diff --git a/src/main/java/electrosphere/engine/signal/SignalService.java b/src/main/java/electrosphere/engine/signal/SignalService.java index 8719e0b7..d903123f 100644 --- a/src/main/java/electrosphere/engine/signal/SignalService.java +++ b/src/main/java/electrosphere/engine/signal/SignalService.java @@ -1,6 +1,9 @@ package electrosphere.engine.signal; +import java.util.Collection; + import electrosphere.engine.service.Service; +import electrosphere.engine.signal.Signal.SignalType; /** * A service that can receive a signal @@ -13,4 +16,10 @@ public interface SignalService extends Service { */ public void post(Signal signal); + /** + * Gets the collection of signal types that this service wants to subscribe to + * @return The collection of signal types + */ + public Collection getSubscriptionTargets(); + } diff --git a/src/main/java/electrosphere/engine/signal/SignalServiceImpl.java b/src/main/java/electrosphere/engine/signal/SignalServiceImpl.java index d0f276b9..e93d4b5b 100644 --- a/src/main/java/electrosphere/engine/signal/SignalServiceImpl.java +++ b/src/main/java/electrosphere/engine/signal/SignalServiceImpl.java @@ -1,9 +1,12 @@ package electrosphere.engine.signal; +import java.util.Arrays; +import java.util.Collection; import java.util.LinkedList; import java.util.List; import java.util.concurrent.Semaphore; +import electrosphere.engine.signal.Signal.SignalType; import electrosphere.logger.LoggerInterface; /** @@ -26,13 +29,20 @@ public class SignalServiceImpl implements SignalService { */ String serviceName; + /** + * The list of signal types that this service wants to listen for + */ + List targetTypes; + /** * Constructor, used to require certain fields be provided to this service instance * @param serviceName The name of the service + * @param types The types of signals this service wants to subscribe to */ - public SignalServiceImpl(String serviceName){ + public SignalServiceImpl(String serviceName, SignalType[] types){ this.serviceName = serviceName; + this.targetTypes = Arrays.asList(types); } @Override @@ -108,5 +118,10 @@ public class SignalServiceImpl implements SignalService { public boolean handle(Signal signal){ throw new UnsupportedOperationException("Unimplemented method 'getName'"); } + + @Override + public Collection getSubscriptionTargets() { + return this.targetTypes; + } } diff --git a/src/main/java/electrosphere/engine/signal/SignalSystem.java b/src/main/java/electrosphere/engine/signal/SignalSystem.java index cee02855..e61b10a0 100644 --- a/src/main/java/electrosphere/engine/signal/SignalSystem.java +++ b/src/main/java/electrosphere/engine/signal/SignalSystem.java @@ -51,7 +51,30 @@ public class SignalSystem implements Service { * @param signalType The type of signal * @param service The service to associate with that signal type */ - public void registerService(SignalType signalType, SignalService service){ + public void registerService(SignalService service){ + systemLock.acquireUninterruptibly(); + LoggerInterface.loggerEngine.DEBUG("[SignalSystem] Register signal service " + service.getName()); + for(SignalType signalType : service.getSubscriptionTargets()){ + if(typeServiceMap.containsKey(signalType)){ + Set services = this.typeServiceMap.get(signalType); + if(!services.contains(service)){ + services.add(service); + } + } else { + Set services = new HashSet(); + services.add(service); + this.typeServiceMap.put(signalType, services); + } + } + systemLock.release(); + } + + /** + * Registers a service to a type of signal + * @param signalType The type of signal + * @param service The service to associate with that signal type + */ + public void registerServiceToSignal(SignalType signalType, SignalService service){ systemLock.acquireUninterruptibly(); LoggerInterface.loggerEngine.DEBUG("[SignalSystem] Register signal service " + service.getName()); if(typeServiceMap.containsKey(signalType)){ @@ -72,7 +95,7 @@ public class SignalSystem implements Service { * @param signalType The type of signal * @param service The signal service to unassociate from that signal type */ - public void deregisterService(SignalType signalType, SignalService service){ + public void deregisterServiceToSignal(SignalType signalType, SignalService service){ systemLock.acquireUninterruptibly(); LoggerInterface.loggerEngine.DEBUG("[SignalSystem] Deregister signal service " + service.getName()); if(typeServiceMap.containsKey(signalType)){ diff --git a/src/main/java/electrosphere/engine/signal/SynchronousSignalHandling.java b/src/main/java/electrosphere/engine/signal/SynchronousSignalHandling.java new file mode 100644 index 00000000..ff7ad27e --- /dev/null +++ b/src/main/java/electrosphere/engine/signal/SynchronousSignalHandling.java @@ -0,0 +1,18 @@ +package electrosphere.engine.signal; + +import electrosphere.engine.Globals; + +/** + * Synchronously handles signals + */ +public class SynchronousSignalHandling { + + /** + * Runs the main thread signal handlers + */ + public static void runMainThreadSignalHandlers(){ + Globals.scriptEngine.handleAllSignals(); + Globals.mainThreadSignalService.handleAllSignals(); + } + +} diff --git a/src/main/java/electrosphere/engine/signal/sync/MainThreadSignalService.java b/src/main/java/electrosphere/engine/signal/sync/MainThreadSignalService.java new file mode 100644 index 00000000..c5dbd9aa --- /dev/null +++ b/src/main/java/electrosphere/engine/signal/sync/MainThreadSignalService.java @@ -0,0 +1,38 @@ +package electrosphere.engine.signal.sync; + +import electrosphere.engine.loadingthreads.MainMenuLoading; +import electrosphere.engine.signal.Signal; +import electrosphere.engine.signal.Signal.SignalType; +import electrosphere.engine.signal.SignalServiceImpl; +import electrosphere.logger.LoggerInterface; + +public class MainThreadSignalService extends SignalServiceImpl { + + /** + * Constructor + */ + public MainThreadSignalService() { + super( + "MainThreadSignalService", + new SignalType[]{ + SignalType.ENGINE_RETURN_TO_TITLE, + } + ); + } + + @Override + public boolean handle(Signal signal){ + boolean rVal = false; + switch(signal.getType()){ + case ENGINE_RETURN_TO_TITLE: { + MainMenuLoading.returnToMainMenu(null); + rVal = true; + } break; + default: { + LoggerInterface.loggerEngine.WARNING("MainThreadSignalService received signal that it does not have handling for! " + signal); + } break; + } + return rVal; + } + +} diff --git a/src/main/java/electrosphere/engine/threads/ThreadManager.java b/src/main/java/electrosphere/engine/threads/ThreadManager.java index 2353c289..74e7a71e 100644 --- a/src/main/java/electrosphere/engine/threads/ThreadManager.java +++ b/src/main/java/electrosphere/engine/threads/ThreadManager.java @@ -112,7 +112,7 @@ public class ThreadManager { Globals.server.close(); } - if(Globals.realmManager.getRealms() != null){ + if(Globals.realmManager != null && Globals.realmManager.getRealms() != null){ for(Realm realm : Globals.realmManager.getRealms()){ if(realm.getServerWorldData() != null && realm.getServerWorldData().getServerTerrainManager() != null){ realm.getServerWorldData().getServerTerrainManager().closeThreads(); diff --git a/src/main/java/electrosphere/net/server/ServerConnectionHandler.java b/src/main/java/electrosphere/net/server/ServerConnectionHandler.java index 8e144085..575792df 100644 --- a/src/main/java/electrosphere/net/server/ServerConnectionHandler.java +++ b/src/main/java/electrosphere/net/server/ServerConnectionHandler.java @@ -8,7 +8,6 @@ import electrosphere.net.parser.net.message.NetworkMessage; import electrosphere.net.parser.net.message.ServerMessage; import electrosphere.net.parser.net.raw.NetworkParser; import electrosphere.net.server.player.Player; -import electrosphere.util.CodeUtils; import java.io.IOException; import java.io.InputStream; @@ -321,7 +320,7 @@ public class ServerConnectionHandler implements Runnable { TimeUnit.MILLISECONDS.sleep(1); } catch (InterruptedException ex) { //silently ignore - CodeUtils.todo(ex, "Handle sleep interrupt on server connection"); + // CodeUtils.todo(ex, "Handle sleep interrupt on server connection"); } return rVal; } diff --git a/src/main/java/electrosphere/renderer/ui/ElementService.java b/src/main/java/electrosphere/renderer/ui/ElementService.java index 598bf9fa..08cf9a14 100644 --- a/src/main/java/electrosphere/renderer/ui/ElementService.java +++ b/src/main/java/electrosphere/renderer/ui/ElementService.java @@ -13,6 +13,7 @@ import org.joml.Vector2i; import electrosphere.engine.Globals; import electrosphere.engine.signal.Signal; +import electrosphere.engine.signal.Signal.SignalType; import electrosphere.engine.signal.SignalServiceImpl; import electrosphere.renderer.ui.elements.Window; import electrosphere.renderer.ui.elementtypes.ContainerElement; @@ -39,7 +40,14 @@ public class ElementService extends SignalServiceImpl { * Constructor */ public ElementService() { - super("ElementService"); + super( + "ElementService", + new SignalType[]{ + SignalType.YOGA_APPLY, + SignalType.YOGA_DESTROY, + SignalType.UI_MODIFICATION, + } + ); } Map elementMap = new ConcurrentHashMap(); diff --git a/src/main/java/electrosphere/script/ScriptEngine.java b/src/main/java/electrosphere/script/ScriptEngine.java index d842d1ce..3cf838be 100644 --- a/src/main/java/electrosphere/script/ScriptEngine.java +++ b/src/main/java/electrosphere/script/ScriptEngine.java @@ -28,6 +28,7 @@ import electrosphere.client.ui.menu.tutorial.TutorialMenus; import electrosphere.engine.Globals; import electrosphere.engine.Main; import electrosphere.engine.signal.Signal; +import electrosphere.engine.signal.Signal.SignalType; import electrosphere.engine.signal.SignalServiceImpl; import electrosphere.logger.LoggerInterface; import electrosphere.script.translation.JSServerUtils; @@ -134,7 +135,12 @@ public class ScriptEngine extends SignalServiceImpl { * Constructor */ public ScriptEngine(){ - super("ScriptEngine"); + super( + "ScriptEngine", + new SignalType[]{ + SignalType.SCRIPT_RECOMPILE, + } + ); sourceMap = new HashMap(); this.fs = FileSystems.getDefault(); try { diff --git a/src/main/java/electrosphere/server/datacell/RealmManager.java b/src/main/java/electrosphere/server/datacell/RealmManager.java index d78ad165..2a0cb919 100644 --- a/src/main/java/electrosphere/server/datacell/RealmManager.java +++ b/src/main/java/electrosphere/server/datacell/RealmManager.java @@ -209,6 +209,11 @@ public class RealmManager { * Resets the realm manager */ public void reset(){ + for(Realm realm : this.realms){ + if(realm.getServerWorldData() != null && realm.getServerWorldData().getServerTerrainManager() != null){ + realm.getServerWorldData().getServerTerrainManager().closeThreads(); + } + } this.realms.clear(); this.entityToRealmMap.clear(); this.playerToRealmMap.clear(); diff --git a/src/test/java/electrosphere/engine/signal/SignalSystemTests.java b/src/test/java/electrosphere/engine/signal/SignalSystemTests.java index 863aff7f..223b4c82 100644 --- a/src/test/java/electrosphere/engine/signal/SignalSystemTests.java +++ b/src/test/java/electrosphere/engine/signal/SignalSystemTests.java @@ -36,7 +36,7 @@ public class SignalSystemTests { SignalService service = Mockito.mock(SignalService.class); - signalSystem.registerService(SignalType.YOGA_APPLY, service); + signalSystem.registerServiceToSignal(SignalType.YOGA_APPLY, service); } @@ -46,7 +46,7 @@ public class SignalSystemTests { signalSystem.init(); SignalService service = Mockito.mock(SignalService.class); - signalSystem.registerService(SignalType.YOGA_APPLY, service); + signalSystem.registerServiceToSignal(SignalType.YOGA_APPLY, service); signalSystem.post(SignalType.YOGA_APPLY); @@ -60,8 +60,8 @@ public class SignalSystemTests { SignalService service = Mockito.mock(SignalService.class); SignalService service2 = Mockito.mock(SignalService.class); - signalSystem.registerService(SignalType.YOGA_APPLY, service); - signalSystem.registerService(SignalType.YOGA_APPLY, service2); + signalSystem.registerServiceToSignal(SignalType.YOGA_APPLY, service); + signalSystem.registerServiceToSignal(SignalType.YOGA_APPLY, service2); signalSystem.post(SignalType.YOGA_APPLY); @@ -77,9 +77,9 @@ public class SignalSystemTests { SignalService service = Mockito.mock(SignalService.class); SignalService service2 = Mockito.mock(SignalService.class); SignalService service3 = Mockito.mock(SignalService.class); - signalSystem.registerService(SignalType.YOGA_APPLY, service); - signalSystem.registerService(SignalType.YOGA_APPLY, service2); - signalSystem.registerService(SignalType.ENGINE_SHUTDOWN, service3); + signalSystem.registerServiceToSignal(SignalType.YOGA_APPLY, service); + signalSystem.registerServiceToSignal(SignalType.YOGA_APPLY, service2); + signalSystem.registerServiceToSignal(SignalType.ENGINE_SHUTDOWN, service3); signalSystem.post(SignalType.YOGA_APPLY); @@ -95,8 +95,8 @@ public class SignalSystemTests { signalSystem.init(); SignalService service = Mockito.mock(SignalService.class); - signalSystem.registerService(SignalType.YOGA_APPLY, service); - signalSystem.deregisterService(SignalType.YOGA_APPLY, service); + signalSystem.registerServiceToSignal(SignalType.YOGA_APPLY, service); + signalSystem.deregisterServiceToSignal(SignalType.YOGA_APPLY, service); signalSystem.post(SignalType.YOGA_APPLY); Mockito.verify(service, Mockito.never()).post(signalCaptor.capture()); diff --git a/src/test/java/electrosphere/test/testutils/Assertions.java b/src/test/java/electrosphere/test/testutils/Assertions.java index b82a536a..60fe86df 100644 --- a/src/test/java/electrosphere/test/testutils/Assertions.java +++ b/src/test/java/electrosphere/test/testutils/Assertions.java @@ -20,7 +20,7 @@ public class Assertions { /** * The threshold at which we say the colors are 'close enough' */ - static final int COLOR_COMPARE_THRESHOLD = 3; + static final int COLOR_COMPARE_THRESHOLD = 4; /** * A very small number for comparisons