work towards async entity creation on server
All checks were successful
studiorailgun/Renderer/pipeline/head This commit looks good

This commit is contained in:
austin 2025-03-28 16:24:39 -04:00
parent 1c50468bdf
commit 028b957b76
5 changed files with 133 additions and 8 deletions

View File

@ -1356,6 +1356,7 @@ Grass height variance with control from ui + file
Fix block mesh ray casting bug due to trimesh overlap Fix block mesh ray casting bug due to trimesh overlap
Fix block mesh samplers incorrectly buffering Fix block mesh samplers incorrectly buffering
Fix block mesh gen algorithm merging quads incorrectly Fix block mesh gen algorithm merging quads incorrectly
Make HitboxManager threadsafe

View File

@ -4,19 +4,32 @@ import electrosphere.collision.CollisionEngine;
import electrosphere.collision.CollisionEngine.CollisionResolutionCallback; import electrosphere.collision.CollisionEngine.CollisionResolutionCallback;
import electrosphere.engine.Globals; import electrosphere.engine.Globals;
import electrosphere.entity.state.hitbox.HitboxCollectionState; import electrosphere.entity.state.hitbox.HitboxCollectionState;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;
/** /**
* Manages all hitboxes on either the server or client * Manages all hitboxes on either the server or client
*/ */
public class HitboxManager { public class HitboxManager {
//the list of all hitboxes /**
CopyOnWriteArrayList<HitboxCollectionState> hitboxes = new CopyOnWriteArrayList<HitboxCollectionState>(); * The list of all hitboxes
*/
List<HitboxCollectionState> hitboxes = new LinkedList<HitboxCollectionState>();
//the collision engine for this hitbox manager /**
* The collision engine for the hitbox manager
*/
CollisionEngine collisionEngine; CollisionEngine collisionEngine;
/**
* Lock for hitbox collections
*/
ReentrantLock lock = new ReentrantLock();
//an id incrementer for hitboxes //an id incrementer for hitboxes
long idIncrementer = 0; long idIncrementer = 0;
@ -34,16 +47,21 @@ public class HitboxManager {
* @param hitbox the hitbox to register * @param hitbox the hitbox to register
*/ */
public void registerHitbox(HitboxCollectionState hitbox){ public void registerHitbox(HitboxCollectionState hitbox){
lock.lock();
hitboxes.add(hitbox); hitboxes.add(hitbox);
idIncrementer++; idIncrementer++;
lock.unlock();
} }
/** /**
* Gets all hitboxes in the manager * Gets all hitboxes in the manager
* @return all hitboxes in the manager * @return all hitboxes in the manager
*/ */
public CopyOnWriteArrayList<HitboxCollectionState> getAllHitboxes(){ public List<HitboxCollectionState> getAllHitboxes(){
return hitboxes; lock.lock();
List<HitboxCollectionState> rVal = Collections.unmodifiableList(hitboxes);
lock.unlock();
return rVal;
} }
/** /**
@ -51,7 +69,9 @@ public class HitboxManager {
* @param hitbox the hitbox to deregister * @param hitbox the hitbox to deregister
*/ */
public void deregisterHitbox(HitboxCollectionState hitbox){ public void deregisterHitbox(HitboxCollectionState hitbox){
lock.lock();
hitboxes.remove(hitbox); hitboxes.remove(hitbox);
lock.unlock();
} }
/** /**
@ -68,10 +88,12 @@ public class HitboxManager {
public void simulate(){ public void simulate(){
//update all positions //update all positions
Globals.profiler.beginCpuSample("Update hitbox positions"); Globals.profiler.beginCpuSample("Update hitbox positions");
lock.lock();
for(HitboxCollectionState state : hitboxes){ for(HitboxCollectionState state : hitboxes){
state.clearCollisions(); state.clearCollisions();
state.updateHitboxPositions(this.collisionEngine); state.updateHitboxPositions(this.collisionEngine);
} }
lock.unlock();
Globals.profiler.endCpuSample(); Globals.profiler.endCpuSample();
//collide hitboxes //collide hitboxes
Globals.profiler.beginCpuSample("Collide hitboxes"); Globals.profiler.beginCpuSample("Collide hitboxes");

View File

@ -40,9 +40,11 @@ public class MainServerFunctions {
if(Globals.realmManager != null){ if(Globals.realmManager != null){
Globals.realmManager.simulate(); Globals.realmManager.simulate();
} }
Globals.profiler.endCpuSample();
// //
//Macro simulation (ie simulating the larger world macro data) //Macro simulation (ie simulating the larger world macro data)
Globals.profiler.beginCpuSample("MainServerFunctions.simulate - Server macro simulation");
LoggerInterface.loggerEngine.DEBUG_LOOP("MainServerFunctions.simulate - Server macro simulation"); LoggerInterface.loggerEngine.DEBUG_LOOP("MainServerFunctions.simulate - Server macro simulation");
if(Globals.macroSimulation != null && Globals.macroSimulation.isReady()){ if(Globals.macroSimulation != null && Globals.macroSimulation.isReady()){
Globals.macroSimulation.simulate(); Globals.macroSimulation.simulate();

View File

@ -0,0 +1,91 @@
package electrosphere.server.datacell.gridded;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.locks.ReentrantLock;
import electrosphere.engine.Globals;
import electrosphere.logger.LoggerInterface;
/**
* Manages loading/unloading data cells for the gridded data cell manager
*/
public class GriddedDataCellLoaderService {
/**
* Used for loading/unloading the cells
*/
protected static final ExecutorService ioThreadService = Executors.newFixedThreadPool(4);
/**
* Lock for structures in this service
*/
private static final ReentrantLock lock = new ReentrantLock();
/**
* Map of cell key -> job queued for that cell
*/
private static final Map<Long,Future<?>> queuedWorkLock = new HashMap<Long,Future<?>>();
/**
* Loads a cell
* @param key The key for the cell
*/
protected static void loadCell(long key, Runnable loadLogic){
lock.lock();
//if there is a job queued and we couldn't cancel it, wait
Future<?> job = queuedWorkLock.get(key);
if(job != null && !job.cancel(false)){
try {
Globals.profiler.beginCpuSample("Waiting for cell io job to finish");
job.wait();
Globals.profiler.endCpuSample();
} catch (InterruptedException e) {
LoggerInterface.loggerEngine.ERROR("Failed to wait for previous job for cell!", e);
}
}
//queue job to load the cell
ioThreadService.submit(() -> {
//load here
loadLogic.run();
//let the service know we've finished this job
lock.lock();
queuedWorkLock.remove(key);
lock.unlock();
});
lock.unlock();
}
/**
* Unloads a cell
* @param key The key for the cell
*/
protected static void unloadCell(long key, Runnable unloadLogic){
lock.lock();
//if there is a job queued and we couldn't cancel it, wait
Future<?> job = queuedWorkLock.get(key);
if(job != null && !job.cancel(false)){
try {
Globals.profiler.beginCpuSample("Waiting for cell io job to finish");
job.wait();
Globals.profiler.endCpuSample();
} catch (InterruptedException e) {
LoggerInterface.loggerEngine.ERROR("Failed to wait for previous job for cell!", e);
}
}
//queue job to load the cell
ioThreadService.submit(() -> {
//unload here
unloadLogic.run();
//let the service know we've finished this job
lock.lock();
queuedWorkLock.remove(key);
lock.unlock();
});
lock.unlock();
}
}

View File

@ -422,8 +422,8 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager
Globals.profiler.beginCpuSample("GriddedDataCellManager.unloadPlayerlessChunks - Deconstruct timed out cells"); Globals.profiler.beginCpuSample("GriddedDataCellManager.unloadPlayerlessChunks - Deconstruct timed out cells");
this.numCleaned = toCleanQueue.size(); this.numCleaned = toCleanQueue.size();
for(ServerDataCell cell : toCleanQueue){ for(ServerDataCell cell : toCleanQueue){
//error check before actually queueing for deletion
boolean containsPlayerEntity = false; boolean containsPlayerEntity = false;
//clear all entities in cell
for(Entity entity : cell.getScene().getEntityList()){ for(Entity entity : cell.getScene().getEntityList()){
if(ServerPlayerViewDirTree.hasTree(entity)){ if(ServerPlayerViewDirTree.hasTree(entity)){
containsPlayerEntity = true; containsPlayerEntity = true;
@ -445,9 +445,10 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager
if(containsPlayerEntity){ if(containsPlayerEntity){
continue; continue;
} }
Vector3i worldPos = this.getCellWorldPosition(cell); Vector3i worldPos = this.getCellWorldPosition(cell);
Long key = this.getServerDataCellKey(worldPos); Long key = this.getServerDataCellKey(worldPos);
//offload all entities in cell to chunk file //offload all entities in cell to chunk file
//entities are saved before tracking is removed. This makes sure that any side effects from calling destroyEntity (ie if it looks up the chunk that we're deleting) //entities are saved before tracking is removed. This makes sure that any side effects from calling destroyEntity (ie if it looks up the chunk that we're deleting)
//don't trigger the chunk to be re-created //don't trigger the chunk to be re-created
@ -714,15 +715,22 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager
Globals.profiler.beginCpuSample("GriddedDataCellManager.createServerDataCell"); Globals.profiler.beginCpuSample("GriddedDataCellManager.createServerDataCell");
ServerDataCell rVal = parent.createNewCell(); ServerDataCell rVal = parent.createNewCell();
Long cellKey = this.getServerDataCellKey(worldPos); Long cellKey = this.getServerDataCellKey(worldPos);
loadedCellsLock.lock();
groundDataCells.put(cellKey,rVal); groundDataCells.put(cellKey,rVal);
cellPlayerlessFrameMap.put(rVal,0); cellPlayerlessFrameMap.put(rVal,0);
LoggerInterface.loggerEngine.DEBUG("Create server data cell with key " + cellKey); LoggerInterface.loggerEngine.DEBUG("Create server data cell with key " + cellKey);
cellPositionMap.put(rVal,new Vector3i(worldPos)); cellPositionMap.put(rVal,new Vector3i(worldPos));
loadedCellsLock.unlock();
//generate content
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
Long key = this.getServerDataCellKey(worldPos); Long key = this.getServerDataCellKey(worldPos);
PhysicsDataCell cell = posPhysicsMap.get(key); PhysicsDataCell cell = posPhysicsMap.get(key);
GriddedDataCellManager.runPhysicsGenerationThread(worldPos,key,cell,this.posPhysicsMap,this.groundDataCells,this.parent); GriddedDataCellManager.runPhysicsGenerationThread(worldPos,key,cell,this.posPhysicsMap,this.groundDataCells,this.parent);
Globals.profiler.endCpuSample(); Globals.profiler.endCpuSample();
return rVal; return rVal;
} }
@ -894,6 +902,7 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager
*/ */
public void halt(){ public void halt(){
generationService.shutdownNow(); generationService.shutdownNow();
GriddedDataCellLoaderService.ioThreadService.shutdownNow();
} }
@Override @Override