Server structures for dealing with block data
This commit is contained in:
parent
3777a9a37c
commit
874dcafa97
@ -1146,6 +1146,8 @@ Add asset manager support for queueing models asynchronously w/ promised path
|
||||
Add promised path support to queued textures
|
||||
Code formatting on queued asset classes
|
||||
Refactoring server side of terrain management
|
||||
Add server manager for block chunk data & management
|
||||
Add endpoint to request strided block data
|
||||
|
||||
|
||||
# TODO
|
||||
|
||||
@ -12,6 +12,16 @@ public class BlockChunkData {
|
||||
*/
|
||||
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
|
||||
*/
|
||||
@ -27,6 +37,11 @@ public class BlockChunkData {
|
||||
*/
|
||||
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
|
||||
@ -44,6 +59,26 @@ public class BlockChunkData {
|
||||
*/
|
||||
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
|
||||
*/
|
||||
@ -174,4 +209,84 @@ public class BlockChunkData {
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package electrosphere.game.server.world;
|
||||
|
||||
import electrosphere.server.block.manager.ServerBlockManager;
|
||||
import electrosphere.server.fluid.generation.DefaultFluidGenerator;
|
||||
import electrosphere.server.fluid.manager.ServerFluidManager;
|
||||
import electrosphere.server.terrain.generation.DefaultChunkGenerator;
|
||||
@ -58,6 +59,11 @@ public class ServerWorldData {
|
||||
|
||||
//fluid data
|
||||
private ServerFluidManager serverFluidManager;
|
||||
|
||||
/**
|
||||
* The block manager
|
||||
*/
|
||||
private ServerBlockManager serverBlockManager;
|
||||
|
||||
|
||||
/**
|
||||
@ -119,19 +125,22 @@ public class ServerWorldData {
|
||||
ServerWorldData serverWorldData = null;
|
||||
ServerTerrainManager serverTerrainManager = null;
|
||||
ServerFluidManager serverFluidManager = null;
|
||||
ServerBlockManager serverBlockManager = null;
|
||||
if(isScene){
|
||||
serverWorldData = FileUtils.loadObjectFromSavePath(sceneOrSaveName, "world.json", ServerWorldData.class);
|
||||
serverTerrainManager = new ServerTerrainManager(serverWorldData, 0, new DefaultChunkGenerator());
|
||||
serverTerrainManager.load(sceneOrSaveName);
|
||||
serverFluidManager = new ServerFluidManager(serverWorldData, serverTerrainManager, 0, new DefaultFluidGenerator());
|
||||
serverBlockManager = new ServerBlockManager(serverWorldData);
|
||||
} else {
|
||||
//TODO: Allow loading procedurally generated terrain from disk (the chunk generator is always default currently)
|
||||
serverWorldData = FileUtils.loadObjectFromSavePath(sceneOrSaveName, "world.json", ServerWorldData.class);
|
||||
serverTerrainManager = new ServerTerrainManager(serverWorldData, 0, new DefaultChunkGenerator());
|
||||
serverTerrainManager.load(sceneOrSaveName);
|
||||
serverFluidManager = new ServerFluidManager(serverWorldData, serverTerrainManager, 0, new DefaultFluidGenerator());
|
||||
serverBlockManager = new ServerBlockManager(serverWorldData);
|
||||
}
|
||||
serverWorldData.setManagers(serverTerrainManager, serverFluidManager);
|
||||
serverWorldData.setManagers(serverTerrainManager, serverFluidManager, serverBlockManager);
|
||||
return serverWorldData;
|
||||
}
|
||||
|
||||
@ -146,6 +155,7 @@ public class ServerWorldData {
|
||||
ServerWorldData serverWorldData = null;
|
||||
ServerTerrainManager serverTerrainManager = null;
|
||||
ServerFluidManager serverFluidManager = null;
|
||||
ServerBlockManager serverBlockManager = null;
|
||||
//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.worldSizeDiscrete = TestGenerationChunkGenerator.GENERATOR_REALM_SIZE;
|
||||
@ -158,7 +168,8 @@ public class ServerWorldData {
|
||||
serverTerrainManager.genTestData(chunkGen);
|
||||
}
|
||||
serverFluidManager = new ServerFluidManager(serverWorldData, serverTerrainManager, 0, new DefaultFluidGenerator());
|
||||
serverWorldData.setManagers(serverTerrainManager, serverFluidManager);
|
||||
serverBlockManager = new ServerBlockManager(serverWorldData);
|
||||
serverWorldData.setManagers(serverTerrainManager, serverFluidManager, serverBlockManager);
|
||||
return serverWorldData;
|
||||
}
|
||||
|
||||
@ -276,14 +287,24 @@ public class ServerWorldData {
|
||||
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
|
||||
* @param serverTerrainManager The terrain 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.serverFluidManager = serverFluidManager;
|
||||
this.serverBlockManager = serverBlockManager;
|
||||
this.serverTerrainManager.setParent(this);
|
||||
this.serverFluidManager.setParent(this);
|
||||
if(this.serverTerrainManager == null || this.serverFluidManager == null){
|
||||
|
||||
@ -204,6 +204,16 @@ public abstract class NetworkMessage {
|
||||
rVal = TerrainMessage.parseSendReducedChunkDataMessage(byteBuffer);
|
||||
}
|
||||
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:
|
||||
if(TerrainMessage.canParseMessage(byteBuffer,secondByte)){
|
||||
rVal = TerrainMessage.parseRequestFluidDataMessage(byteBuffer);
|
||||
|
||||
@ -21,6 +21,8 @@ public class TerrainMessage extends NetworkMessage {
|
||||
SENDCHUNKDATA,
|
||||
REQUESTREDUCEDCHUNKDATA,
|
||||
SENDREDUCEDCHUNKDATA,
|
||||
REQUESTREDUCEDBLOCKDATA,
|
||||
SENDREDUCEDBLOCKDATA,
|
||||
REQUESTFLUIDDATA,
|
||||
SENDFLUIDDATA,
|
||||
UPDATEFLUIDDATA,
|
||||
@ -442,6 +444,14 @@ public class TerrainMessage extends NetworkMessage {
|
||||
}
|
||||
case TypeBytes.TERRAIN_MESSAGE_TYPE_SENDREDUCEDCHUNKDATA:
|
||||
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:
|
||||
if(byteBuffer.getRemaining() >= TypeBytes.TERRAIN_MESSAGE_TYPE_REQUESTFLUIDDATA_SIZE){
|
||||
return true;
|
||||
@ -802,6 +812,99 @@ public class TerrainMessage extends NetworkMessage {
|
||||
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
|
||||
*/
|
||||
@ -1211,6 +1314,63 @@ public class TerrainMessage extends NetworkMessage {
|
||||
rawBytes[26+i] = chunkData[i];
|
||||
}
|
||||
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:
|
||||
rawBytes = new byte[2+4+4+4];
|
||||
//message header
|
||||
|
||||
@ -76,9 +76,11 @@ public class TypeBytes {
|
||||
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_SENDREDUCEDCHUNKDATA = 9;
|
||||
public static final byte TERRAIN_MESSAGE_TYPE_REQUESTFLUIDDATA = 10;
|
||||
public static final byte TERRAIN_MESSAGE_TYPE_SENDFLUIDDATA = 11;
|
||||
public static final byte TERRAIN_MESSAGE_TYPE_UPDATEFLUIDDATA = 12;
|
||||
public static final byte TERRAIN_MESSAGE_TYPE_REQUESTREDUCEDBLOCKDATA = 10;
|
||||
public static final byte TERRAIN_MESSAGE_TYPE_SENDREDUCEDBLOCKDATA = 11;
|
||||
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
|
||||
*/
|
||||
@ -90,6 +92,7 @@ public class TypeBytes {
|
||||
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_REQUESTREDUCEDCHUNKDATA_SIZE = 18;
|
||||
public static final byte TERRAIN_MESSAGE_TYPE_REQUESTREDUCEDBLOCKDATA_SIZE = 18;
|
||||
public static final byte TERRAIN_MESSAGE_TYPE_REQUESTFLUIDDATA_SIZE = 14;
|
||||
|
||||
/*
|
||||
|
||||
@ -3,10 +3,12 @@ package electrosphere.net.server.protocol;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.nio.ShortBuffer;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.joml.Vector3d;
|
||||
|
||||
import electrosphere.client.block.BlockChunkData;
|
||||
import electrosphere.client.terrain.cache.ChunkData;
|
||||
import electrosphere.engine.Globals;
|
||||
import electrosphere.logger.LoggerInterface;
|
||||
@ -39,6 +41,12 @@ public class TerrainProtocol implements ServerProtocolTemplate<TerrainMessage> {
|
||||
);
|
||||
return null;
|
||||
}
|
||||
case REQUESTREDUCEDBLOCKDATA: {
|
||||
TerrainProtocol.sendBlocksAsyncStrided(connectionHandler,
|
||||
message.getworldX(), message.getworldY(), message.getworldZ(), message.getchunkResolution()
|
||||
);
|
||||
return null;
|
||||
}
|
||||
default: {
|
||||
} break;
|
||||
}
|
||||
@ -80,6 +88,8 @@ public class TerrainProtocol implements ServerProtocolTemplate<TerrainMessage> {
|
||||
case SENDFLUIDDATA:
|
||||
case REQUESTREDUCEDCHUNKDATA:
|
||||
case SENDREDUCEDCHUNKDATA:
|
||||
case REQUESTREDUCEDBLOCKDATA:
|
||||
case SENDREDUCEDBLOCKDATA:
|
||||
//silently ignore
|
||||
break;
|
||||
}
|
||||
@ -262,6 +272,57 @@ public class TerrainProtocol implements ServerProtocolTemplate<TerrainMessage> {
|
||||
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
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@ -218,6 +218,29 @@
|
||||
"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",
|
||||
"description" : "Requests a fluid data from the server",
|
||||
|
||||
Loading…
Reference in New Issue
Block a user