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 samplers incorrectly buffering
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.engine.Globals;
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
*/
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;
/**
* Lock for hitbox collections
*/
ReentrantLock lock = new ReentrantLock();
//an id incrementer for hitboxes
long idIncrementer = 0;
@ -34,16 +47,21 @@ public class HitboxManager {
* @param hitbox the hitbox to register
*/
public void registerHitbox(HitboxCollectionState hitbox){
lock.lock();
hitboxes.add(hitbox);
idIncrementer++;
lock.unlock();
}
/**
* Gets all hitboxes in the manager
* @return all hitboxes in the manager
*/
public CopyOnWriteArrayList<HitboxCollectionState> getAllHitboxes(){
return hitboxes;
public List<HitboxCollectionState> getAllHitboxes(){
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
*/
public void deregisterHitbox(HitboxCollectionState hitbox){
lock.lock();
hitboxes.remove(hitbox);
lock.unlock();
}
/**
@ -68,10 +88,12 @@ public class HitboxManager {
public void simulate(){
//update all positions
Globals.profiler.beginCpuSample("Update hitbox positions");
lock.lock();
for(HitboxCollectionState state : hitboxes){
state.clearCollisions();
state.updateHitboxPositions(this.collisionEngine);
}
lock.unlock();
Globals.profiler.endCpuSample();
//collide hitboxes
Globals.profiler.beginCpuSample("Collide hitboxes");

View File

@ -40,9 +40,11 @@ public class MainServerFunctions {
if(Globals.realmManager != null){
Globals.realmManager.simulate();
}
Globals.profiler.endCpuSample();
//
//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");
if(Globals.macroSimulation != null && Globals.macroSimulation.isReady()){
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");
this.numCleaned = toCleanQueue.size();
for(ServerDataCell cell : toCleanQueue){
//error check before actually queueing for deletion
boolean containsPlayerEntity = false;
//clear all entities in cell
for(Entity entity : cell.getScene().getEntityList()){
if(ServerPlayerViewDirTree.hasTree(entity)){
containsPlayerEntity = true;
@ -445,9 +445,10 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager
if(containsPlayerEntity){
continue;
}
Vector3i worldPos = this.getCellWorldPosition(cell);
Long key = this.getServerDataCellKey(worldPos);
//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)
//don't trigger the chunk to be re-created
@ -714,15 +715,22 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager
Globals.profiler.beginCpuSample("GriddedDataCellManager.createServerDataCell");
ServerDataCell rVal = parent.createNewCell();
Long cellKey = this.getServerDataCellKey(worldPos);
loadedCellsLock.lock();
groundDataCells.put(cellKey,rVal);
cellPlayerlessFrameMap.put(rVal,0);
LoggerInterface.loggerEngine.DEBUG("Create server data cell with key " + cellKey);
cellPositionMap.put(rVal,new Vector3i(worldPos));
loadedCellsLock.unlock();
//generate content
serverContentManager.generateContentForDataCell(parent, worldPos, rVal, cellKey);
//generates physics for the cell in a dedicated thread then finally registers
Long key = this.getServerDataCellKey(worldPos);
PhysicsDataCell cell = posPhysicsMap.get(key);
GriddedDataCellManager.runPhysicsGenerationThread(worldPos,key,cell,this.posPhysicsMap,this.groundDataCells,this.parent);
Globals.profiler.endCpuSample();
return rVal;
}
@ -894,6 +902,7 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager
*/
public void halt(){
generationService.shutdownNow();
GriddedDataCellLoaderService.ioThreadService.shutdownNow();
}
@Override