diff --git a/docs/src/progress/renderertodo.md b/docs/src/progress/renderertodo.md index 79747574..3c8a5bd1 100644 --- a/docs/src/progress/renderertodo.md +++ b/docs/src/progress/renderertodo.md @@ -623,6 +623,7 @@ Setup MantisBT (08/22/2024) Fix rendering testing on jenkins Fix entity scene test spinup by preventing networking sockets from closing +Thread manager/tracker + properly closing threads on engine close # TODO diff --git a/src/main/java/electrosphere/engine/Globals.java b/src/main/java/electrosphere/engine/Globals.java index ba577478..fe929ede 100644 --- a/src/main/java/electrosphere/engine/Globals.java +++ b/src/main/java/electrosphere/engine/Globals.java @@ -1,8 +1,6 @@ package electrosphere.engine; import java.util.ArrayList; -import java.util.LinkedList; -import java.util.List; import org.joml.Matrix4f; import org.joml.Vector3f; @@ -32,7 +30,6 @@ import electrosphere.controls.ScrollCallback; import electrosphere.engine.assetmanager.AssetDataStrings; import electrosphere.engine.assetmanager.AssetManager; import electrosphere.engine.loadingthreads.InitialAssetLoading; -import electrosphere.engine.loadingthreads.LoadingThread; import electrosphere.engine.profiler.Profiler; import electrosphere.engine.time.Timekeeper; import electrosphere.entity.Entity; @@ -87,6 +84,11 @@ public class Globals { //Process data // public static String javaPID; + + // + //Thread manager + // + public static ThreadManager threadManager = new ThreadManager(); // //Top level user settings object @@ -148,7 +150,6 @@ public class Globals { // //Server manager thing // - public static Thread serverThread; public static Server server; public static ServerSynchronizationManager serverSynchronizationManager = new ServerSynchronizationManager(); public static boolean RUN_SERVER = true; @@ -343,7 +344,6 @@ public class Globals { public static ArrayList skyboxColors; //thread for loading different game states - public static List loadingThreadsList = new LinkedList(); public static InitialAssetLoading initialAssetLoadingThread = new InitialAssetLoading(); //manager for all widgets currently being drawn to screen diff --git a/src/main/java/electrosphere/engine/Main.java b/src/main/java/electrosphere/engine/Main.java index 9a6efe4a..a5151b73 100644 --- a/src/main/java/electrosphere/engine/Main.java +++ b/src/main/java/electrosphere/engine/Main.java @@ -154,7 +154,7 @@ public class Main { Globals.controlHandler.hintUpdateControlState(ControlsState.TITLE_MENU); //start initial asset loading - new Thread(Globals.initialAssetLoadingThread).start(); + Globals.threadManager.start(new Thread(Globals.initialAssetLoadingThread)); } //Sets a hook that fires when the engine process stops @@ -204,12 +204,10 @@ public class Main { LoggerInterface.loggerStartup.INFO("Fire off loading thread"); if(Globals.RUN_DEMO){ LoadingThread serverThread = new LoadingThread(LoadingThreadType.DEMO_MENU); - Globals.loadingThreadsList.add(serverThread); - serverThread.start(); + Globals.threadManager.start(serverThread); } else if(Globals.RUN_CLIENT){ LoadingThread serverThread = new LoadingThread(LoadingThreadType.TITLE_MENU); - Globals.loadingThreadsList.add(serverThread); - serverThread.start(); + Globals.threadManager.start(serverThread); } else { throw new IllegalStateException("Need to add handling for only running server again"); } @@ -247,9 +245,10 @@ public class Main { LoggerInterface.loggerEngine.DEBUG_LOOP("Begin Main Loop Frame"); // - //Update timekeeper + //Update timekeeper and thread manager // Globals.timekeeper.update(); + Globals.threadManager.update(); @@ -432,13 +431,7 @@ public class Main { glfwTerminate(); } //used to signal threads to stop - running = false; - if(Globals.server != null){ - Globals.server.close(); - if(Globals.serverThread != null){ - Globals.serverThread.interrupt(); - } - } + Globals.threadManager.close(); //shut down audio engine if(!Globals.HEADLESS && Globals.RUN_CLIENT && Globals.RUN_AUDIO && Globals.audioEngine != null && Globals.audioEngine.initialized()){ Globals.audioEngine.shutdown(); @@ -460,11 +453,6 @@ public class Main { } - public static boolean isRunning(){ - return running; - } - - public static void initControlHandler(){ diff --git a/src/main/java/electrosphere/engine/ThreadManager.java b/src/main/java/electrosphere/engine/ThreadManager.java new file mode 100644 index 00000000..b8ccc6d1 --- /dev/null +++ b/src/main/java/electrosphere/engine/ThreadManager.java @@ -0,0 +1,110 @@ +package electrosphere.engine; + +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.Semaphore; + +import electrosphere.engine.loadingthreads.LoadingThread; + +/** + * Manages all running threads + */ +public class ThreadManager { + + //Threadsafes the manager + Semaphore threadLock = new Semaphore(1); + + //All threads that are actively running + private List activeThreads = new LinkedList(); + + //All loading threads that are actively running + private List loadingThreads = new LinkedList(); + + //Used by main thread to alert other threads whether they should keep running or not + private boolean shouldKeepRunning = true; + + + + /** + * Updates what threads are being tracked + */ + public void update(){ + threadLock.acquireUninterruptibly(); + // + //remove loading threads + List threadsToRemove = new LinkedList(); + for(LoadingThread thread : loadingThreads){ + if(thread.isDone()){ + System.out.println("Collect loading thread of type " + thread.getType()); + threadsToRemove.add(thread); + } + } + for(Thread thread : threadsToRemove){ + while(loadingThreads.contains(thread)){ + loadingThreads.remove(thread); + } + } + + threadLock.release(); + } + + /** + * Starts a new thread with tracking + * @param thread The thread to start + */ + public void start(Thread thread){ + threadLock.acquireUninterruptibly(); + activeThreads.add(thread); + thread.start(); + threadLock.release(); + } + + /** + * Starts a new loading thread with tracking + * @param thread The loading thread to start + */ + public void start(LoadingThread thread){ + threadLock.acquireUninterruptibly(); + activeThreads.add(thread); + loadingThreads.add(thread); + thread.start(); + threadLock.release(); + } + + /** + * Checks if any loading threads are active + * @return true if there is an active loading thread, false otherwise + */ + public boolean isLoading(){ + return loadingThreads.size() > 0; + } + + /** + * Tries to close all threads + */ + public void close(){ + this.shouldKeepRunning = false; + threadLock.acquireUninterruptibly(); + + //for some reason, server must be explicitly closed + if(Globals.server != null){ + Globals.server.close(); + } + + // + //interrupt all threads + for(Thread thread : activeThreads){ + thread.interrupt(); + } + threadLock.release(); + } + + /** + * Checks if the thread should keep running or not + * @return true if should keep running, false otherwise + */ + public boolean shouldKeepRunning(){ + return this.shouldKeepRunning; + } + +} diff --git a/src/main/java/electrosphere/engine/loadingthreads/ClientLoading.java b/src/main/java/electrosphere/engine/loadingthreads/ClientLoading.java index 170e634d..cdca9561 100644 --- a/src/main/java/electrosphere/engine/loadingthreads/ClientLoading.java +++ b/src/main/java/electrosphere/engine/loadingthreads/ClientLoading.java @@ -27,11 +27,20 @@ import electrosphere.renderer.ui.elements.Window; public class ClientLoading { + /** + * Loads the main menu + * @param params Params (this thread type does not accept any) + */ protected static void loadMainMenu(Object[] params){ Window loadingWindow = (Window)Globals.elementManager.getWindow(WindowStrings.WINDOW_LOADING); - WindowUtils.recursiveSetVisible(loadingWindow,false); + if(loadingWindow != null){ + WindowUtils.recursiveSetVisible(loadingWindow,false); + } WindowUtils.focusWindow(WindowStrings.WINDOW_MENU_MAIN); - WindowUtils.recursiveSetVisible(Globals.elementManager.getWindow(WindowStrings.WINDOW_MENU_MAIN), true); + Window mainMenuWindow = (Window)Globals.elementManager.getWindow(WindowStrings.WINDOW_MENU_MAIN); + if(mainMenuWindow != null){ + WindowUtils.recursiveSetVisible(mainMenuWindow, true); + } } @@ -118,11 +127,9 @@ public class ClientLoading { */ private static void initClientThread(){ //start client networking - Thread clientThread = null; if(Globals.RUN_CLIENT){ Globals.clientConnection = new ClientNetworking(NetUtils.getAddress(),NetUtils.getPort()); - clientThread = new Thread(Globals.clientConnection); - clientThread.start(); + Globals.threadManager.start(new Thread(Globals.clientConnection)); } } diff --git a/src/main/java/electrosphere/engine/loadingthreads/LoadingThread.java b/src/main/java/electrosphere/engine/loadingthreads/LoadingThread.java index 91c1c7d6..8562a960 100644 --- a/src/main/java/electrosphere/engine/loadingthreads/LoadingThread.java +++ b/src/main/java/electrosphere/engine/loadingthreads/LoadingThread.java @@ -1,7 +1,5 @@ package electrosphere.engine.loadingthreads; -import java.util.concurrent.Semaphore; - /** * Threads for loading engine state */ @@ -61,9 +59,9 @@ public class LoadingThread extends Thread { //the params provided to this thread in particular Object[] params; - - //a lock to track when the loading had completed and block until then - Semaphore lock; + + //tracks whether the thread is done loading or not + boolean isDone = false; /** * Creates the work for a loading thread @@ -73,12 +71,11 @@ public class LoadingThread extends Thread { public LoadingThread(LoadingThreadType type, Object ... params){ threadType = type; this.params = params; - lock = new Semaphore(1); } @Override public void run(){ - lock.acquireUninterruptibly(); + System.out.println("Start loading thread of type " + this.threadType); switch(threadType){ case TITLE_MENU: { @@ -119,7 +116,7 @@ public class LoadingThread extends Thread { } break; } - lock.release(); + isDone = true; } /** @@ -127,11 +124,15 @@ public class LoadingThread extends Thread { * @return true if it has finished, false otherwise */ public boolean isDone(){ - boolean rVal = lock.tryAcquire(); - if(rVal == true){ - lock.release(); - } - return rVal; + return isDone; + } + + /** + * Gets the type of the loading thread + * @return The type + */ + public LoadingThreadType getType(){ + return this.threadType; } } diff --git a/src/main/java/electrosphere/engine/loadingthreads/LoadingUtils.java b/src/main/java/electrosphere/engine/loadingthreads/LoadingUtils.java index 7289d1f9..dfb73f76 100644 --- a/src/main/java/electrosphere/engine/loadingthreads/LoadingUtils.java +++ b/src/main/java/electrosphere/engine/loadingthreads/LoadingUtils.java @@ -63,8 +63,8 @@ public class LoadingUtils { //start server networking if(Globals.RUN_SERVER){ Globals.server = new Server(NetUtils.getPort()); - Globals.serverThread = new Thread(Globals.server); - Globals.serverThread.start(); + Thread serverThread = new Thread(Globals.server); + Globals.threadManager.start(serverThread); } } @@ -78,11 +78,9 @@ public class LoadingUtils { static void initClientThread(){ //start client networking - Thread clientThread = null; if(Globals.RUN_CLIENT){ Globals.clientConnection = new ClientNetworking(NetUtils.getAddress(),NetUtils.getPort()); - clientThread = new Thread(Globals.clientConnection); - clientThread.start(); + Globals.threadManager.start(new Thread(Globals.clientConnection)); } } @@ -106,9 +104,7 @@ public class LoadingUtils { rVal = Globals.server.addLocalPlayer(serverInput, serverOutput); //start client communication thread Globals.clientConnection = new ClientNetworking(clientInput,clientOutput); - Thread clientThread = null; - clientThread = new Thread(Globals.clientConnection); - clientThread.start(); + Globals.threadManager.start(new Thread(Globals.clientConnection)); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); diff --git a/src/main/java/electrosphere/menu/MenuGenerators.java b/src/main/java/electrosphere/menu/MenuGenerators.java index 5cbb4f1b..69542198 100644 --- a/src/main/java/electrosphere/menu/MenuGenerators.java +++ b/src/main/java/electrosphere/menu/MenuGenerators.java @@ -51,13 +51,11 @@ public class MenuGenerators { Globals.clientUsername = "username"; Globals.clientPassword = AuthenticationManager.getHashedString("password"); LoadingThread clientThread = new LoadingThread(LoadingThreadType.CHARACTER_SERVER); - Globals.loadingThreadsList.add(clientThread); LoadingThread serverThread = new LoadingThread(LoadingThreadType.MAIN_GAME); - Globals.loadingThreadsList.add(serverThread); Globals.RUN_CLIENT = true; Globals.RUN_SERVER = true; - serverThread.start(); - clientThread.start(); + Globals.threadManager.start(serverThread); + Globals.threadManager.start(clientThread); } else { SaveUtils.loadSave(saveName.toLowerCase(), false); WindowUtils.replaceMainMenuContents(MenuGenerators.createSaveCreationMenu()); @@ -182,13 +180,11 @@ public class MenuGenerators { rVal.addChild(hostButton); hostButton.setOnClick(new ClickableElement.ClickEventCallback(){public boolean execute(ClickEvent event){ LoadingThread clientThread = new LoadingThread(LoadingThreadType.CHARACTER_SERVER); - Globals.loadingThreadsList.add(clientThread); LoadingThread serverThread = new LoadingThread(LoadingThreadType.MAIN_GAME); - Globals.loadingThreadsList.add(serverThread); Globals.RUN_CLIENT = true; Globals.RUN_SERVER = true; - clientThread.start(); - serverThread.start(); + Globals.threadManager.start(serverThread); + Globals.threadManager.start(clientThread); return false; }}); @@ -290,10 +286,9 @@ public class MenuGenerators { Globals.clientUsername = usernameInput.getText(); Globals.clientPassword = AuthenticationManager.getHashedString(passwordInput.getText()); LoadingThread clientThread = new LoadingThread(LoadingThreadType.CHARACTER_SERVER); - Globals.loadingThreadsList.add(clientThread); Globals.RUN_CLIENT = true; Globals.RUN_SERVER = false; - clientThread.start(); + Globals.threadManager.start(clientThread); return false; }}); diff --git a/src/main/java/electrosphere/menu/mainmenu/MenuGeneratorsLevelEditor.java b/src/main/java/electrosphere/menu/mainmenu/MenuGeneratorsLevelEditor.java index aa58f255..41d2690f 100644 --- a/src/main/java/electrosphere/menu/mainmenu/MenuGeneratorsLevelEditor.java +++ b/src/main/java/electrosphere/menu/mainmenu/MenuGeneratorsLevelEditor.java @@ -89,10 +89,9 @@ public class MenuGeneratorsLevelEditor { Button launchButton = Button.createButton(saveName, () -> { //launch level LoadingThread loadingThread = new LoadingThread(LoadingThreadType.LEVEL, saveName); - Globals.loadingThreadsList.add(loadingThread); Globals.RUN_CLIENT = true; Globals.RUN_SERVER = true; - loadingThread.start(); + Globals.threadManager.start(loadingThread); }); // @@ -101,8 +100,7 @@ public class MenuGeneratorsLevelEditor { Button editButton = Button.createButton("Edit", () -> { //launch level editor LoadingThread loadingThread = new LoadingThread(LoadingThreadType.LEVEL_EDITOR, saveName); - Globals.loadingThreadsList.add(loadingThread); - loadingThread.start(); + Globals.threadManager.start(loadingThread); }); //create row @@ -205,8 +203,7 @@ public class MenuGeneratorsLevelEditor { rVal.addChild(Button.createButton("Create Level", () -> { //launch level editor LoadingThread loadingThread = new LoadingThread(LoadingThreadType.LEVEL_EDITOR, inFlightLevel); - Globals.loadingThreadsList.add(loadingThread); - loadingThread.start(); + Globals.threadManager.start(loadingThread); })); diff --git a/src/main/java/electrosphere/menu/mainmenu/MenuGeneratorsTitleMenu.java b/src/main/java/electrosphere/menu/mainmenu/MenuGeneratorsTitleMenu.java index 6873587d..aea394f3 100644 --- a/src/main/java/electrosphere/menu/mainmenu/MenuGeneratorsTitleMenu.java +++ b/src/main/java/electrosphere/menu/mainmenu/MenuGeneratorsTitleMenu.java @@ -89,10 +89,9 @@ public class MenuGeneratorsTitleMenu { rVal.addChild(uiDebugSPQuickstartButton); uiDebugSPQuickstartButton.setOnClick(new ClickableElement.ClickEventCallback(){public boolean execute(ClickEvent event){ LoadingThread loadingThread = new LoadingThread(LoadingThreadType.DEBUG_RANDOM_SP_WORLD); - Globals.loadingThreadsList.add(loadingThread); Globals.RUN_CLIENT = true; Globals.RUN_SERVER = true; - loadingThread.start(); + Globals.threadManager.start(loadingThread); return false; }}); diff --git a/src/main/java/electrosphere/net/client/ClientNetworking.java b/src/main/java/electrosphere/net/client/ClientNetworking.java index 0474d5ba..7ca1c75a 100644 --- a/src/main/java/electrosphere/net/client/ClientNetworking.java +++ b/src/main/java/electrosphere/net/client/ClientNetworking.java @@ -1,14 +1,12 @@ package electrosphere.net.client; import electrosphere.engine.Globals; -import electrosphere.engine.Main; import electrosphere.logger.LoggerInterface; import electrosphere.net.parser.net.message.NetworkMessage; import electrosphere.net.parser.net.message.ServerMessage; import electrosphere.net.parser.net.message.NetworkMessage.MessageType; import electrosphere.net.parser.net.message.ServerMessage.ServerMessageType; import electrosphere.net.parser.net.raw.NetworkParser; -import electrosphere.net.server.ServerConnectionHandler; import java.io.IOException; import java.io.InputStream; @@ -170,7 +168,7 @@ public class ClientNetworking implements Runnable { //start parsing messages initialized = true; - while((Main.isRunning() || !ServerConnectionHandler.AUTO_CLOSE_THREAD)){ + while(Globals.threadManager.shouldKeepRunning()){ //attempt poll incoming messages parser.readMessagesIn(); //outgoing messages @@ -207,6 +205,8 @@ public class ClientNetworking implements Runnable { break; } } + + LoggerInterface.loggerNetworking.WARNING("Client networking thread ended"); } diff --git a/src/main/java/electrosphere/net/client/protocol/CharacterProtocol.java b/src/main/java/electrosphere/net/client/protocol/CharacterProtocol.java index 8a630c3a..80e2d73c 100644 --- a/src/main/java/electrosphere/net/client/protocol/CharacterProtocol.java +++ b/src/main/java/electrosphere/net/client/protocol/CharacterProtocol.java @@ -25,8 +25,7 @@ public class CharacterProtocol implements ClientProtocolTemplate