diff --git a/docs/src/progress/renderertodo.md b/docs/src/progress/renderertodo.md index 873598be..720fac7d 100644 --- a/docs/src/progress/renderertodo.md +++ b/docs/src/progress/renderertodo.md @@ -1988,6 +1988,8 @@ Performance improvements - Reduce bones on LOD human model Increase human move speed LOD components re-attach physics +Memory improvements + - Gridded data cell physics cell pooling diff --git a/src/main/java/electrosphere/server/datacell/gridded/GriddedDataCellManager.java b/src/main/java/electrosphere/server/datacell/gridded/GriddedDataCellManager.java index c06d1151..668e7be0 100644 --- a/src/main/java/electrosphere/server/datacell/gridded/GriddedDataCellManager.java +++ b/src/main/java/electrosphere/server/datacell/gridded/GriddedDataCellManager.java @@ -72,112 +72,117 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager /** * The number of frames without players that must pass before a server data cell is unloaded */ - static final int UNLOAD_FRAME_THRESHOLD = 100; + private static final int UNLOAD_FRAME_THRESHOLD = 100; /** * The distance at which simulation is queued */ - static final double SIMULATION_DISTANCE_CUTOFF = 5; + private static final double SIMULATION_DISTANCE_CUTOFF = 5; /** * Big number used when scanning for a data cell to spawn a macro object within */ - static final double MACRO_SCANNING_BIG_NUMBER = 10000; + private static final double MACRO_SCANNING_BIG_NUMBER = 10000; /** * Used for generating physics chunks */ - ExecutorService generationService = null; + private ExecutorService generationService = null; /** * The service for loading data cells from disk */ - GriddedDataCellLoaderService loaderService; + private GriddedDataCellLoaderService loaderService; /** * Tracks whether this manager has been flagged to unload cells or not */ - boolean unloadCells = true; + private boolean unloadCells = true; /** * These are going to be the natural ground grid of data cells, but we're going to have more than this */ - Map groundDataCells = new HashMap(); + private Map groundDataCells = new HashMap(); /** * Map of server cell to its world position */ - Map cellPositionMap = new HashMap(); + private Map cellPositionMap = new HashMap(); /** * Map of server data cell to the number of frames said cell has had no players */ - Map cellPlayerlessFrameMap = new HashMap(); + private Map cellPlayerlessFrameMap = new HashMap(); /** * A map of ServerDataCell->GriddedDataCellTrackingData */ - Map cellTrackingMap = new HashMap(); + private Map cellTrackingMap = new HashMap(); /** * Loaded cells */ - ReentrantLock loadedCellsLock = new ReentrantLock(); + private ReentrantLock loadedCellsLock = new ReentrantLock(); /** * Parent realm */ - Realm parent; + private Realm parent; /** * The world data of the parent */ - ServerWorldData serverWorldData; + private ServerWorldData serverWorldData; /** * Manager for terrain for this particular cell manager */ - ServerTerrainManager serverTerrainManager; + private ServerTerrainManager serverTerrainManager; /** * Manager for fluids for this particular cell manager */ - ServerFluidManager serverFluidManager; + private ServerFluidManager serverFluidManager; /** * Lock for terrain editing */ - Semaphore terrainEditLock = new Semaphore(1); + private Semaphore terrainEditLock = new Semaphore(1); /** * Manager for getting entities to fill in a cell */ - ServerContentManager serverContentManager; + private ServerContentManager serverContentManager; /** * Used for cleaning server data cells no longer in use from the realm */ - Set toCleanQueue = new HashSet(); + private Set toCleanQueue = new HashSet(); /** * Map of world position key -> physics cell */ - Map posPhysicsMap = new HashMap(); + private Map posPhysicsMap = new HashMap(); + + /** + * Pooling for physics data cells + */ + private LinkedList cellPool = new LinkedList(); /** * Number of data cells cleaned up in the most recent frame */ - int numCleaned = 0; + private int numCleaned = 0; /** * Queue of cells that need to have their physics regenerated (ie on block edits) */ - Map physicsQueue = new HashMap(); + private Map physicsQueue = new HashMap(); /** * The pathfinder for the manager */ - VoxelPathfinder pathfinder; + private VoxelPathfinder pathfinder; /** * Caches lookups for nearby entities between simulate() calls @@ -359,7 +364,7 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager loadedCellsLock.lock(); if(posPhysicsMap.containsKey(key)){ PhysicsDataCell cell = posPhysicsMap.get(key); - cell.retireCell(); + cell.destroyEntities(); } loadedCellsLock.unlock(); //get data to generate with @@ -384,7 +389,7 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager //the server will not be able to synchronize it properly. ServerEntityUtils.initiallyPositionEntity(parent,blockEntity,realPos); ServerEntityUtils.initiallyPositionEntity(parent,terrainEntity,realPos); - PhysicsDataCell cell = PhysicsDataCell.createPhysicsCell(worldPos, terrainEntity, blockEntity); + PhysicsDataCell cell = this.getPhysicsDataCell(worldPos, terrainEntity, blockEntity); cell.setTerrainChunk(terrainChunk); cell.setBlockChunk(blockChunkData); cell.generatePhysics(); @@ -545,39 +550,8 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager continue; } - - Vector3i worldPos = this.getCellWorldPosition(cell); - Long key = this.getServerDataCellKey(worldPos); - //entities are serialized before tracking is removed. This makes sure that any side effects from calling destroyEntity (ie if it looks up the chunk that we're deleting) - //don't trigger the chunk to be re-created - Globals.profiler.beginCpuSample("GriddedDataCellManager.unloadPlayerlessChunks - Serialize entities"); - ContentSerialization serializedEntities = ContentSerialization.constructContentSerialization(cell.getScene().getEntityList()); - Globals.profiler.endCpuSample(); - - - Globals.profiler.beginCpuSample("GriddedDataCellManager.unloadPlayerlessChunks - Destroy entities"); - for(Entity entity : cell.getScene().getEntityList()){ - ServerEntityUtils.destroyEntity(entity); - } - Globals.profiler.endCpuSample(); - - //save terrain to disk - //terrain is saved before tracking is removed. This makes sure that any side effects from calling savePositionToDisk (ie if it looks up the chunk that we're deleting) - //don't trigger the chunk to be re-created - Globals.profiler.beginCpuSample("GriddedDataCellManager.unloadPlayerlessChunks - Store data"); - this.loaderService.queueLocationBasedOperation(key, () -> { - serverContentManager.saveSerializationToDisk(key, serializedEntities); - serverTerrainManager.savePositionToDisk(worldPos); - }); - Globals.profiler.endCpuSample(); - - //deregister from all tracking structures - parent.deregisterCell(cell); - groundDataCells.remove(key); - this.posPhysicsMap.remove(key); - this.cellPositionMap.remove(cell); - this.cellTrackingMap.remove(cell); - this.cellPlayerlessFrameMap.remove(cell); + this.unloadCell(cell); + } Globals.profiler.endCpuSample(); loadedCellsLock.unlock(); @@ -598,26 +572,73 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager toCleanQueue.add(cell); } for(ServerDataCell cell : toCleanQueue){ - parent.deregisterCell(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 - for(Entity entity : cell.getScene().getEntityList()){ - ServerEntityUtils.destroyEntity(entity); - } + this.unloadCell(cell); } loadedCellsLock.unlock(); this.serverTerrainManager.evictAll(); toCleanQueue.clear(); } + /** + * Unloads a server data cell + * @param cell The cell + */ + public void unloadCell(ServerDataCell cell){ + Vector3i worldPos = this.getCellWorldPosition(cell); + Long key = this.getServerDataCellKey(worldPos); + //entities are serialized before tracking is removed. This makes sure that any side effects from calling destroyEntity (ie if it looks up the chunk that we're deleting) + //don't trigger the chunk to be re-created + Globals.profiler.beginCpuSample("GriddedDataCellManager.unloadPlayerlessChunks - Serialize entities"); + ContentSerialization serializedEntities = ContentSerialization.constructContentSerialization(cell.getScene().getEntityList()); + Globals.profiler.endCpuSample(); + + + Globals.profiler.beginCpuSample("GriddedDataCellManager.unloadPlayerlessChunks - Destroy entities"); + for(Entity entity : cell.getScene().getEntityList()){ + ServerEntityUtils.destroyEntity(entity); + } + Globals.profiler.endCpuSample(); + + //save terrain to disk + //terrain is saved before tracking is removed. This makes sure that any side effects from calling savePositionToDisk (ie if it looks up the chunk that we're deleting) + //don't trigger the chunk to be re-created + Globals.profiler.beginCpuSample("GriddedDataCellManager.unloadPlayerlessChunks - Store data"); + this.loaderService.queueLocationBasedOperation(key, () -> { + serverContentManager.saveSerializationToDisk(key, serializedEntities); + serverTerrainManager.savePositionToDisk(worldPos); + }); + Globals.profiler.endCpuSample(); + + //deregister from all tracking structures + this.parent.deregisterCell(cell); + this.groundDataCells.remove(key); + PhysicsDataCell releasedCell = this.posPhysicsMap.remove(key); + if(releasedCell != null){ + this.cellPool.add(releasedCell); + releasedCell.destroyEntities(); + } + this.cellPositionMap.remove(cell); + this.cellTrackingMap.remove(cell); + this.cellPlayerlessFrameMap.remove(cell); + } + + /** + * Gets a physics data cell from the pool + * @return The physics data cell + */ + private PhysicsDataCell getPhysicsDataCell(Vector3i worldPos, Entity physicsEntity, Entity blockPhysicsEntity){ + PhysicsDataCell rVal = null; + loadedCellsLock.lock(); + if(cellPool.size() > 0){ + rVal = this.cellPool.poll(); + rVal.reset(worldPos, physicsEntity, blockPhysicsEntity); + } else { + rVal = PhysicsDataCell.createPhysicsCell(physicsEntity, blockPhysicsEntity); + } + loadedCellsLock.unlock(); + return rVal; + } + /** * Get data cell at a given real point in this realm * @param point The real point @@ -819,7 +840,7 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager ServerEntityUtils.initiallyPositionEntity(realm,blockEntity,realPos); ServerEntityUtils.initiallyPositionEntity(realm,terrainEntity,realPos); - PhysicsDataCell targetCell = PhysicsDataCell.createPhysicsCell(worldPos, terrainEntity, blockEntity); + PhysicsDataCell targetCell = this.getPhysicsDataCell(worldPos, terrainEntity, blockEntity); if(cell == null){ posPhysicsMap.put(key, targetCell); } else { @@ -847,7 +868,7 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager //create physics entities if(cell != null){ - cell.retireCell(); + cell.destroyEntities(); cell.generatePhysics(); } else { targetCell.generatePhysics(); @@ -882,6 +903,9 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager Long cellKey = this.getServerDataCellKey(localWorldPos); loadedCellsLock.lock(); + if(groundDataCells.containsKey(cellKey)){ + throw new Error("Creating server data cell at position that already has a cell! " + localWorldPos); + } groundDataCells.put(cellKey,rVal); cellPlayerlessFrameMap.put(rVal,0); LoggerInterface.loggerEngine.DEBUG("Create server data cell with key " + cellKey); @@ -1154,6 +1178,21 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager return cellPlayerlessFrameMap; } + /** + * Gets the tracking data for a cell + * @param cell The cell + * @return The tracking data as a string + */ + public String getCellData(ServerDataCell cell){ + String message = "Failed to find position of cell!\n" + + "groundDataCells: " + this.groundDataCells.values().contains(cell) + "\n" + + "cellPositionMap: " + this.cellPositionMap.keySet().contains(cell) + "\n" + + "cellPlayerlessFrameMap: " + this.cellPositionMap.keySet().contains(cell) + "\n" + + "cellTrackingMap: " + this.cellTrackingMap.keySet().contains(cell) + "\n" + + ""; + return message; + } + @Override public List findPath(Vector3d start, Vector3d end){ Vector3i startChunkPos = ServerWorldData.convertRealToChunkSpace(start); diff --git a/src/main/java/electrosphere/server/datacell/physics/PhysicsDataCell.java b/src/main/java/electrosphere/server/datacell/physics/PhysicsDataCell.java index d5d3432c..68423af5 100644 --- a/src/main/java/electrosphere/server/datacell/physics/PhysicsDataCell.java +++ b/src/main/java/electrosphere/server/datacell/physics/PhysicsDataCell.java @@ -3,7 +3,6 @@ package electrosphere.server.datacell.physics; import electrosphere.client.block.BlockChunkData; import electrosphere.client.terrain.cache.ChunkData; import electrosphere.client.terrain.data.TerrainChunkData; -import electrosphere.engine.Globals; import electrosphere.entity.Entity; import electrosphere.entity.EntityDataStrings; import electrosphere.entity.ServerEntityUtils; @@ -11,39 +10,54 @@ import electrosphere.entity.types.terrain.BlockChunkEntity; import electrosphere.entity.types.terrain.TerrainChunk; import electrosphere.renderer.meshgen.BlockMeshgen; import electrosphere.renderer.meshgen.BlockMeshgen.BlockMeshData; -import electrosphere.server.datacell.Realm; import electrosphere.server.physics.terrain.manager.ServerTerrainChunk; import org.joml.Vector3i; -import org.ode4j.ode.DBody; /** * An entity which contains physics for terrain for a given chunk on the server */ public class PhysicsDataCell { - - Vector3i worldPos; - Entity physicsEntity; - Entity blockPhysicsEntity; + /** + * The terrain physics entity + */ + private Entity physicsEntity; - ServerTerrainChunk terrainChunk; - BlockChunkData blockChunk; - - DBody physicsObject; + /** + * The block physics entity + */ + private Entity blockPhysicsEntity; - float[][][] weights = new float[ServerTerrainChunk.CHUNK_DATA_GENERATOR_SIZE][ServerTerrainChunk.CHUNK_DATA_GENERATOR_SIZE][ServerTerrainChunk.CHUNK_DATA_GENERATOR_SIZE]; - int[][][] types = new int[ServerTerrainChunk.CHUNK_DATA_GENERATOR_SIZE][ServerTerrainChunk.CHUNK_DATA_GENERATOR_SIZE][ServerTerrainChunk.CHUNK_DATA_GENERATOR_SIZE]; + /** + * The terrain chunk data + */ + private ServerTerrainChunk terrainChunk; + + /** + * The block chunk data + */ + private BlockChunkData blockChunk; + + /** + * The weight data + */ + private float[][][] weights = new float[ServerTerrainChunk.CHUNK_DATA_GENERATOR_SIZE][ServerTerrainChunk.CHUNK_DATA_GENERATOR_SIZE][ServerTerrainChunk.CHUNK_DATA_GENERATOR_SIZE]; + + /** + * The type data + */ + private int[][][] types = new int[ServerTerrainChunk.CHUNK_DATA_GENERATOR_SIZE][ServerTerrainChunk.CHUNK_DATA_GENERATOR_SIZE][ServerTerrainChunk.CHUNK_DATA_GENERATOR_SIZE]; /** * The terrain vertex data */ - TerrainChunkData terrainChunkData; + private TerrainChunkData terrainChunkData; /** * The block vertex data */ - BlockMeshData blockData; + private BlockMeshData blockData; /** * Creates a physics cell @@ -52,7 +66,6 @@ public class PhysicsDataCell { * @return The cell */ public static PhysicsDataCell createPhysicsCell( - Vector3i worldPos, Entity physicsEntity, Entity blockPhysicsEntity @@ -60,19 +73,40 @@ public class PhysicsDataCell { PhysicsDataCell rVal = new PhysicsDataCell(); rVal.physicsEntity = physicsEntity; rVal.blockPhysicsEntity = blockPhysicsEntity; - rVal.worldPos = worldPos; return rVal; } /** * Retires a physics data cell */ - public void retireCell(){ + public void destroyEntities(){ ServerEntityUtils.destroyEntity(physicsEntity); this.physicsEntity = null; ServerEntityUtils.destroyEntity(blockPhysicsEntity); this.blockPhysicsEntity = null; } + + /** + * Resets the state of the physics data cell + */ + public void reset( + Vector3i worldPos, + Entity physicsEntity, + Entity blockPhysicsEntity + ){ + if(this.physicsEntity != null){ + ServerEntityUtils.destroyEntity(this.physicsEntity); + } + if(this.blockPhysicsEntity != null){ + ServerEntityUtils.destroyEntity(this.blockPhysicsEntity); + } + this.physicsEntity = physicsEntity; + this.blockPhysicsEntity = blockPhysicsEntity; + this.terrainChunk = null; + this.blockChunk = null; + this.terrainChunkData = null; + this.blockData = null; + } /** * Generates the physics entity for this chunk @@ -105,15 +139,6 @@ public class PhysicsDataCell { localBlockPhysicsEntity.putData(EntityDataStrings.TERRAIN_IS_TERRAIN, true); localBlockPhysicsEntity.putData(EntityDataStrings.BLOCK_ENTITY, true); } - - /** - * Destroys the physics for this data cell - */ - public void destroyPhysics(){ - Realm realm = Globals.serverState.realmManager.getEntityRealm(physicsEntity); - realm.getCollisionEngine().destroyPhysics(physicsEntity); - realm.getCollisionEngine().destroyPhysics(blockPhysicsEntity); - } /** * Fills in the internal arrays of data for generate terrain models