Server structures for dealing with block data

This commit is contained in:
austin 2024-11-23 22:27:08 -05:00
parent 3777a9a37c
commit 874dcafa97
13 changed files with 1188 additions and 6 deletions

View File

@ -1146,6 +1146,8 @@ Add asset manager support for queueing models asynchronously w/ promised path
Add promised path support to queued textures Add promised path support to queued textures
Code formatting on queued asset classes Code formatting on queued asset classes
Refactoring server side of terrain management Refactoring server side of terrain management
Add server manager for block chunk data & management
Add endpoint to request strided block data
# TODO # TODO

View File

@ -12,6 +12,16 @@ public class BlockChunkData {
*/ */
public static final int CHUNK_DATA_WIDTH = 64; public static final int CHUNK_DATA_WIDTH = 64;
/**
* Total width of the data arrays
*/
public static final int TOTAL_DATA_WIDTH = CHUNK_DATA_WIDTH * CHUNK_DATA_WIDTH * CHUNK_DATA_WIDTH;
/**
* Size of a buffer that stores this chunk's data
*/
public static final int BUFFER_SIZE = TOTAL_DATA_WIDTH * 2 * 2;
/** /**
* The number of blocks to place within each unit of distance * The number of blocks to place within each unit of distance
*/ */
@ -27,6 +37,11 @@ public class BlockChunkData {
*/ */
public static final short NOT_HOMOGENOUS = -1; public static final short NOT_HOMOGENOUS = -1;
/**
* The LOD value for a full res chunk data
*/
public static final int LOD_FULL_RES = 0;
/** /**
* The type of block at a given position * The type of block at a given position
@ -44,6 +59,26 @@ public class BlockChunkData {
*/ */
short homogenousValue = NOT_HOMOGENOUS; short homogenousValue = NOT_HOMOGENOUS;
/**
* The level of detail of the block data
*/
int lod;
/**
* The x coordinate of the world position of the chunk
*/
int worldX;
/**
* The y coordinate of the world position of the chunk
*/
int worldY;
/**
* The z coordinate of the world position of the chunk
*/
int worldZ;
/** /**
* Constructor * Constructor
*/ */
@ -174,4 +209,84 @@ public class BlockChunkData {
return this.homogenousValue != NOT_HOMOGENOUS; return this.homogenousValue != NOT_HOMOGENOUS;
} }
/**
* Gets the level of detail of the block data
* @return The level of detail of the block data
*/
public int getLod() {
return lod;
}
/**
* Sets
* @return
*/
public void setLod(int lod) {
this.lod = lod;
}
/**
* Gets the x coordinate of the world position of the chunk
* @return The x coordinate of the world position of the chunk
*/
public int getWorldX() {
return worldX;
}
/**
* Sets the x coordinate of the world position of the chunk
* @return The x coordinate of the world position of the chunk
*/
public void setWorldX(int worldX) {
this.worldX = worldX;
}
/**
* Gets the y coordinate of the world position of the chunk
* @return The y coordinate of the world position of the chunk
*/
public int getWorldY() {
return worldY;
}
/**
* Sets the y coordinate of the world position of the chunk
* @return The y coordinate of the world position of the chunk
*/
public void setWorldY(int worldY) {
this.worldY = worldY;
}
/**
* Gets the z coordinate of the world position of the chunk
* @return The z coordinate of the world position of the chunk
*/
public int getWorldZ() {
return worldZ;
}
/**
* Sets the z coordinate of the world position of the chunk
* @return The z coordinate of the world position of the chunk
*/
public void setWorldZ(int worldZ) {
this.worldZ = worldZ;
}
/**
* Gets the homogenous value for this chunk
* @return The homogenous value
*/
public short getHomogenousValue(){
return this.homogenousValue;
}
/**
* Sets the homogenous value
* @param homogenousValue The homogenous value
*/
public void setHomogenousValue(short homogenousValue){
this.homogenousValue = homogenousValue;
}
} }

View File

@ -1,5 +1,6 @@
package electrosphere.game.server.world; package electrosphere.game.server.world;
import electrosphere.server.block.manager.ServerBlockManager;
import electrosphere.server.fluid.generation.DefaultFluidGenerator; import electrosphere.server.fluid.generation.DefaultFluidGenerator;
import electrosphere.server.fluid.manager.ServerFluidManager; import electrosphere.server.fluid.manager.ServerFluidManager;
import electrosphere.server.terrain.generation.DefaultChunkGenerator; import electrosphere.server.terrain.generation.DefaultChunkGenerator;
@ -58,6 +59,11 @@ public class ServerWorldData {
//fluid data //fluid data
private ServerFluidManager serverFluidManager; private ServerFluidManager serverFluidManager;
/**
* The block manager
*/
private ServerBlockManager serverBlockManager;
/** /**
@ -119,19 +125,22 @@ public class ServerWorldData {
ServerWorldData serverWorldData = null; ServerWorldData serverWorldData = null;
ServerTerrainManager serverTerrainManager = null; ServerTerrainManager serverTerrainManager = null;
ServerFluidManager serverFluidManager = null; ServerFluidManager serverFluidManager = null;
ServerBlockManager serverBlockManager = null;
if(isScene){ if(isScene){
serverWorldData = FileUtils.loadObjectFromSavePath(sceneOrSaveName, "world.json", ServerWorldData.class); serverWorldData = FileUtils.loadObjectFromSavePath(sceneOrSaveName, "world.json", ServerWorldData.class);
serverTerrainManager = new ServerTerrainManager(serverWorldData, 0, new DefaultChunkGenerator()); serverTerrainManager = new ServerTerrainManager(serverWorldData, 0, new DefaultChunkGenerator());
serverTerrainManager.load(sceneOrSaveName); serverTerrainManager.load(sceneOrSaveName);
serverFluidManager = new ServerFluidManager(serverWorldData, serverTerrainManager, 0, new DefaultFluidGenerator()); serverFluidManager = new ServerFluidManager(serverWorldData, serverTerrainManager, 0, new DefaultFluidGenerator());
serverBlockManager = new ServerBlockManager(serverWorldData);
} else { } else {
//TODO: Allow loading procedurally generated terrain from disk (the chunk generator is always default currently) //TODO: Allow loading procedurally generated terrain from disk (the chunk generator is always default currently)
serverWorldData = FileUtils.loadObjectFromSavePath(sceneOrSaveName, "world.json", ServerWorldData.class); serverWorldData = FileUtils.loadObjectFromSavePath(sceneOrSaveName, "world.json", ServerWorldData.class);
serverTerrainManager = new ServerTerrainManager(serverWorldData, 0, new DefaultChunkGenerator()); serverTerrainManager = new ServerTerrainManager(serverWorldData, 0, new DefaultChunkGenerator());
serverTerrainManager.load(sceneOrSaveName); serverTerrainManager.load(sceneOrSaveName);
serverFluidManager = new ServerFluidManager(serverWorldData, serverTerrainManager, 0, new DefaultFluidGenerator()); serverFluidManager = new ServerFluidManager(serverWorldData, serverTerrainManager, 0, new DefaultFluidGenerator());
serverBlockManager = new ServerBlockManager(serverWorldData);
} }
serverWorldData.setManagers(serverTerrainManager, serverFluidManager); serverWorldData.setManagers(serverTerrainManager, serverFluidManager, serverBlockManager);
return serverWorldData; return serverWorldData;
} }
@ -146,6 +155,7 @@ public class ServerWorldData {
ServerWorldData serverWorldData = null; ServerWorldData serverWorldData = null;
ServerTerrainManager serverTerrainManager = null; ServerTerrainManager serverTerrainManager = null;
ServerFluidManager serverFluidManager = null; ServerFluidManager serverFluidManager = null;
ServerBlockManager serverBlockManager = null;
//TODO: Allow loading procedurally generated terrain from disk (the chunk generator is always default currently) //TODO: Allow loading procedurally generated terrain from disk (the chunk generator is always default currently)
serverWorldData = ServerWorldData.createFixedWorldData(new Vector3d(0),new Vector3d(TestGenerationChunkGenerator.GENERATOR_REALM_SIZE * ServerTerrainChunk.CHUNK_DIMENSION)); serverWorldData = ServerWorldData.createFixedWorldData(new Vector3d(0),new Vector3d(TestGenerationChunkGenerator.GENERATOR_REALM_SIZE * ServerTerrainChunk.CHUNK_DIMENSION));
serverWorldData.worldSizeDiscrete = TestGenerationChunkGenerator.GENERATOR_REALM_SIZE; serverWorldData.worldSizeDiscrete = TestGenerationChunkGenerator.GENERATOR_REALM_SIZE;
@ -158,7 +168,8 @@ public class ServerWorldData {
serverTerrainManager.genTestData(chunkGen); serverTerrainManager.genTestData(chunkGen);
} }
serverFluidManager = new ServerFluidManager(serverWorldData, serverTerrainManager, 0, new DefaultFluidGenerator()); serverFluidManager = new ServerFluidManager(serverWorldData, serverTerrainManager, 0, new DefaultFluidGenerator());
serverWorldData.setManagers(serverTerrainManager, serverFluidManager); serverBlockManager = new ServerBlockManager(serverWorldData);
serverWorldData.setManagers(serverTerrainManager, serverFluidManager, serverBlockManager);
return serverWorldData; return serverWorldData;
} }
@ -276,14 +287,24 @@ public class ServerWorldData {
return this.serverFluidManager; return this.serverFluidManager;
} }
/**
* Gets the block manager for this world
* @return The block manager if it exists, null otherwise
*/
public ServerBlockManager getServerBlockManager(){
return this.serverBlockManager;
}
/** /**
* Sets the chunk managers * Sets the chunk managers
* @param serverTerrainManager The terrain manager * @param serverTerrainManager The terrain manager
* @param serverFluidManager The fluid manager * @param serverFluidManager The fluid manager
* @param serverBlockManager The server block manager
*/ */
public void setManagers(ServerTerrainManager serverTerrainManager, ServerFluidManager serverFluidManager){ public void setManagers(ServerTerrainManager serverTerrainManager, ServerFluidManager serverFluidManager, ServerBlockManager serverBlockManager){
this.serverTerrainManager = serverTerrainManager; this.serverTerrainManager = serverTerrainManager;
this.serverFluidManager = serverFluidManager; this.serverFluidManager = serverFluidManager;
this.serverBlockManager = serverBlockManager;
this.serverTerrainManager.setParent(this); this.serverTerrainManager.setParent(this);
this.serverFluidManager.setParent(this); this.serverFluidManager.setParent(this);
if(this.serverTerrainManager == null || this.serverFluidManager == null){ if(this.serverTerrainManager == null || this.serverFluidManager == null){

View File

@ -204,6 +204,16 @@ public abstract class NetworkMessage {
rVal = TerrainMessage.parseSendReducedChunkDataMessage(byteBuffer); rVal = TerrainMessage.parseSendReducedChunkDataMessage(byteBuffer);
} }
break; break;
case TypeBytes.TERRAIN_MESSAGE_TYPE_REQUESTREDUCEDBLOCKDATA:
if(TerrainMessage.canParseMessage(byteBuffer,secondByte)){
rVal = TerrainMessage.parseRequestReducedBlockDataMessage(byteBuffer);
}
break;
case TypeBytes.TERRAIN_MESSAGE_TYPE_SENDREDUCEDBLOCKDATA:
if(TerrainMessage.canParseMessage(byteBuffer,secondByte)){
rVal = TerrainMessage.parseSendReducedBlockDataMessage(byteBuffer);
}
break;
case TypeBytes.TERRAIN_MESSAGE_TYPE_REQUESTFLUIDDATA: case TypeBytes.TERRAIN_MESSAGE_TYPE_REQUESTFLUIDDATA:
if(TerrainMessage.canParseMessage(byteBuffer,secondByte)){ if(TerrainMessage.canParseMessage(byteBuffer,secondByte)){
rVal = TerrainMessage.parseRequestFluidDataMessage(byteBuffer); rVal = TerrainMessage.parseRequestFluidDataMessage(byteBuffer);

View File

@ -21,6 +21,8 @@ public class TerrainMessage extends NetworkMessage {
SENDCHUNKDATA, SENDCHUNKDATA,
REQUESTREDUCEDCHUNKDATA, REQUESTREDUCEDCHUNKDATA,
SENDREDUCEDCHUNKDATA, SENDREDUCEDCHUNKDATA,
REQUESTREDUCEDBLOCKDATA,
SENDREDUCEDBLOCKDATA,
REQUESTFLUIDDATA, REQUESTFLUIDDATA,
SENDFLUIDDATA, SENDFLUIDDATA,
UPDATEFLUIDDATA, UPDATEFLUIDDATA,
@ -442,6 +444,14 @@ public class TerrainMessage extends NetworkMessage {
} }
case TypeBytes.TERRAIN_MESSAGE_TYPE_SENDREDUCEDCHUNKDATA: case TypeBytes.TERRAIN_MESSAGE_TYPE_SENDREDUCEDCHUNKDATA:
return TerrainMessage.canParseSendReducedChunkDataMessage(byteBuffer); return TerrainMessage.canParseSendReducedChunkDataMessage(byteBuffer);
case TypeBytes.TERRAIN_MESSAGE_TYPE_REQUESTREDUCEDBLOCKDATA:
if(byteBuffer.getRemaining() >= TypeBytes.TERRAIN_MESSAGE_TYPE_REQUESTREDUCEDBLOCKDATA_SIZE){
return true;
} else {
return false;
}
case TypeBytes.TERRAIN_MESSAGE_TYPE_SENDREDUCEDBLOCKDATA:
return TerrainMessage.canParseSendReducedBlockDataMessage(byteBuffer);
case TypeBytes.TERRAIN_MESSAGE_TYPE_REQUESTFLUIDDATA: case TypeBytes.TERRAIN_MESSAGE_TYPE_REQUESTFLUIDDATA:
if(byteBuffer.getRemaining() >= TypeBytes.TERRAIN_MESSAGE_TYPE_REQUESTFLUIDDATA_SIZE){ if(byteBuffer.getRemaining() >= TypeBytes.TERRAIN_MESSAGE_TYPE_REQUESTFLUIDDATA_SIZE){
return true; return true;
@ -802,6 +812,99 @@ public class TerrainMessage extends NetworkMessage {
return rVal; return rVal;
} }
/**
* Parses a message of type RequestReducedBlockData
*/
public static TerrainMessage parseRequestReducedBlockDataMessage(CircularByteBuffer byteBuffer){
TerrainMessage rVal = new TerrainMessage(TerrainMessageType.REQUESTREDUCEDBLOCKDATA);
stripPacketHeader(byteBuffer);
rVal.setworldX(ByteStreamUtils.popIntFromByteQueue(byteBuffer));
rVal.setworldY(ByteStreamUtils.popIntFromByteQueue(byteBuffer));
rVal.setworldZ(ByteStreamUtils.popIntFromByteQueue(byteBuffer));
rVal.setchunkResolution(ByteStreamUtils.popIntFromByteQueue(byteBuffer));
return rVal;
}
/**
* Constructs a message of type RequestReducedBlockData
*/
public static TerrainMessage constructRequestReducedBlockDataMessage(int worldX,int worldY,int worldZ,int chunkResolution){
TerrainMessage rVal = new TerrainMessage(TerrainMessageType.REQUESTREDUCEDBLOCKDATA);
rVal.setworldX(worldX);
rVal.setworldY(worldY);
rVal.setworldZ(worldZ);
rVal.setchunkResolution(chunkResolution);
rVal.serialize();
return rVal;
}
/**
* Checks if a message of type SendReducedBlockData can be parsed from the byte stream
*/
public static boolean canParseSendReducedBlockDataMessage(CircularByteBuffer byteBuffer){
int currentStreamLength = byteBuffer.getRemaining();
List<Byte> temporaryByteQueue = new LinkedList<Byte>();
if(currentStreamLength < 6){
return false;
}
if(currentStreamLength < 10){
return false;
}
if(currentStreamLength < 14){
return false;
}
if(currentStreamLength < 18){
return false;
}
if(currentStreamLength < 22){
return false;
}
int chunkDataSize = 0;
if(currentStreamLength < 26){
return false;
} else {
temporaryByteQueue.add(byteBuffer.peek(22 + 0));
temporaryByteQueue.add(byteBuffer.peek(22 + 1));
temporaryByteQueue.add(byteBuffer.peek(22 + 2));
temporaryByteQueue.add(byteBuffer.peek(22 + 3));
chunkDataSize = ByteStreamUtils.popIntFromByteQueue(temporaryByteQueue);
}
if(currentStreamLength < 26 + chunkDataSize){
return false;
}
return true;
}
/**
* Parses a message of type SendReducedBlockData
*/
public static TerrainMessage parseSendReducedBlockDataMessage(CircularByteBuffer byteBuffer){
TerrainMessage rVal = new TerrainMessage(TerrainMessageType.SENDREDUCEDBLOCKDATA);
stripPacketHeader(byteBuffer);
rVal.setworldX(ByteStreamUtils.popIntFromByteQueue(byteBuffer));
rVal.setworldY(ByteStreamUtils.popIntFromByteQueue(byteBuffer));
rVal.setworldZ(ByteStreamUtils.popIntFromByteQueue(byteBuffer));
rVal.setchunkResolution(ByteStreamUtils.popIntFromByteQueue(byteBuffer));
rVal.sethomogenousValue(ByteStreamUtils.popIntFromByteQueue(byteBuffer));
rVal.setchunkData(ByteStreamUtils.popByteArrayFromByteQueue(byteBuffer));
return rVal;
}
/**
* Constructs a message of type SendReducedBlockData
*/
public static TerrainMessage constructSendReducedBlockDataMessage(int worldX,int worldY,int worldZ,int chunkResolution,int homogenousValue,byte[] chunkData){
TerrainMessage rVal = new TerrainMessage(TerrainMessageType.SENDREDUCEDBLOCKDATA);
rVal.setworldX(worldX);
rVal.setworldY(worldY);
rVal.setworldZ(worldZ);
rVal.setchunkResolution(chunkResolution);
rVal.sethomogenousValue(homogenousValue);
rVal.setchunkData(chunkData);
rVal.serialize();
return rVal;
}
/** /**
* Parses a message of type RequestFluidData * Parses a message of type RequestFluidData
*/ */
@ -1211,6 +1314,63 @@ public class TerrainMessage extends NetworkMessage {
rawBytes[26+i] = chunkData[i]; rawBytes[26+i] = chunkData[i];
} }
break; break;
case REQUESTREDUCEDBLOCKDATA:
rawBytes = new byte[2+4+4+4+4];
//message header
rawBytes[0] = TypeBytes.MESSAGE_TYPE_TERRAIN;
//entity messaage header
rawBytes[1] = TypeBytes.TERRAIN_MESSAGE_TYPE_REQUESTREDUCEDBLOCKDATA;
intValues = ByteStreamUtils.serializeIntToBytes(worldX);
for(int i = 0; i < 4; i++){
rawBytes[2+i] = intValues[i];
}
intValues = ByteStreamUtils.serializeIntToBytes(worldY);
for(int i = 0; i < 4; i++){
rawBytes[6+i] = intValues[i];
}
intValues = ByteStreamUtils.serializeIntToBytes(worldZ);
for(int i = 0; i < 4; i++){
rawBytes[10+i] = intValues[i];
}
intValues = ByteStreamUtils.serializeIntToBytes(chunkResolution);
for(int i = 0; i < 4; i++){
rawBytes[14+i] = intValues[i];
}
break;
case SENDREDUCEDBLOCKDATA:
rawBytes = new byte[2+4+4+4+4+4+4+chunkData.length];
//message header
rawBytes[0] = TypeBytes.MESSAGE_TYPE_TERRAIN;
//entity messaage header
rawBytes[1] = TypeBytes.TERRAIN_MESSAGE_TYPE_SENDREDUCEDBLOCKDATA;
intValues = ByteStreamUtils.serializeIntToBytes(worldX);
for(int i = 0; i < 4; i++){
rawBytes[2+i] = intValues[i];
}
intValues = ByteStreamUtils.serializeIntToBytes(worldY);
for(int i = 0; i < 4; i++){
rawBytes[6+i] = intValues[i];
}
intValues = ByteStreamUtils.serializeIntToBytes(worldZ);
for(int i = 0; i < 4; i++){
rawBytes[10+i] = intValues[i];
}
intValues = ByteStreamUtils.serializeIntToBytes(chunkResolution);
for(int i = 0; i < 4; i++){
rawBytes[14+i] = intValues[i];
}
intValues = ByteStreamUtils.serializeIntToBytes(homogenousValue);
for(int i = 0; i < 4; i++){
rawBytes[18+i] = intValues[i];
}
intValues = ByteStreamUtils.serializeIntToBytes(chunkData.length);
for(int i = 0; i < 4; i++){
rawBytes[22+i] = intValues[i];
}
for(int i = 0; i < chunkData.length; i++){
rawBytes[26+i] = chunkData[i];
}
break;
case REQUESTFLUIDDATA: case REQUESTFLUIDDATA:
rawBytes = new byte[2+4+4+4]; rawBytes = new byte[2+4+4+4];
//message header //message header

View File

@ -76,9 +76,11 @@ public class TypeBytes {
public static final byte TERRAIN_MESSAGE_TYPE_SENDCHUNKDATA = 7; public static final byte TERRAIN_MESSAGE_TYPE_SENDCHUNKDATA = 7;
public static final byte TERRAIN_MESSAGE_TYPE_REQUESTREDUCEDCHUNKDATA = 8; public static final byte TERRAIN_MESSAGE_TYPE_REQUESTREDUCEDCHUNKDATA = 8;
public static final byte TERRAIN_MESSAGE_TYPE_SENDREDUCEDCHUNKDATA = 9; public static final byte TERRAIN_MESSAGE_TYPE_SENDREDUCEDCHUNKDATA = 9;
public static final byte TERRAIN_MESSAGE_TYPE_REQUESTFLUIDDATA = 10; public static final byte TERRAIN_MESSAGE_TYPE_REQUESTREDUCEDBLOCKDATA = 10;
public static final byte TERRAIN_MESSAGE_TYPE_SENDFLUIDDATA = 11; public static final byte TERRAIN_MESSAGE_TYPE_SENDREDUCEDBLOCKDATA = 11;
public static final byte TERRAIN_MESSAGE_TYPE_UPDATEFLUIDDATA = 12; public static final byte TERRAIN_MESSAGE_TYPE_REQUESTFLUIDDATA = 12;
public static final byte TERRAIN_MESSAGE_TYPE_SENDFLUIDDATA = 13;
public static final byte TERRAIN_MESSAGE_TYPE_UPDATEFLUIDDATA = 14;
/* /*
Terrain packet sizes Terrain packet sizes
*/ */
@ -90,6 +92,7 @@ public class TypeBytes {
public static final byte TERRAIN_MESSAGE_TYPE_SPAWNPOSITION_SIZE = 26; public static final byte TERRAIN_MESSAGE_TYPE_SPAWNPOSITION_SIZE = 26;
public static final byte TERRAIN_MESSAGE_TYPE_REQUESTCHUNKDATA_SIZE = 14; public static final byte TERRAIN_MESSAGE_TYPE_REQUESTCHUNKDATA_SIZE = 14;
public static final byte TERRAIN_MESSAGE_TYPE_REQUESTREDUCEDCHUNKDATA_SIZE = 18; public static final byte TERRAIN_MESSAGE_TYPE_REQUESTREDUCEDCHUNKDATA_SIZE = 18;
public static final byte TERRAIN_MESSAGE_TYPE_REQUESTREDUCEDBLOCKDATA_SIZE = 18;
public static final byte TERRAIN_MESSAGE_TYPE_REQUESTFLUIDDATA_SIZE = 14; public static final byte TERRAIN_MESSAGE_TYPE_REQUESTFLUIDDATA_SIZE = 14;
/* /*

View File

@ -3,10 +3,12 @@ package electrosphere.net.server.protocol;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.FloatBuffer; import java.nio.FloatBuffer;
import java.nio.IntBuffer; import java.nio.IntBuffer;
import java.nio.ShortBuffer;
import java.util.function.Consumer; import java.util.function.Consumer;
import org.joml.Vector3d; import org.joml.Vector3d;
import electrosphere.client.block.BlockChunkData;
import electrosphere.client.terrain.cache.ChunkData; import electrosphere.client.terrain.cache.ChunkData;
import electrosphere.engine.Globals; import electrosphere.engine.Globals;
import electrosphere.logger.LoggerInterface; import electrosphere.logger.LoggerInterface;
@ -39,6 +41,12 @@ public class TerrainProtocol implements ServerProtocolTemplate<TerrainMessage> {
); );
return null; return null;
} }
case REQUESTREDUCEDBLOCKDATA: {
TerrainProtocol.sendBlocksAsyncStrided(connectionHandler,
message.getworldX(), message.getworldY(), message.getworldZ(), message.getchunkResolution()
);
return null;
}
default: { default: {
} break; } break;
} }
@ -80,6 +88,8 @@ public class TerrainProtocol implements ServerProtocolTemplate<TerrainMessage> {
case SENDFLUIDDATA: case SENDFLUIDDATA:
case REQUESTREDUCEDCHUNKDATA: case REQUESTREDUCEDCHUNKDATA:
case SENDREDUCEDCHUNKDATA: case SENDREDUCEDCHUNKDATA:
case REQUESTREDUCEDBLOCKDATA:
case SENDREDUCEDBLOCKDATA:
//silently ignore //silently ignore
break; break;
} }
@ -262,6 +272,57 @@ public class TerrainProtocol implements ServerProtocolTemplate<TerrainMessage> {
Globals.profiler.endCpuSample(); Globals.profiler.endCpuSample();
} }
/**
* Sends a subchunk to the client
* @param connectionHandler The connection handler
* @param worldX the world x
* @param worldY the world y
* @param worldZ the world z
* @param stride The stride of the data
*/
static void sendBlocksAsyncStrided(ServerConnectionHandler connectionHandler, int worldX, int worldY, int worldZ, int stride){
Globals.profiler.beginAggregateCpuSample("TerrainProtocol(server).sendWorldSubChunk");
// System.out.println("Received request for chunk " + message.getworldX() + " " + message.getworldY());
Realm realm = Globals.playerManager.getPlayerRealm(connectionHandler.getPlayer());
if(realm.getServerWorldData().getServerBlockManager() == null){
return;
}
Consumer<BlockChunkData> onLoad = (BlockChunkData chunk) -> {
//The length along each access of the chunk data. Typically, should be at least 17.
//Because CHUNK_SIZE is 16, 17 adds the necessary extra value. Each chunk needs the value of the immediately following position to generate
//chunk data that connects seamlessly to the next chunk.
byte[] toSend = null;
if(chunk.getHomogenousValue() == ChunkData.NOT_HOMOGENOUS){
ByteBuffer buffer = ByteBuffer.allocate(BlockChunkData.BUFFER_SIZE);
ShortBuffer shortView = buffer.asShortBuffer();
short[] type = chunk.getType();
short[] metadata = chunk.getMetadata();
for(int i = 0; i < BlockChunkData.TOTAL_DATA_WIDTH; i++){
shortView.put(type[i]);
}
for(int i = 0; i < BlockChunkData.TOTAL_DATA_WIDTH; i++){
shortView.put(metadata[i]);
}
toSend = buffer.array();
} else {
toSend = new byte[]{ 0 };
}
LoggerInterface.loggerNetworking.DEBUG("(Server) Send block data at " + worldX + " " + worldY + " " + worldZ);
connectionHandler.addMessagetoOutgoingQueue(TerrainMessage.constructSendReducedBlockDataMessage(worldX, worldY, worldZ, stride, chunk.getHomogenousValue(), toSend));
};
//request chunk
realm.getServerWorldData().getServerBlockManager().getChunkAsync(worldX, worldY, worldZ, stride, onLoad);
Globals.profiler.endCpuSample();
}
/** /**
* Sends a fluid sub chunk to the client * Sends a fluid sub chunk to the client

View File

@ -0,0 +1,205 @@
package electrosphere.server.block.diskmap;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ShortBuffer;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.InflaterOutputStream;
import electrosphere.client.block.BlockChunkData;
import electrosphere.engine.Globals;
import electrosphere.logger.LoggerInterface;
import electrosphere.util.FileUtils;
import electrosphere.util.annotation.Exclude;
import io.github.studiorailgun.HashUtils;
/**
* An interface for accessing the disk map of chunk information
*/
public class ServerBlockChunkDiskMap {
/**
* The map of world position+chunk type to the file that actually houses that information
*/
Map<Long,String> worldPosFileMap;
/**
* Locks the chunk disk map for thread safety
*/
@Exclude
ReentrantLock lock = new ReentrantLock();
/**
* Constructor
*/
private ServerBlockChunkDiskMap(){
worldPosFileMap = new HashMap<Long,String>();
}
/**
* Gets a key for a given chunk file based on a world coordinate
* @param worldX The x component
* @param worldY The y component
* @param worldZ The z component
* @return The key
*/
private static long getBlockChunkKey(int worldX, int worldY, int worldZ){
return HashUtils.cantorHash(worldX, worldY, worldZ);
}
/**
* Initializes a diskmap based on a given save name
* @param saveName The save name
*/
public static ServerBlockChunkDiskMap init(String saveName){
ServerBlockChunkDiskMap rVal = null;
LoggerInterface.loggerEngine.DEBUG("INIT CHUNK MAP " + saveName);
if(FileUtils.getSaveFile(saveName, "chunk.map").exists()){
rVal = FileUtils.loadObjectFromSavePath(saveName, "chunk.map", ServerBlockChunkDiskMap.class);
LoggerInterface.loggerEngine.DEBUG("POS FILE MAP: " + rVal.worldPosFileMap.keySet());
} else {
rVal = new ServerBlockChunkDiskMap();
}
return rVal;
}
/**
* Initializes a diskmap based on a given save name
* @param saveName The save name
*/
public static ServerBlockChunkDiskMap init(){
return new ServerBlockChunkDiskMap();
}
/**
* Saves the disk map to disk
*/
public void save(){
FileUtils.serializeObjectToSavePath(Globals.currentSave.getName(), "blockchunk.map", worldPosFileMap);
}
/**
* Checks if the map contains a given chunk position
* @param worldX The x component
* @param worldY The y component
* @param worldZ The z component
* @return True if the map contains the chunk, false otherwise
*/
public boolean containsBlocksAtPosition(int worldX, int worldY, int worldZ){
lock.lock();
boolean rVal = worldPosFileMap.containsKey(getBlockChunkKey(worldX, worldY, worldZ));
lock.unlock();
return rVal;
}
/**
* Gets the block data chunk from disk if it exists, otherwise returns null
* @param worldX The x coordinate
* @param worldY The y coordinate
* @param worldZ The z coordinate
* @return The block data chunk if it exists, null otherwise
*/
public BlockChunkData getBlockChunk(int worldX, int worldY, int worldZ){
lock.lock();
LoggerInterface.loggerEngine.INFO("Load chunk " + worldX + " " + worldY + " " + worldZ);
BlockChunkData rVal = null;
if(containsBlocksAtPosition(worldX, worldY, worldZ)){
//read file
String fileName = worldPosFileMap.get(getBlockChunkKey(worldX, worldY, worldZ));
byte[] rawDataCompressed = FileUtils.loadBinaryFromSavePath(Globals.currentSave.getName(), fileName);
//decompress
byte[] rawData = null;
ByteArrayOutputStream out = new ByteArrayOutputStream();
InflaterOutputStream inflaterInputStream = new InflaterOutputStream(out);
try {
inflaterInputStream.write(rawDataCompressed);
inflaterInputStream.flush();
inflaterInputStream.close();
rawData = out.toByteArray();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//parse
if(rawData != null){
ByteBuffer buffer = ByteBuffer.wrap(rawData);
ShortBuffer shortView = buffer.asShortBuffer();
short[] type = new short[BlockChunkData.TOTAL_DATA_WIDTH];
short[] metadata = new short[BlockChunkData.TOTAL_DATA_WIDTH];
short firstType = -1;
boolean homogenous = true;
for(int i = 0; i < BlockChunkData.TOTAL_DATA_WIDTH; i++){
type[i] = shortView.get();
if(firstType == -1){
firstType = type[i];
} else if(homogenous && firstType == type[i]){
homogenous = false;
}
}
for(int i = 0; i < BlockChunkData.TOTAL_DATA_WIDTH; i++){
metadata[i] = shortView.get();
}
rVal = new BlockChunkData();
rVal.setType(type);
rVal.setMetadata(metadata);
rVal.setHomogenousValue(homogenous ? firstType : BlockChunkData.NOT_HOMOGENOUS);
rVal.setWorldX(worldX);
rVal.setWorldY(worldY);
rVal.setWorldZ(worldZ);
rVal.setLod(BlockChunkData.LOD_FULL_RES);
}
}
lock.unlock();
return rVal;
}
/**
* Saves a block data chunk to disk
* @param chunkData The block data chunk
*/
public void saveToDisk(BlockChunkData chunkData){
lock.lock();
LoggerInterface.loggerEngine.DEBUG("Save to disk: " + chunkData.getWorldX() + " " + chunkData.getWorldY() + " " + chunkData.getWorldZ());
//get the file name for this chunk
String fileName = null;
Long chunkKey = getBlockChunkKey(chunkData.getWorldX(),chunkData.getWorldY(),chunkData.getWorldZ());
if(worldPosFileMap.containsKey(chunkKey)){
fileName = worldPosFileMap.get(chunkKey);
} else {
fileName = chunkKey + "b.dat";
}
//generate binary for the file
short[] type = chunkData.getType();
short[] metadata = chunkData.getMetadata();
ByteBuffer buffer = ByteBuffer.allocate(BlockChunkData.TOTAL_DATA_WIDTH * 2 * 2);
ShortBuffer shortView = buffer.asShortBuffer();
for(int i = 0; i < BlockChunkData.TOTAL_DATA_WIDTH; i++){
shortView.put(type[i]);
}
for(int i = 0; i < BlockChunkData.TOTAL_DATA_WIDTH; i++){
shortView.put(metadata[i]);
}
//compress
ByteArrayOutputStream out = new ByteArrayOutputStream();
DeflaterOutputStream deflaterInputStream = new DeflaterOutputStream(out);
try {
deflaterInputStream.write(buffer.array());
deflaterInputStream.flush();
deflaterInputStream.close();
//write to disk
FileUtils.saveBinaryToSavePath(Globals.currentSave.getName(), fileName, out.toByteArray());
//save to the map of filenames
worldPosFileMap.put(chunkKey,fileName);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
lock.unlock();
}
}

View File

@ -0,0 +1,30 @@
package electrosphere.server.block.editing;
import org.joml.Vector3d;
import org.joml.Vector3i;
import electrosphere.server.datacell.Realm;
/**
* Provides utilities for editing block (particularly brushes, etc)
*/
public class ServerBlockEditing {
/**
* The minimum value before hard setting to 0
*/
static final float MINIMUM_FULL_VALUE = 0.01f;
/**
* Performs a block chunk edit. Basically has a sphere around the provided position that it attempts to add value to
* @param realm The realm to modify in
* @param worldPos The world position
* @param voxelPos The block position within the chunk at the world position
* @param type The new type of block
* @param metadata The new metadata for the block
*/
public static void editBlockChunk(Realm realm, Vector3d worldPos, Vector3i voxelPos, short type, short metadata){
throw new UnsupportedOperationException("Unimplemented");
}
}

View File

@ -0,0 +1,237 @@
package electrosphere.server.block.manager;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Semaphore;
import electrosphere.client.block.BlockChunkData;
import io.github.studiorailgun.HashUtils;
/**
* Caches chunk data on the server
*/
public class BlockChunkCache {
/**
* Number of chunks to cache
*/
static final int CACHE_SIZE = 1500;
/**
* The size of the cache
*/
int cacheSize = CACHE_SIZE;
/**
* The map of full res chunk key -> chunk data
*/
Map<Long,BlockChunkData> cacheMapFullRes = new ConcurrentHashMap<Long,BlockChunkData>();
/**
* The map of half res chunk key -> chunk data
*/
Map<Long,BlockChunkData> cacheMapHalfRes = new ConcurrentHashMap<Long,BlockChunkData>();
/**
* The map of quarter res chunk key -> chunk data
*/
Map<Long,BlockChunkData> cacheMapQuarterRes = new ConcurrentHashMap<Long,BlockChunkData>();
/**
* The map of eighth res chunk key -> chunk data
*/
Map<Long,BlockChunkData> cacheMapEighthRes = new ConcurrentHashMap<Long,BlockChunkData>();
/**
* The map of sixteenth res chunk key -> chunk data
*/
Map<Long,BlockChunkData> cacheMapSixteenthRes = new ConcurrentHashMap<Long,BlockChunkData>();
/**
* Tracks how recently a chunk has been queries for (used for evicting old chunks from cache)
*/
List<Long> queryRecencyQueue = new LinkedList<Long>();
/**
* Tracks what chunks are already queued to be asynchronously loaded. Used so we don't have two threads generating/fetching the same chunk
*/
Map<Long, Boolean> queuedChunkMap = new HashMap<Long,Boolean>();
/**
* The lock for thread safety
*/
Semaphore lock = new Semaphore(1);
/**
* Gets the collection of server block chunks that are cached
* @return The collection of chunks
*/
public Collection<BlockChunkData> getContents(){
lock.acquireUninterruptibly();
Collection<BlockChunkData> rVal = Collections.unmodifiableCollection(cacheMapFullRes.values());
lock.release();
return rVal;
}
/**
* Evicts all chunks in the cache
*/
public void clear(){
lock.acquireUninterruptibly();
cacheMapFullRes.clear();
cacheMapHalfRes.clear();
cacheMapQuarterRes.clear();
cacheMapEighthRes.clear();
cacheMapSixteenthRes.clear();
lock.release();
}
/**
* Gets the chunk at a given world position
* @param worldX The world x coordinate
* @param worldY The world y coordinate
* @param worldZ The world z coordinate
* @param stride The stride of the data
* @return The chunk
*/
public BlockChunkData get(int worldX, int worldY, int worldZ, int stride){
BlockChunkData rVal = null;
Long key = this.getKey(worldX, worldY, worldZ);
lock.acquireUninterruptibly();
queryRecencyQueue.remove(key);
queryRecencyQueue.add(0, key);
Map<Long,BlockChunkData> cache = this.getCache(stride);
rVal = cache.get(key);
lock.release();
return rVal;
}
/**
* Adds a chunk to the cache
* @param worldX The world x coordinate of the chunk
* @param worldY The world y coordinate of the chunk
* @param worldZ The world z coordinate of the chunk
* @param stride The stride of the data
* @param chunk The chunk itself
*/
public void add(int worldX, int worldY, int worldZ, int stride, BlockChunkData chunk){
Long key = this.getKey(worldX, worldY, worldZ);
lock.acquireUninterruptibly();
queryRecencyQueue.add(0, key);
Map<Long,BlockChunkData> cache = this.getCache(stride);
cache.put(key, chunk);
while(queryRecencyQueue.size() > cacheSize){
Long oldKey = queryRecencyQueue.remove(queryRecencyQueue.size() - 1);
cacheMapFullRes.remove(oldKey);
cacheMapHalfRes.remove(oldKey);
cacheMapQuarterRes.remove(oldKey);
cacheMapEighthRes.remove(oldKey);
cacheMapSixteenthRes.remove(oldKey);
}
lock.release();
}
/**
* Checks if the cache contains the chunk at a given world position
* @param worldX The world x coordinate
* @param worldY The world y coordinate
* @param worldZ The world z coordinate
* @param stride The stride of the data
* @return true if the cache contains this chunk, false otherwise
*/
public boolean containsChunk(int worldX, int worldY, int worldZ, int stride){
Long key = this.getKey(worldX,worldY,worldZ);
lock.acquireUninterruptibly();
Map<Long,BlockChunkData> cache = this.getCache(stride);
boolean rVal = cache.containsKey(key);
lock.release();
return rVal;
}
/**
* Gets the key for a given world position
* @param worldX The x component
* @param worldY The y component
* @param worldZ The z component
* @return The key
*/
public long getKey(int worldX, int worldY, int worldZ){
return HashUtils.cantorHash(worldX, worldY, worldZ);
}
/**
* Checks if the chunk is already queued or not
* @param worldX The world x position of the chunk
* @param worldY The world y position of the chunk
* @param worldZ The world z position of the chunk
* @return true if the chunk is already queued, false otherwise
*/
public boolean chunkIsQueued(int worldX, int worldY, int worldZ){
Long key = this.getKey(worldX,worldY,worldZ);
lock.acquireUninterruptibly();
boolean rVal = this.queuedChunkMap.containsKey(key);
lock.release();
return rVal;
}
/**
* Flags a chunk as queued
* @param worldX The world x position of the chunk
* @param worldY The world y position of the chunk
* @param worldZ The world z position of the chunk
*/
public void queueChunk(int worldX, int worldY, int worldZ){
Long key = this.getKey(worldX,worldY,worldZ);
lock.acquireUninterruptibly();
this.queuedChunkMap.put(key,true);
lock.release();
}
/**
* Unflags a chunk as queued
* @param worldX The world x position of the chunk
* @param worldY The world y position of the chunk
* @param worldZ The world z position of the chunk
* @param stride The stride of the chunk
*/
public void unqueueChunk(int worldX, int worldY, int worldZ, int stride){
Long key = this.getKey(worldX,worldY,worldZ);
lock.acquireUninterruptibly();
this.queuedChunkMap.remove(key);
lock.release();
}
/**
* Gets the cache
* @param stride The stride of the data
* @return The cache to use
*/
public Map<Long,BlockChunkData> getCache(int stride){
switch(stride){
case 0: {
return cacheMapFullRes;
}
case 1: {
return cacheMapHalfRes;
}
case 2: {
return cacheMapQuarterRes;
}
case 3: {
return cacheMapEighthRes;
}
case 4: {
return cacheMapSixteenthRes;
}
default: {
throw new Error("Invalid stride probided! " + stride);
}
}
}
}

View File

@ -0,0 +1,131 @@
package electrosphere.server.block.manager;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import electrosphere.client.block.BlockChunkData;
import electrosphere.engine.Globals;
import electrosphere.logger.LoggerInterface;
import electrosphere.server.block.diskmap.ServerBlockChunkDiskMap;
/**
* A job that fetches a chunk, either by generating it or by reading it from disk
*/
public class ServerBlockChunkGenerationThread implements Runnable {
/**
* The number of milliseconds to wait per iteration
*/
static final int WAIT_TIME_MS = 2;
/**
* The maximum number of iterations to wait before failing
*/
static final int MAX_TIME_TO_WAIT = 10;
/**
* The chunk disk map
*/
ServerBlockChunkDiskMap chunkDiskMap;
/**
* The chunk cache on the server
*/
BlockChunkCache chunkCache;
/**
* The world x coordinate
*/
int worldX;
/**
* The world y coordinate
*/
int worldY;
/**
* The world z coordinate
*/
int worldZ;
/**
* The stride of the data
*/
int stride;
/**
* The work to do once the chunk is available
*/
Consumer<BlockChunkData> onLoad;
/**
* Creates the chunk generation job
* @param chunkDiskMap The chunk disk map
* @param chunkCache The chunk cache on the server
* @param worldX The world x coordinate
* @param worldY The world y coordinate
* @param worldZ The world z coordinate
* @param stride The stride of the data
* @param onLoad The work to do once the chunk is available
*/
public ServerBlockChunkGenerationThread(
ServerBlockChunkDiskMap chunkDiskMap,
BlockChunkCache chunkCache,
int worldX, int worldY, int worldZ,
int stride,
Consumer<BlockChunkData> onLoad
){
this.chunkDiskMap = chunkDiskMap;
this.chunkCache = chunkCache;
this.worldX = worldX;
this.worldY = worldY;
this.worldZ = worldZ;
this.stride = stride;
this.onLoad = onLoad;
}
@Override
public void run() {
BlockChunkData chunk = null;
int i = 0;
try {
while(chunk == null && i < MAX_TIME_TO_WAIT && Globals.threadManager.shouldKeepRunning()){
if(chunkCache.containsChunk(worldX, worldY, worldZ, stride)){
chunk = chunkCache.get(worldX, worldY, worldZ, stride);
} else {
//pull from disk if it exists
if(chunkDiskMap != null){
if(chunkDiskMap.containsBlocksAtPosition(worldX, worldY, worldZ)){
chunk = chunkDiskMap.getBlockChunk(worldX, worldY, worldZ);
}
}
//generate if it does not exist
if(chunk == null){
//TODO: generate from macro-level data
chunk = BlockChunkData.allocate();
}
if(chunk != null){
chunkCache.add(worldX, worldY, worldZ, stride, chunk);
}
}
if(chunk == null){
try {
TimeUnit.MILLISECONDS.sleep(WAIT_TIME_MS);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
i++;
}
if(i >= MAX_TIME_TO_WAIT){
throw new Error("Failed to resolve chunk!");
}
this.onLoad.accept(chunk);
} catch (Error e){
LoggerInterface.loggerEngine.ERROR(e);
} catch(Exception e){
LoggerInterface.loggerEngine.ERROR(e);
}
}
}

View File

@ -0,0 +1,184 @@
package electrosphere.server.block.manager;
import electrosphere.client.block.BlockChunkData;
import electrosphere.engine.Globals;
import electrosphere.game.server.world.ServerWorldData;
import electrosphere.server.block.diskmap.ServerBlockChunkDiskMap;
import electrosphere.util.annotation.Exclude;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Consumer;
import org.joml.Vector3i;
/**
* Provides an interface for the server to query information about block chunks
*/
public class ServerBlockManager {
/**
* The number of threads for chunk generation
*/
public static final int GENERATION_THREAD_POOL_SIZE = 2;
/**
* The parent world data
*/
ServerWorldData parent;
/**
* The cache of chunks
*/
@Exclude
BlockChunkCache chunkCache = new BlockChunkCache();
//The map of chunk position <-> file on disk containing chunk data
ServerBlockChunkDiskMap chunkDiskMap = null;
/**
* The threadpool for chunk generation
*/
@Exclude
static final ExecutorService chunkExecutorService = Executors.newFixedThreadPool(GENERATION_THREAD_POOL_SIZE);
/**
* Constructor
*/
public ServerBlockManager(
ServerWorldData parent
){
this.parent = parent;
}
ServerBlockManager(){
}
/**
* Inits the chunk disk map
*/
public void generate(){
this.chunkDiskMap = ServerBlockChunkDiskMap.init();
}
/**
* Saves the block cache backing this manager to a save file
* @param saveName The name of the save
*/
public void save(String saveName){
//for each chunk, save via disk map
for(BlockChunkData chunk : this.chunkCache.getContents()){
chunkDiskMap.saveToDisk(chunk);
}
//save disk map itself
if(chunkDiskMap != null){
chunkDiskMap.save();
}
}
/**
* Loads a block manager from a save file
* @param saveName The name of the save
*/
public void load(String saveName){
//load chunk disk map
chunkDiskMap = ServerBlockChunkDiskMap.init(saveName);
}
/**
* Evicts all cached chunks
*/
public void evictAll(){
this.chunkCache.clear();
}
/**
* Performs logic once a server chunk is available
* @param worldX The world x position
* @param worldY The world y position
* @param worldZ The world z position
* @return The BlockChunkData
*/
public BlockChunkData getChunk(int worldX, int worldY, int worldZ){
Globals.profiler.beginAggregateCpuSample("ServerBlockManager.getChunk");
//THIS FIRES IF THERE IS A MAIN GAME WORLD RUNNING
BlockChunkData returnedChunk = null;
if(chunkCache.containsChunk(worldX,worldY,worldZ,BlockChunkData.LOD_FULL_RES)){
returnedChunk = chunkCache.get(worldX,worldY,worldZ, BlockChunkData.LOD_FULL_RES);
} else {
//pull from disk if it exists
if(chunkDiskMap != null){
if(chunkDiskMap.containsBlocksAtPosition(worldX, worldY, worldZ)){
returnedChunk = chunkDiskMap.getBlockChunk(worldX, worldY, worldZ);
}
}
//generate if it does not exist
if(returnedChunk == null){
returnedChunk = BlockChunkData.allocate();
returnedChunk.setWorldX(worldX);
returnedChunk.setWorldY(worldY);
returnedChunk.setWorldZ(worldZ);
}
this.chunkCache.add(worldX, worldY, worldZ, BlockChunkData.LOD_FULL_RES, returnedChunk);
}
Globals.profiler.endCpuSample();
return returnedChunk;
}
/**
* Performs logic once a server chunk is available
* @param worldX The world x position
* @param worldY The world y position
* @param worldZ The world z position
* @param stride The stride of the data
* @param onLoad The logic to run once the chunk is available
*/
public void getChunkAsync(int worldX, int worldY, int worldZ, int stride, Consumer<BlockChunkData> onLoad){
Globals.profiler.beginAggregateCpuSample("ServerBlockManager.getChunkAsync");
chunkExecutorService.submit(new ServerBlockChunkGenerationThread(chunkDiskMap, chunkCache, worldX, worldY, worldZ, stride, onLoad));
Globals.profiler.endCpuSample();
}
/**
* Saves a given position's chunk to disk.
* Uses the current global save name
* @param position The position to save
*/
public void savePositionToDisk(Vector3i position){
if(chunkDiskMap != null){
chunkDiskMap.saveToDisk(getChunk(position.x, position.y, position.z));
}
}
/**
* Applies an edit to a block at a given location
* @param worldPos The world coordinates of the chunk to modify
* @param voxelPos The voxel coordinates of the voxel to modify
* @param type The type of block
* @param metadata The metadata of the block
*/
public void editBlockAtLocationToValue(Vector3i worldPos, Vector3i voxelPos, short type, short metadata){
if(chunkCache.containsChunk(worldPos.x,worldPos.y,worldPos.z,BlockChunkData.LOD_FULL_RES)){
BlockChunkData chunk = chunkCache.get(worldPos.x,worldPos.y,worldPos.z, BlockChunkData.LOD_FULL_RES);
chunk.setType(voxelPos.x, voxelPos.y, voxelPos.z, type);
chunk.setType(voxelPos.x, voxelPos.y, voxelPos.z, metadata);
}
}
/**
* Sets the parent world data of this manager
* @param serverWorldData The parent world data
*/
public void setParent(ServerWorldData serverWorldData){
this.parent = serverWorldData;
}
/**
* Closes the generation threadpool
*/
public void closeThreads(){
chunkExecutorService.shutdownNow();
}
}

View File

@ -218,6 +218,29 @@
"chunkData" "chunkData"
] ]
}, },
{
"messageName" : "RequestReducedBlockData",
"description" : "Requests reduced resolution block data from the server",
"data" : [
"worldX",
"worldY",
"worldZ",
"chunkResolution"
]
},
{
"messageName" : "SendReducedBlockData",
"description" : "Sends block data to the client",
"data" : [
"worldX",
"worldY",
"worldZ",
"chunkResolution",
"homogenousValue",
"chunkData"
]
},
{ {
"messageName" : "RequestFluidData", "messageName" : "RequestFluidData",
"description" : "Requests a fluid data from the server", "description" : "Requests a fluid data from the server",