969 lines
		
	
	
		
			42 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
			
		
		
	
	
			969 lines
		
	
	
		
			42 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
| package electrosphere.server.datacell.gridded;
 | |
| 
 | |
| 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.Objects;
 | |
| 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;
 | |
| import electrosphere.game.server.world.ServerWorldData;
 | |
| import electrosphere.logger.LoggerInterface;
 | |
| import electrosphere.net.parser.net.message.EntityMessage;
 | |
| import electrosphere.net.parser.net.message.TerrainMessage;
 | |
| import electrosphere.net.server.player.Player;
 | |
| import electrosphere.net.server.protocol.TerrainProtocol;
 | |
| import electrosphere.server.block.manager.ServerBlockManager;
 | |
| import electrosphere.server.content.ServerContentManager;
 | |
| import electrosphere.server.datacell.Realm;
 | |
| import electrosphere.server.datacell.ServerDataCell;
 | |
| import electrosphere.server.datacell.interfaces.DataCellManager;
 | |
| import electrosphere.server.datacell.interfaces.VoxelCellManager;
 | |
| import electrosphere.server.datacell.physics.PhysicsDataCell;
 | |
| import electrosphere.server.fluid.manager.ServerFluidChunk;
 | |
| import electrosphere.server.fluid.manager.ServerFluidManager;
 | |
| import electrosphere.server.terrain.manager.ServerTerrainManager;
 | |
| import electrosphere.server.terrain.models.TerrainModel;
 | |
| import electrosphere.server.terrain.manager.ServerTerrainChunk;
 | |
| 
 | |
| /**
 | |
|  * Implementation of DataCellManager that lays out cells in a logical grid (array). Useful for eg 3d terrain gridded world.
 | |
|  */
 | |
| public class GriddedDataCellManager implements DataCellManager, VoxelCellManager {
 | |
| 
 | |
|     /**
 | |
|      * The minimum grid size allowed
 | |
|      */
 | |
|     public static final int MIN_GRID_SIZE = 1;
 | |
| 
 | |
|     /**
 | |
|      * The max grid size allowed
 | |
|      */
 | |
|     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;
 | |
| 
 | |
|     /**
 | |
|      * Used for generating physics chunks
 | |
|      */
 | |
|     static final ExecutorService generationService = Executors.newFixedThreadPool(4);
 | |
| 
 | |
|     /**
 | |
|      * 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
 | |
|      */
 | |
|     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<ServerDataCell,Integer> cellPlayerlessFrameMap = new HashMap<ServerDataCell,Integer>();
 | |
| 
 | |
|     /**
 | |
|      * Loaded cells
 | |
|      */
 | |
|     ReentrantLock loadedCellsLock = new ReentrantLock();
 | |
| 
 | |
|     /**
 | |
|      * Parent realm
 | |
|      */
 | |
|     Realm parent;
 | |
| 
 | |
|     /**
 | |
|      * The world data of the parent
 | |
|      */
 | |
|     ServerWorldData serverWorldData;
 | |
| 
 | |
|     /**
 | |
|      * Manager for terrain for this particular cell manager
 | |
|      */
 | |
|     ServerTerrainManager serverTerrainManager;
 | |
| 
 | |
|     /**
 | |
|      * Manager for fluids for this particular cell manager
 | |
|      */
 | |
|     ServerFluidManager serverFluidManager;
 | |
| 
 | |
|     /**
 | |
|      * Lock for terrain editing
 | |
|      */
 | |
|     Semaphore terrainEditLock = new Semaphore(1);
 | |
| 
 | |
|     /**
 | |
|      * 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
 | |
|      */
 | |
|     Map<Long,PhysicsDataCell> posPhysicsMap = new HashMap<Long,PhysicsDataCell>();
 | |
| 
 | |
|     /**
 | |
|      * Number of data cells cleaned up in the most recent frame
 | |
|      */
 | |
|     int numCleaned = 0;
 | |
|     
 | |
|     /**
 | |
|      * Constructor
 | |
|      * @param parent The gridded data cell manager's parent realm
 | |
|      */
 | |
|     public GriddedDataCellManager(
 | |
|         Realm parent
 | |
|     ) {
 | |
|         this.parent = parent;
 | |
|         this.serverWorldData = this.parent.getServerWorldData();
 | |
|         this.serverTerrainManager = serverWorldData.getServerTerrainManager();
 | |
|         this.serverFluidManager = serverWorldData.getServerFluidManager();
 | |
|         this.serverContentManager = this.parent.getServerContentManager();
 | |
| 
 | |
|         //Assert the gridded data cell manager was given good data
 | |
|         if(
 | |
|             this.parent == null ||
 | |
|             this.serverWorldData == null ||
 | |
|             this.serverTerrainManager == null ||
 | |
|             this.serverFluidManager == null ||
 | |
|             this.serverContentManager == null
 | |
|         ){
 | |
|             throw new IllegalStateException("Tried to create a GriddedDataCellManager with invalid parameters " +
 | |
|             this.parent + " " +
 | |
|             this.serverWorldData + " " +
 | |
|             this.serverTerrainManager + " " +
 | |
|             this.serverFluidManager + " " +
 | |
|             this.serverContentManager + " "
 | |
|             );
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Adds a player to the realm that this manager controls. Should do this intelligently based on the player's location
 | |
|      * @param player The player
 | |
|      */
 | |
|     public void addPlayerToRealm(Player player){
 | |
|         Globals.realmManager.setPlayerRealm(player, parent);
 | |
|         int playerSimulationRadius = player.getSimulationRadius();
 | |
|         Vector3i worldPos = player.getWorldPos();
 | |
|         for(int x = worldPos.x - playerSimulationRadius; x < worldPos.x + playerSimulationRadius + 1; x++){
 | |
|             for(int y = worldPos.y - playerSimulationRadius; y < worldPos.y + playerSimulationRadius + 1; y++){
 | |
|                 for(int z = worldPos.z - playerSimulationRadius; z < worldPos.z + playerSimulationRadius + 1; z++){
 | |
|                     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.lock();
 | |
|                         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
 | |
|                             this.createServerDataCell(targetPos);
 | |
|                             //add player
 | |
|                             groundDataCells.get(getServerDataCellKey(targetPos)).addPlayer(player);
 | |
|                         }
 | |
|                         loadedCellsLock.unlock();
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Moves a player to a new position
 | |
|      * @param player The player
 | |
|      * @param newPosition The new position
 | |
|      */
 | |
|     public void movePlayer(Player player, Vector3i newPosition){
 | |
|         int playerSimulationRadius = player.getSimulationRadius();
 | |
|         player.setWorldPos(newPosition);
 | |
|         for(ServerDataCell cell : this.groundDataCells.values()){
 | |
|             Vector3i worldPos = this.getCellWorldPosition(cell);
 | |
|             if(cell.containsPlayer(player) && !this.shouldContainPlayer(worldPos, newPosition, 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())
 | |
|                     );
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         for(int x = newPosition.x - playerSimulationRadius; x < newPosition.x + playerSimulationRadius + 1; x++){
 | |
|             for(int y = newPosition.y - playerSimulationRadius; y < newPosition.y + playerSimulationRadius + 1; y++){
 | |
|                 for(int z = newPosition.x - playerSimulationRadius; z < newPosition.z + playerSimulationRadius + 1; z++){
 | |
|                     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.lock();
 | |
|                             groundDataCells.get(this.getServerDataCellKey(targetPos)).addPlayer(player);
 | |
|                             loadedCellsLock.unlock();
 | |
|                         } else {
 | |
|                             loadedCellsLock.lock();
 | |
|                             //create data cell
 | |
|                             this.createServerDataCell(targetPos);
 | |
|                             //add player
 | |
|                             groundDataCells.get(this.getServerDataCellKey(targetPos)).addPlayer(player);
 | |
|                             loadedCellsLock.unlock();
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Checks if a player should be contained in a cell
 | |
|      * @param cellWorldPos The world position of the cell
 | |
|      * @param playerPos The world position of the player
 | |
|      * @param simRadius The simulation radius of the player
 | |
|      * @return true if the player should be contained in the cell, false otherwise
 | |
|      */
 | |
|     private boolean shouldContainPlayer(Vector3i cellWorldPos, Vector3i playerPos, int simRadius){
 | |
|         return cellWorldPos.distance(playerPos) < simRadius;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Checks if a cell can be created at the position
 | |
|      * @param cellPos The position to check
 | |
|      * @return true if a cell can be created at that position, false otherwise
 | |
|      */
 | |
|     private boolean canCreateCell(Vector3i cellPos){
 | |
|         return this.canCreateCell(cellPos.x, cellPos.y, cellPos.z);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Checks if a cell can be created at the position
 | |
|      * @return true if a cell can be created at that position, false otherwise
 | |
|      */
 | |
|     private boolean canCreateCell(int x, int y, int z){
 | |
|         return
 | |
|             x >= 0 && x < this.serverWorldData.getWorldSizeDiscrete() &&
 | |
|             y >= 0 && y < this.serverWorldData.getWorldSizeDiscrete() &&
 | |
|             z >= 0 && z < this.serverWorldData.getWorldSizeDiscrete()
 | |
|         ;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Broadcasts messages to player to destroy all entities in a given cell
 | |
|      * @param player The player
 | |
|      * @param cell The cell
 | |
|      */
 | |
|     private void broadcastDestructionToPlayer(Player player, ServerDataCell cell){
 | |
|         for(Entity entity : cell.getScene().getEntityList()){
 | |
|             player.addMessage(EntityMessage.constructDestroyMessage(entity.getId()));
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Creates physics entities when new data cell being created
 | |
|      */
 | |
|     private void createTerrainPhysicsEntities(Vector3i worldPos){
 | |
|         Long key = this.getServerDataCellKey(worldPos);
 | |
|         if(posPhysicsMap.containsKey(key)){
 | |
|             PhysicsDataCell cell = posPhysicsMap.get(key);
 | |
|             cell.retireCell();
 | |
|         }
 | |
|         //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
 | |
|      * @return True if the player changed cell, false otherwise
 | |
|      */
 | |
|     public boolean updatePlayerPositions(){
 | |
|         Globals.profiler.beginCpuSample("GriddedDataCellManager.updatePlayerPositions");
 | |
|         boolean playerChangedChunk = false;
 | |
|         for(Player player : Globals.playerManager.getPlayers()){
 | |
|             Entity playerEntity = player.getPlayerEntity();
 | |
|             if(playerEntity != null && !parent.getLoadingDataCell().containsPlayer(player)){
 | |
|                 Vector3d position = EntityUtils.getPosition(playerEntity);
 | |
|                 int currentWorldX = parent.getServerWorldData().convertRealToChunkSpace(position.x);
 | |
|                 int currentWorldY = parent.getServerWorldData().convertRealToChunkSpace(position.y);
 | |
|                 int currentWorldZ = parent.getServerWorldData().convertRealToChunkSpace(position.z);
 | |
|                 Vector3i newPosition = new Vector3i(currentWorldX,currentWorldY,currentWorldZ);
 | |
|                 player.setWorldPos(newPosition);
 | |
| 
 | |
|                 int playerSimulationRadius = player.getSimulationRadius();
 | |
| 
 | |
|                 //remove from cells that are out of range
 | |
|                 loadedCellsLock.lock();
 | |
|                 Globals.profiler.beginCpuSample("GriddedDataCellManager.updatePlayerPositions - Remove from old cells");
 | |
|                 for(ServerDataCell cell : this.groundDataCells.values()){
 | |
|                     Vector3i cellWorldPos = this.getCellWorldPosition(cell);
 | |
|                     if(cell.containsPlayer(player) && !this.shouldContainPlayer(cellWorldPos, newPosition, 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);
 | |
|                     }
 | |
|                 }
 | |
|                 Globals.profiler.endCpuSample();
 | |
|                 loadedCellsLock.unlock();
 | |
| 
 | |
|                 //Add to cells that are in range
 | |
|                 Globals.profiler.beginCpuSample("GriddedDataCellManager.updatePlayerPositions - Create new cells");
 | |
|                 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.z - playerSimulationRadius + 1; z < newPosition.z + playerSimulationRadius; z++){
 | |
|                             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.lock();
 | |
|                                     ServerDataCell cell = groundDataCells.get(this.getServerDataCellKey(targetPos));
 | |
|                                     if(!cell.containsPlayer(player)){
 | |
|                                         cell.addPlayer(player);
 | |
|                                     }
 | |
|                                     loadedCellsLock.unlock();
 | |
|                                 } else {
 | |
|                                     loadedCellsLock.lock();
 | |
|                                     //create data cell
 | |
|                                     this.createServerDataCell(targetPos);
 | |
|                                     //add player
 | |
|                                     groundDataCells.get(this.getServerDataCellKey(targetPos)).addPlayer(player);
 | |
|                                     loadedCellsLock.unlock();
 | |
|                                 }
 | |
|                             }
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
|                 Globals.profiler.endCpuSample();
 | |
|             }
 | |
|         }
 | |
|         Globals.profiler.endCpuSample();
 | |
|         return playerChangedChunk;
 | |
|     }
 | |
| 
 | |
| 
 | |
|     /**
 | |
|      * Unloads all chunks that haven't had players in them for a set amount of time
 | |
|      */
 | |
|     public void unloadPlayerlessChunks(){
 | |
|         Globals.profiler.beginCpuSample("GriddedDataCellManager.unloadPlayerlessChunks");
 | |
|         if(this.unloadCells){
 | |
|             //TODO: improve to make have less performance impact
 | |
|             loadedCellsLock.lock();
 | |
|             Globals.profiler.beginCpuSample("GriddedDataCellManager.unloadPlayerlessChunks - Increment cleaning time");
 | |
|             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);
 | |
|                         if(frameCount > UNLOAD_FRAME_THRESHOLD){
 | |
|                             toCleanQueue.add(cell);
 | |
|                         }
 | |
|                     } else {
 | |
|                         if(cellPlayerlessFrameMap.get(cell) > 0){
 | |
|                             cellPlayerlessFrameMap.put(cell, 0);
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|             Globals.profiler.endCpuSample();
 | |
|             Globals.profiler.beginCpuSample("GriddedDataCellManager.unloadPlayerlessChunks - Deconstruct timed out cells");
 | |
|             this.numCleaned = toCleanQueue.size();
 | |
|             for(ServerDataCell cell : toCleanQueue){
 | |
|                 boolean containsPlayerEntity = false;
 | |
|                 //clear all entities in cell
 | |
|                 for(Entity entity : cell.getScene().getEntityList()){
 | |
|                     if(ServerPlayerViewDirTree.hasTree(entity)){
 | |
|                         containsPlayerEntity = true;
 | |
|                         break;
 | |
|                         // int playerId = CreatureUtils.getControllerPlayerId(entity);
 | |
|                         // Player player = Globals.playerManager.getPlayerFromId(playerId);
 | |
|                         // throw new Error(
 | |
|                         //     "Trying to unload a player's entity! " +
 | |
|                         //     "entity: " + entity + "\n" +
 | |
|                         //     "entity pos  (real): " + EntityUtils.getPosition(entity) + "\n" +
 | |
|                         //     "entity pos (world): " + serverWorldData.convertRealToWorldSpace(EntityUtils.getPosition(entity)) + "\n" +
 | |
|                         //     "chunk  pos (world): " + worldPos + "\n" +
 | |
|                         //     "player pos (world): " + player.getWorldPos() + "\n" +
 | |
|                         //     "Number of players in cell: " + cell.getPlayers().size()
 | |
|                         // );
 | |
|                     }
 | |
|                     // ServerEntityUtils.destroyEntity(entity);
 | |
|                 }
 | |
|                 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
 | |
|                 Globals.profiler.beginCpuSample("GriddedDataCellManager.unloadPlayerlessChunks - Destroy entities");
 | |
|                 serverContentManager.saveContentToDisk(key, cell.getScene().getEntityList());
 | |
|                 for(Entity entity : cell.getScene().getEntityList()){
 | |
|                     ServerEntityUtils.destroyEntity(entity);
 | |
|                 }
 | |
|                 Globals.profiler.endCpuSample();
 | |
| 
 | |
|                 //save terrain to disk
 | |
|                 //terrain is saved before tracking is removed. This makes sure that any side effects from calling savePositionToDisk (ie if it looks up the chunk that we're deleting)
 | |
|                 //don't trigger the chunk to be re-created
 | |
|                 Globals.profiler.beginCpuSample("GriddedDataCellManager.unloadPlayerlessChunks - Store terrain");
 | |
|                 serverTerrainManager.savePositionToDisk(worldPos);
 | |
|                 Globals.profiler.endCpuSample();
 | |
| 
 | |
|                 //deregister from all tracking structures
 | |
|                 parent.deregisterCell(cell);
 | |
|                 groundDataCells.remove(key);
 | |
|                 this.posPhysicsMap.remove(key);
 | |
|                 this.cellPositionMap.remove(cell);
 | |
|                 this.cellPlayerlessFrameMap.remove(cell);
 | |
|             }
 | |
|             Globals.profiler.endCpuSample();
 | |
|             loadedCellsLock.unlock();
 | |
|             toCleanQueue.clear();
 | |
|         }
 | |
|         Globals.profiler.endCpuSample();
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Evicts all loaded chunks.
 | |
|      */
 | |
|     public void evictAll(){
 | |
|         //TODO: improve to make have less performance impact
 | |
|         loadedCellsLock.lock();
 | |
|         for(ServerDataCell cell : this.groundDataCells.values()){
 | |
|             int frameCount = cellPlayerlessFrameMap.get(cell) + 1;
 | |
|             cellPlayerlessFrameMap.put(cell,frameCount);
 | |
|             toCleanQueue.add(cell);
 | |
|         }
 | |
|         for(ServerDataCell cell : toCleanQueue){
 | |
|             parent.deregisterCell(cell);
 | |
|             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
 | |
|             for(Entity entity : cell.getScene().getEntityList()){
 | |
|                 ServerEntityUtils.destroyEntity(entity);
 | |
|             }
 | |
|         }
 | |
|         loadedCellsLock.unlock();
 | |
|         this.serverTerrainManager.evictAll();
 | |
|         toCleanQueue.clear();
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Get data cell at a given real point in this realm
 | |
|      * @param point The real point
 | |
|      * @return Either the data cell if found, or null if not found
 | |
|      */
 | |
|     public ServerDataCell getDataCellAtPoint(Vector3d point){
 | |
|         ServerDataCell rVal = null;
 | |
|         int worldX = parent.getServerWorldData().convertRealToChunkSpace(point.x);
 | |
|         int worldY = parent.getServerWorldData().convertRealToChunkSpace(point.y);
 | |
|         int worldZ = parent.getServerWorldData().convertRealToChunkSpace(point.z);
 | |
|         Vector3i worldPos = new Vector3i(worldX,worldY,worldZ);
 | |
|         if(
 | |
|             //in bounds of array
 | |
|             worldX >= 0 && worldX < this.serverWorldData.getWorldSizeDiscrete() &&
 | |
|             worldY >= 0 && worldY < this.serverWorldData.getWorldSizeDiscrete() &&
 | |
|             worldZ >= 0 && worldZ < this.serverWorldData.getWorldSizeDiscrete() &&
 | |
|             //isn't null
 | |
|             groundDataCells.get(getServerDataCellKey(worldPos)) != null
 | |
|         ){
 | |
|             LoggerInterface.loggerEngine.DEBUG("Get server data cell key: " + this.getServerDataCellKey(worldPos));
 | |
|             rVal = groundDataCells.get(getServerDataCellKey(worldPos));
 | |
|         } else {
 | |
|             LoggerInterface.loggerEngine.DEBUG("Failed to get server data cell at: " + worldPos);
 | |
|         }
 | |
|         return rVal;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Tries to create a data cell at a given real point
 | |
|      * @param point The real point
 | |
|      * @return The data cell if created, null otherwise
 | |
|      */
 | |
|     public ServerDataCell tryCreateCellAtPoint(Vector3d point){
 | |
|         int worldX = parent.getServerWorldData().convertRealToChunkSpace(point.x);
 | |
|         int worldY = parent.getServerWorldData().convertRealToChunkSpace(point.y);
 | |
|         int worldZ = parent.getServerWorldData().convertRealToChunkSpace(point.z);
 | |
|         Vector3i worldPos = new Vector3i(worldX,worldY,worldZ);
 | |
|         return tryCreateCellAtPoint(worldPos);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Tries to create a data cell at a given discrete point
 | |
|      * @param point The discrete point
 | |
|      * @return The data cell if created, null otherwise
 | |
|      */
 | |
|     public ServerDataCell tryCreateCellAtPoint(Vector3i worldPos){
 | |
|         if(this.canCreateCell(worldPos) && groundDataCells.get(this.getServerDataCellKey(worldPos)) == null){
 | |
|             loadedCellsLock.lock();
 | |
|             //create data cell
 | |
|             this.createServerDataCell(worldPos);
 | |
|             loadedCellsLock.unlock();
 | |
|         } else if(groundDataCells.get(this.getServerDataCellKey(worldPos)) == null) {
 | |
|             LoggerInterface.loggerEngine.ERROR(
 | |
|                 new Error(
 | |
|                     "Trying to create data cell outside world bounds!\n" +
 | |
|                     worldPos + "\n" +
 | |
|                     this.serverWorldData.getWorldSizeDiscrete()
 | |
|                 )
 | |
|             );
 | |
|         }
 | |
|         return groundDataCells.get(this.getServerDataCellKey(worldPos));
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Gets a data cell at a given world position
 | |
|      * @param position The world position
 | |
|      * @return The data cell if found, null otherwise
 | |
|      */
 | |
|     public ServerDataCell getCellAtWorldPosition(Vector3i position){
 | |
|         if(this.canCreateCell(position) && groundDataCells.get(this.getServerDataCellKey(position)) != null){
 | |
|             return groundDataCells.get(this.getServerDataCellKey(position));
 | |
|         }
 | |
|         return null;
 | |
|     }
 | |
| 
 | |
| 
 | |
|     /**
 | |
|      * Calls the simulate function on all loaded cells
 | |
|      */
 | |
|     public void simulate(){
 | |
|         Globals.profiler.beginCpuSample("GriddedDataCellManager.simulate");
 | |
|         loadedCellsLock.lock();
 | |
|         for(ServerDataCell cell : this.groundDataCells.values()){
 | |
|             if(Globals.microSimulation != null && Globals.microSimulation.isReady()){
 | |
|                 Globals.microSimulation.simulate(cell);
 | |
|             }
 | |
| 
 | |
|             //queue fluid simulation
 | |
|             if(Globals.RUN_FLUIDS){
 | |
|                 Vector3i cellPos = this.getCellWorldPosition(cell);
 | |
|                 if(cellPos != null){
 | |
|                     this.serverFluidManager.queue(cellPos.x, cellPos.y, cellPos.z);
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         //simulate fluids
 | |
|         if(Globals.RUN_FLUIDS){
 | |
|             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())
 | |
|                 );
 | |
|             });
 | |
|         }
 | |
| 
 | |
|         loadedCellsLock.unlock();
 | |
|         this.unloadPlayerlessChunks();
 | |
|         this.updatePlayerPositions();
 | |
|         Globals.profiler.endCpuSample();
 | |
|     }
 | |
| 
 | |
| 
 | |
|     /**
 | |
|      * Gets the server terrain manager for this realm if it exists
 | |
|      * @return The server terrain manager if it exists, null otherwise
 | |
|      */
 | |
|     public ServerTerrainManager getServerTerrainManager(){
 | |
|         return serverTerrainManager;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Gets the server fluid manager for this realm if it exists
 | |
|      * @return The server fluid manager if it exists, null otherwise
 | |
|      */
 | |
|     public ServerFluidManager getServerFluidManager(){
 | |
|         return serverFluidManager;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Runs code to generate physics entities and register cell in a dedicated thread.
 | |
|      * Because cell hasn't been registered yet, no simulation is performed until the physics is created.
 | |
|      * @param 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
 | |
|             if(cell != null){
 | |
|                 cell.retireCell();
 | |
|                 cell.generatePhysics();
 | |
|             } else {
 | |
|                 targetCell.generatePhysics();
 | |
|             }
 | |
|             //set ready
 | |
|             dataCell.setReady(true);
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Gets the key in the groundDataCells map for the data cell at the provided world pos
 | |
|      * @param worldPos The position in world coordinates of the server data cell
 | |
|      * @return The server data cell if it exists, otherwise null
 | |
|      */
 | |
|     private Long getServerDataCellKey(Vector3i worldPos){
 | |
|         return (long)Objects.hash(worldPos.x, worldPos.y, worldPos.z);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Registers a server data cell with the internal datastructure for tracking them
 | |
|      * @param key The key to register the cell at
 | |
|      * @param cell The cell itself
 | |
|      */
 | |
|     private ServerDataCell createServerDataCell(Vector3i worldPos){
 | |
|         Globals.profiler.beginCpuSample("GriddedDataCellManager.createServerDataCell");
 | |
|         ServerDataCell rVal = parent.createNewCell();
 | |
|         Long cellKey = this.getServerDataCellKey(worldPos);
 | |
|         groundDataCells.put(cellKey,rVal);
 | |
|         cellPlayerlessFrameMap.put(rVal,0);
 | |
|         LoggerInterface.loggerEngine.DEBUG("Create server data cell with key " + cellKey);
 | |
|         cellPositionMap.put(rVal,new Vector3i(worldPos));
 | |
|         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;
 | |
|     }
 | |
| 
 | |
|     @Override
 | |
|     /**
 | |
|      * Gets the weight of a single voxel at a position
 | |
|      * @param worldPosition The position in world coordinates of the chunk to grab data from
 | |
|      * @param voxelPosition The position in voxel coordinates (local/relative to the chunk) to get voxel values from
 | |
|      * @return The weight of the described voxel
 | |
|      */
 | |
|     public float getVoxelWeightAtLocalPosition(Vector3i worldPosition, Vector3i voxelPosition) {
 | |
|         return serverTerrainManager.getChunk(worldPosition.x, worldPosition.y, worldPosition.z).getWeights()[voxelPosition.x][voxelPosition.y][voxelPosition.z];
 | |
|     }
 | |
| 
 | |
|     @Override
 | |
|     /**
 | |
|      * Gets the type of a single voxel at a position
 | |
|      * @param worldPosition The position in world coordinates of the chunk to grab data from
 | |
|      * @param voxelPosition The position in voxel coordinates (local/relative to the chunk) to get voxel values from
 | |
|      * @return The type of the described voxel
 | |
|      */
 | |
|     public int getVoxelTypeAtLocalPosition(Vector3i worldPosition, Vector3i voxelPosition) {
 | |
|         return serverTerrainManager.getChunk(worldPosition.x, worldPosition.y, worldPosition.z).getValues()[voxelPosition.x][voxelPosition.y][voxelPosition.z];
 | |
|     }
 | |
| 
 | |
|     @Override
 | |
|     /**
 | |
|      * Gets the chunk data at a given world position
 | |
|      * @param worldPosition The position in world coordinates
 | |
|      * @return The ServerTerrainChunk of data at that position, or null if it is out of bounds or otherwise doesn't exist
 | |
|      */
 | |
|     public ServerTerrainChunk getChunkAtPosition(Vector3i worldPosition) {
 | |
|         return serverTerrainManager.getChunk(worldPosition.x, worldPosition.y, worldPosition.z);
 | |
|     }
 | |
| 
 | |
|     @Override
 | |
|     /**
 | |
|      * Edits a single voxel
 | |
|      * @param worldPosition The world position of the chunk to edit
 | |
|      * @param voxelPosition The voxel position of the voxel to edit
 | |
|      * @param weight The weight to set the voxel to
 | |
|      * @param type The type to set the voxel to
 | |
|      */
 | |
|     public void editChunk(Vector3i worldPosition, Vector3i voxelPosition, float weight, int type) {
 | |
|         terrainEditLock.acquireUninterruptibly();
 | |
|         List<Vector3i> worldPositionsToUpdate = new LinkedList<Vector3i>();
 | |
|         worldPositionsToUpdate.add(worldPosition);
 | |
|         if(voxelPosition.x < 1){
 | |
|             worldPositionsToUpdate.add(new Vector3i(worldPosition).sub(1,0,0));
 | |
|             if(voxelPosition.y < 1){
 | |
|                 worldPositionsToUpdate.add(new Vector3i(worldPosition).sub(1,1,0));
 | |
|                 if(voxelPosition.z < 1){
 | |
|                     worldPositionsToUpdate.add(new Vector3i(worldPosition).sub(1,1,1));
 | |
|                 }
 | |
|             }
 | |
|             if(voxelPosition.z < 1){
 | |
|                 worldPositionsToUpdate.add(new Vector3i(worldPosition).sub(1,0,1));
 | |
|             }
 | |
|         }
 | |
|         if(voxelPosition.y < 1){
 | |
|             worldPositionsToUpdate.add(new Vector3i(worldPosition).sub(0,1,0));
 | |
|             if(voxelPosition.z < 1){
 | |
|                 worldPositionsToUpdate.add(new Vector3i(worldPosition).sub(0,1,1));
 | |
|             }
 | |
|         }
 | |
|         if(voxelPosition.z < 1){
 | |
|             worldPositionsToUpdate.add(new Vector3i(worldPosition).sub(0,0,1));
 | |
|         }
 | |
|         //update all loaded cells
 | |
|         for(Vector3i toUpdate : worldPositionsToUpdate){
 | |
|             if(
 | |
|                 toUpdate.x >= 0 && toUpdate.x < this.serverWorldData.getWorldSizeDiscrete() &&
 | |
|                 toUpdate.y >= 0 && toUpdate.y < this.serverWorldData.getWorldSizeDiscrete() &&
 | |
|                 toUpdate.z >= 0 && toUpdate.z < this.serverWorldData.getWorldSizeDiscrete()
 | |
|             ){
 | |
|                 //update terrain
 | |
|                 int localVoxelX = voxelPosition.x + (ServerTerrainChunk.CHUNK_DATA_GENERATOR_SIZE - 1) * (worldPosition.x - toUpdate.x);
 | |
|                 int localVoxelY = voxelPosition.y + (ServerTerrainChunk.CHUNK_DATA_GENERATOR_SIZE - 1) * (worldPosition.y - toUpdate.y);
 | |
|                 int localVoxelZ = voxelPosition.z + (ServerTerrainChunk.CHUNK_DATA_GENERATOR_SIZE - 1) * (worldPosition.z - toUpdate.z);
 | |
|                 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
 | |
|                     this.createTerrainPhysicsEntities(toUpdate);
 | |
| 
 | |
|                     //broadcast update
 | |
|                     cell.broadcastNetworkMessage(TerrainMessage.constructUpdateVoxelMessage(
 | |
|                         toUpdate.x, toUpdate.y, toUpdate.z, 
 | |
|                         localVoxelX, localVoxelY, localVoxelZ, 
 | |
|                         weight, type));
 | |
|                 }
 | |
|                 this.loadedCellsLock.unlock();
 | |
|             }
 | |
|         }
 | |
|         terrainEditLock.release();
 | |
|     }
 | |
| 
 | |
| 
 | |
|     /**
 | |
|      * Gets the world position of a given data cell
 | |
|      * @param cell The data cell
 | |
|      * @return The world position
 | |
|      */
 | |
|     public Vector3i getCellWorldPosition(ServerDataCell cell){
 | |
|         return cellPositionMap.get(cell);
 | |
|     }
 | |
| 
 | |
|     @Override
 | |
|     /**
 | |
|      * Gets the fluid chunk at a given position
 | |
|      */
 | |
|     public ServerFluidChunk getFluidChunkAtPosition(Vector3i worldPosition) {
 | |
|         return serverFluidManager.getChunk(worldPosition.x, worldPosition.y, worldPosition.z);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Loads all cells
 | |
|      */
 | |
|     public void loadAllCells(){
 | |
|         this.unloadCells = false;
 | |
|         for(int x = 0; x < this.serverWorldData.getWorldSizeDiscrete(); x++){
 | |
|             for(int y = 0; y < this.serverWorldData.getWorldSizeDiscrete(); y++){
 | |
|                 for(int z = 0; z < this.serverWorldData.getWorldSizeDiscrete(); z++){
 | |
|                     this.tryCreateCellAtPoint(new Vector3i(x,y,z));
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     @Override
 | |
|     public void save(String saveName) {
 | |
|         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());
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     @Override
 | |
|     public Vector3d guaranteePositionIsInBounds(Vector3d positionToTest) {
 | |
|         Vector3d returnPos = new Vector3d(positionToTest);
 | |
|         if(positionToTest.x < 0){
 | |
|             returnPos.x = 0;
 | |
|         }
 | |
|         if(positionToTest.x >= parent.getServerWorldData().convertChunkToRealSpace(parent.getServerWorldData().getWorldSizeDiscrete())){
 | |
|             returnPos.x = parent.getServerWorldData().convertChunkToRealSpace(parent.getServerWorldData().getWorldSizeDiscrete()) - 1;
 | |
|         }
 | |
|         if(positionToTest.y < 0){
 | |
|             returnPos.y = 0;
 | |
|         }
 | |
|         if(positionToTest.y >= parent.getServerWorldData().convertChunkToRealSpace(parent.getServerWorldData().getWorldSizeDiscrete())){
 | |
|             returnPos.y = parent.getServerWorldData().convertChunkToRealSpace(parent.getServerWorldData().getWorldSizeDiscrete()) - 1;
 | |
|         }
 | |
|         if(positionToTest.z < 0){
 | |
|             returnPos.z = 0;
 | |
|         }
 | |
|         if(positionToTest.z >= parent.getServerWorldData().convertChunkToRealSpace(parent.getServerWorldData().getWorldSizeDiscrete())){
 | |
|             returnPos.z = parent.getServerWorldData().convertChunkToRealSpace(parent.getServerWorldData().getWorldSizeDiscrete()) - 1;
 | |
|         }
 | |
|         return returnPos;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Stops the executor service
 | |
|      */
 | |
|     public void halt(){
 | |
|         generationService.shutdownNow();
 | |
|     }
 | |
| 
 | |
|     @Override
 | |
|     /**
 | |
|      * Gets the block chunk data at a given world position
 | |
|      */
 | |
|     public BlockChunkData getBlocksAtPosition(Vector3i worldPosition) {
 | |
|         return this.serverWorldData.getServerBlockManager().getChunk(worldPosition.x, worldPosition.y, worldPosition.z);
 | |
|     }
 | |
| 
 | |
|     @Override
 | |
|     /**
 | |
|      * Edits a block chunk in the world
 | |
|      */
 | |
|     public void editBlock(Vector3i worldPosition, Vector3i voxelPosition, short type, short metadata) {
 | |
|         terrainEditLock.acquireUninterruptibly();
 | |
|         if(
 | |
|             worldPosition.x >= 0 && worldPosition.x < this.serverWorldData.getWorldSizeDiscrete() &&
 | |
|             worldPosition.y >= 0 && worldPosition.y < this.serverWorldData.getWorldSizeDiscrete() &&
 | |
|             worldPosition.z >= 0 && worldPosition.z < this.serverWorldData.getWorldSizeDiscrete()
 | |
|         ){
 | |
|             ServerBlockManager serverBlockManager = this.serverWorldData.getServerBlockManager();
 | |
|             //update terrain
 | |
|             int localVoxelX = voxelPosition.x;
 | |
|             int localVoxelY = voxelPosition.y;
 | |
|             int localVoxelZ = voxelPosition.z;
 | |
|             serverBlockManager.editBlockAtLocationToValue(worldPosition, voxelPosition, type, metadata);
 | |
| 
 | |
|             //update anything loaded
 | |
|             this.loadedCellsLock.lock();
 | |
|             ServerDataCell cell = groundDataCells.get(this.getServerDataCellKey(worldPosition));
 | |
|             if(cell != null){
 | |
|                 //update physics
 | |
|                 this.createTerrainPhysicsEntities(worldPosition);
 | |
| 
 | |
|                 //broadcast update
 | |
|                 cell.broadcastNetworkMessage(TerrainMessage.constructUpdateBlockMessage(
 | |
|                     worldPosition.x, worldPosition.y, worldPosition.z, 
 | |
|                     localVoxelX, localVoxelY, localVoxelZ, 
 | |
|                     type, metadata
 | |
|                 ));
 | |
|             }
 | |
|             this.loadedCellsLock.unlock();
 | |
|         }
 | |
|         terrainEditLock.release();
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Gets the set of loaded cells
 | |
|      * @return The set of loaded cells
 | |
|      */
 | |
|     public Collection<ServerDataCell> getLoadedCells(){
 | |
|         return Collections.unmodifiableCollection(this.groundDataCells.values());
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Gets the number of cells cleaned in the most recent frame
 | |
|      * @return The number of cells cleaned
 | |
|      */
 | |
|     public int getNumCleaned(){
 | |
|         return this.numCleaned;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Gets the playerless cell->frame count map
 | |
|      * @return The playerless cell->frame count map
 | |
|      */
 | |
|     public Map<ServerDataCell,Integer> getCellPlayerlessFrameMap(){
 | |
|         return cellPlayerlessFrameMap;
 | |
|     }
 | |
| 
 | |
| }
 |