thread manager
Some checks reported errors
studiorailgun/Renderer/pipeline/head Something is wrong with the build of this commit

This commit is contained in:
austin 2024-08-22 21:24:44 -04:00
parent d0e5f3b7d2
commit 5563b4366f
18 changed files with 189 additions and 97 deletions

View File

@ -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

View File

@ -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<Vector3f> skyboxColors;
//thread for loading different game states
public static List<LoadingThread> loadingThreadsList = new LinkedList<LoadingThread>();
public static InitialAssetLoading initialAssetLoadingThread = new InitialAssetLoading();
//manager for all widgets currently being drawn to screen

View File

@ -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(){

View File

@ -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<Thread> activeThreads = new LinkedList<Thread>();
//All loading threads that are actively running
private List<LoadingThread> loadingThreads = new LinkedList<LoadingThread>();
//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<Thread> threadsToRemove = new LinkedList<Thread>();
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;
}
}

View File

@ -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));
}
}

View File

@ -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;
}
}

View File

@ -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();

View File

@ -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;
}});

View File

@ -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);
}));

View File

@ -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;
}});

View File

@ -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");
}

View File

@ -25,8 +25,7 @@ public class CharacterProtocol implements ClientProtocolTemplate<CharacterMessag
Globals.clientConnection.queueOutgoingMessage(CharacterMessage.constructRequestSpawnCharacterMessage());
Globals.clientConnection.queueOutgoingMessage(TerrainMessage.constructRequestMetadataMessage());
LoadingThread clientThread = new LoadingThread(LoadingThreadType.CLIENT_WORLD);
Globals.loadingThreadsList.add(clientThread);
clientThread.start();
Globals.threadManager.start(clientThread);
break;
case REQUESTCHARACTERLIST:
case REQUESTCREATECHARACTER:

View File

@ -1,7 +1,6 @@
package electrosphere.net.server;
import electrosphere.engine.Globals;
import electrosphere.engine.Main;
import electrosphere.entity.ServerEntityUtils;
import electrosphere.logger.LoggerInterface;
import electrosphere.net.NetUtils;
@ -24,7 +23,7 @@ import java.util.concurrent.Semaphore;
/**
* Lowest level networking class for the server
*/
public class Server implements Runnable{
public class Server implements Runnable {
//the port the server is running on
int port;
@ -75,7 +74,7 @@ public class Server implements Runnable{
} catch (IOException ex) {
LoggerInterface.loggerNetworking.ERROR("Failed to start server socket!",ex);
}
while(Main.isRunning()){
while(Globals.threadManager.shouldKeepRunning()){
Socket newSocket;
try {
newSocket = serverSocket.accept();
@ -84,7 +83,7 @@ public class Server implements Runnable{
// clientMap.put(newSocket.getInetAddress().getHostAddress(), newClient);
socketConnectionMap.put(newSocket, newClient);
activeConnections.add(newClient);
new Thread(newClient).start();
Globals.threadManager.start(new Thread(newClient));
connectListLock.release();
} catch (SocketException ex){
LoggerInterface.loggerNetworking.ERROR("Socket closed!",ex);
@ -92,6 +91,7 @@ public class Server implements Runnable{
LoggerInterface.loggerNetworking.ERROR("Socket error on client socket!",ex);
}
}
LoggerInterface.loggerNetworking.WARNING("Server socket thread ended");
}
/**
@ -142,7 +142,7 @@ public class Server implements Runnable{
connectListLock.acquireUninterruptibly();
ServerConnectionHandler newClient = new ServerConnectionHandler(serverInputStream,serverOutputStream);
activeConnections.add(newClient);
new Thread(newClient).start();
Globals.threadManager.start(new Thread(newClient));
connectListLock.release();
return newClient;
}

View File

@ -2,13 +2,13 @@ package electrosphere.net.server;
import electrosphere.entity.types.creature.CreatureTemplate;
import electrosphere.engine.Globals;
import electrosphere.engine.Main;
import electrosphere.logger.LoggerInterface;
import electrosphere.net.parser.net.message.AuthMessage;
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;
@ -25,12 +25,6 @@ import java.util.concurrent.TimeUnit;
* A connection to the server
*/
public class ServerConnectionHandler implements Runnable {
/**
* Automatically close threads if the main thread ends.
* Used to make sure threads don't close while running test suite.
*/
public static boolean AUTO_CLOSE_THREAD = true;
//the player id associated with this connection
static int playerIdIncrementer = 0;
@ -199,7 +193,7 @@ public class ServerConnectionHandler implements Runnable {
initialized = true;
while((Main.isRunning() || !ServerConnectionHandler.AUTO_CLOSE_THREAD) && this.isConnected == true){
while(Globals.threadManager.shouldKeepRunning() && this.isConnected == true){
//
// Main Loop
@ -314,7 +308,7 @@ public class ServerConnectionHandler implements Runnable {
//sleep
TimeUnit.MILLISECONDS.sleep(1);
} catch (InterruptedException ex) {
ex.printStackTrace();
CodeUtils.todo(ex, "Handle sleep interrupt on server connection");
}
}

View File

@ -439,7 +439,7 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager
}
});
groundDataCells.get(getServerDataCellKey(worldPos)).setReady(false);
thread.start();
Globals.threadManager.start(thread);
}
/**

View File

@ -11,7 +11,6 @@ public class StartupTest {
@IntegrationSetup
public void testStartupHeadless(){
LoggerInterface.loggerEngine.INFO("[Test] Startup Headless");
Globals.RUN_CLIENT = false;
Globals.RUN_SERVER = true;
Globals.HEADLESS = true;
@ -23,7 +22,6 @@ public class StartupTest {
@IntegrationTest
public void testEmpty() {
LoggerInterface.loggerEngine.INFO("[Test] Empty test");
}
}

View File

@ -3,7 +3,8 @@ package template;
import org.junit.jupiter.api.Tag;
import annotations.IntegrationSetup;
import electrosphere.net.server.ServerConnectionHandler;
import annotations.IntegrationTeardown;
import electrosphere.engine.Main;
import testutils.EngineInit;
import testutils.TestEngineUtils;
@ -13,17 +14,25 @@ import testutils.TestEngineUtils;
*/
public abstract class EntityTestTemplate extends RenderingTestTemplate {
/**
* Starts up the entity scene
*/
@IntegrationSetup
@Override
public void initEngine(){
//init engine
TestEngineUtils.initGraphicalEngine();
//make sure the server network threads continue to execute even if the main thread dies
ServerConnectionHandler.AUTO_CLOSE_THREAD = false;
//load scene
EngineInit.setupConnectedTestScene();
}
/**
* Shuts down the engine
*/
@IntegrationTeardown
public void closeEngine(){
Main.shutdown();
}
}

View File

@ -5,8 +5,6 @@ import java.util.concurrent.TimeUnit;
import electrosphere.engine.Globals;
import electrosphere.engine.loadingthreads.LoadingThread;
import electrosphere.engine.loadingthreads.LoadingThread.LoadingThreadType;
import electrosphere.menu.WindowStrings;
import electrosphere.renderer.ui.elements.Window;
public class EngineInit {
@ -17,11 +15,11 @@ public class EngineInit {
//
//load the scene
LoadingThread loadingThread = new LoadingThread(LoadingThreadType.LEVEL,"testscene1");
loadingThread.start();
Globals.threadManager.start(loadingThread);
//
//wait for client to be fully init'd
while(((Window)Globals.elementManager.getWindow(WindowStrings.WINDOW_LOADING)).getVisible() || Globals.playerEntity == null){
while(Globals.threadManager.isLoading()){
TestEngineUtils.simulateFrames(1);
try {
TimeUnit.MILLISECONDS.sleep(1);