diff --git a/.vscode/launch.json b/.vscode/launch.json index 812ba153..b162a4c5 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -9,14 +9,14 @@ "name": "Launch Current File", "request": "launch", "mainClass": "${file}", - "vmArgs": "-Xmx4G -Xms1024m -Djava.library.path=./shared-folder -XX:+UseZGC -XX:SoftMaxHeapSize=3G -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=\"./tmp/heap.dump\"" + "vmArgs": "-Xmx4G -Xms1024m -Djava.library.path=./shared-folder -XX:+UseZGC -XX:SoftMaxHeapSize=3G -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=\"./tmp/heap.hprof\"" }, { "type": "java", "name": "Launch Main", "request": "launch", "mainClass": "electrosphere.engine.Main", - "vmArgs": "-Xmx4G -Xms1024m -Djava.library.path=./shared-folder -XX:+UseZGC -XX:SoftMaxHeapSize=3G -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=\"./tmp/heap.dump\"", + "vmArgs": "-Xmx4G -Xms1024m -Djava.library.path=./shared-folder -XX:+UseZGC -XX:SoftMaxHeapSize=3G -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=\"./tmp/heap.hprof\"", "projectName": "Renderer" }, { @@ -24,7 +24,7 @@ "name": "Launch Main (Debug Memory)", "request": "launch", "mainClass": "electrosphere.engine.Main", - "vmArgs": "-Xmx4G -Xms1024m -Djava.library.path=./shared-folder -XX:+UseZGC -XX:SoftMaxHeapSize=3G -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=\"./tmp/heap.dump\" -javaagent:./lwjglx-debug-1.0.0.jar=t;o=trace.log", + "vmArgs": "-Xmx4G -Xms1024m -Djava.library.path=./shared-folder -XX:+UseZGC -XX:SoftMaxHeapSize=3G -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=\"./tmp/heap.hprof\" -javaagent:./lwjglx-debug-1.0.0.jar=t;o=trace.log", "projectName": "Renderer" }, { @@ -35,7 +35,7 @@ "env": { "ALSOFT_LOGLEVEL": 4, }, - "vmArgs": "-Xmx4G -Xms1024m -Djava.library.path=./shared-folder -XX:+UseZGC -XX:SoftMaxHeapSize=3G -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=\"./tmp/heap.dump\"", + "vmArgs": "-Xmx4G -Xms1024m -Djava.library.path=./shared-folder -XX:+UseZGC -XX:SoftMaxHeapSize=3G -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=\"./tmp/heap.hprof\"", "projectName": "Renderer" }, { diff --git a/docs/src/progress/renderertodo.md b/docs/src/progress/renderertodo.md index 4cb21ffc..96d1e33f 100644 --- a/docs/src/progress/renderertodo.md +++ b/docs/src/progress/renderertodo.md @@ -1206,6 +1206,7 @@ Move header file generation location Add more debugging tools for fluids Remove conditional update check in fluid sim Explicit memory management of fluid chunk cache buffers +Fix GriddedDataCellManager memory leak caused by physics and ConcurrentHashMap diff --git a/docs/src/tools/profiling/eclipsememoryanalyzer.md b/docs/src/tools/profiling/eclipsememoryanalyzer.md new file mode 100644 index 00000000..af2862fa --- /dev/null +++ b/docs/src/tools/profiling/eclipsememoryanalyzer.md @@ -0,0 +1,3 @@ +@page eclipsememoryanalyzer Eclipse Memory Analyzer + +Useful for viewing memory usage (particularly gives good reports of memory leaks) \ No newline at end of file diff --git a/docs/src/tools/profiling/visualvm.md b/docs/src/tools/profiling/visualvm.md new file mode 100644 index 00000000..b2ad56dc --- /dev/null +++ b/docs/src/tools/profiling/visualvm.md @@ -0,0 +1,3 @@ +@page visualvm VisualVM + +Useful for profiling CPU usage \ No newline at end of file diff --git a/docs/src/tools/toolsindex.md b/docs/src/tools/toolsindex.md index 3c92d938..602942c8 100644 --- a/docs/src/tools/toolsindex.md +++ b/docs/src/tools/toolsindex.md @@ -6,3 +6,5 @@ - @subpage indexdocumentation - @subpage jenkins - @subpage gimp + - @subpage visualvm + - @subpage eclipsememoryanalyzer \ No newline at end of file diff --git a/src/main/java/electrosphere/client/block/BlockChunkCache.java b/src/main/java/electrosphere/client/block/BlockChunkCache.java index 0770fc91..e2cda10a 100644 --- a/src/main/java/electrosphere/client/block/BlockChunkCache.java +++ b/src/main/java/electrosphere/client/block/BlockChunkCache.java @@ -18,7 +18,7 @@ public class BlockChunkCache { /** * Number of chunks to cache */ - static final int CACHE_SIZE = 1500; + static final int CACHE_SIZE = 500; /** * The size of the cache diff --git a/src/main/java/electrosphere/entity/types/terrain/BlockChunkEntity.java b/src/main/java/electrosphere/entity/types/terrain/BlockChunkEntity.java index e372d12a..8872aa74 100644 --- a/src/main/java/electrosphere/entity/types/terrain/BlockChunkEntity.java +++ b/src/main/java/electrosphere/entity/types/terrain/BlockChunkEntity.java @@ -17,12 +17,10 @@ import electrosphere.entity.Entity; import electrosphere.entity.EntityCreationUtils; import electrosphere.entity.EntityDataStrings; import electrosphere.entity.EntityUtils; -import electrosphere.entity.ServerEntityUtils; import electrosphere.entity.types.collision.CollisionObjUtils; import electrosphere.logger.LoggerInterface; import electrosphere.renderer.meshgen.BlockMeshgen; import electrosphere.renderer.meshgen.BlockMeshgen.BlockMeshData; -import electrosphere.server.datacell.Realm; /** * Generates block chunk entities @@ -104,30 +102,16 @@ public class BlockChunkEntity { /** * Creates a block chunk entity on the server - * @param realm The realm - * @param position The position of the chunk + * @param entity The entity to populate * @param weights The weights for the block chunk * @param values The values of each voxel in the chunk * @return The block entity */ - public static Entity serverCreateBlockChunkEntity(Realm realm, Vector3d position, BlockMeshData blockChunkData){ - - Entity rVal = EntityCreationUtils.createServerEntity(realm, position); + public static void serverCreateBlockChunkEntity(Entity entity, BlockMeshData blockChunkData){ if(blockChunkData.getVertices().length > 0){ - PhysicsEntityUtils.serverAttachTriGeomRigidBody(rVal, blockChunkData); - rVal.putData(EntityDataStrings.TERRAIN_IS_TERRAIN, true); + PhysicsEntityUtils.serverAttachTriGeomRigidBody(entity, blockChunkData); + entity.putData(EntityDataStrings.TERRAIN_IS_TERRAIN, true); } - - - //position entity - //this needs to be called at the end of this function. - //Burried underneath this is function call to initialize a server side entity. - //The server initialization logic checks what type of entity this is, if this function is called prior to its type being stored - //the server will not be able to synchronize it properly. - ServerEntityUtils.initiallyPositionEntity(realm,rVal,position); - - - return rVal; } /** diff --git a/src/main/java/electrosphere/entity/types/terrain/TerrainChunk.java b/src/main/java/electrosphere/entity/types/terrain/TerrainChunk.java index ebac2a84..d56d81dd 100644 --- a/src/main/java/electrosphere/entity/types/terrain/TerrainChunk.java +++ b/src/main/java/electrosphere/entity/types/terrain/TerrainChunk.java @@ -18,12 +18,10 @@ import electrosphere.entity.Entity; import electrosphere.entity.EntityCreationUtils; import electrosphere.entity.EntityDataStrings; import electrosphere.entity.EntityUtils; -import electrosphere.entity.ServerEntityUtils; import electrosphere.entity.types.collision.CollisionObjUtils; import electrosphere.logger.LoggerInterface; import electrosphere.renderer.meshgen.TransvoxelModelGeneration; import electrosphere.renderer.meshgen.TransvoxelModelGeneration.TransvoxelChunkData; -import electrosphere.server.datacell.Realm; /** * Utilities for creating terrain chunk entities @@ -104,38 +102,24 @@ public class TerrainChunk { /** * Creates a terrain chunk entity on the server - * @param realm The realm - * @param position The position of the chunk + * @param entity The entity to populate * @param weights The weights for the terrain chunk * @param values The values of each voxel in the chunk - * @return The terrain entity */ - public static Entity serverCreateTerrainChunkEntity(Realm realm, Vector3d position, float[][][] weights, int[][][] values){ + public static void serverCreateTerrainChunkEntity(Entity entity, float[][][] weights, int[][][] values){ TransvoxelChunkData chunkData = new TransvoxelChunkData(weights, values, ClientDrawCellManager.FULL_RES_LOD); TerrainChunkData data = TransvoxelModelGeneration.generateTerrainChunkData(chunkData); - Entity rVal = EntityCreationUtils.createServerEntity(realm, position); if(data.vertices.length > 0){ - PhysicsEntityUtils.serverAttachTriGeomRigidBody(rVal, data); - rVal.putData(EntityDataStrings.TERRAIN_IS_TERRAIN, true); + PhysicsEntityUtils.serverAttachTriGeomRigidBody(entity, data); + entity.putData(EntityDataStrings.TERRAIN_IS_TERRAIN, true); // ServerEntityUtils.initiallyPositionEntity(realm, rVal, position); // physicsObject = PhysicsUtils.attachTerrainRigidBody(physicsEntity,heightmap,true); // Realm realm = Globals.realmManager.getEntityRealm(physicsEntity); // realm.getCollisionEngine().registerPhysicsEntity(physicsEntity); } - - - //position entity - //this needs to be called at the end of this function. - //Burried underneath this is function call to initialize a server side entity. - //The server initialization logic checks what type of entity this is, if this function is called prior to its type being stored - //the server will not be able to synchronize it properly. - ServerEntityUtils.initiallyPositionEntity(realm,rVal,position); - - - return rVal; } /** diff --git a/src/main/java/electrosphere/server/datacell/GriddedDataCellManager.java b/src/main/java/electrosphere/server/datacell/GriddedDataCellManager.java index d3d171f8..8d04f135 100644 --- a/src/main/java/electrosphere/server/datacell/GriddedDataCellManager.java +++ b/src/main/java/electrosphere/server/datacell/GriddedDataCellManager.java @@ -11,12 +11,15 @@ import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; +import java.util.concurrent.locks.ReentrantLock; import org.joml.Vector3d; import org.joml.Vector3i; +import electrosphere.client.block.BlockChunkData; import electrosphere.engine.Globals; import electrosphere.entity.Entity; +import electrosphere.entity.EntityCreationUtils; import electrosphere.entity.EntityUtils; import electrosphere.entity.ServerEntityUtils; import electrosphere.entity.state.server.ServerPlayerViewDirTree; @@ -85,7 +88,7 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager /** * Loaded cells */ - Semaphore loadedCellsLock = new Semaphore(1); + ReentrantLock loadedCellsLock = new ReentrantLock(); /** * Parent realm @@ -172,7 +175,7 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager if(this.canCreateCell(x, y, z) && this.shouldContainPlayer(new Vector3i(x,y,z), worldPos, playerSimulationRadius)){ Vector3i targetPos = new Vector3i(x,y,z); LoggerInterface.loggerEngine.DEBUG("GriddedDataCellManager: Add player to " + x + " " + y + " " + z); - loadedCellsLock.acquireUninterruptibly(); + loadedCellsLock.lock(); if(groundDataCells.get(getServerDataCellKey(targetPos)) != null){ groundDataCells.get(getServerDataCellKey(targetPos)).addPlayer(player); } else { @@ -184,7 +187,7 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager //add player groundDataCells.get(getServerDataCellKey(targetPos)).addPlayer(player); } - loadedCellsLock.release(); + loadedCellsLock.unlock(); } } } @@ -221,18 +224,18 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager 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(getServerDataCellKey(targetPos)) != null){ - loadedCellsLock.acquireUninterruptibly(); + loadedCellsLock.lock(); groundDataCells.get(getServerDataCellKey(targetPos)).addPlayer(player); - loadedCellsLock.release(); + loadedCellsLock.unlock(); } else { - loadedCellsLock.acquireUninterruptibly(); + loadedCellsLock.lock(); //create data cell createServerDataCell(targetPos); //add to loaded cells cellPlayerlessFrameMap.put(groundDataCells.get(getServerDataCellKey(targetPos)),0); //add player groundDataCells.get(getServerDataCellKey(targetPos)).addPlayer(player); - loadedCellsLock.release(); + loadedCellsLock.unlock(); } } } @@ -290,13 +293,32 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager Long key = this.getServerDataCellKey(worldPos); if(posPhysicsMap.containsKey(key)){ PhysicsDataCell cell = posPhysicsMap.get(key); - cell.retireCell(); cell.generatePhysics(); - } else { - PhysicsDataCell cell = PhysicsDataCell.createPhysicsCell(parent, worldPos); - cell.generatePhysics(); - posPhysicsMap.put(key, cell); } + //get data to generate with + Vector3d realPos = new Vector3d( + worldPos.x * ServerTerrainChunk.CHUNK_PLACEMENT_OFFSET, + worldPos.y * ServerTerrainChunk.CHUNK_PLACEMENT_OFFSET, + worldPos.z * ServerTerrainChunk.CHUNK_PLACEMENT_OFFSET + ); + + BlockChunkData blockChunkData = parent.getServerWorldData().getServerBlockManager().getChunk(worldPos.x, worldPos.y, worldPos.z); + ServerTerrainChunk terrainChunk = parent.getServerWorldData().getServerTerrainManager().getChunk(worldPos.x, worldPos.y, worldPos.z); + + //create entities + Entity blockEntity = EntityCreationUtils.createServerEntity(parent, realPos); + Entity terrainEntity = EntityCreationUtils.createServerEntity(parent, realPos); + + //position entity + //this needs to be called at the end of this function. + //Burried underneath this is function call to initialize a server side entity. + //The server initialization logic checks what type of entity this is, if this function is called prior to its type being stored + //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, terrainChunk, blockChunkData); + cell.generatePhysics(); + posPhysicsMap.put(key, cell); } /** @@ -318,6 +340,7 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager int playerSimulationRadius = player.getSimulationRadius(); //remove from cells that are out of range + loadedCellsLock.lock(); for(ServerDataCell cell : this.groundDataCells.values()){ Vector3i cellWorldPos = this.getCellWorldPosition(cell); if(cell.containsPlayer(player) && !this.shouldContainPlayer(cellWorldPos, newPosition, playerSimulationRadius)){ @@ -328,6 +351,7 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager this.broadcastDestructionToPlayer(player, cell); } } + loadedCellsLock.unlock(); //Add to cells that are in range for(int x = newPosition.x - playerSimulationRadius + 1; x < newPosition.x + playerSimulationRadius; x++){ @@ -336,21 +360,21 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager 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){ - loadedCellsLock.acquireUninterruptibly(); + loadedCellsLock.lock(); ServerDataCell cell = groundDataCells.get(this.getServerDataCellKey(targetPos)); if(!cell.containsPlayer(player)){ cell.addPlayer(player); } - loadedCellsLock.release(); + loadedCellsLock.unlock(); } else { - loadedCellsLock.acquireUninterruptibly(); + loadedCellsLock.lock(); //create data cell this.createServerDataCell(targetPos); //add to loaded cells cellPlayerlessFrameMap.put(groundDataCells.get(this.getServerDataCellKey(targetPos)),0); //add player groundDataCells.get(this.getServerDataCellKey(targetPos)).addPlayer(player); - loadedCellsLock.release(); + loadedCellsLock.unlock(); } } } @@ -368,17 +392,19 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager public void unloadPlayerlessChunks(){ if(this.unloadCells){ //TODO: improve to make have less performance impact - loadedCellsLock.acquireUninterruptibly(); + loadedCellsLock.lock(); for(ServerDataCell cell : this.groundDataCells.values()){ - if(cell.isReady() && cell.getPlayers().size() < 1){ - int frameCount = cellPlayerlessFrameMap.get(cell) + 1; - cellPlayerlessFrameMap.put(cell,frameCount); - if(frameCount > UNLOAD_FRAME_THRESHOLD){ - toCleanQueue.add(cell); - } - } else { - if(cellPlayerlessFrameMap.get(cell) > 0){ - cellPlayerlessFrameMap.put(cell, 0); + if(cellPlayerlessFrameMap.containsKey(cell)){ + if(cell.isReady() && cell.getPlayers().size() < 1){ + int frameCount = cellPlayerlessFrameMap.get(cell) + 1; + cellPlayerlessFrameMap.put(cell,frameCount); + if(frameCount > UNLOAD_FRAME_THRESHOLD){ + toCleanQueue.add(cell); + } + } else { + if(cellPlayerlessFrameMap.get(cell) > 0){ + cellPlayerlessFrameMap.put(cell, 0); + } } } } @@ -410,6 +436,9 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager Vector3i worldPos = getCellWorldPosition(cell); Long key = getServerDataCellKey(worldPos); groundDataCells.remove(key); + this.posPhysicsMap.remove(key); + this.cellPositionMap.remove(cell); + this.cellPlayerlessFrameMap.remove(cell); //offload all entities in cell to chunk file serverContentManager.saveContentToDisk(key, cell.getScene().getEntityList()); for(Entity entity : cell.getScene().getEntityList()){ @@ -418,7 +447,7 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager //save terrain to disk serverTerrainManager.savePositionToDisk(worldPos); } - loadedCellsLock.release(); + loadedCellsLock.unlock(); toCleanQueue.clear(); } } @@ -428,7 +457,7 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager */ public void evictAll(){ //TODO: improve to make have less performance impact - loadedCellsLock.acquireUninterruptibly(); + loadedCellsLock.lock(); for(ServerDataCell cell : this.groundDataCells.values()){ int frameCount = cellPlayerlessFrameMap.get(cell) + 1; cellPlayerlessFrameMap.put(cell,frameCount); @@ -439,6 +468,9 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager Vector3i worldPos = getCellWorldPosition(cell); Long key = getServerDataCellKey(worldPos); groundDataCells.remove(key); + this.posPhysicsMap.remove(key); + this.cellPositionMap.remove(cell); + this.cellPlayerlessFrameMap.remove(cell); //offload all entities in cell to chunk file serverContentManager.saveContentToDisk(key, cell.getScene().getEntityList()); //clear all entities in cell @@ -446,7 +478,7 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager ServerEntityUtils.destroyEntity(entity); } } - loadedCellsLock.release(); + loadedCellsLock.unlock(); this.serverTerrainManager.evictAll(); toCleanQueue.clear(); } @@ -498,12 +530,12 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager */ public ServerDataCell tryCreateCellAtPoint(Vector3i worldPos){ if(this.canCreateCell(worldPos) && groundDataCells.get(this.getServerDataCellKey(worldPos)) == null){ - loadedCellsLock.acquireUninterruptibly(); + loadedCellsLock.lock(); //create data cell this.createServerDataCell(worldPos); //add to loaded cells cellPlayerlessFrameMap.put(groundDataCells.get(this.getServerDataCellKey(worldPos)),0); - loadedCellsLock.release(); + loadedCellsLock.unlock(); } else if(groundDataCells.get(this.getServerDataCellKey(worldPos)) == null) { LoggerInterface.loggerEngine.WARNING( "Trying to create data cell outside world bounds!\n" + @@ -532,7 +564,7 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager */ public void simulate(){ Globals.profiler.beginCpuSample("GriddedDataCellManager.simulate"); - loadedCellsLock.acquireUninterruptibly(); + loadedCellsLock.lock(); for(ServerDataCell cell : this.groundDataCells.values()){ if(Globals.microSimulation != null && Globals.microSimulation.isReady()){ Globals.microSimulation.simulate(cell); @@ -540,19 +572,21 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager //queue fluid simulation Vector3i cellPos = this.getCellWorldPosition(cell); - this.serverFluidManager.queue(cellPos.x, cellPos.y, cellPos.z); + if(cellPos != null){ + this.serverFluidManager.queue(cellPos.x, cellPos.y, cellPos.z); + } } //simulate fluids - this.serverFluidManager.simulate(); + this.serverFluidManager.simulate((ServerFluidChunk fluidChunk) -> { + ServerDataCell cell = getCellAtWorldPosition(fluidChunk.getWorldPosition()); + ServerFluidChunk chunk = getFluidChunkAtPosition(fluidChunk.getWorldPosition()); + cell.broadcastNetworkMessage( + TerrainMessage.constructupdateFluidDataMessage(fluidChunk.getWorldX(), fluidChunk.getWorldY(), fluidChunk.getWorldZ(), TerrainProtocol.constructFluidByteBuffer(chunk).array()) + ); + }); - //rebroadcast updated chunks - for(ServerFluidChunk chunk : this.serverFluidManager.getBroadcastQueue()){ - this.rebroadcastFluidChunk(chunk.getWorldPosition()); - } - this.serverFluidManager.getBroadcastQueue().clear(); - - loadedCellsLock.release(); + loadedCellsLock.unlock(); this.unloadPlayerlessChunks(); this.updatePlayerPositions(); Globals.profiler.endCpuSample(); @@ -580,18 +614,55 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager * Because cell hasn't been registered yet, no simulation is performed until the physics is created. * @param worldPos */ - private void runPhysicsGenerationThread(Vector3i worldPos){ + private static void runPhysicsGenerationThread( + Vector3i worldPos, + Long key, + PhysicsDataCell cell, + Map posPhysicsMap, + Map groundDataCells, + Realm realm + ){ + //get data to generate with + Vector3d realPos = new Vector3d( + worldPos.x * ServerTerrainChunk.CHUNK_PLACEMENT_OFFSET, + worldPos.y * ServerTerrainChunk.CHUNK_PLACEMENT_OFFSET, + worldPos.z * ServerTerrainChunk.CHUNK_PLACEMENT_OFFSET + ); + BlockChunkData blockChunkData = realm.getServerWorldData().getServerBlockManager().getChunk(worldPos.x, worldPos.y, worldPos.z); + ServerTerrainChunk terrainChunk = realm.getServerWorldData().getServerTerrainManager().getChunk(worldPos.x, worldPos.y, worldPos.z); + ServerDataCell dataCell = groundDataCells.get(key); + + //create entities + Entity blockEntity = EntityCreationUtils.createServerEntity(realm, realPos); + Entity terrainEntity = EntityCreationUtils.createServerEntity(realm, realPos); + + //position entity + //this needs to be called at the end of this function. + //Burried underneath this is function call to initialize a server side entity. + //The server initialization logic checks what type of entity this is, if this function is called prior to its type being stored + //the server will not be able to synchronize it properly. + ServerEntityUtils.initiallyPositionEntity(realm,blockEntity,realPos); + ServerEntityUtils.initiallyPositionEntity(realm,terrainEntity,realPos); + + PhysicsDataCell targetCell = PhysicsDataCell.createPhysicsCell(worldPos, terrainEntity, blockEntity, terrainChunk, blockChunkData); + if(cell == null){ + posPhysicsMap.put(key, targetCell); + } else { + ServerEntityUtils.destroyEntity(terrainEntity); + ServerEntityUtils.destroyEntity(blockEntity); + } + generationService.submit(() -> { //create physics entities - createTerrainPhysicsEntities(worldPos); - //set ready - if(groundDataCells.get(getServerDataCellKey(worldPos)) != null){ - groundDataCells.get(getServerDataCellKey(worldPos)).setReady(true); + if(cell != null){ + cell.retireCell(); + cell.generatePhysics(); } else { - LoggerInterface.loggerEngine.WARNING("Finished generating physics for server cell, but cell is null!"); + targetCell.generatePhysics(); } + //set ready + dataCell.setReady(true); }); - groundDataCells.get(getServerDataCellKey(worldPos)).setReady(false); } /** @@ -616,7 +687,9 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager cellPositionMap.put(rVal,new Vector3i(worldPos)); serverContentManager.generateContentForDataCell(parent, worldPos, rVal, cellKey); //generates physics for the cell in a dedicated thread then finally registers - this.runPhysicsGenerationThread(worldPos); + Long key = this.getServerDataCellKey(worldPos); + PhysicsDataCell cell = posPhysicsMap.get(key); + GriddedDataCellManager.runPhysicsGenerationThread(worldPos,key,cell,this.posPhysicsMap,this.groundDataCells,this.parent); return rVal; } @@ -699,6 +772,7 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager serverTerrainManager.deformTerrainAtLocationToValue(toUpdate, new Vector3i(localVoxelX, localVoxelY, localVoxelZ), weight, type); //update anything loaded + this.loadedCellsLock.lock(); ServerDataCell cell = groundDataCells.get(this.getServerDataCellKey(toUpdate)); if(cell != null){ //update physics @@ -710,6 +784,7 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager localVoxelX, localVoxelY, localVoxelZ, weight, type)); } + this.loadedCellsLock.unlock(); } } terrainEditLock.release(); @@ -747,20 +822,6 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager } } - /** - * Rebroadcasts the fluid cell at a given position - * @param worldPosition the world position - */ - private void rebroadcastFluidChunk(Vector3i worldPosition){ - Globals.profiler.beginAggregateCpuSample("GriddedDataCellManager.rebroadcastFluidChunk"); - ServerDataCell cell = getCellAtWorldPosition(worldPosition); - ServerFluidChunk chunk = getFluidChunkAtPosition(worldPosition); - cell.broadcastNetworkMessage( - TerrainMessage.constructupdateFluidDataMessage(worldPosition.x, worldPosition.y, worldPosition.z, TerrainProtocol.constructFluidByteBuffer(chunk).array()) - ); - Globals.profiler.endCpuSample(); - } - @Override public void save(String saveName) { for(ServerDataCell cell : this.groundDataCells.values()){ diff --git a/src/main/java/electrosphere/server/datacell/RealmManager.java b/src/main/java/electrosphere/server/datacell/RealmManager.java index 77bfdfae..722126d1 100644 --- a/src/main/java/electrosphere/server/datacell/RealmManager.java +++ b/src/main/java/electrosphere/server/datacell/RealmManager.java @@ -1,9 +1,10 @@ package electrosphere.server.datacell; +import java.util.HashMap; import java.util.Map; import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArraySet; +import java.util.concurrent.locks.ReentrantLock; import org.joml.Vector3d; @@ -27,9 +28,14 @@ public class RealmManager { //All realms in this manager Set realms = new CopyOnWriteArraySet(); //Map of entities to the realm the entity is in - Map entityToRealmMap = new ConcurrentHashMap(); + Map entityToRealmMap = new HashMap(); //Map of player to the realm the player is in - Map playerToRealmMap = new ConcurrentHashMap(); + Map playerToRealmMap = new HashMap(); + + /** + * Lock for thread-safing the manager + */ + ReentrantLock lock = new ReentrantLock(); /** * Constructor @@ -126,7 +132,9 @@ public class RealmManager { * @param realm The realm */ public void mapEntityToRealm(Entity entity, Realm realm){ + lock.lock(); entityToRealmMap.put(entity, realm); + lock.unlock(); } /** @@ -134,7 +142,9 @@ public class RealmManager { * @param entity The entity to remove */ public void removeEntity(Entity entity){ + lock.lock(); entityToRealmMap.remove(entity); + lock.unlock(); } /** @@ -143,7 +153,10 @@ public class RealmManager { * @return The realm, or null if the entity is not inside a realm */ public Realm getEntityRealm(Entity entity){ - return entityToRealmMap.get(entity); + lock.lock(); + Realm rVal = entityToRealmMap.get(entity); + lock.unlock(); + return rVal; } /** @@ -173,7 +186,9 @@ public class RealmManager { * @param realm The realm */ public void setPlayerRealm(Player player, Realm realm){ + lock.lock(); playerToRealmMap.put(player, realm); + lock.unlock(); } /** @@ -182,7 +197,10 @@ public class RealmManager { * @return The realm */ public Realm getPlayerRealm(Player player){ - return playerToRealmMap.get(player); + lock.lock(); + Realm rVal = playerToRealmMap.get(player); + lock.unlock(); + return rVal; } /** @@ -221,8 +239,10 @@ public class RealmManager { } } this.realms.clear(); + lock.lock(); this.entityToRealmMap.clear(); this.playerToRealmMap.clear(); + lock.unlock(); } /** diff --git a/src/main/java/electrosphere/server/datacell/physics/PhysicsDataCell.java b/src/main/java/electrosphere/server/datacell/physics/PhysicsDataCell.java index 509d64b9..cc8c0ea3 100644 --- a/src/main/java/electrosphere/server/datacell/physics/PhysicsDataCell.java +++ b/src/main/java/electrosphere/server/datacell/physics/PhysicsDataCell.java @@ -1,5 +1,6 @@ package electrosphere.server.datacell.physics; +import electrosphere.client.block.BlockChunkData; import electrosphere.client.terrain.cache.ChunkData; import electrosphere.engine.Globals; import electrosphere.entity.Entity; @@ -11,9 +12,7 @@ import electrosphere.renderer.meshgen.BlockMeshgen; import electrosphere.renderer.meshgen.BlockMeshgen.BlockMeshData; import electrosphere.server.datacell.Realm; import electrosphere.server.terrain.manager.ServerTerrainChunk; -import electrosphere.server.terrain.manager.ServerTerrainManager; -import org.joml.Vector3d; import org.joml.Vector3i; import org.ode4j.ode.DBody; @@ -26,12 +25,12 @@ public class PhysicsDataCell { Entity physicsEntity; Entity blockPhysicsEntity; + + ServerTerrainChunk terrainChunk; + BlockChunkData blockChunk; DBody physicsObject; - Realm realm; - ServerTerrainManager serverTerrainManager; - 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]; @@ -47,13 +46,19 @@ public class PhysicsDataCell { * @return The cell */ public static PhysicsDataCell createPhysicsCell( - Realm realm, - Vector3i worldPos + Vector3i worldPos, + Entity physicsEntity, + Entity blockPhysicsEntity, + ServerTerrainChunk currentChunk, + BlockChunkData blockChunk + ){ PhysicsDataCell rVal = new PhysicsDataCell(); - rVal.realm = realm; - rVal.serverTerrainManager = realm.getServerWorldData().getServerTerrainManager(); + rVal.physicsEntity = physicsEntity; + rVal.blockPhysicsEntity = blockPhysicsEntity; rVal.worldPos = worldPos; + rVal.blockChunk = blockChunk; + rVal.terrainChunk = currentChunk; return rVal; } @@ -72,31 +77,17 @@ public class PhysicsDataCell { */ public void generatePhysics(){ //if the entity hasn't already been created for some reason, need to create it - if(physicsEntity == null){ - // - //fill in weights and types maps - // - this.fillInData(); + // + //fill in weights and types maps + // + this.fillInData(); - Vector3d realPos = new Vector3d( - worldPos.x * ServerTerrainChunk.CHUNK_PLACEMENT_OFFSET, - worldPos.y * ServerTerrainChunk.CHUNK_PLACEMENT_OFFSET, - worldPos.z * ServerTerrainChunk.CHUNK_PLACEMENT_OFFSET - ); - physicsEntity = TerrainChunk.serverCreateTerrainChunkEntity(realm, realPos, weights, types); - physicsEntity.putData(EntityDataStrings.TERRAIN_IS_TERRAIN, true); - } - if(blockPhysicsEntity == null){ - Vector3d realPos = new Vector3d( - worldPos.x * ServerTerrainChunk.CHUNK_PLACEMENT_OFFSET, - worldPos.y * ServerTerrainChunk.CHUNK_PLACEMENT_OFFSET, - worldPos.z * ServerTerrainChunk.CHUNK_PLACEMENT_OFFSET - ); - BlockMeshData meshData = BlockMeshgen.rasterize(realm.getServerWorldData().getServerBlockManager().getChunk(worldPos.x, worldPos.y, worldPos.z)); - blockPhysicsEntity = BlockChunkEntity.serverCreateBlockChunkEntity(realm, realPos, meshData); - blockPhysicsEntity.putData(EntityDataStrings.TERRAIN_IS_TERRAIN, true); - } + TerrainChunk.serverCreateTerrainChunkEntity(physicsEntity, weights, types); + physicsEntity.putData(EntityDataStrings.TERRAIN_IS_TERRAIN, true); + BlockMeshData meshData = BlockMeshgen.rasterize(blockChunk); + BlockChunkEntity.serverCreateBlockChunkEntity(blockPhysicsEntity, meshData); + blockPhysicsEntity.putData(EntityDataStrings.TERRAIN_IS_TERRAIN, true); // //then actually perform the attach // physicsObject = PhysicsUtils.attachTerrainRigidBody(physicsEntity,heightmap,true); // Realm realm = Globals.realmManager.getEntityRealm(physicsEntity); @@ -120,12 +111,11 @@ public class PhysicsDataCell { //fill in data // //main chunk - ServerTerrainChunk currentChunk = serverTerrainManager.getChunk(worldPos.x, worldPos.y, worldPos.z); for(int x = 0; x < ChunkData.CHUNK_DATA_SIZE; x++){ for(int y = 0; y < ChunkData.CHUNK_DATA_SIZE; y++){ for(int z = 0; z < ChunkData.CHUNK_DATA_SIZE; z++){ - weights[x][y][z] = currentChunk.getWeight(x,y,z); - types[x][y][z] = currentChunk.getType(x,y,z); + weights[x][y][z] = terrainChunk.getWeight(x,y,z); + types[x][y][z] = terrainChunk.getType(x,y,z); } } } diff --git a/src/main/java/electrosphere/server/fluid/manager/ServerFluidManager.java b/src/main/java/electrosphere/server/fluid/manager/ServerFluidManager.java index c0a28306..f29aeb2b 100644 --- a/src/main/java/electrosphere/server/fluid/manager/ServerFluidManager.java +++ b/src/main/java/electrosphere/server/fluid/manager/ServerFluidManager.java @@ -14,11 +14,13 @@ import electrosphere.util.annotation.Exclude; import java.nio.ByteBuffer; import java.nio.FloatBuffer; +import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Consumer; import org.joml.Vector3i; @@ -30,7 +32,7 @@ public class ServerFluidManager { /** * DEfault size of the cache */ - static final int DEFAULT_CACHE_SIZE = 500; + static final int DEFAULT_CACHE_SIZE = 50; /** * The number of frames to wait between updates @@ -88,13 +90,13 @@ public class ServerFluidManager { /** * The queued of chunks to simulate */ - List simulationQueue = new LinkedList(); + List simulationQueue = new ArrayList(); @Exclude /** * The queue of chunks to broadcast */ - List broadcastQueue = new LinkedList(); + List broadcastQueue = new ArrayList(); @Exclude /** @@ -287,14 +289,16 @@ public class ServerFluidManager { * @param worldZ The world z coordinate of the chunk */ public void queue(int worldX, int worldY, int worldZ){ - ServerFluidChunk fluidChunk = this.getChunk(worldX, worldY, worldZ); - this.simulationQueue.add(fluidChunk); + if(simulate){ + ServerFluidChunk fluidChunk = this.getChunk(worldX, worldY, worldZ); + this.simulationQueue.add(fluidChunk); + } } /** * Simulates a chunk */ - public void simulate(){ + public void simulate(Consumer onUpdate){ Globals.profiler.beginAggregateCpuSample("ServerFluidManager.simulate"); lock.lock(); if(simulate){ @@ -304,8 +308,15 @@ public class ServerFluidManager { } } - this.simulationQueue.clear(); + //clear both queues + while(this.simulationQueue.size() > 0){ + this.simulationQueue.remove(0); + } this.broadcastSize = this.broadcastQueue.size(); + while(this.broadcastQueue.size() > 0){ + ServerFluidChunk toBroadcast = this.broadcastQueue.remove(0); + onUpdate.accept(toBroadcast); + } updatePhase++; if(updatePhase > UPDATE_RATE){