Fix memory leak
	
		
			
	
		
	
	
		
	
		
			All checks were successful
		
		
	
	
		
			
				
	
				studiorailgun/Renderer/pipeline/head This commit looks good
				
			
		
		
	
	
				
					
				
			
		
			All checks were successful
		
		
	
	studiorailgun/Renderer/pipeline/head This commit looks good
				
			This commit is contained in:
		
							parent
							
								
									0e2c29d8a1
								
							
						
					
					
						commit
						569311a23e
					
				
							
								
								
									
										8
									
								
								.vscode/launch.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								.vscode/launch.json
									
									
									
									
										vendored
									
									
								
							| @ -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" | ||||
|         }, | ||||
|         { | ||||
|  | ||||
| @ -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 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										3
									
								
								docs/src/tools/profiling/eclipsememoryanalyzer.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								docs/src/tools/profiling/eclipsememoryanalyzer.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,3 @@ | ||||
| @page eclipsememoryanalyzer Eclipse Memory Analyzer | ||||
| 
 | ||||
| Useful for viewing memory usage (particularly gives good reports of memory leaks) | ||||
							
								
								
									
										3
									
								
								docs/src/tools/profiling/visualvm.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								docs/src/tools/profiling/visualvm.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,3 @@ | ||||
| @page visualvm VisualVM | ||||
| 
 | ||||
| Useful for profiling CPU usage | ||||
| @ -6,3 +6,5 @@ | ||||
|  - @subpage indexdocumentation | ||||
|  - @subpage jenkins | ||||
|  - @subpage gimp | ||||
|  - @subpage visualvm | ||||
|  - @subpage eclipsememoryanalyzer | ||||
| @ -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 | ||||
|  | ||||
| @ -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; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | ||||
| @ -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; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | ||||
| @ -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,14 +293,33 @@ 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); | ||||
|         } | ||||
|         //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); | ||||
|     } | ||||
|     } | ||||
|      | ||||
|     /** | ||||
|      * 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(); | ||||
| 
 | ||||
|                 //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,8 +392,9 @@ 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(cellPlayerlessFrameMap.containsKey(cell)){ | ||||
|                     if(cell.isReady() && cell.getPlayers().size() < 1){ | ||||
|                         int frameCount = cellPlayerlessFrameMap.get(cell) + 1; | ||||
|                         cellPlayerlessFrameMap.put(cell,frameCount); | ||||
| @ -382,6 +407,7 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             for(ServerDataCell cell : toCleanQueue){ | ||||
|                 boolean containsPlayerEntity = false; | ||||
|                 //clear all entities in cell | ||||
| @ -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); | ||||
|             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<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(() -> { | ||||
|             //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()){ | ||||
|  | ||||
| @ -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<Realm> realms = new CopyOnWriteArraySet<Realm>(); | ||||
|     //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<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 | ||||
| @ -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(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | ||||
| @ -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; | ||||
| 
 | ||||
| @ -27,10 +26,10 @@ public class PhysicsDataCell { | ||||
|     Entity physicsEntity; | ||||
|     Entity blockPhysicsEntity; | ||||
| 
 | ||||
|     DBody physicsObject; | ||||
|     ServerTerrainChunk terrainChunk; | ||||
|     BlockChunkData blockChunk; | ||||
|      | ||||
|     Realm realm; | ||||
|     ServerTerrainManager serverTerrainManager; | ||||
|     DBody physicsObject; | ||||
| 
 | ||||
|     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(); | ||||
| 
 | ||||
|             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); | ||||
|         TerrainChunk.serverCreateTerrainChunkEntity(physicsEntity, 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); | ||||
|         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); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
| @ -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<ServerFluidChunk> simulationQueue = new LinkedList<ServerFluidChunk>(); | ||||
|     List<ServerFluidChunk> simulationQueue = new ArrayList<ServerFluidChunk>(); | ||||
| 
 | ||||
|     @Exclude | ||||
|     /** | ||||
|      * The queue of chunks to broadcast | ||||
|      */ | ||||
|     List<ServerFluidChunk> broadcastQueue = new LinkedList<ServerFluidChunk>(); | ||||
|     List<ServerFluidChunk> broadcastQueue = new ArrayList<ServerFluidChunk>(); | ||||
| 
 | ||||
|     @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){ | ||||
|         if(simulate){ | ||||
|             ServerFluidChunk fluidChunk = this.getChunk(worldX, worldY, worldZ); | ||||
|             this.simulationQueue.add(fluidChunk); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Simulates a chunk | ||||
|      */ | ||||
|     public void simulate(){ | ||||
|     public void simulate(Consumer<ServerFluidChunk> 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){ | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user