grid manager entity and player tracking overhaul
Some checks failed
studiorailgun/Renderer/pipeline/head There was a failure building this commit

This commit is contained in:
austin 2024-11-08 12:11:40 -05:00
parent e12dd55703
commit 795fbb6a1c
24 changed files with 400 additions and 220 deletions

View File

@ -1,6 +1,6 @@
{
"gameplayGenerateWorld" : false,
"gameplayPhysicsCellRadius" : 2,
"gameplayPhysicsCellRadius" : 3,
"displayWidth" : 1920,
"displayHeight" : 1080,

View File

@ -9,3 +9,8 @@ sines pulling from sines
noise functions pulling from sines
applying 2d values to the surface (top) of a 3d noise map
subtracting true 3d noise from a 2d heightmap to account for cave entrances
spatially partitioning 3d shapes using the same trick as the voronoi noise but in 3d
IE, have a torus defined in 3d that is bounded in cells

View File

@ -0,0 +1,51 @@
package electrosphere.client.ui.menu.debug;
import electrosphere.engine.Globals;
import electrosphere.renderer.ui.imgui.ImGuiWindow;
import electrosphere.renderer.ui.imgui.ImGuiWindow.ImGuiWindowCallback;
import electrosphere.server.datacell.GriddedDataCellManager;
import electrosphere.server.datacell.Realm;
import imgui.ImGui;
/**
*
*/
public class ImGuiGriddedManager {
//window for viewing information about the ai state
protected static ImGuiWindow griddedManagerWindow;
/**
* Creates the windows in this file
*/
protected static void createGriddedManagerWindows(){
createGriddedManagerWindow();
}
/**
* Client scene entity view
*/
protected static void createGriddedManagerWindow(){
griddedManagerWindow = new ImGuiWindow("Gridded Manager");
griddedManagerWindow.setCallback(new ImGuiWindowCallback() {
@Override
public void exec() {
GriddedDataCellManager manager = null;
if(Globals.realmManager != null && Globals.realmManager.first() != null){
Realm realm = Globals.realmManager.first();
if(realm.getDataCellManager() instanceof GriddedDataCellManager){
manager = (GriddedDataCellManager)realm.getDataCellManager();
}
}
if(manager != null && manager.getLoadedCells() != null){
ImGui.text("Loaded Cells: " + manager.getLoadedCells().size());
}
}
});
griddedManagerWindow.setOpen(false);
Globals.renderingEngine.getImGuiPipeline().addImGuiWindow(griddedManagerWindow);
}
}

View File

@ -52,6 +52,7 @@ public class ImGuiWindowMacros {
ImGuiRenderer.createRendererWindows();
ImGuiTestGen.createTestGenWindows();
ImGuiChunkMonitor.createChunkMonitorWindows();
ImGuiGriddedManager.createGriddedManagerWindows();
}
/**
@ -188,6 +189,10 @@ public class ImGuiWindowMacros {
if(ImGui.button("Chunk Monitor")){
ImGuiChunkMonitor.chunkMonitorWindow.setOpen(true);
}
//gridded data cell monitor
if(ImGui.button("Gridded Data Cell Monitor")){
ImGuiGriddedManager.griddedManagerWindow.setOpen(true);
}
//close button
if(ImGui.button("Close")){
mainDebugWindow.setOpen(false);

View File

@ -50,10 +50,15 @@ public class EntityCreationUtils {
throw new IllegalStateException("Failed to create a server data cell");
}
//register to entity data cell mapper
realm.getEntityDataCellMapper().registerEntity(rVal, cell);
Globals.entityDataCellMapper.registerEntity(rVal, cell);
//enable behavior tree tracking
ServerBehaviorTreeUtils.registerEntity(rVal);
if(Globals.entityDataCellMapper.getEntityDataCell(rVal) == null){
Globals.entityDataCellMapper.registerEntity(rVal, cell);
throw new Error("Failed to map entity to cell!");
}
return rVal;
}

View File

@ -43,11 +43,11 @@ public class EntityUtils {
Realm realm = Globals.realmManager.getEntityRealm(e);
if(realm != null){
//get data cell
ServerDataCell dataCell = realm.getEntityDataCellMapper().getEntityDataCell(e);
ServerDataCell dataCell = Globals.entityDataCellMapper.getEntityDataCell(e);
if(dataCell != null){
dataCell.getScene().deregisterEntity(e);
}
realm.getEntityDataCellMapper().ejectEntity(e);
Globals.entityDataCellMapper.ejectEntity(e);
}
Globals.realmManager.removeEntity(e);
}

View File

@ -33,6 +33,9 @@ public class ServerEntityUtils {
* @param position
*/
public static void initiallyPositionEntity(Realm realm, Entity entity, Vector3d position){
if(position == null){
throw new Error("Trying to set server entity position to null!");
}
//reposition entity, if the position isn't correct then it will spawn at 0,0,0 when the synchronization part is called
CollisionObjUtils.serverPositionCharacter(entity, position);
//get current server data cell
@ -43,6 +46,9 @@ public class ServerEntityUtils {
} else {
//if it doesn't already exist, try creating it and if successfull move creature
cell = realm.getDataCellManager().tryCreateCellAtPoint(position);
if(cell == null){
throw new Error("Trying to initially position entity to position that cannot generate a data cell! " + position);
}
//initialize server datacell tracking of this entity
realm.initializeServerSideEntity(entity, cell);
}
@ -54,25 +60,34 @@ public class ServerEntityUtils {
* @param position
*/
public static void repositionEntity(Entity entity, Vector3d position){
if(position == null){
throw new Error("Trying to set server entity position to null!");
}
Realm realm = Globals.realmManager.getEntityRealm(entity);
//if server, get current server data cell
ServerDataCell oldDataCell = realm.getDataCellManager().getDataCellAtPoint(EntityUtils.getPosition(entity));
ServerDataCell oldDataCell = Globals.entityDataCellMapper.getEntityDataCell(entity);
ServerDataCell newDataCell = realm.getDataCellManager().getDataCellAtPoint(position);
if(oldDataCell == null){
LoggerInterface.loggerEngine.WARNING("Trying to reposition entity on server when it's former position is null!");
LoggerInterface.loggerEngine.WARNING(
"Trying to reposition entity on server when it's former cell is null!\n" +
"Entity original position: " + EntityUtils.getPosition(entity) + "\n"
);
}
if(oldDataCell != newDataCell){
if(newDataCell != null){
ServerDataCell.moveEntityFromCellToCell(entity, oldDataCell, newDataCell);
ServerBehaviorTreeUtils.updateCell(entity, oldDataCell);
} else {
//if it doesn't already exist, try creating it and if successfull move creature
newDataCell = realm.getDataCellManager().tryCreateCellAtPoint(EntityUtils.getPosition(entity));
if(newDataCell != null){
ServerDataCell.moveEntityFromCellToCell(entity, oldDataCell, newDataCell);
ServerBehaviorTreeUtils.updateCell(entity, oldDataCell);
if(newDataCell == null){
newDataCell = realm.getDataCellManager().tryCreateCellAtPoint(position);
if(newDataCell == null){
LoggerInterface.loggerEngine.WARNING(
"Trying to reposition entity on server when it's new cell is null!\n" +
"Entity new position: " + position + "\n"
);
}
}
ServerDataCell.moveEntityFromCellToCell(entity, oldDataCell, newDataCell);
ServerBehaviorTreeUtils.updateCell(entity, oldDataCell);
if(oldDataCell.getScene().containsEntity(entity)){
throw new Error("Entity not removed from scene!");
}
}
//reposition entity
CollisionObjUtils.serverPositionCharacter(entity, position);

View File

@ -7,6 +7,7 @@ import electrosphere.entity.btree.BehaviorTree;
import electrosphere.entity.state.attach.AttachUtils;
import electrosphere.logger.LoggerInterface;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
@ -19,13 +20,24 @@ import java.util.concurrent.CopyOnWriteArraySet;
*/
public class Scene {
/**
* The map of id -> entity
*/
Map<Integer,Entity> entityIdMap;
Map<String,Set<Entity>> tagEntityMap;
List<Entity> entityList = new CopyOnWriteArrayList<Entity>();
/**
* The map of tag -> set of entities corresponding to that tag
*/
Map<String,Set<Entity>> tagEntityMap;
/**
* The list of behavior trees
*/
List<BehaviorTree> behaviorTreeList;
/**
* Constructor
*/
public Scene(){
entityIdMap = new ConcurrentHashMap<Integer,Entity>();
tagEntityMap = new ConcurrentHashMap<String,Set<Entity>>();
@ -53,9 +65,6 @@ public class Scene {
* @param e The entity to register
*/
public void registerEntity(Entity e){
if(!entityIdMap.containsKey(e.getId())){
entityList.add(e);
}
entityIdMap.put(e.getId(), e);
}
@ -101,7 +110,6 @@ public class Scene {
tagEntityMap.get(key).remove(e);
}
entityIdMap.remove(e.getId());
entityList.remove(e);
}
/**
@ -112,10 +120,10 @@ public class Scene {
if(AttachUtils.hasChildren(target)){
List<Entity> childrenList = AttachUtils.getChildrenList(target);
for(Entity currentChild : childrenList){
recursiveDeregister(currentChild);
this.recursiveDeregister(currentChild);
}
}
deregisterEntity(target);
this.deregisterEntity(target);
}
/**
@ -166,11 +174,11 @@ public class Scene {
}
/**
* Gets the list of all entities in the scene
* @return The list of all entities in the scene
* Gets the collection of all entities in the scene
* @return The collection of all entities in the scene
*/
public List<Entity> getEntityList(){
return entityList;
public Collection<Entity> getEntityList(){
return this.entityIdMap.values();
}
/**
@ -178,7 +186,7 @@ public class Scene {
*/
public void describeScene(){
LoggerInterface.loggerEngine.WARNING("Entities present in scene:");
for(Entity entity : this.entityList){
for(Entity entity : this.entityIdMap.values()){
LoggerInterface.loggerEngine.WARNING(entity.getId() + "");
}
}

View File

@ -166,6 +166,9 @@ public class InventoryUtils {
* @return The in-inventory item
*/
public static Entity serverAttemptStoreItemTransform(Entity creature, Entity item){
if(item == null){
throw new Error("Null item provided! " + item);
}
boolean creatureIsCreature = CreatureUtils.isCreature(creature);
boolean itemIsItem = ItemUtils.isItem(item);
boolean hasInventory = hasNaturalInventory(creature);
@ -360,7 +363,7 @@ public class InventoryUtils {
Realm realm = Globals.realmManager.getEntityRealm(realWorldItem);
if(realm != null){
//get closest chunk
ServerDataCell dataCell = realm.getEntityDataCellMapper().getEntityDataCell(realWorldItem);
ServerDataCell dataCell = Globals.entityDataCellMapper.getEntityDataCell(realWorldItem);
//broadcast destroy item
NetworkMessage unequipMessage = InventoryMessage.constructserverCommandUnequipItemMessage(creature.getId(), InventoryProtocol.INVENTORY_TYPE_EQUIP, inventorySlot);
dataCell.broadcastNetworkMessage(unequipMessage);

View File

@ -240,56 +240,6 @@ public class ClientEditorMovementTree implements BehaviorTree {
Quaterniond movementQuaternion = new Quaterniond().rotationTo(SpatialMathUtils.getOriginVector(), new Vector3d(facingVector.x,0,facingVector.z)).normalize();
Quaterniond rotation = EntityUtils.getRotation(parent);
//parse attached network messages
for(EntityMessage message : networkMessageQueue){
networkMessageQueue.remove(message);
long updateTime = message.gettime();
// System.out.println("MOVE to " + message.getX() + " " + message.getY() + " " + message.getZ());
switch(message.getMessageSubtype()){
case MOVEUPDATE:
if(updateTime >= lastUpdateTime){
lastUpdateTime = updateTime;
switch(message.gettreeState()){
case 0:
state = MovementTreeState.STARTUP;
// System.out.println("Set state STARTUP");
GravityUtils.clientAttemptActivateGravity(parent);
break;
case 1:
state = MovementTreeState.MOVE;
// System.out.println("Set state MOVE");
GravityUtils.clientAttemptActivateGravity(parent);
break;
case 2:
state = MovementTreeState.SLOWDOWN;
// System.out.println("Set state SLOWDOWN");
GravityUtils.clientAttemptActivateGravity(parent);
break;
case 3:
state = MovementTreeState.IDLE;
// System.out.println("Set state IDLE");
break;
}
//this should only fire on the client, we don't want the server snap updating due to client position reporting
lastServerPosition = new Vector3d(message.getpositionX(),message.getpositionY(),message.getpositionZ());
if(position.distance(lastServerPosition) > STATE_DIFFERENCE_HARD_UPDATE_THRESHOLD){
EntityUtils.getPosition(parent).set(lastServerPosition);
} else if(position.distance(lastServerPosition) > STATE_DIFFERENCE_SOFT_UPDATE_THRESHOLD){
EntityUtils.getPosition(parent).lerp(lastServerPosition,SOFT_UPDATE_MULTIPLIER);
}
//we want to always update the server facing vector with where the client says they're facing
EntityUtils.getRotation(parent).set(message.getrotationX(),message.getrotationY(),message.getrotationZ(),message.getrotationW());
CollisionObjUtils.clientPositionCharacter(parent, position, rotation);
// CreatureUtils.setFacingVector(parent, new Vector3d(message.getrotationX(),message.getrotationY(),message.getrotationZ()));
break;
}
break;
default:
break;
}
}
// System.out.println(movementVector + " " + velocity * Main.deltaTime);
//state machine
switch(state){

View File

@ -65,6 +65,11 @@ public class CollisionObjUtils {
entity.putData(EntityDataStrings.PHYSICS_MASS, mass);
}
/**
* Repositions an entity on the server
* @param e The entity
* @param position The server
*/
public static void serverPositionCharacter(Entity e, Vector3d position){
EntityUtils.getPosition(e).set(position);
Quaterniond rotation = EntityUtils.getRotation(e);
@ -75,6 +80,12 @@ public class CollisionObjUtils {
}
}
/**
* Positions an entity on the client
* @param e The entity
* @param position The position
* @param rotation The rotation
*/
public static void clientPositionCharacter(Entity e, Vector3d position, Quaterniond rotation){
EntityUtils.getPosition(e).set(position);
DBody body = PhysicsEntityUtils.getDBody(e);

View File

@ -689,6 +689,10 @@ public class CommonEntityUtils {
ServerEntityTagUtils.attachTagToEntity(entity, EntityTags.LIFE_STATE);
}
if(Globals.entityDataCellMapper.getEntityDataCell(entity) == null){
throw new Error("Failed to map entity to cell!");
}
return entity;
}

View File

@ -402,6 +402,13 @@ public class CreatureUtils {
if(CreatureUtils.hasControllerPlayerId(creature)){
LoggerInterface.loggerNetworking.INFO("Sending controller packets");
player.addMessage(NetUtils.createSetCreatureControllerIdEntityMessage(creature));
Player entityOwner = Globals.playerManager.getPlayerFromId(CreatureUtils.getControllerPlayerId(creature));
if(entityOwner == player){
player.setHasSentPlayerEntity(true);
}
if(Globals.playerEntity != null && player.getId() == Globals.clientPlayer.getId() && player.getPlayerEntity() == creature){
throw new Error("Re-sending player entity to player!");
}
}
}

View File

@ -53,6 +53,11 @@ public class Player {
* The player's primary entity
*/
Entity playerEntity;
/**
* Tracks whether the player's entity has been sent or not
*/
boolean hasSentPlayerEntity = false;
/**
* Constructor
@ -151,6 +156,24 @@ public class Player {
idIncrementerLock.release();
return rVal;
}
/**
* Gets whether the player has been sent their entity or not
* @return true if has been sent, false otherwise
*/
public boolean hasSentPlayerEntity() {
return hasSentPlayerEntity;
}
/**
* Sets whether the player has been sent their entity or not
* @param hasSentPlayerEntity true if has been sent, false otherwise
*/
public void setHasSentPlayerEntity(boolean hasSentPlayerEntity) {
this.hasSentPlayerEntity = hasSentPlayerEntity;
}
}

View File

@ -64,14 +64,16 @@ public class ServerContentGenerator {
double scale = foliageDescription.getScale();
double regularity = foliageDescription.getRegularity();
double threshold = foliageDescription.getThreshold();
if(NoiseUtils.relaxedPointGen(x * scale, z * scale, regularity, threshold) > 0){
double realX = realm.getServerWorldData().convertVoxelToRealSpace(x, worldPos.x);
double realZ = realm.getServerWorldData().convertVoxelToRealSpace(z, worldPos.z);
if(NoiseUtils.relaxedPointGen(realX * scale, realZ * scale, regularity, threshold) > 0){
String type = foliageDescription.getEntityIDs().get(random.nextInt(0,foliageDescription.getEntityIDs().size()));
FoliageUtils.serverSpawnTreeFoliage(
realm,
new Vector3d(
realm.getServerWorldData().convertVoxelToRealSpace(x, worldPos.x),
realX,
height,
realm.getServerWorldData().convertVoxelToRealSpace(z, worldPos.z)
realZ
),
type,
random.nextLong()

View File

@ -1,5 +1,6 @@
package electrosphere.server.content;
import java.util.Collection;
import java.util.List;
import org.joml.Vector3i;
@ -73,9 +74,9 @@ public class ServerContentManager {
/**
* Saves entity content to disk
* @param locationKey the location key to save under
* @param entities the list of entities to save
* @param entities the collection of entities to save
*/
public void saveContentToDisk(Long locationKey, List<Entity> entities){
public void saveContentToDisk(Long locationKey, Collection<Entity> entities){
ContentSerialization serialization = ContentSerialization.constructContentSerialization(entities);
String dirPath = SaveUtils.deriveSaveDirectoryPath(Globals.currentSave.getName());
String fullPath = dirPath + "/content/" + locationKey + ".dat";

View File

@ -1,5 +1,6 @@
package electrosphere.server.content.serialization;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
@ -31,7 +32,7 @@ public class ContentSerialization {
* @param entities The entities
* @return The content serialization
*/
public static ContentSerialization constructContentSerialization(List<Entity> entities){
public static ContentSerialization constructContentSerialization(Collection<Entity> entities){
ContentSerialization rVal = new ContentSerialization();
for(Entity entity : entities){
if(!CreatureUtils.hasControllerPlayerId(entity)){

View File

@ -19,6 +19,9 @@ public class EntityDataCellMapper {
* @param serverDataCell The server data cell to register this entity to
*/
public void registerEntity(Entity entity, ServerDataCell serverDataCell){
if(serverDataCell == null){
throw new Error("Mapping entity to null!");
}
entityDataCellMap.put(entity, serverDataCell);
}

View File

@ -1,12 +1,13 @@
package electrosphere.server.datacell;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.Semaphore;
import org.joml.Vector3d;
@ -50,34 +51,71 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager
*/
public static final int MAX_GRID_SIZE = TerrainModel.MAX_MACRO_DATA_SIZE * TerrainModel.DEFAULT_MACRO_DATA_SCALE * ServerTerrainChunk.CHUNK_DIMENSION;
/**
* The number of frames without players that must pass before a server data cell is unloaded
*/
static final int UNLOAD_FRAME_THRESHOLD = 100;
/**
* Tracks whether this manager has been flagged to unload cells or not
*/
boolean unloadCells = true;
//these are going to be the natural ground grid of data cells, but we're going to have more than this
/**
* These are going to be the natural ground grid of data cells, but we're going to have more than this
*/
Map<Long,ServerDataCell> groundDataCells = new HashMap<Long,ServerDataCell>();
/**
* Map of server cell to its world position
*/
Map<ServerDataCell,Vector3i> cellPositionMap = new HashMap<ServerDataCell,Vector3i>();
//Map of server data cell to the number of frames said cell has had no players
/**
* Map of server data cell to the number of frames said cell has had no players
*/
Map<ServerDataCell,Integer> cellPlayerlessFrameMap = new HashMap<ServerDataCell,Integer>();
//The number of frames without players that must pass before a server data cell is unloaded
static final int UNLOAD_FRAME_THRESHOLD = 100;
//loaded cells
/**
* Loaded cells
*/
Semaphore loadedCellsLock = new Semaphore(1);
Set<ServerDataCell> loadedCells = new CopyOnWriteArraySet<ServerDataCell>();
//parent realm
/**
* Parent realm
*/
Realm parent;
//the world data of the parent
/**
* The world data of the parent
*/
ServerWorldData serverWorldData;
//Manager for terrain for this particular cell manager
/**
* Manager for terrain for this particular cell manager
*/
ServerTerrainManager serverTerrainManager;
//manager for fluids for this particular cell manager
/**
* Manager for fluids for this particular cell manager
*/
ServerFluidManager serverFluidManager;
//lock for terrain editing
/**
* Lock for terrain editing
*/
Semaphore terrainEditLock = new Semaphore(1);
//manager for getting entities to fill in a cell
/**
* Manager for getting entities to fill in a cell
*/
ServerContentManager serverContentManager;
/**
* Used for cleaning server data cells no longer in use from the realm
*/
Set<ServerDataCell> toCleanQueue = new HashSet<ServerDataCell>();
/**
* Map of world position key -> physics cell
*/
@ -132,22 +170,21 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager
){
Vector3i targetPos = new Vector3i(x,y,z);
LoggerInterface.loggerEngine.DEBUG("GriddedDataCellManager: Add player to " + x + " " + y + " " + z);
loadedCellsLock.acquireUninterruptibly();
if(groundDataCells.get(getServerDataCellKey(targetPos)) != null){
groundDataCells.get(getServerDataCellKey(targetPos)).addPlayer(player);
} else {
LoggerInterface.loggerEngine.DEBUG("Creating new cell @ " + x + " " + y + " " + z);
//create data cell
createServerDataCell(targetPos);
this.createServerDataCell(targetPos);
///generates physics for the cell in a dedicated thread then finally registers
runPhysicsGenerationThread(targetPos);
this.runPhysicsGenerationThread(targetPos);
//add to loaded cells
loadedCellsLock.acquireUninterruptibly();
loadedCells.add(groundDataCells.get(getServerDataCellKey(targetPos)));
cellPlayerlessFrameMap.put(groundDataCells.get(getServerDataCellKey(targetPos)),0);
loadedCellsLock.release();
cellPlayerlessFrameMap.put(groundDataCells.get(getServerDataCellKey(targetPos)),0);
//add player
groundDataCells.get(getServerDataCellKey(targetPos)).addPlayer(player);
}
loadedCellsLock.release();
}
}
}
@ -163,36 +200,29 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager
int playerSimulationRadius = player.getSimulationRadius();
Vector3i oldPosition = player.getWorldPos();
player.setWorldPos(newPosition);
// System.out.println("=======" + "SET" + newX + " " + newY + " FROM " + oldX + " " + oldY + "========");
// int removals = 0;
// int additions = 0;
for(int x = oldPosition.x - playerSimulationRadius; x < oldPosition.x + playerSimulationRadius + 1; x++){
for(int y = oldPosition.y - playerSimulationRadius; y < oldPosition.y + playerSimulationRadius + 1; y++){
for(int z = oldPosition.z - playerSimulationRadius; z < oldPosition.z + playerSimulationRadius + 1; z++){
if(
x >= 0 && x < this.serverWorldData.getWorldSizeDiscrete() &&
y >= 0 && y < this.serverWorldData.getWorldSizeDiscrete() &&
z >= 0 && z < this.serverWorldData.getWorldSizeDiscrete() &&
(
x < newPosition.x - playerSimulationRadius ||
x > newPosition.x + playerSimulationRadius ||
y < newPosition.y - playerSimulationRadius ||
y > newPosition.y + playerSimulationRadius ||
z < newPosition.z - playerSimulationRadius ||
z > newPosition.z + playerSimulationRadius
)
){
Vector3i targetPos = new Vector3i(x,y,z);
Long key = this.getServerDataCellKey(targetPos);
if(
groundDataCells.get(key) != null &&
groundDataCells.get(key).containsPlayer(player)
){
// removals++;
groundDataCells.get(key).removePlayer(player);
this.broadcastDestructionToPlayer(player, groundDataCells.get(key));
}
}
for(ServerDataCell cell : this.groundDataCells.values()){
Vector3i worldPos = this.getCellWorldPosition(cell);
if(
cell.containsPlayer(player) &&
(
worldPos.x < newPosition.x - playerSimulationRadius ||
worldPos.x > newPosition.x + playerSimulationRadius ||
worldPos.y < newPosition.y - playerSimulationRadius ||
worldPos.y > newPosition.y + playerSimulationRadius ||
worldPos.z < newPosition.z - playerSimulationRadius ||
worldPos.z > newPosition.z + playerSimulationRadius
)
){
cell.removePlayer(player);
this.broadcastDestructionToPlayer(player, cell);
if(cell.getScene().containsEntity(player.getPlayerEntity())){
throw new Error(
"Unregistering player from cell that contains player's entity!\n " +
player + "\n " +
worldPos + "\n " +
player.getPlayerEntity() + "\n " +
EntityUtils.getPosition(player.getPlayerEntity())
);
}
}
}
@ -213,31 +243,26 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager
)
){
Vector3i targetPos = new Vector3i(x,y,z);
// System.out.println("Add player to " + x + " " + y);
if(groundDataCells.get(getServerDataCellKey(targetPos)) != null){
loadedCellsLock.acquireUninterruptibly();
groundDataCells.get(getServerDataCellKey(targetPos)).addPlayer(player);
loadedCellsLock.release();
} else {
loadedCellsLock.acquireUninterruptibly();
//create data cell
createServerDataCell(targetPos);
//generates physics for the cell in a dedicated thread then finally registers
runPhysicsGenerationThread(targetPos);
//add to loaded cells
loadedCellsLock.acquireUninterruptibly();
loadedCells.add(groundDataCells.get(getServerDataCellKey(targetPos)));
cellPlayerlessFrameMap.put(groundDataCells.get(getServerDataCellKey(targetPos)),0);
loadedCellsLock.release();
//add player
groundDataCells.get(getServerDataCellKey(targetPos)).addPlayer(player);
loadedCellsLock.release();
}
// additions++;
} else {
// System.out.println(x + "\t" + (oldX - playerSimulationRadius) + "\t" + (oldX + playerSimulationRadius));
// System.out.println(y + "\t" + (oldY - playerSimulationRadius) + "\t" + (oldY + playerSimulationRadius));
}
}
}
}
// System.out.println("removals: " + removals + "\tadditions: " + additions);
}
/**
@ -280,25 +305,80 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager
int currentWorldX = parent.getServerWorldData().convertRealToChunkSpace(position.x);
int currentWorldY = parent.getServerWorldData().convertRealToChunkSpace(position.y);
int currentWorldZ = parent.getServerWorldData().convertRealToChunkSpace(position.z);
if(currentWorldX != player.getWorldPos().x || currentWorldY != player.getWorldPos().y || currentWorldZ != player.getWorldPos().z){
movePlayer(player,new Vector3i(currentWorldX,currentWorldY,currentWorldZ));
playerChangedChunk = true;
Vector3i newPosition = new Vector3i(currentWorldX,currentWorldY,currentWorldZ);
player.setWorldPos(newPosition);
int playerSimulationRadius = player.getSimulationRadius();
//remove from cells that are out of range
for(ServerDataCell cell : this.groundDataCells.values()){
Vector3i cellWorldPos = this.getCellWorldPosition(cell);
if(
cell.containsPlayer(player) &&
(
cellWorldPos.x < newPosition.x - playerSimulationRadius ||
cellWorldPos.x > newPosition.x + playerSimulationRadius ||
cellWorldPos.y < newPosition.y - playerSimulationRadius ||
cellWorldPos.y > newPosition.y + playerSimulationRadius ||
cellWorldPos.z < newPosition.z - playerSimulationRadius ||
cellWorldPos.z > newPosition.z + playerSimulationRadius
)
){
if(cell.getScene().containsEntity(player.getPlayerEntity())){
throw new Error("Trying to remove player from a cell that contains its entity!");
}
cell.removePlayer(player);
this.broadcastDestructionToPlayer(player, cell);
}
}
//Add to cells that are in range
for(int x = newPosition.x - playerSimulationRadius + 1; x < newPosition.x + playerSimulationRadius; x++){
for(int y = newPosition.y - playerSimulationRadius + 1; y < newPosition.y + playerSimulationRadius; y++){
for(int z = newPosition.x - playerSimulationRadius + 1; z < newPosition.z + playerSimulationRadius; z++){
if(
x >= 0 && x < this.serverWorldData.getWorldSizeDiscrete() &&
y >= 0 && y < this.serverWorldData.getWorldSizeDiscrete() &&
z >= 0 && z < this.serverWorldData.getWorldSizeDiscrete()
){
Vector3i targetPos = new Vector3i(x,y,z);
if(groundDataCells.get(this.getServerDataCellKey(targetPos)) != null){
loadedCellsLock.acquireUninterruptibly();
ServerDataCell cell = groundDataCells.get(this.getServerDataCellKey(targetPos));
if(!cell.containsPlayer(player)){
cell.addPlayer(player);
}
loadedCellsLock.release();
} else {
loadedCellsLock.acquireUninterruptibly();
//create data cell
this.createServerDataCell(targetPos);
//generates physics for the cell in a dedicated thread then finally registers
this.runPhysicsGenerationThread(targetPos);
//add to loaded cells
cellPlayerlessFrameMap.put(groundDataCells.get(this.getServerDataCellKey(targetPos)),0);
//add player
groundDataCells.get(this.getServerDataCellKey(targetPos)).addPlayer(player);
loadedCellsLock.release();
}
}
}
}
}
}
}
return playerChangedChunk;
}
//Used for cleaning server data cells no longer in use from the realm
Set<ServerDataCell> toCleanQueue = new HashSet<ServerDataCell>();
/**
* Unloads all chunks that haven't had players in them for a set amount of time
*/
public void unloadPlayerlessChunks(){
if(this.unloadCells){
//TODO: improve to make have less performance impact
for(ServerDataCell cell : loadedCells){
loadedCellsLock.acquireUninterruptibly();
loadedCellsLock.acquireUninterruptibly();
for(ServerDataCell cell : this.groundDataCells.values()){
if(cell.getPlayers().size() < 1){
int frameCount = cellPlayerlessFrameMap.get(cell) + 1;
cellPlayerlessFrameMap.put(cell,frameCount);
@ -310,11 +390,9 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager
cellPlayerlessFrameMap.put(cell, 0);
}
}
loadedCellsLock.release();
}
for(ServerDataCell cell : toCleanQueue){
parent.deregisterCell(cell);
loadedCells.remove(cell);
Vector3i worldPos = getCellWorldPosition(cell);
Long key = getServerDataCellKey(worldPos);
groundDataCells.remove(key);
@ -323,13 +401,20 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager
//clear all entities in cell
for(Entity entity : cell.getScene().getEntityList()){
if(ServerPlayerViewDirTree.hasTree(entity)){
throw new Error("Trying to unload a player's entity! " + entity + " " + worldPos);
throw new Error(
"Trying to unload a player's entity! " +
entity + "\n" +
EntityUtils.getPosition(entity) + "\n" +
serverWorldData.convertRealToWorldSpace(EntityUtils.getPosition(entity)) + "\n" +
worldPos
);
}
ServerEntityUtils.destroyEntity(entity);
}
//save terrain to disk
serverTerrainManager.savePositionToDisk(worldPos);
}
loadedCellsLock.release();
toCleanQueue.clear();
}
}
@ -342,16 +427,14 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager
*/
public void evictAll(){
//TODO: improve to make have less performance impact
for(ServerDataCell cell : loadedCells){
loadedCellsLock.acquireUninterruptibly();
loadedCellsLock.acquireUninterruptibly();
for(ServerDataCell cell : this.groundDataCells.values()){
int frameCount = cellPlayerlessFrameMap.get(cell) + 1;
cellPlayerlessFrameMap.put(cell,frameCount);
toCleanQueue.add(cell);
loadedCellsLock.release();
}
for(ServerDataCell cell : toCleanQueue){
parent.deregisterCell(cell);
loadedCells.remove(cell);
Vector3i worldPos = getCellWorldPosition(cell);
Long key = getServerDataCellKey(worldPos);
groundDataCells.remove(key);
@ -362,6 +445,7 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager
ServerEntityUtils.destroyEntity(entity);
}
}
loadedCellsLock.release();
this.serverTerrainManager.evictAll();
toCleanQueue.clear();
}
@ -420,13 +504,12 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager
//isn't null
groundDataCells.get(this.getServerDataCellKey(worldPos)) == null
){
loadedCellsLock.acquireUninterruptibly();
//create data cell
this.createServerDataCell(worldPos);
//generates physics for the cell in a dedicated thread then finally registers
this.runPhysicsGenerationThread(worldPos);
//add to loaded cells
loadedCellsLock.acquireUninterruptibly();
loadedCells.add(groundDataCells.get(this.getServerDataCellKey(worldPos)));
cellPlayerlessFrameMap.put(groundDataCells.get(this.getServerDataCellKey(worldPos)),0);
loadedCellsLock.release();
} else if(groundDataCells.get(this.getServerDataCellKey(worldPos)) == null) {
@ -464,7 +547,7 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager
*/
public void simulate(){
loadedCellsLock.acquireUninterruptibly();
for(ServerDataCell cell : loadedCells){
for(ServerDataCell cell : this.groundDataCells.values()){
if(Globals.microSimulation != null && Globals.microSimulation.isReady()){
Globals.microSimulation.simulate(cell);
}
@ -477,7 +560,7 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager
}
}
loadedCellsLock.release();
updatePlayerPositions();
this.updatePlayerPositions();
}
@ -536,7 +619,7 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager
*/
private ServerDataCell createServerDataCell(Vector3i worldPos){
ServerDataCell rVal = parent.createNewCell();
Long cellKey = getServerDataCellKey(worldPos);
Long cellKey = this.getServerDataCellKey(worldPos);
groundDataCells.put(cellKey,rVal);
LoggerInterface.loggerEngine.DEBUG("Create server data cell with key " + cellKey);
cellPositionMap.put(rVal,new Vector3i(worldPos));
@ -674,7 +757,7 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager
@Override
public void save(String saveName) {
for(ServerDataCell cell : loadedCells){
for(ServerDataCell cell : this.groundDataCells.values()){
Long key = this.getServerDataCellKey(this.getCellWorldPosition(cell));
//offload all entities in cell to chunk file
serverContentManager.saveContentToDisk(key, cell.getScene().getEntityList());
@ -705,4 +788,12 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager
return returnPos;
}
/**
* Gets the set of loaded cells
* @return The set of loaded cells
*/
public Collection<ServerDataCell> getLoadedCells(){
return Collections.unmodifiableCollection(this.groundDataCells.values());
}
}

View File

@ -150,14 +150,6 @@ public class Realm {
Globals.entityDataCellMapper.registerEntity(entity, cell);
}
/**
* Gets the entity data cell mapper for this realm
* @return The entity data cell mapper for this realm
*/
public EntityDataCellMapper getEntityDataCellMapper(){
return this.entityDataCellMapper;
}
/**
* Gets the data cell manager for this realm
@ -167,14 +159,6 @@ public class Realm {
return this.dataCellManager;
}
/**
* Sets the entity data cell mapper for this realm
* @param entityDataCellMapper The entity data cell mapper for this realm
*/
protected void setEntityDataCellMapper(EntityDataCellMapper entityDataCellMapper){
this.entityDataCellMapper = entityDataCellMapper;
}
/**
* Sets the data cell manager for this realm
* @param dataCellManager The data cell manager for this realm

View File

@ -10,6 +10,7 @@ import org.joml.Vector3d;
import electrosphere.collision.CollisionEngine;
import electrosphere.collision.CollisionWorldData;
import electrosphere.collision.hitbox.HitboxManager;
import electrosphere.engine.Globals;
import electrosphere.entity.Entity;
import electrosphere.game.server.world.ServerWorldData;
import electrosphere.net.server.player.Player;
@ -76,10 +77,8 @@ public class RealmManager {
);
//create function classes
GriddedDataCellManager griddedDataCellManager = new GriddedDataCellManager(realm);
EntityDataCellMapper entityDataCellMapper = new EntityDataCellMapper();
//add function classes to realm
realm.setDataCellManager(griddedDataCellManager);
realm.setEntityDataCellMapper(entityDataCellMapper);
//register within the manager
realms.add(realm);
return realm;
@ -113,7 +112,6 @@ public class RealmManager {
//add function classes to realm
realm.setDataCellManager(ViewportDataCellManager.create(realm));
realm.setEntityDataCellMapper(new EntityDataCellMapper());
//register
realms.add(realm);

View File

@ -22,7 +22,7 @@ import java.util.Set;
* of simulation. It just acts as an object to relate players and entities by location.
* This SHOULD be used for networking purposes. This is the mechanism to scope
* network messages by location. If you are looking for something closer to a scene from
* a traditional game engine, EntityManager is effectively a scene for all intents and
* a traditional game engine, Realm is effectively a scene for all intents and
* purposes.
*
*/
@ -62,9 +62,9 @@ public class ServerDataCell {
* @param p
*/
public void addPlayer(Player p){
if(!activePlayers.contains(p)){
if(!this.containsPlayer(p)){
activePlayers.add(p);
serializeStateToPlayer(p);
this.serializeStateToPlayer(p);
}
}
@ -134,7 +134,7 @@ public class ServerDataCell {
*/
void serializeStateToPlayer(Player player){
for(Entity entity : scene.getEntityList()){
serializeEntityToPlayer(entity,player);
this.serializeEntityToPlayer(entity,player);
}
}
@ -144,21 +144,23 @@ public class ServerDataCell {
* @param player The player to send the entity to
*/
void serializeEntityToPlayer(Entity entity, Player player){
EntityType type = CommonEntityUtils.getEntityType(entity);
if(type != null){
switch(type){
case CREATURE: {
CreatureUtils.sendEntityToPlayer(player, entity);
} break;
case ITEM: {
ItemUtils.sendEntityToPlayer(player, entity);
} break;
case FOLIAGE: {
FoliageUtils.sendFoliageToPlayer(player, entity);
} break;
case COMMON: {
CommonEntityUtils.sendEntityToPlayer(player, entity);
} break;
if(!player.hasSentPlayerEntity() || player.getPlayerEntity() == null || player.getPlayerEntity() != entity){
EntityType type = CommonEntityUtils.getEntityType(entity);
if(type != null){
switch(type){
case CREATURE: {
CreatureUtils.sendEntityToPlayer(player, entity);
} break;
case ITEM: {
ItemUtils.sendEntityToPlayer(player, entity);
} break;
case FOLIAGE: {
FoliageUtils.sendFoliageToPlayer(player, entity);
} break;
case COMMON: {
CommonEntityUtils.sendEntityToPlayer(player, entity);
} break;
}
}
}
}
@ -181,32 +183,42 @@ public class ServerDataCell {
* @param newCell The new datacell it's moving to
*/
public static void moveEntityFromCellToCell(Entity entity, ServerDataCell oldCell, ServerDataCell newCell){
//swap which holds the entity
if(oldCell != null){
oldCell.getScene().deregisterEntity(entity);
if(entity == null){
throw new Error("Passed null entity! " + entity);
}
if(oldCell == null){
throw new Error("Passed null oldCell! " + oldCell);
}
if(newCell == null){
throw new Error("Passed null newCell! " + newCell);
}
//swap which holds the entity
oldCell.getScene().deregisterEntity(entity);
newCell.getScene().registerEntity(entity);
//update entity data cell mapper
Globals.entityDataCellMapper.updateEntityCell(entity, newCell);
//send the entity to new players that should care about it
for(Player player : newCell.activePlayers){
if(oldCell != null){
//if the player hasn't already seen the entity, serialize it
if(!oldCell.containsPlayer(player)){
if(player.getPlayerEntity() == null || player.getPlayerEntity() != entity){
if(oldCell != null){
//if the player hasn't already seen the entity, serialize it
if(!oldCell.containsPlayer(player)){
newCell.serializeEntityToPlayer(entity, player);
}
} else {
//if the entity wasn't in a previous cell, send it to all players
newCell.serializeEntityToPlayer(entity, player);
}
} else {
//if the entity wasn't in a previous cell, send it to all players
newCell.serializeEntityToPlayer(entity, player);
}
}
//delete the entity for players that dont care about it
if(oldCell != null){
for(Player player : oldCell.activePlayers){
if(!newCell.containsPlayer(player)){
//if the player isn't also in the new cell, delete the entity
player.addMessage(EntityMessage.constructDestroyMessage(entity.getId()));
}
for(Player player : oldCell.activePlayers){
if(
!newCell.containsPlayer(player) &&
(player.getPlayerEntity() == null || player.getPlayerEntity() != entity)
){
//if the player isn't also in the new cell, delete the entity
player.addMessage(EntityMessage.constructDestroyMessage(entity.getId()));
}
}
}

View File

@ -29,7 +29,7 @@ public class DataCellSearchUtils {
LoggerInterface.loggerEngine.ERROR(new IllegalArgumentException("Trying to get entity data cell of an entity that is not assigned to a realm!"));
return null;
}
return realm.getEntityDataCellMapper().getEntityDataCell(entity);
return Globals.entityDataCellMapper.getEntityDataCell(entity);
}
/**

View File

@ -1,5 +1,6 @@
package electrosphere.server.datacell.utils;
import electrosphere.engine.Globals;
import electrosphere.entity.Entity;
import electrosphere.server.datacell.ServerDataCell;
@ -14,7 +15,7 @@ public class ServerEntityTagUtils {
* @param tag The tag
*/
public static void attachTagToEntity(Entity entity, String tag){
ServerDataCell cell = DataCellSearchUtils.getEntityDataCell(entity);
ServerDataCell cell = Globals.entityDataCellMapper.getEntityDataCell(entity);
cell.getScene().registerEntityToTag(entity, tag);
}
@ -24,7 +25,7 @@ public class ServerEntityTagUtils {
* @param tag The tag
*/
public static void removeTagFromEntity(Entity entity, String tag){
ServerDataCell cell = DataCellSearchUtils.getEntityDataCell(entity);
ServerDataCell cell = Globals.entityDataCellMapper.getEntityDataCell(entity);
cell.getScene().removeEntityFromTag(entity, tag);
}