Fix memory leak
All checks were successful
studiorailgun/Renderer/pipeline/head This commit looks good

This commit is contained in:
austin 2024-12-01 15:21:54 -05:00
parent 0e2c29d8a1
commit 569311a23e
12 changed files with 214 additions and 155 deletions

8
.vscode/launch.json vendored
View File

@ -9,14 +9,14 @@
"name": "Launch Current File", "name": "Launch Current File",
"request": "launch", "request": "launch",
"mainClass": "${file}", "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", "type": "java",
"name": "Launch Main", "name": "Launch Main",
"request": "launch", "request": "launch",
"mainClass": "electrosphere.engine.Main", "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" "projectName": "Renderer"
}, },
{ {
@ -24,7 +24,7 @@
"name": "Launch Main (Debug Memory)", "name": "Launch Main (Debug Memory)",
"request": "launch", "request": "launch",
"mainClass": "electrosphere.engine.Main", "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" "projectName": "Renderer"
}, },
{ {
@ -35,7 +35,7 @@
"env": { "env": {
"ALSOFT_LOGLEVEL": 4, "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" "projectName": "Renderer"
}, },
{ {

View File

@ -1206,6 +1206,7 @@ Move header file generation location
Add more debugging tools for fluids Add more debugging tools for fluids
Remove conditional update check in fluid sim Remove conditional update check in fluid sim
Explicit memory management of fluid chunk cache buffers Explicit memory management of fluid chunk cache buffers
Fix GriddedDataCellManager memory leak caused by physics and ConcurrentHashMap

View File

@ -0,0 +1,3 @@
@page eclipsememoryanalyzer Eclipse Memory Analyzer
Useful for viewing memory usage (particularly gives good reports of memory leaks)

View File

@ -0,0 +1,3 @@
@page visualvm VisualVM
Useful for profiling CPU usage

View File

@ -6,3 +6,5 @@
- @subpage indexdocumentation - @subpage indexdocumentation
- @subpage jenkins - @subpage jenkins
- @subpage gimp - @subpage gimp
- @subpage visualvm
- @subpage eclipsememoryanalyzer

View File

@ -18,7 +18,7 @@ public class BlockChunkCache {
/** /**
* Number of chunks to cache * Number of chunks to cache
*/ */
static final int CACHE_SIZE = 1500; static final int CACHE_SIZE = 500;
/** /**
* The size of the cache * The size of the cache

View File

@ -17,12 +17,10 @@ import electrosphere.entity.Entity;
import electrosphere.entity.EntityCreationUtils; import electrosphere.entity.EntityCreationUtils;
import electrosphere.entity.EntityDataStrings; import electrosphere.entity.EntityDataStrings;
import electrosphere.entity.EntityUtils; import electrosphere.entity.EntityUtils;
import electrosphere.entity.ServerEntityUtils;
import electrosphere.entity.types.collision.CollisionObjUtils; import electrosphere.entity.types.collision.CollisionObjUtils;
import electrosphere.logger.LoggerInterface; import electrosphere.logger.LoggerInterface;
import electrosphere.renderer.meshgen.BlockMeshgen; import electrosphere.renderer.meshgen.BlockMeshgen;
import electrosphere.renderer.meshgen.BlockMeshgen.BlockMeshData; import electrosphere.renderer.meshgen.BlockMeshgen.BlockMeshData;
import electrosphere.server.datacell.Realm;
/** /**
* Generates block chunk entities * Generates block chunk entities
@ -104,30 +102,16 @@ public class BlockChunkEntity {
/** /**
* Creates a block chunk entity on the server * Creates a block chunk entity on the server
* @param realm The realm * @param entity The entity to populate
* @param position The position of the chunk
* @param weights The weights for the block chunk * @param weights The weights for the block chunk
* @param values The values of each voxel in the chunk * @param values The values of each voxel in the chunk
* @return The block entity * @return The block entity
*/ */
public static Entity serverCreateBlockChunkEntity(Realm realm, Vector3d position, BlockMeshData blockChunkData){ public static void serverCreateBlockChunkEntity(Entity entity, BlockMeshData blockChunkData){
Entity rVal = EntityCreationUtils.createServerEntity(realm, position);
if(blockChunkData.getVertices().length > 0){ if(blockChunkData.getVertices().length > 0){
PhysicsEntityUtils.serverAttachTriGeomRigidBody(rVal, blockChunkData); PhysicsEntityUtils.serverAttachTriGeomRigidBody(entity, blockChunkData);
rVal.putData(EntityDataStrings.TERRAIN_IS_TERRAIN, true); 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;
} }
/** /**

View File

@ -18,12 +18,10 @@ import electrosphere.entity.Entity;
import electrosphere.entity.EntityCreationUtils; import electrosphere.entity.EntityCreationUtils;
import electrosphere.entity.EntityDataStrings; import electrosphere.entity.EntityDataStrings;
import electrosphere.entity.EntityUtils; import electrosphere.entity.EntityUtils;
import electrosphere.entity.ServerEntityUtils;
import electrosphere.entity.types.collision.CollisionObjUtils; import electrosphere.entity.types.collision.CollisionObjUtils;
import electrosphere.logger.LoggerInterface; import electrosphere.logger.LoggerInterface;
import electrosphere.renderer.meshgen.TransvoxelModelGeneration; import electrosphere.renderer.meshgen.TransvoxelModelGeneration;
import electrosphere.renderer.meshgen.TransvoxelModelGeneration.TransvoxelChunkData; import electrosphere.renderer.meshgen.TransvoxelModelGeneration.TransvoxelChunkData;
import electrosphere.server.datacell.Realm;
/** /**
* Utilities for creating terrain chunk entities * Utilities for creating terrain chunk entities
@ -104,38 +102,24 @@ public class TerrainChunk {
/** /**
* Creates a terrain chunk entity on the server * Creates a terrain chunk entity on the server
* @param realm The realm * @param entity The entity to populate
* @param position The position of the chunk
* @param weights The weights for the terrain chunk * @param weights The weights for the terrain chunk
* @param values The values of each voxel in the 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); TransvoxelChunkData chunkData = new TransvoxelChunkData(weights, values, ClientDrawCellManager.FULL_RES_LOD);
TerrainChunkData data = TransvoxelModelGeneration.generateTerrainChunkData(chunkData); TerrainChunkData data = TransvoxelModelGeneration.generateTerrainChunkData(chunkData);
Entity rVal = EntityCreationUtils.createServerEntity(realm, position);
if(data.vertices.length > 0){ if(data.vertices.length > 0){
PhysicsEntityUtils.serverAttachTriGeomRigidBody(rVal, data); PhysicsEntityUtils.serverAttachTriGeomRigidBody(entity, data);
rVal.putData(EntityDataStrings.TERRAIN_IS_TERRAIN, true); entity.putData(EntityDataStrings.TERRAIN_IS_TERRAIN, true);
// ServerEntityUtils.initiallyPositionEntity(realm, rVal, position); // ServerEntityUtils.initiallyPositionEntity(realm, rVal, position);
// physicsObject = PhysicsUtils.attachTerrainRigidBody(physicsEntity,heightmap,true); // physicsObject = PhysicsUtils.attachTerrainRigidBody(physicsEntity,heightmap,true);
// Realm realm = Globals.realmManager.getEntityRealm(physicsEntity); // Realm realm = Globals.realmManager.getEntityRealm(physicsEntity);
// realm.getCollisionEngine().registerPhysicsEntity(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;
} }
/** /**

View File

@ -11,12 +11,15 @@ import java.util.Set;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore; import java.util.concurrent.Semaphore;
import java.util.concurrent.locks.ReentrantLock;
import org.joml.Vector3d; import org.joml.Vector3d;
import org.joml.Vector3i; import org.joml.Vector3i;
import electrosphere.client.block.BlockChunkData;
import electrosphere.engine.Globals; import electrosphere.engine.Globals;
import electrosphere.entity.Entity; import electrosphere.entity.Entity;
import electrosphere.entity.EntityCreationUtils;
import electrosphere.entity.EntityUtils; import electrosphere.entity.EntityUtils;
import electrosphere.entity.ServerEntityUtils; import electrosphere.entity.ServerEntityUtils;
import electrosphere.entity.state.server.ServerPlayerViewDirTree; import electrosphere.entity.state.server.ServerPlayerViewDirTree;
@ -85,7 +88,7 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager
/** /**
* Loaded cells * Loaded cells
*/ */
Semaphore loadedCellsLock = new Semaphore(1); ReentrantLock loadedCellsLock = new ReentrantLock();
/** /**
* Parent realm * 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)){ if(this.canCreateCell(x, y, z) && this.shouldContainPlayer(new Vector3i(x,y,z), worldPos, playerSimulationRadius)){
Vector3i targetPos = new Vector3i(x,y,z); Vector3i targetPos = new Vector3i(x,y,z);
LoggerInterface.loggerEngine.DEBUG("GriddedDataCellManager: Add player to " + x + " " + y + " " + z); LoggerInterface.loggerEngine.DEBUG("GriddedDataCellManager: Add player to " + x + " " + y + " " + z);
loadedCellsLock.acquireUninterruptibly(); loadedCellsLock.lock();
if(groundDataCells.get(getServerDataCellKey(targetPos)) != null){ if(groundDataCells.get(getServerDataCellKey(targetPos)) != null){
groundDataCells.get(getServerDataCellKey(targetPos)).addPlayer(player); groundDataCells.get(getServerDataCellKey(targetPos)).addPlayer(player);
} else { } else {
@ -184,7 +187,7 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager
//add player //add player
groundDataCells.get(getServerDataCellKey(targetPos)).addPlayer(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)){ if(this.canCreateCell(x, y, z) && this.shouldContainPlayer(new Vector3i(x, y, z), newPosition, playerSimulationRadius)){
Vector3i targetPos = new Vector3i(x,y,z); Vector3i targetPos = new Vector3i(x,y,z);
if(groundDataCells.get(getServerDataCellKey(targetPos)) != null){ if(groundDataCells.get(getServerDataCellKey(targetPos)) != null){
loadedCellsLock.acquireUninterruptibly(); loadedCellsLock.lock();
groundDataCells.get(getServerDataCellKey(targetPos)).addPlayer(player); groundDataCells.get(getServerDataCellKey(targetPos)).addPlayer(player);
loadedCellsLock.release(); loadedCellsLock.unlock();
} else { } else {
loadedCellsLock.acquireUninterruptibly(); loadedCellsLock.lock();
//create data cell //create data cell
createServerDataCell(targetPos); createServerDataCell(targetPos);
//add to loaded cells //add to loaded cells
cellPlayerlessFrameMap.put(groundDataCells.get(getServerDataCellKey(targetPos)),0); cellPlayerlessFrameMap.put(groundDataCells.get(getServerDataCellKey(targetPos)),0);
//add player //add player
groundDataCells.get(getServerDataCellKey(targetPos)).addPlayer(player); groundDataCells.get(getServerDataCellKey(targetPos)).addPlayer(player);
loadedCellsLock.release(); loadedCellsLock.unlock();
} }
} }
} }
@ -290,14 +293,33 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager
Long key = this.getServerDataCellKey(worldPos); Long key = this.getServerDataCellKey(worldPos);
if(posPhysicsMap.containsKey(key)){ if(posPhysicsMap.containsKey(key)){
PhysicsDataCell cell = posPhysicsMap.get(key); PhysicsDataCell cell = posPhysicsMap.get(key);
cell.retireCell();
cell.generatePhysics(); cell.generatePhysics();
} else { }
PhysicsDataCell cell = PhysicsDataCell.createPhysicsCell(parent, worldPos); //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(); cell.generatePhysics();
posPhysicsMap.put(key, cell); posPhysicsMap.put(key, cell);
} }
}
/** /**
* For every player, looks at their entity and determines what data cell they should be considered inside of * For every player, looks at their entity and determines what data cell they should be considered inside of
@ -318,6 +340,7 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager
int playerSimulationRadius = player.getSimulationRadius(); int playerSimulationRadius = player.getSimulationRadius();
//remove from cells that are out of range //remove from cells that are out of range
loadedCellsLock.lock();
for(ServerDataCell cell : this.groundDataCells.values()){ for(ServerDataCell cell : this.groundDataCells.values()){
Vector3i cellWorldPos = this.getCellWorldPosition(cell); Vector3i cellWorldPos = this.getCellWorldPosition(cell);
if(cell.containsPlayer(player) && !this.shouldContainPlayer(cellWorldPos, newPosition, playerSimulationRadius)){ if(cell.containsPlayer(player) && !this.shouldContainPlayer(cellWorldPos, newPosition, playerSimulationRadius)){
@ -328,6 +351,7 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager
this.broadcastDestructionToPlayer(player, cell); this.broadcastDestructionToPlayer(player, cell);
} }
} }
loadedCellsLock.unlock();
//Add to cells that are in range //Add to cells that are in range
for(int x = newPosition.x - playerSimulationRadius + 1; x < newPosition.x + playerSimulationRadius; x++){ 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)){ if(this.canCreateCell(x,y,z) && this.shouldContainPlayer(new Vector3i(x,y,z), newPosition, playerSimulationRadius)){
Vector3i targetPos = new Vector3i(x,y,z); Vector3i targetPos = new Vector3i(x,y,z);
if(groundDataCells.get(this.getServerDataCellKey(targetPos)) != null){ if(groundDataCells.get(this.getServerDataCellKey(targetPos)) != null){
loadedCellsLock.acquireUninterruptibly(); loadedCellsLock.lock();
ServerDataCell cell = groundDataCells.get(this.getServerDataCellKey(targetPos)); ServerDataCell cell = groundDataCells.get(this.getServerDataCellKey(targetPos));
if(!cell.containsPlayer(player)){ if(!cell.containsPlayer(player)){
cell.addPlayer(player); cell.addPlayer(player);
} }
loadedCellsLock.release(); loadedCellsLock.unlock();
} else { } else {
loadedCellsLock.acquireUninterruptibly(); loadedCellsLock.lock();
//create data cell //create data cell
this.createServerDataCell(targetPos); this.createServerDataCell(targetPos);
//add to loaded cells //add to loaded cells
cellPlayerlessFrameMap.put(groundDataCells.get(this.getServerDataCellKey(targetPos)),0); cellPlayerlessFrameMap.put(groundDataCells.get(this.getServerDataCellKey(targetPos)),0);
//add player //add player
groundDataCells.get(this.getServerDataCellKey(targetPos)).addPlayer(player); groundDataCells.get(this.getServerDataCellKey(targetPos)).addPlayer(player);
loadedCellsLock.release(); loadedCellsLock.unlock();
} }
} }
} }
@ -368,8 +392,9 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager
public void unloadPlayerlessChunks(){ public void unloadPlayerlessChunks(){
if(this.unloadCells){ if(this.unloadCells){
//TODO: improve to make have less performance impact //TODO: improve to make have less performance impact
loadedCellsLock.acquireUninterruptibly(); loadedCellsLock.lock();
for(ServerDataCell cell : this.groundDataCells.values()){ for(ServerDataCell cell : this.groundDataCells.values()){
if(cellPlayerlessFrameMap.containsKey(cell)){
if(cell.isReady() && cell.getPlayers().size() < 1){ if(cell.isReady() && cell.getPlayers().size() < 1){
int frameCount = cellPlayerlessFrameMap.get(cell) + 1; int frameCount = cellPlayerlessFrameMap.get(cell) + 1;
cellPlayerlessFrameMap.put(cell,frameCount); cellPlayerlessFrameMap.put(cell,frameCount);
@ -382,6 +407,7 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager
} }
} }
} }
}
for(ServerDataCell cell : toCleanQueue){ for(ServerDataCell cell : toCleanQueue){
boolean containsPlayerEntity = false; boolean containsPlayerEntity = false;
//clear all entities in cell //clear all entities in cell
@ -410,6 +436,9 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager
Vector3i worldPos = getCellWorldPosition(cell); Vector3i worldPos = getCellWorldPosition(cell);
Long key = getServerDataCellKey(worldPos); Long key = getServerDataCellKey(worldPos);
groundDataCells.remove(key); groundDataCells.remove(key);
this.posPhysicsMap.remove(key);
this.cellPositionMap.remove(cell);
this.cellPlayerlessFrameMap.remove(cell);
//offload all entities in cell to chunk file //offload all entities in cell to chunk file
serverContentManager.saveContentToDisk(key, cell.getScene().getEntityList()); serverContentManager.saveContentToDisk(key, cell.getScene().getEntityList());
for(Entity entity : cell.getScene().getEntityList()){ for(Entity entity : cell.getScene().getEntityList()){
@ -418,7 +447,7 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager
//save terrain to disk //save terrain to disk
serverTerrainManager.savePositionToDisk(worldPos); serverTerrainManager.savePositionToDisk(worldPos);
} }
loadedCellsLock.release(); loadedCellsLock.unlock();
toCleanQueue.clear(); toCleanQueue.clear();
} }
} }
@ -428,7 +457,7 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager
*/ */
public void evictAll(){ public void evictAll(){
//TODO: improve to make have less performance impact //TODO: improve to make have less performance impact
loadedCellsLock.acquireUninterruptibly(); loadedCellsLock.lock();
for(ServerDataCell cell : this.groundDataCells.values()){ for(ServerDataCell cell : this.groundDataCells.values()){
int frameCount = cellPlayerlessFrameMap.get(cell) + 1; int frameCount = cellPlayerlessFrameMap.get(cell) + 1;
cellPlayerlessFrameMap.put(cell,frameCount); cellPlayerlessFrameMap.put(cell,frameCount);
@ -439,6 +468,9 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager
Vector3i worldPos = getCellWorldPosition(cell); Vector3i worldPos = getCellWorldPosition(cell);
Long key = getServerDataCellKey(worldPos); Long key = getServerDataCellKey(worldPos);
groundDataCells.remove(key); groundDataCells.remove(key);
this.posPhysicsMap.remove(key);
this.cellPositionMap.remove(cell);
this.cellPlayerlessFrameMap.remove(cell);
//offload all entities in cell to chunk file //offload all entities in cell to chunk file
serverContentManager.saveContentToDisk(key, cell.getScene().getEntityList()); serverContentManager.saveContentToDisk(key, cell.getScene().getEntityList());
//clear all entities in cell //clear all entities in cell
@ -446,7 +478,7 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager
ServerEntityUtils.destroyEntity(entity); ServerEntityUtils.destroyEntity(entity);
} }
} }
loadedCellsLock.release(); loadedCellsLock.unlock();
this.serverTerrainManager.evictAll(); this.serverTerrainManager.evictAll();
toCleanQueue.clear(); toCleanQueue.clear();
} }
@ -498,12 +530,12 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager
*/ */
public ServerDataCell tryCreateCellAtPoint(Vector3i worldPos){ public ServerDataCell tryCreateCellAtPoint(Vector3i worldPos){
if(this.canCreateCell(worldPos) && groundDataCells.get(this.getServerDataCellKey(worldPos)) == null){ if(this.canCreateCell(worldPos) && groundDataCells.get(this.getServerDataCellKey(worldPos)) == null){
loadedCellsLock.acquireUninterruptibly(); loadedCellsLock.lock();
//create data cell //create data cell
this.createServerDataCell(worldPos); this.createServerDataCell(worldPos);
//add to loaded cells //add to loaded cells
cellPlayerlessFrameMap.put(groundDataCells.get(this.getServerDataCellKey(worldPos)),0); cellPlayerlessFrameMap.put(groundDataCells.get(this.getServerDataCellKey(worldPos)),0);
loadedCellsLock.release(); loadedCellsLock.unlock();
} else if(groundDataCells.get(this.getServerDataCellKey(worldPos)) == null) { } else if(groundDataCells.get(this.getServerDataCellKey(worldPos)) == null) {
LoggerInterface.loggerEngine.WARNING( LoggerInterface.loggerEngine.WARNING(
"Trying to create data cell outside world bounds!\n" + "Trying to create data cell outside world bounds!\n" +
@ -532,7 +564,7 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager
*/ */
public void simulate(){ public void simulate(){
Globals.profiler.beginCpuSample("GriddedDataCellManager.simulate"); Globals.profiler.beginCpuSample("GriddedDataCellManager.simulate");
loadedCellsLock.acquireUninterruptibly(); loadedCellsLock.lock();
for(ServerDataCell cell : this.groundDataCells.values()){ for(ServerDataCell cell : this.groundDataCells.values()){
if(Globals.microSimulation != null && Globals.microSimulation.isReady()){ if(Globals.microSimulation != null && Globals.microSimulation.isReady()){
Globals.microSimulation.simulate(cell); Globals.microSimulation.simulate(cell);
@ -540,19 +572,21 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager
//queue fluid simulation //queue fluid simulation
Vector3i cellPos = this.getCellWorldPosition(cell); Vector3i cellPos = this.getCellWorldPosition(cell);
if(cellPos != null){
this.serverFluidManager.queue(cellPos.x, cellPos.y, cellPos.z); this.serverFluidManager.queue(cellPos.x, cellPos.y, cellPos.z);
} }
}
//simulate fluids //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 loadedCellsLock.unlock();
for(ServerFluidChunk chunk : this.serverFluidManager.getBroadcastQueue()){
this.rebroadcastFluidChunk(chunk.getWorldPosition());
}
this.serverFluidManager.getBroadcastQueue().clear();
loadedCellsLock.release();
this.unloadPlayerlessChunks(); this.unloadPlayerlessChunks();
this.updatePlayerPositions(); this.updatePlayerPositions();
Globals.profiler.endCpuSample(); 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. * Because cell hasn't been registered yet, no simulation is performed until the physics is created.
* @param worldPos * @param worldPos
*/ */
private void runPhysicsGenerationThread(Vector3i worldPos){ private static void runPhysicsGenerationThread(
Vector3i worldPos,
Long key,
PhysicsDataCell cell,
Map<Long, PhysicsDataCell> posPhysicsMap,
Map<Long, ServerDataCell> 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(() -> { generationService.submit(() -> {
//create physics entities //create physics entities
createTerrainPhysicsEntities(worldPos); if(cell != null){
//set ready cell.retireCell();
if(groundDataCells.get(getServerDataCellKey(worldPos)) != null){ cell.generatePhysics();
groundDataCells.get(getServerDataCellKey(worldPos)).setReady(true);
} else { } 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)); cellPositionMap.put(rVal,new Vector3i(worldPos));
serverContentManager.generateContentForDataCell(parent, worldPos, rVal, cellKey); serverContentManager.generateContentForDataCell(parent, worldPos, rVal, cellKey);
//generates physics for the cell in a dedicated thread then finally registers //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; return rVal;
} }
@ -699,6 +772,7 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager
serverTerrainManager.deformTerrainAtLocationToValue(toUpdate, new Vector3i(localVoxelX, localVoxelY, localVoxelZ), weight, type); serverTerrainManager.deformTerrainAtLocationToValue(toUpdate, new Vector3i(localVoxelX, localVoxelY, localVoxelZ), weight, type);
//update anything loaded //update anything loaded
this.loadedCellsLock.lock();
ServerDataCell cell = groundDataCells.get(this.getServerDataCellKey(toUpdate)); ServerDataCell cell = groundDataCells.get(this.getServerDataCellKey(toUpdate));
if(cell != null){ if(cell != null){
//update physics //update physics
@ -710,6 +784,7 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager
localVoxelX, localVoxelY, localVoxelZ, localVoxelX, localVoxelY, localVoxelZ,
weight, type)); weight, type));
} }
this.loadedCellsLock.unlock();
} }
} }
terrainEditLock.release(); 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 @Override
public void save(String saveName) { public void save(String saveName) {
for(ServerDataCell cell : this.groundDataCells.values()){ for(ServerDataCell cell : this.groundDataCells.values()){

View File

@ -1,9 +1,10 @@
package electrosphere.server.datacell; package electrosphere.server.datacell;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.locks.ReentrantLock;
import org.joml.Vector3d; import org.joml.Vector3d;
@ -27,9 +28,14 @@ public class RealmManager {
//All realms in this manager //All realms in this manager
Set<Realm> realms = new CopyOnWriteArraySet<Realm>(); Set<Realm> realms = new CopyOnWriteArraySet<Realm>();
//Map of entities to the realm the entity is in //Map of entities to the realm the entity is in
Map<Entity,Realm> entityToRealmMap = new ConcurrentHashMap<Entity,Realm>(); Map<Entity,Realm> entityToRealmMap = new HashMap<Entity,Realm>();
//Map of player to the realm the player is in //Map of player to the realm the player is in
Map<Player,Realm> playerToRealmMap = new ConcurrentHashMap<Player,Realm>(); Map<Player,Realm> playerToRealmMap = new HashMap<Player,Realm>();
/**
* Lock for thread-safing the manager
*/
ReentrantLock lock = new ReentrantLock();
/** /**
* Constructor * Constructor
@ -126,7 +132,9 @@ public class RealmManager {
* @param realm The realm * @param realm The realm
*/ */
public void mapEntityToRealm(Entity entity, Realm realm){ public void mapEntityToRealm(Entity entity, Realm realm){
lock.lock();
entityToRealmMap.put(entity, realm); entityToRealmMap.put(entity, realm);
lock.unlock();
} }
/** /**
@ -134,7 +142,9 @@ public class RealmManager {
* @param entity The entity to remove * @param entity The entity to remove
*/ */
public void removeEntity(Entity entity){ public void removeEntity(Entity entity){
lock.lock();
entityToRealmMap.remove(entity); 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 * @return The realm, or null if the entity is not inside a realm
*/ */
public Realm getEntityRealm(Entity entity){ 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 * @param realm The realm
*/ */
public void setPlayerRealm(Player player, Realm realm){ public void setPlayerRealm(Player player, Realm realm){
lock.lock();
playerToRealmMap.put(player, realm); playerToRealmMap.put(player, realm);
lock.unlock();
} }
/** /**
@ -182,7 +197,10 @@ public class RealmManager {
* @return The realm * @return The realm
*/ */
public Realm getPlayerRealm(Player player){ 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(); this.realms.clear();
lock.lock();
this.entityToRealmMap.clear(); this.entityToRealmMap.clear();
this.playerToRealmMap.clear(); this.playerToRealmMap.clear();
lock.unlock();
} }
/** /**

View File

@ -1,5 +1,6 @@
package electrosphere.server.datacell.physics; package electrosphere.server.datacell.physics;
import electrosphere.client.block.BlockChunkData;
import electrosphere.client.terrain.cache.ChunkData; import electrosphere.client.terrain.cache.ChunkData;
import electrosphere.engine.Globals; import electrosphere.engine.Globals;
import electrosphere.entity.Entity; import electrosphere.entity.Entity;
@ -11,9 +12,7 @@ import electrosphere.renderer.meshgen.BlockMeshgen;
import electrosphere.renderer.meshgen.BlockMeshgen.BlockMeshData; import electrosphere.renderer.meshgen.BlockMeshgen.BlockMeshData;
import electrosphere.server.datacell.Realm; import electrosphere.server.datacell.Realm;
import electrosphere.server.terrain.manager.ServerTerrainChunk; import electrosphere.server.terrain.manager.ServerTerrainChunk;
import electrosphere.server.terrain.manager.ServerTerrainManager;
import org.joml.Vector3d;
import org.joml.Vector3i; import org.joml.Vector3i;
import org.ode4j.ode.DBody; import org.ode4j.ode.DBody;
@ -27,10 +26,10 @@ public class PhysicsDataCell {
Entity physicsEntity; Entity physicsEntity;
Entity blockPhysicsEntity; Entity blockPhysicsEntity;
DBody physicsObject; ServerTerrainChunk terrainChunk;
BlockChunkData blockChunk;
Realm realm; DBody physicsObject;
ServerTerrainManager serverTerrainManager;
float[][][] weights = new float[ServerTerrainChunk.CHUNK_DATA_GENERATOR_SIZE][ServerTerrainChunk.CHUNK_DATA_GENERATOR_SIZE][ServerTerrainChunk.CHUNK_DATA_GENERATOR_SIZE]; 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]; 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 * @return The cell
*/ */
public static PhysicsDataCell createPhysicsCell( public static PhysicsDataCell createPhysicsCell(
Realm realm, Vector3i worldPos,
Vector3i worldPos Entity physicsEntity,
Entity blockPhysicsEntity,
ServerTerrainChunk currentChunk,
BlockChunkData blockChunk
){ ){
PhysicsDataCell rVal = new PhysicsDataCell(); PhysicsDataCell rVal = new PhysicsDataCell();
rVal.realm = realm; rVal.physicsEntity = physicsEntity;
rVal.serverTerrainManager = realm.getServerWorldData().getServerTerrainManager(); rVal.blockPhysicsEntity = blockPhysicsEntity;
rVal.worldPos = worldPos; rVal.worldPos = worldPos;
rVal.blockChunk = blockChunk;
rVal.terrainChunk = currentChunk;
return rVal; return rVal;
} }
@ -72,31 +77,17 @@ public class PhysicsDataCell {
*/ */
public void generatePhysics(){ public void generatePhysics(){
//if the entity hasn't already been created for some reason, need to create it //if the entity hasn't already been created for some reason, need to create it
if(physicsEntity == null){
// //
//fill in weights and types maps //fill in weights and types maps
// //
this.fillInData(); this.fillInData();
Vector3d realPos = new Vector3d( TerrainChunk.serverCreateTerrainChunkEntity(physicsEntity, weights, types);
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); physicsEntity.putData(EntityDataStrings.TERRAIN_IS_TERRAIN, true);
} BlockMeshData meshData = BlockMeshgen.rasterize(blockChunk);
if(blockPhysicsEntity == null){ BlockChunkEntity.serverCreateBlockChunkEntity(blockPhysicsEntity, meshData);
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); blockPhysicsEntity.putData(EntityDataStrings.TERRAIN_IS_TERRAIN, true);
}
// //then actually perform the attach // //then actually perform the attach
// physicsObject = PhysicsUtils.attachTerrainRigidBody(physicsEntity,heightmap,true); // physicsObject = PhysicsUtils.attachTerrainRigidBody(physicsEntity,heightmap,true);
// Realm realm = Globals.realmManager.getEntityRealm(physicsEntity); // Realm realm = Globals.realmManager.getEntityRealm(physicsEntity);
@ -120,12 +111,11 @@ public class PhysicsDataCell {
//fill in data //fill in data
// //
//main chunk //main chunk
ServerTerrainChunk currentChunk = serverTerrainManager.getChunk(worldPos.x, worldPos.y, worldPos.z);
for(int x = 0; x < ChunkData.CHUNK_DATA_SIZE; x++){ for(int x = 0; x < ChunkData.CHUNK_DATA_SIZE; x++){
for(int y = 0; y < ChunkData.CHUNK_DATA_SIZE; y++){ for(int y = 0; y < ChunkData.CHUNK_DATA_SIZE; y++){
for(int z = 0; z < ChunkData.CHUNK_DATA_SIZE; z++){ for(int z = 0; z < ChunkData.CHUNK_DATA_SIZE; z++){
weights[x][y][z] = currentChunk.getWeight(x,y,z); weights[x][y][z] = terrainChunk.getWeight(x,y,z);
types[x][y][z] = currentChunk.getType(x,y,z); types[x][y][z] = terrainChunk.getType(x,y,z);
} }
} }
} }

View File

@ -14,11 +14,13 @@ import electrosphere.util.annotation.Exclude;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.FloatBuffer; import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer;
import org.joml.Vector3i; import org.joml.Vector3i;
@ -30,7 +32,7 @@ public class ServerFluidManager {
/** /**
* DEfault size of the cache * 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 * The number of frames to wait between updates
@ -88,13 +90,13 @@ public class ServerFluidManager {
/** /**
* The queued of chunks to simulate * The queued of chunks to simulate
*/ */
List<ServerFluidChunk> simulationQueue = new LinkedList<ServerFluidChunk>(); List<ServerFluidChunk> simulationQueue = new ArrayList<ServerFluidChunk>();
@Exclude @Exclude
/** /**
* The queue of chunks to broadcast * The queue of chunks to broadcast
*/ */
List<ServerFluidChunk> broadcastQueue = new LinkedList<ServerFluidChunk>(); List<ServerFluidChunk> broadcastQueue = new ArrayList<ServerFluidChunk>();
@Exclude @Exclude
/** /**
@ -287,14 +289,16 @@ public class ServerFluidManager {
* @param worldZ The world z coordinate of the chunk * @param worldZ The world z coordinate of the chunk
*/ */
public void queue(int worldX, int worldY, int worldZ){ public void queue(int worldX, int worldY, int worldZ){
if(simulate){
ServerFluidChunk fluidChunk = this.getChunk(worldX, worldY, worldZ); ServerFluidChunk fluidChunk = this.getChunk(worldX, worldY, worldZ);
this.simulationQueue.add(fluidChunk); this.simulationQueue.add(fluidChunk);
} }
}
/** /**
* Simulates a chunk * Simulates a chunk
*/ */
public void simulate(){ public void simulate(Consumer<ServerFluidChunk> onUpdate){
Globals.profiler.beginAggregateCpuSample("ServerFluidManager.simulate"); Globals.profiler.beginAggregateCpuSample("ServerFluidManager.simulate");
lock.lock(); lock.lock();
if(simulate){ 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(); this.broadcastSize = this.broadcastQueue.size();
while(this.broadcastQueue.size() > 0){
ServerFluidChunk toBroadcast = this.broadcastQueue.remove(0);
onUpdate.accept(toBroadcast);
}
updatePhase++; updatePhase++;
if(updatePhase > UPDATE_RATE){ if(updatePhase > UPDATE_RATE){