From 74167038f724c2d2cbab84e58a74254a1a8e12b4 Mon Sep 17 00:00:00 2001 From: austin Date: Fri, 16 Aug 2024 08:04:44 -0400 Subject: [PATCH] server tlc --- buildNumber.properties | 4 +- docs/src/debug/BugLog.md | 9 +++ docs/src/debug/LoadingHalting.md | 6 +- docs/src/debug/indexdebug.md | 1 + docs/src/progress/renderertodo.md | 4 ++ .../loadingthreads/LevelEditorLoading.java | 2 +- .../entity/ServerEntityUtils.java | 25 ++++--- .../groundmove/ClientGroundMovementTree.java | 60 +++++++++-------- .../net/client/ClientNetworking.java | 6 +- .../net/parser/net/raw/NetworkParser.java | 12 ++-- .../net/server/MessageProtocol.java | 2 + .../java/electrosphere/net/server/Server.java | 65 ++++++++++++++++--- .../net/server/ServerConnectionHandler.java | 43 +++++++++--- .../server/MainServerFunctions.java | 6 ++ .../utils/ServerBehaviorTreeUtils.java | 7 +- 15 files changed, 180 insertions(+), 72 deletions(-) create mode 100644 docs/src/debug/BugLog.md diff --git a/buildNumber.properties b/buildNumber.properties index 04588389..c51a7039 100644 --- a/buildNumber.properties +++ b/buildNumber.properties @@ -1,3 +1,3 @@ #maven.buildNumber.plugin properties file -#Thu Aug 15 18:38:57 EDT 2024 -buildNumber=204 +#Fri Aug 16 07:57:34 EDT 2024 +buildNumber=233 diff --git a/docs/src/debug/BugLog.md b/docs/src/debug/BugLog.md new file mode 100644 index 00000000..8b753bed --- /dev/null +++ b/docs/src/debug/BugLog.md @@ -0,0 +1,9 @@ +@page BugLog Bug Log + +A log of bugs encountered + + +### 08-16-2024 + - Starting a server with a local client didn't actually create the server socket/thread. This meant no other players could connect. + - On a client disconnection, the server connection was cleared by IP instead of by socket, so if two connections were from the same IP (ie localhost) it would disconnect the first one to connect. + diff --git a/docs/src/debug/LoadingHalting.md b/docs/src/debug/LoadingHalting.md index 050740b6..bfabe481 100644 --- a/docs/src/debug/LoadingHalting.md +++ b/docs/src/debug/LoadingHalting.md @@ -10,4 +10,8 @@ Arena was not loading because handling of edge-of-world chunks was not being han for a world of 2x2x2, when loading the chunk at (1,0,0) you need data for chunk (2,0,0) (2,0,0) is out of bounds of the world size, but the drawcellmanager would not mark the chunk data as present because it wasn't properly checking for bounds This was fixed by properly checking for bounds in drawcellmanager; however, it then started failing to fetch data for (2,0,0) and NPEing -Fixed by guarding in the method that generates chunk data \ No newline at end of file +Fixed by guarding in the method that generates chunk data + +### 08-16-2024 +Second client could not connect to server started with local player because the routine to start local server didn't actually start a socket + diff --git a/docs/src/debug/indexdebug.md b/docs/src/debug/indexdebug.md index 4dd3a7ed..4b8bb173 100644 --- a/docs/src/debug/indexdebug.md +++ b/docs/src/debug/indexdebug.md @@ -2,4 +2,5 @@ [TOC] +- @subpage BugLog - @subpage LoadingHalting \ No newline at end of file diff --git a/docs/src/progress/renderertodo.md b/docs/src/progress/renderertodo.md index 6ab644ee..20158635 100644 --- a/docs/src/progress/renderertodo.md +++ b/docs/src/progress/renderertodo.md @@ -584,6 +584,10 @@ Play animations offset by network delay - Attack animation Fix viewmodel animation framerate +(08/16/2024) +Fix server not starting +Fix client disconnection causing wrong socket to be closed from server + # TODO diff --git a/src/main/java/electrosphere/engine/loadingthreads/LevelEditorLoading.java b/src/main/java/electrosphere/engine/loadingthreads/LevelEditorLoading.java index 3f1378ac..864bf9f4 100644 --- a/src/main/java/electrosphere/engine/loadingthreads/LevelEditorLoading.java +++ b/src/main/java/electrosphere/engine/loadingthreads/LevelEditorLoading.java @@ -72,7 +72,7 @@ public class LevelEditorLoading { //initialize the local connection Globals.clientUsername = "leveleditor"; Globals.clientPassword = AuthenticationManager.getHashedString("leveleditor"); - ServerConnectionHandler serverPlayerConnection = LoadingUtils.initLocalConnection(false); + ServerConnectionHandler serverPlayerConnection = LoadingUtils.initLocalConnection(true); //wait for player object creation while(Globals.playerManager.getPlayers().size() < 1){ try { diff --git a/src/main/java/electrosphere/entity/ServerEntityUtils.java b/src/main/java/electrosphere/entity/ServerEntityUtils.java index 91d0496a..97f6bd8e 100644 --- a/src/main/java/electrosphere/entity/ServerEntityUtils.java +++ b/src/main/java/electrosphere/entity/ServerEntityUtils.java @@ -85,10 +85,9 @@ public class ServerEntityUtils { * @param entity the entity to destroy */ public static void destroyEntity(Entity entity){ - // - //get info required to destroy - ServerDataCell cell = DataCellSearchUtils.getEntityDataCell(entity); - Realm realm = Globals.realmManager.getEntityRealm(entity); + if(entity == null){ + throw new IllegalArgumentException("Trying to destroy null!"); + } // //destroy the child entities, too @@ -100,22 +99,28 @@ public class ServerEntityUtils { } // - //cell specific logic - if(cell != null){ - cell.broadcastNetworkMessage(EntityMessage.constructDestroyMessage(entity.getId())); - cell.getScene().deregisterEntity(entity); - } + //get info required to destroy + Realm realm = Globals.realmManager.getEntityRealm(entity); + ServerDataCell cell = null; // //realm specific logic if(realm != null){ realm.getCollisionEngine().destroyPhysics(entity); + cell = DataCellSearchUtils.getEntityDataCell(entity); + } + + // + //cell specific logic + if(cell != null){ + cell.broadcastNetworkMessage(EntityMessage.constructDestroyMessage(entity.getId())); + ServerBehaviorTreeUtils.deregisterEntity(entity); + cell.getScene().deregisterEntity(entity); } // //detatch from all global tracking HitboxCollectionState.destroyHitboxState(entity); - ServerBehaviorTreeUtils.deregisterEntity(entity); Globals.realmManager.removeEntity(entity); EntityLookupUtils.removeEntity(entity); Globals.aiManager.removeAI(entity); diff --git a/src/main/java/electrosphere/entity/state/movement/groundmove/ClientGroundMovementTree.java b/src/main/java/electrosphere/entity/state/movement/groundmove/ClientGroundMovementTree.java index 946161ee..aeebf84c 100644 --- a/src/main/java/electrosphere/entity/state/movement/groundmove/ClientGroundMovementTree.java +++ b/src/main/java/electrosphere/entity/state/movement/groundmove/ClientGroundMovementTree.java @@ -146,6 +146,37 @@ public class ClientGroundMovementTree implements BehaviorTree { Vector3d position = EntityUtils.getPosition(parent); Quaterniond rotation = EntityUtils.getRotation(parent); float velocity = CreatureUtils.getVelocity(parent); + if(this.parent == Globals.playerEntity){ + Globals.clientConnection.queueOutgoingMessage( + EntityMessage.constructmoveUpdateMessage( + Globals.clientSceneWrapper.mapClientToServerId(parent.getId()), + Globals.timekeeper.getNumberOfSimFramesElapsed(), + position.x, + position.y, + position.z, + rotation.x, + rotation.y, + rotation.z, + rotation.w, + velocity, + ClientGroundMovementTree.getMovementRelativeFacingEnumAsShort(facing), + 0 //magic number corresponding to state startup + ) + ); + } + } + } + + /** + * Requests to the server that the movetree stop + */ + public void slowdown(){ + state = MovementTreeState.SLOWDOWN; + //if we aren't the server, alert the server we intend to slow down + Vector3d position = EntityUtils.getPosition(parent); + Quaterniond rotation = EntityUtils.getRotation(parent); + float velocity = CreatureUtils.getVelocity(parent); + if(this.parent == Globals.playerEntity){ Globals.clientConnection.queueOutgoingMessage( EntityMessage.constructmoveUpdateMessage( Globals.clientSceneWrapper.mapClientToServerId(parent.getId()), @@ -159,39 +190,12 @@ public class ClientGroundMovementTree implements BehaviorTree { rotation.w, velocity, ClientGroundMovementTree.getMovementRelativeFacingEnumAsShort(facing), - 0 //magic number corresponding to state startup + 2 //magic number corresponding to state slowdown ) ); } } - /** - * Requests to the server that the movetree stop - */ - public void slowdown(){ - state = MovementTreeState.SLOWDOWN; - //if we aren't the server, alert the server we intend to slow down - Vector3d position = EntityUtils.getPosition(parent); - Quaterniond rotation = EntityUtils.getRotation(parent); - float velocity = CreatureUtils.getVelocity(parent); - Globals.clientConnection.queueOutgoingMessage( - EntityMessage.constructmoveUpdateMessage( - Globals.clientSceneWrapper.mapClientToServerId(parent.getId()), - Globals.timekeeper.getNumberOfSimFramesElapsed(), - position.x, - position.y, - position.z, - rotation.x, - rotation.y, - rotation.z, - rotation.w, - velocity, - ClientGroundMovementTree.getMovementRelativeFacingEnumAsShort(facing), - 2 //magic number corresponding to state slowdown - ) - ); - } - @Override public void simulate(float deltaTime){ Actor entityActor = EntityUtils.getActor(parent); diff --git a/src/main/java/electrosphere/net/client/ClientNetworking.java b/src/main/java/electrosphere/net/client/ClientNetworking.java index 544c5efc..6262b980 100644 --- a/src/main/java/electrosphere/net/client/ClientNetworking.java +++ b/src/main/java/electrosphere/net/client/ClientNetworking.java @@ -172,7 +172,11 @@ public class ClientNetworking implements Runnable { //attempt poll incoming messages parser.readMessagesIn(); //outgoing messages - parser.pushMessagesOut(); + try { + parser.pushMessagesOut(); + } catch(IOException e){ + LoggerInterface.loggerNetworking.ERROR(e); + } //parses messages asynchronously this.parseMessagesAsynchronously(); diff --git a/src/main/java/electrosphere/net/parser/net/raw/NetworkParser.java b/src/main/java/electrosphere/net/parser/net/raw/NetworkParser.java index 325a7302..dcd156d7 100644 --- a/src/main/java/electrosphere/net/parser/net/raw/NetworkParser.java +++ b/src/main/java/electrosphere/net/parser/net/raw/NetworkParser.java @@ -1,6 +1,6 @@ -package electrosphere.net.parser.net.raw; - -import electrosphere.net.parser.net.message.NetworkMessage; +package electrosphere.net.parser.net.raw; + +import electrosphere.net.parser.net.message.NetworkMessage; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -55,15 +55,11 @@ public class NetworkParser { } } - public void pushMessagesOut(){ + public void pushMessagesOut() throws IOException { for(NetworkMessage message : outgoingMessageQueue){ outgoingMessageQueue.remove(message); - try { // System.out.println("Write message of type " + message.getType()); outgoingStream.write(message.getRawBytes()); - } catch (IOException ex) { - ex.printStackTrace(); - } } } diff --git a/src/main/java/electrosphere/net/server/MessageProtocol.java b/src/main/java/electrosphere/net/server/MessageProtocol.java index f9662e80..b35f84d3 100644 --- a/src/main/java/electrosphere/net/server/MessageProtocol.java +++ b/src/main/java/electrosphere/net/server/MessageProtocol.java @@ -106,6 +106,7 @@ public class MessageProtocol { if(result != null){ this.synchronousMessageLock.acquireUninterruptibly(); this.synchronousMessageQueue.add(result); + LoggerInterface.loggerNetworking.DEBUG_LOOP("ADD SYNC MESSAGE [Sync queue size: " + this.synchronousMessageQueue.size() + "]"); this.synchronousMessageLock.release(); } Globals.profiler.endCpuSample(); @@ -114,6 +115,7 @@ public class MessageProtocol { public void handleSyncMessages(){ Globals.profiler.beginAggregateCpuSample("MessageProtocol(client).handleSyncMessages"); this.synchronousMessageLock.acquireUninterruptibly(); + LoggerInterface.loggerNetworking.DEBUG_LOOP("HANDLE SYNC MESSAGE [Sync queue size: " + this.synchronousMessageQueue.size() + "]"); for(NetworkMessage message : synchronousMessageQueue){ switch(message.getType()){ case AUTH_MESSAGE: diff --git a/src/main/java/electrosphere/net/server/Server.java b/src/main/java/electrosphere/net/server/Server.java index 5b65445d..27bd4a52 100644 --- a/src/main/java/electrosphere/net/server/Server.java +++ b/src/main/java/electrosphere/net/server/Server.java @@ -2,6 +2,7 @@ package electrosphere.net.server; import electrosphere.engine.Globals; import electrosphere.engine.Main; +import electrosphere.entity.ServerEntityUtils; import electrosphere.logger.LoggerInterface; import electrosphere.net.NetUtils; import electrosphere.net.parser.net.message.NetworkMessage; @@ -14,7 +15,11 @@ import java.net.ServerSocket; import java.net.Socket; import java.net.SocketException; import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; import java.util.Map; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.Semaphore; /** * Lowest level networking class for the server @@ -27,10 +32,17 @@ public class Server implements Runnable{ //the socket for the server ServerSocket serverSocket; - //the map of ip->connection handler - Map clientMap = new HashMap(); - + //Used to synchronize additions/subtractions to the connections stored by this server + Semaphore connectListLock = new Semaphore(1); + + //map of socket->connection + Map socketConnectionMap = new HashMap(); + + //the list of active connections + List activeConnections = new LinkedList(); + //The list of connections to clean up + List connectionsToCleanup = new CopyOnWriteArrayList(); /** @@ -60,19 +72,20 @@ public class Server implements Runnable{ } } catch(BindException ex){ LoggerInterface.loggerNetworking.ERROR("Failed to bind server socket!",ex); - ex.printStackTrace(); } catch (IOException ex) { LoggerInterface.loggerNetworking.ERROR("Failed to start server socket!",ex); - ex.printStackTrace(); - LoggerInterface.loggerNetworking.ERROR("", ex); } while(Main.isRunning()){ Socket newSocket; try { newSocket = serverSocket.accept(); + connectListLock.acquireUninterruptibly(); ServerConnectionHandler newClient = new ServerConnectionHandler(newSocket); - clientMap.put(newSocket.getInetAddress().getHostAddress(), newClient); + // clientMap.put(newSocket.getInetAddress().getHostAddress(), newClient); + socketConnectionMap.put(newSocket, newClient); + activeConnections.add(newClient); new Thread(newClient).start(); + connectListLock.release(); } catch (SocketException ex){ LoggerInterface.loggerNetworking.ERROR("Socket closed!",ex); } catch (IOException ex) { @@ -85,9 +98,11 @@ public class Server implements Runnable{ * Synchronously handles queued packets for each client connection */ public void synchronousPacketHandling(){ - for(ServerConnectionHandler connectionHandler : this.clientMap.values()){ + connectListLock.acquireUninterruptibly(); + for(ServerConnectionHandler connectionHandler : activeConnections){ connectionHandler.handleSynchronousPacketQueue(); } + connectListLock.release(); } /** @@ -108,11 +123,13 @@ public class Server implements Runnable{ * @param message The message to broadcast */ public void broadcastMessage(NetworkMessage message){ - for(ServerConnectionHandler client : clientMap.values()){ + connectListLock.acquireUninterruptibly(); + for(ServerConnectionHandler client : activeConnections){ if(Globals.clientPlayer == null || client.playerID != Globals.clientPlayer.getId()){ client.addMessagetoOutgoingQueue(message); } } + connectListLock.release(); } /** @@ -122,9 +139,37 @@ public class Server implements Runnable{ * @return The connection object for the provided streams */ public ServerConnectionHandler addLocalPlayer(InputStream serverInputStream, OutputStream serverOutputStream){ + connectListLock.acquireUninterruptibly(); ServerConnectionHandler newClient = new ServerConnectionHandler(serverInputStream,serverOutputStream); - clientMap.put("127.0.0.1", newClient); + activeConnections.add(newClient); new Thread(newClient).start(); + connectListLock.release(); return newClient; } + + /** + * Adds a client to the queue of connections to cleanup + * @param serverConnectionHandler The connection + */ + public void addClientToCleanup(ServerConnectionHandler serverConnectionHandler){ + this.connectListLock.acquireUninterruptibly(); + this.connectionsToCleanup.add(serverConnectionHandler); + this.connectListLock.release(); + } + + /** + * Cleans up dead connections on the server + */ + public void cleanupDeadConnections(){ + this.connectListLock.acquireUninterruptibly(); + for(ServerConnectionHandler connection : this.connectionsToCleanup){ + //tell all clients to destroy the entity + ServerEntityUtils.destroyEntity(connection.getPlayer().getPlayerEntity()); + this.activeConnections.remove(connection); + if(connection.getSocket() != null){ + this.socketConnectionMap.remove(connection.getSocket()); + } + } + this.connectListLock.release(); + } } diff --git a/src/main/java/electrosphere/net/server/ServerConnectionHandler.java b/src/main/java/electrosphere/net/server/ServerConnectionHandler.java index 92686791..9d7df697 100644 --- a/src/main/java/electrosphere/net/server/ServerConnectionHandler.java +++ b/src/main/java/electrosphere/net/server/ServerConnectionHandler.java @@ -3,7 +3,6 @@ package electrosphere.net.server; import electrosphere.entity.types.creature.CreatureTemplate; import electrosphere.engine.Globals; import electrosphere.engine.Main; -import electrosphere.entity.ServerEntityUtils; import electrosphere.logger.LoggerInterface; import electrosphere.net.parser.net.message.AuthMessage; import electrosphere.net.parser.net.message.NetworkMessage; @@ -61,6 +60,11 @@ public class ServerConnectionHandler implements Runnable { //the player's entity id int playerEntityID; + /** + * Tracks whether this connection is still communicating with the client + */ + boolean isConnected = true; + //the creature template associated with this player CreatureTemplate currentCreatureTemplate; @@ -189,7 +193,7 @@ public class ServerConnectionHandler implements Runnable { initialized = true; - while(Main.isRunning()){ + while(Main.isRunning() && this.isConnected == true){ // // Main Loop @@ -203,6 +207,16 @@ public class ServerConnectionHandler implements Runnable { //TODO: fix, this doesn't actually catch the socket exception which is exceedingly obnoxious socketException = true; LoggerInterface.loggerNetworking.DEBUG(e.getLocalizedMessage()); + this.disconnect(); + break; + } catch (IOException e){ + //if we get a SocketException broken pipe (basically the client dc'd without telling us) + //set flag to disconnect client + //TODO: fix, this doesn't actually catch the socket exception which is exceedingly obnoxious + socketException = true; + LoggerInterface.loggerNetworking.DEBUG(e.getLocalizedMessage()); + this.disconnect(); + break; } // @@ -233,6 +247,8 @@ public class ServerConnectionHandler implements Runnable { break; } } + + LoggerInterface.loggerNetworking.WARNING("Server connection thread ended"); } /** @@ -240,7 +256,7 @@ public class ServerConnectionHandler implements Runnable { * without my linter freaking out * @throws SocketException */ - void parseMessages() throws SocketException { + void parseMessages() throws SocketException, IOException { // //Read in messages // @@ -333,6 +349,10 @@ public class ServerConnectionHandler implements Runnable { return Globals.playerManager.getPlayerFromId(playerID); } + /** + * Gets the ip address of the connection + * @return The ip address + */ public String getIPAddress(){ if(local){ return "127.0.0.1"; @@ -340,6 +360,14 @@ public class ServerConnectionHandler implements Runnable { return socket.getRemoteSocketAddress().toString(); } } + + /** + * Gets the socket of the connection, if it is a socket based connection. Otherwise returns null. + * @return The socket if it exists, null otherwise + */ + public Socket getSocket(){ + return this.socket; + } public void addMessagetoOutgoingQueue(NetworkMessage message){ networkParser.addOutgoingMessage(message); @@ -372,7 +400,7 @@ public class ServerConnectionHandler implements Runnable { /** * Routine to run when the client disconnects */ - void disconnect(){ + private void disconnect(){ //close socket if(socket.isConnected()){ try { @@ -381,10 +409,9 @@ public class ServerConnectionHandler implements Runnable { LoggerInterface.loggerNetworking.ERROR("Error closing socket", e); } } - //figure out what player we are - Player playerObject = Globals.playerManager.getPlayerFromId(getPlayerId()); - //tell all clients to destroy the entity - ServerEntityUtils.destroyEntity(playerObject.getPlayerEntity()); + this.isConnected = false; + //add connection to server list of connections to cleanup + Globals.server.addClientToCleanup(this); } } diff --git a/src/main/java/electrosphere/server/MainServerFunctions.java b/src/main/java/electrosphere/server/MainServerFunctions.java index 35fdce16..ba931dea 100644 --- a/src/main/java/electrosphere/server/MainServerFunctions.java +++ b/src/main/java/electrosphere/server/MainServerFunctions.java @@ -13,6 +13,12 @@ public class MainServerFunctions { */ public static void simulate(){ + // + //Cleanup disconnected clients + if(Globals.server != null){ + Globals.server.cleanupDeadConnections(); + } + // //Synchronous player message parsing\ Globals.profiler.beginCpuSample("Server synchronous packet parsing"); diff --git a/src/main/java/electrosphere/server/datacell/utils/ServerBehaviorTreeUtils.java b/src/main/java/electrosphere/server/datacell/utils/ServerBehaviorTreeUtils.java index 981fd41c..60623879 100644 --- a/src/main/java/electrosphere/server/datacell/utils/ServerBehaviorTreeUtils.java +++ b/src/main/java/electrosphere/server/datacell/utils/ServerBehaviorTreeUtils.java @@ -34,10 +34,11 @@ public class ServerBehaviorTreeUtils { public static void deregisterEntity(Entity entity){ Set trees = entityBTreeMap.remove(entity); ServerDataCell currentCell = DataCellSearchUtils.getEntityDataCell(entity); - for(BehaviorTree tree : trees){ - currentCell.getScene().deregisterBehaviorTree(tree); + if(trees != null){ + for(BehaviorTree tree : trees){ + currentCell.getScene().deregisterBehaviorTree(tree); + } } - currentCell.getScene().deregisterEntity(entity); } /**