From 86c867fe313a462a624f26b2a2a1372c158ee440 Mon Sep 17 00:00:00 2001 From: austin Date: Fri, 4 Apr 2025 16:34:13 -0400 Subject: [PATCH] GriddedDataCellTrackingData + reduce allocs --- docs/src/progress/renderertodo.md | 5 + .../server/datacell/ServerDataCell.java | 29 ++++-- .../gridded/GriddedDataCellManager.java | 95 ++++++++++++++----- .../gridded/GriddedDataCellTrackingData.java | 36 +++++++ 4 files changed, 132 insertions(+), 33 deletions(-) create mode 100644 src/main/java/electrosphere/server/datacell/gridded/GriddedDataCellTrackingData.java diff --git a/docs/src/progress/renderertodo.md b/docs/src/progress/renderertodo.md index 07776c62..37881d97 100644 --- a/docs/src/progress/renderertodo.md +++ b/docs/src/progress/renderertodo.md @@ -1432,6 +1432,11 @@ Fix PoseActor position/rotation caching bug Bush drops sticks Unarmed combat +(04/04/2025) +GriddedDataCellTrackingData created +Reduce allocations in GriddedDataCellManager methods that loop cells +Implement max distance for queueing for simulation + diff --git a/src/main/java/electrosphere/server/datacell/ServerDataCell.java b/src/main/java/electrosphere/server/datacell/ServerDataCell.java index 914cefbd..358abb59 100644 --- a/src/main/java/electrosphere/server/datacell/ServerDataCell.java +++ b/src/main/java/electrosphere/server/datacell/ServerDataCell.java @@ -28,17 +28,25 @@ import java.util.Set; */ public class ServerDataCell { - //all players attached to this server data cell + /** + * All players attached to this server data cell + */ Set activePlayers = new HashSet(); - //the navmesh for the data cell + /** + * The navmesh for the data cell + */ NavMesh navMesh; - //the scene backing the server data cell + /** + * The scene backing the server data cell + */ Scene scene; - //controls whether the server data cell simulates its entities or not - boolean ready = false; + /** + * Controls whether the server data cell simulates its entities or not + */ + boolean ready = false; /** * Constructs a datacell based on a virtual cell. Should be used when a player @@ -50,6 +58,11 @@ public class ServerDataCell { } + /** + * Creates a server data cell + * @param scene The scene to wrap the server data cell around + * @return The server data cell + */ protected static ServerDataCell createServerDataCell(Scene scene){ return new ServerDataCell(scene); } @@ -120,10 +133,10 @@ public class ServerDataCell { for(Player player : this.activePlayers){ if(previousCell != null){ if(!previousCell.containsPlayer(player)){ - serializeEntityToPlayer(creature,player); + this.serializeEntityToPlayer(creature,player); } } else { - serializeEntityToPlayer(creature,player); + this.serializeEntityToPlayer(creature,player); } } } @@ -143,7 +156,7 @@ public class ServerDataCell { * @param entity The entity to serialize * @param player The player to send the entity to */ - void serializeEntityToPlayer(Entity entity, Player player){ + private void serializeEntityToPlayer(Entity entity, Player player){ if(!player.hasSentPlayerEntity() || player.getPlayerEntity() == null || player.getPlayerEntity() != entity){ EntityType type = CommonEntityUtils.getEntityType(entity); if(type != null){ diff --git a/src/main/java/electrosphere/server/datacell/gridded/GriddedDataCellManager.java b/src/main/java/electrosphere/server/datacell/gridded/GriddedDataCellManager.java index 030f54b5..f5347cde 100644 --- a/src/main/java/electrosphere/server/datacell/gridded/GriddedDataCellManager.java +++ b/src/main/java/electrosphere/server/datacell/gridded/GriddedDataCellManager.java @@ -64,6 +64,11 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager */ static final int UNLOAD_FRAME_THRESHOLD = 100; + /** + * The distance at which simulation is queued + */ + static final double SIMULATION_DISTANCE_CUTOFF = 5; + /** * Used for generating physics chunks */ @@ -89,6 +94,11 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager */ Map cellPlayerlessFrameMap = new HashMap(); + /** + * A map of ServerDataCell->GriddedDataCellTrackingData + */ + Map cellTrackingMap = new HashMap(); + /** * Loaded cells */ @@ -178,21 +188,23 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager Globals.realmManager.setPlayerRealm(player, parent); int playerSimulationRadius = player.getSimulationRadius(); Vector3i worldPos = player.getWorldPos(); + Vector3i tempVec = new Vector3i(); for(int x = worldPos.x - playerSimulationRadius; x < worldPos.x + playerSimulationRadius + 1; x++){ for(int y = worldPos.y - playerSimulationRadius; y < worldPos.y + playerSimulationRadius + 1; y++){ for(int z = worldPos.z - playerSimulationRadius; z < worldPos.z + playerSimulationRadius + 1; z++){ - if(this.canCreateCell(x, y, z) && this.shouldContainPlayer(new Vector3i(x,y,z), worldPos, playerSimulationRadius)){ - Vector3i targetPos = new Vector3i(x,y,z); + tempVec.set(x,y,z); + double distance = this.calcDistance(tempVec, worldPos); + if(this.canCreateCell(x, y, z) && this.shouldContainPlayer(distance, playerSimulationRadius)){ LoggerInterface.loggerEngine.DEBUG("GriddedDataCellManager: Add player to " + x + " " + y + " " + z); loadedCellsLock.lock(); - if(groundDataCells.get(getServerDataCellKey(targetPos)) != null){ - groundDataCells.get(getServerDataCellKey(targetPos)).addPlayer(player); + if(groundDataCells.get(this.getServerDataCellKey(tempVec)) != null){ + groundDataCells.get(this.getServerDataCellKey(tempVec)).addPlayer(player); } else { LoggerInterface.loggerEngine.DEBUG("Creating new cell @ " + x + " " + y + " " + z); //create data cell - this.createServerDataCell(targetPos); + this.createServerDataCell(tempVec); //add player - groundDataCells.get(getServerDataCellKey(targetPos)).addPlayer(player); + groundDataCells.get(this.getServerDataCellKey(tempVec)).addPlayer(player); } loadedCellsLock.unlock(); } @@ -211,7 +223,7 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager player.setWorldPos(newPosition); for(ServerDataCell cell : this.groundDataCells.values()){ Vector3i worldPos = this.getCellWorldPosition(cell); - if(cell.containsPlayer(player) && !this.shouldContainPlayer(worldPos, newPosition, playerSimulationRadius)){ + if(cell.containsPlayer(player) && !this.shouldContainPlayer(this.calcDistance(worldPos, newPosition), playerSimulationRadius)){ cell.removePlayer(player); this.broadcastDestructionToPlayer(player, cell); if(cell.getScene().containsEntity(player.getPlayerEntity())){ @@ -228,7 +240,7 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager for(int x = newPosition.x - playerSimulationRadius; x < newPosition.x + playerSimulationRadius + 1; x++){ for(int y = newPosition.y - playerSimulationRadius; y < newPosition.y + playerSimulationRadius + 1; y++){ for(int z = newPosition.x - playerSimulationRadius; z < newPosition.z + playerSimulationRadius + 1; z++){ - if(this.canCreateCell(x, y, z) && this.shouldContainPlayer(new Vector3i(x, y, z), newPosition, playerSimulationRadius)){ + if(this.canCreateCell(x, y, z) && this.shouldContainPlayer(this.calcDistance(new Vector3i(x, y, z), newPosition), playerSimulationRadius)){ Vector3i targetPos = new Vector3i(x,y,z); if(groundDataCells.get(this.getServerDataCellKey(targetPos)) != null){ loadedCellsLock.lock(); @@ -255,8 +267,18 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager * @param simRadius The simulation radius of the player * @return true if the player should be contained in the cell, false otherwise */ - private boolean shouldContainPlayer(Vector3i cellWorldPos, Vector3i playerPos, int simRadius){ - return cellWorldPos.distance(playerPos) < simRadius; + private boolean shouldContainPlayer(double distance, int simRadius){ + return distance < simRadius; + } + + /** + * Calculates the distance from the player to the cell + * @param playerPos The player + * @param cellWorldPos The cell + * @return The distance + */ + public double calcDistance(Vector3i playerPos, Vector3i cellWorldPos){ + return cellWorldPos.distance(playerPos); } /** @@ -337,7 +359,17 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager * @return True if the player changed cell, false otherwise */ public boolean updatePlayerPositions(){ - Globals.profiler.beginCpuSample("GriddedDataCellManager.updatePlayerPositions"); + Globals.profiler.beginCpuSample("GriddedDataCellManager.updatePlayerPositions - Reset chunk distances"); + loadedCellsLock.lock(); + Globals.profiler.beginCpuSample("GriddedDataCellManager.updatePlayerPositions - Remove from old cells"); + for(ServerDataCell cell : this.groundDataCells.values()){ + GriddedDataCellTrackingData trackingData = this.cellTrackingMap.get(cell); + trackingData.setClosestPlayer(GriddedDataCellTrackingData.REALLY_LARGE_DISTANCE); + } + loadedCellsLock.unlock(); + Globals.profiler.endCpuSample(); + + Globals.profiler.beginCpuSample("GriddedDataCellManager.updatePlayerPositions - Actually update player positions"); boolean playerChangedChunk = false; for(Player player : Globals.playerManager.getPlayers()){ Entity playerEntity = player.getPlayerEntity(); @@ -355,8 +387,13 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager loadedCellsLock.lock(); Globals.profiler.beginCpuSample("GriddedDataCellManager.updatePlayerPositions - Remove from old cells"); for(ServerDataCell cell : this.groundDataCells.values()){ + GriddedDataCellTrackingData trackingData = this.cellTrackingMap.get(cell); Vector3i cellWorldPos = this.getCellWorldPosition(cell); - if(cell.containsPlayer(player) && !this.shouldContainPlayer(cellWorldPos, newPosition, playerSimulationRadius)){ + double distance = this.calcDistance(cellWorldPos, newPosition); + if(distance < trackingData.getClosestPlayer()){ + trackingData.setClosestPlayer(distance); + } + if(cell.containsPlayer(player) && !this.shouldContainPlayer(distance, playerSimulationRadius)){ if(cell.getScene().containsEntity(player.getPlayerEntity())){ throw new Error("Trying to remove player from a cell that contains its entity!"); } @@ -369,14 +406,16 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager //Add to cells that are in range Globals.profiler.beginCpuSample("GriddedDataCellManager.updatePlayerPositions - Create new cells"); + Vector3i tempVec = new Vector3i(); for(int x = newPosition.x - playerSimulationRadius + 1; x < newPosition.x + playerSimulationRadius; x++){ for(int y = newPosition.y - playerSimulationRadius + 1; y < newPosition.y + playerSimulationRadius; y++){ for(int z = newPosition.z - playerSimulationRadius + 1; z < newPosition.z + playerSimulationRadius; z++){ - if(this.canCreateCell(x,y,z) && this.shouldContainPlayer(new Vector3i(x,y,z), newPosition, playerSimulationRadius)){ - Vector3i targetPos = new Vector3i(x,y,z); - if(groundDataCells.get(this.getServerDataCellKey(targetPos)) != null){ + tempVec.set(x,y,z); + double distance = this.calcDistance(tempVec, newPosition); + if(this.canCreateCell(x,y,z) && this.shouldContainPlayer(distance, playerSimulationRadius)){ + if(groundDataCells.get(this.getServerDataCellKey(tempVec)) != null){ loadedCellsLock.lock(); - ServerDataCell cell = groundDataCells.get(this.getServerDataCellKey(targetPos)); + ServerDataCell cell = groundDataCells.get(this.getServerDataCellKey(tempVec)); if(!cell.containsPlayer(player)){ cell.addPlayer(player); } @@ -384,9 +423,9 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager } else { loadedCellsLock.lock(); //create data cell - this.createServerDataCell(targetPos); + this.createServerDataCell(tempVec); //add player - groundDataCells.get(this.getServerDataCellKey(targetPos)).addPlayer(player); + groundDataCells.get(this.getServerDataCellKey(tempVec)).addPlayer(player); loadedCellsLock.unlock(); } } @@ -484,6 +523,7 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager groundDataCells.remove(key); this.posPhysicsMap.remove(key); this.cellPositionMap.remove(cell); + this.cellTrackingMap.remove(cell); this.cellPlayerlessFrameMap.remove(cell); } Globals.profiler.endCpuSample(); @@ -506,12 +546,13 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager } for(ServerDataCell cell : toCleanQueue){ parent.deregisterCell(cell); - Vector3i worldPos = getCellWorldPosition(cell); + Vector3i worldPos = this.getCellWorldPosition(cell); Long key = getServerDataCellKey(worldPos); groundDataCells.remove(key); this.posPhysicsMap.remove(key); this.cellPositionMap.remove(cell); this.cellPlayerlessFrameMap.remove(cell); + this.cellTrackingMap.remove(cell); //offload all entities in cell to chunk file serverContentManager.saveContentToDisk(key, cell.getScene().getEntityList()); //clear all entities in cell @@ -644,7 +685,8 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager * @return true if it should be simulated, false otherwise */ private boolean shouldSimulate(ServerDataCell cell){ - return cell.getPlayers().size() > 0; + GriddedDataCellTrackingData trackingData = this.cellTrackingMap.get(cell); + return cell.getPlayers().size() > 0 && trackingData.getClosestPlayer() < SIMULATION_DISTANCE_CUTOFF; } @@ -739,24 +781,27 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager private ServerDataCell createServerDataCell(Vector3i worldPos){ Globals.profiler.beginCpuSample("GriddedDataCellManager.createServerDataCell"); ServerDataCell rVal = parent.createNewCell(); - Long cellKey = this.getServerDataCellKey(worldPos); + Vector3i localWorldPos = new Vector3i(worldPos); + Long cellKey = this.getServerDataCellKey(localWorldPos); loadedCellsLock.lock(); groundDataCells.put(cellKey,rVal); cellPlayerlessFrameMap.put(rVal,0); LoggerInterface.loggerEngine.DEBUG("Create server data cell with key " + cellKey); - cellPositionMap.put(rVal,new Vector3i(worldPos)); + cellPositionMap.put(rVal,localWorldPos); + GriddedDataCellTrackingData trackingData = new GriddedDataCellTrackingData(); + this.cellTrackingMap.put(rVal,trackingData); loadedCellsLock.unlock(); - Long key = this.getServerDataCellKey(worldPos); + Long key = this.getServerDataCellKey(localWorldPos); //generate content GriddedDataCellLoaderService.queueLocationBasedOperation(key, () -> { - serverContentManager.generateContentForDataCell(parent, worldPos, rVal, cellKey); + serverContentManager.generateContentForDataCell(parent, localWorldPos, rVal, cellKey); }); //generates physics for the cell in a dedicated thread then finally registers loadedCellsLock.lock(); PhysicsDataCell cell = posPhysicsMap.get(key); - GriddedDataCellManager.runPhysicsGenerationThread(worldPos,key,cell,this.posPhysicsMap,this.groundDataCells,this.parent); + GriddedDataCellManager.runPhysicsGenerationThread(localWorldPos,key,cell,this.posPhysicsMap,this.groundDataCells,this.parent); loadedCellsLock.unlock(); diff --git a/src/main/java/electrosphere/server/datacell/gridded/GriddedDataCellTrackingData.java b/src/main/java/electrosphere/server/datacell/gridded/GriddedDataCellTrackingData.java new file mode 100644 index 00000000..fddab20d --- /dev/null +++ b/src/main/java/electrosphere/server/datacell/gridded/GriddedDataCellTrackingData.java @@ -0,0 +1,36 @@ +package electrosphere.server.datacell.gridded; + +/** + * Data associated with a ServerDataCell by the GriddedDataCellManager + */ +public class GriddedDataCellTrackingData { + + /** + * A really large distance used to reset the position + */ + public static final double REALLY_LARGE_DISTANCE = 1000; + + /** + * The from the cell to the closest player + */ + double closestPlayer; + + /** + * Gets the distance from the cell to the closest player + * @return The distance + */ + public double getClosestPlayer() { + return closestPlayer; + } + + /** + * Sets the distance to the closest player + * @param closestPlayer The distance to the closest player + */ + public void setClosestPlayer(double closestPlayer) { + this.closestPlayer = closestPlayer; + } + + + +}