SerializableData data types can now be indexed

This commit is contained in:
themode 2020-08-29 22:21:45 +02:00
parent daf835dd53
commit 38dbaecb8f
10 changed files with 365 additions and 196 deletions

View File

@ -30,12 +30,13 @@ import net.minestom.server.item.metadata.MapMeta;
import net.minestom.server.network.ConnectionManager;
import net.minestom.server.ping.ResponseDataConsumer;
import net.minestom.server.scoreboard.Sidebar;
import net.minestom.server.storage.StorageFolder;
import net.minestom.server.storage.StorageOptions;
import net.minestom.server.utils.BlockPosition;
import net.minestom.server.utils.MathUtils;
import net.minestom.server.utils.Position;
import net.minestom.server.utils.Vector;
import net.minestom.server.utils.time.TimeUnit;
import net.minestom.server.world.DimensionType;
import java.util.Map;
import java.util.UUID;
@ -47,11 +48,11 @@ public class PlayerInit {
private static volatile Inventory inventory;
static {
//StorageFolder storageFolder = MinecraftServer.getStorageManager().getFolder("instance_data", new StorageOptions().setCompression(true));
StorageFolder storageFolder = MinecraftServer.getStorageManager().getFolder("instance_data", new StorageOptions().setCompression(true));
ChunkGeneratorDemo chunkGeneratorDemo = new ChunkGeneratorDemo();
NoiseTestGenerator noiseTestGenerator = new NoiseTestGenerator();
//instanceContainer = MinecraftServer.getInstanceManager().createInstanceContainer(storageFolder);
instanceContainer = MinecraftServer.getInstanceManager().createInstanceContainer(DimensionType.OVERWORLD);
instanceContainer = MinecraftServer.getInstanceManager().createInstanceContainer(storageFolder);
//instanceContainer = MinecraftServer.getInstanceManager().createInstanceContainer(DimensionType.OVERWORLD);
instanceContainer.enableAutoChunkLoad(true);
//instanceContainer.setChunkDecider((x,y) -> (pos) -> pos.getY()>40?(short)0:(short)1);
instanceContainer.setChunkGenerator(noiseTestGenerator);

View File

@ -69,7 +69,7 @@ public class SimpleCommand implements CommandProcessor {
System.gc();
//player.getInstance().saveChunksToStorageFolder(() -> System.out.println("end save"));
player.getInstance().saveChunksToStorageFolder(() -> System.out.println("end save"));
return true;
}

View File

@ -3,6 +3,7 @@ package fr.themode.demo.generator;
import de.articdive.jnoise.JNoise;
import de.articdive.jnoise.interpolation.InterpolationType;
import net.minestom.server.MinecraftServer;
import net.minestom.server.data.SerializableData;
import net.minestom.server.instance.Chunk;
import net.minestom.server.instance.ChunkGenerator;
import net.minestom.server.instance.ChunkPopulator;
@ -11,168 +12,174 @@ import net.minestom.server.instance.block.Block;
import net.minestom.server.utils.BlockPosition;
import net.minestom.server.world.biomes.Biome;
import java.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
public class NoiseTestGenerator extends ChunkGenerator {
private Random random = new Random();
private JNoise jNoise = JNoise.newBuilder().perlin().setInterpolation(InterpolationType.LINEAR).setSeed(random.nextInt()).setFrequency(0.4).build();
private JNoise jNoise2 = JNoise.newBuilder().perlin().setInterpolation(InterpolationType.LINEAR).setSeed(random.nextInt()).setFrequency(0.6).build();
private TreePopulator treeGen = new TreePopulator();
private Random random = new Random();
private JNoise jNoise = JNoise.newBuilder().perlin().setInterpolation(InterpolationType.LINEAR).setSeed(random.nextInt()).setFrequency(0.4).build();
private JNoise jNoise2 = JNoise.newBuilder().perlin().setInterpolation(InterpolationType.LINEAR).setSeed(random.nextInt()).setFrequency(0.6).build();
private TreePopulator treeGen = new TreePopulator();
public int getHeight(int x, int z) {
double preHeight = jNoise.getNoise(x / 16.0, z / 16.0);
return (int) ((preHeight > 0 ? preHeight * 6 : preHeight * 4) + 64);
}
public int getHeight(int x, int z) {
double preHeight = jNoise.getNoise(x / 16.0, z / 16.0);
return (int) ((preHeight > 0 ? preHeight * 6 : preHeight * 4) + 64);
}
@Override
public void generateChunkData(ChunkBatch batch, int chunkX, int chunkZ) {
for (int x = 0; x < Chunk.CHUNK_SIZE_X; x++) {
for (int z = 0; z < Chunk.CHUNK_SIZE_Z; z++) {
int height = getHeight(x + chunkX * 16, z + chunkZ * 16);
for (int y = 0; y < height; y++) {
//if (random.nextInt(100) > 10) {
// batch.setBlock(x, y, z, Block.DIAMOND_BLOCK);
//} else {
// batch.setBlock(x, y, z, Block.GOLD_BLOCK);
//}
if (y == 0) {
batch.setBlock(x, y, z, Block.BEDROCK);
} else if (y == height-1) {
batch.setBlock(x, y, z, Block.GRASS_BLOCK);
} else if (y > height-7) {
batch.setBlock(x, y, z, Block.DIRT);
} else {
batch.setBlock(x, y, z, Block.STONE);
}
}
if (height < 61) {
batch.setBlock(x, height-1, z, Block.DIRT);
for (int y = 0; y < 61 - height; y++) {
batch.setBlock(x, y + height, z, Block.WATER);
}
}
}
}
}
@Override
public void generateChunkData(ChunkBatch batch, int chunkX, int chunkZ) {
for (int x = 0; x < Chunk.CHUNK_SIZE_X; x++) {
for (int z = 0; z < Chunk.CHUNK_SIZE_Z; z++) {
final int height = getHeight(x + chunkX * 16, z + chunkZ * 16);
for (int y = 0; y < height; y++) {
//if (random.nextInt(100) > 10) {
// batch.setBlock(x, y, z, Block.DIAMOND_BLOCK);
//} else {
// batch.setBlock(x, y, z, Block.GOLD_BLOCK);
//}
if (y == 0) {
batch.setBlock(x, y, z, Block.BEDROCK);
} else if (y == height - 1) {
batch.setBlock(x, y, z, Block.GRASS_BLOCK);
} else if (y > height - 7) {
// Data for debugging purpose
SerializableData serializableData = new SerializableData();
serializableData.set("test", 55, Integer.class);
batch.setBlockStateId(x, y, z, Block.DIRT.getBlockId(), serializableData);
} else {
batch.setBlock(x, y, z, Block.STONE);
}
}
if (height < 61) {
batch.setBlock(x, height - 1, z, Block.DIRT);
for (int y = 0; y < 61 - height; y++) {
batch.setBlock(x, y + height, z, Block.WATER);
}
}
}
}
}
@Override
public void fillBiomes(Biome[] biomes, int chunkX, int chunkZ) {
Arrays.fill(biomes, MinecraftServer.getBiomeManager().getById(0));
}
@Override
public void fillBiomes(Biome[] biomes, int chunkX, int chunkZ) {
Arrays.fill(biomes, MinecraftServer.getBiomeManager().getById(0));
}
@Override
public List<ChunkPopulator> getPopulators() {
List<ChunkPopulator> list = new ArrayList<>();
list.add(treeGen);
return list;
}
@Override
public List<ChunkPopulator> getPopulators() {
List<ChunkPopulator> list = new ArrayList<>();
list.add(treeGen);
return list;
}
private class TreePopulator implements ChunkPopulator {
private class TreePopulator implements ChunkPopulator {
final Structure tree;
final Structure tree;
public TreePopulator() {
tree = new Structure();
tree.addBlock(Block.DIRT, 0, -1, 0);
tree.addBlock(Block.OAK_LOG, 0, 0, 0);
tree.addBlock(Block.OAK_LOG, 0, 1, 0);
tree.addBlock(Block.OAK_LOG, 0, 2, 0);
tree.addBlock(Block.OAK_LOG, 0, 3, 0);
public TreePopulator() {
tree = new Structure();
tree.addBlock(Block.DIRT, 0, -1, 0);
tree.addBlock(Block.OAK_LOG, 0, 0, 0);
tree.addBlock(Block.OAK_LOG, 0, 1, 0);
tree.addBlock(Block.OAK_LOG, 0, 2, 0);
tree.addBlock(Block.OAK_LOG, 0, 3, 0);
tree.addBlock(Block.OAK_LEAVES, 1, 1, 0);
tree.addBlock(Block.OAK_LEAVES, 2, 1, 0);
tree.addBlock(Block.OAK_LEAVES, -1, 1, 0);
tree.addBlock(Block.OAK_LEAVES, -2, 1, 0);
tree.addBlock(Block.OAK_LEAVES, 1, 1, 0);
tree.addBlock(Block.OAK_LEAVES, 2, 1, 0);
tree.addBlock(Block.OAK_LEAVES, -1, 1, 0);
tree.addBlock(Block.OAK_LEAVES, -2, 1, 0);
tree.addBlock(Block.OAK_LEAVES, 1, 1, 1);
tree.addBlock(Block.OAK_LEAVES, 2, 1, 1);
tree.addBlock(Block.OAK_LEAVES, 0, 1, 1);
tree.addBlock(Block.OAK_LEAVES, -1, 1, 1);
tree.addBlock(Block.OAK_LEAVES, -2, 1, 1);
tree.addBlock(Block.OAK_LEAVES, 1, 1, 1);
tree.addBlock(Block.OAK_LEAVES, 2, 1, 1);
tree.addBlock(Block.OAK_LEAVES, 0, 1, 1);
tree.addBlock(Block.OAK_LEAVES, -1, 1, 1);
tree.addBlock(Block.OAK_LEAVES, -2, 1, 1);
tree.addBlock(Block.OAK_LEAVES, 1, 1, 2);
tree.addBlock(Block.OAK_LEAVES, 2, 1, 2);
tree.addBlock(Block.OAK_LEAVES, 0, 1, 2);
tree.addBlock(Block.OAK_LEAVES, -1, 1, 2);
tree.addBlock(Block.OAK_LEAVES, -2, 1, 2);
tree.addBlock(Block.OAK_LEAVES, 1, 1, 2);
tree.addBlock(Block.OAK_LEAVES, 2, 1, 2);
tree.addBlock(Block.OAK_LEAVES, 0, 1, 2);
tree.addBlock(Block.OAK_LEAVES, -1, 1, 2);
tree.addBlock(Block.OAK_LEAVES, -2, 1, 2);
tree.addBlock(Block.OAK_LEAVES, 1, 1, -1);
tree.addBlock(Block.OAK_LEAVES, 2, 1, -1);
tree.addBlock(Block.OAK_LEAVES, 0, 1, -1);
tree.addBlock(Block.OAK_LEAVES, -1, 1, -1);
tree.addBlock(Block.OAK_LEAVES, -2, 1, -1);
tree.addBlock(Block.OAK_LEAVES, 1, 1, -1);
tree.addBlock(Block.OAK_LEAVES, 2, 1, -1);
tree.addBlock(Block.OAK_LEAVES, 0, 1, -1);
tree.addBlock(Block.OAK_LEAVES, -1, 1, -1);
tree.addBlock(Block.OAK_LEAVES, -2, 1, -1);
tree.addBlock(Block.OAK_LEAVES, 1, 1, -2);
tree.addBlock(Block.OAK_LEAVES, 2, 1, -2);
tree.addBlock(Block.OAK_LEAVES, 0, 1, -2);
tree.addBlock(Block.OAK_LEAVES, -1, 1, -2);
tree.addBlock(Block.OAK_LEAVES, -2, 1, -2);
tree.addBlock(Block.OAK_LEAVES, 1, 1, -2);
tree.addBlock(Block.OAK_LEAVES, 2, 1, -2);
tree.addBlock(Block.OAK_LEAVES, 0, 1, -2);
tree.addBlock(Block.OAK_LEAVES, -1, 1, -2);
tree.addBlock(Block.OAK_LEAVES, -2, 1, -2);
tree.addBlock(Block.OAK_LEAVES, 1, 2, 0);
tree.addBlock(Block.OAK_LEAVES, 2, 2, 0);
tree.addBlock(Block.OAK_LEAVES, -1, 2, 0);
tree.addBlock(Block.OAK_LEAVES, -2, 2, 0);
tree.addBlock(Block.OAK_LEAVES, 1, 2, 0);
tree.addBlock(Block.OAK_LEAVES, 2, 2, 0);
tree.addBlock(Block.OAK_LEAVES, -1, 2, 0);
tree.addBlock(Block.OAK_LEAVES, -2, 2, 0);
tree.addBlock(Block.OAK_LEAVES, 1, 2, 1);
tree.addBlock(Block.OAK_LEAVES, 2, 2, 1);
tree.addBlock(Block.OAK_LEAVES, 0, 2, 1);
tree.addBlock(Block.OAK_LEAVES, -1, 2, 1);
tree.addBlock(Block.OAK_LEAVES, -2, 2, 1);
tree.addBlock(Block.OAK_LEAVES, 1, 2, 1);
tree.addBlock(Block.OAK_LEAVES, 2, 2, 1);
tree.addBlock(Block.OAK_LEAVES, 0, 2, 1);
tree.addBlock(Block.OAK_LEAVES, -1, 2, 1);
tree.addBlock(Block.OAK_LEAVES, -2, 2, 1);
tree.addBlock(Block.OAK_LEAVES, 1, 2, 2);
tree.addBlock(Block.OAK_LEAVES, 2, 2, 2);
tree.addBlock(Block.OAK_LEAVES, 0, 2, 2);
tree.addBlock(Block.OAK_LEAVES, -1, 2, 2);
tree.addBlock(Block.OAK_LEAVES, -2, 2, 2);
tree.addBlock(Block.OAK_LEAVES, 1, 2, 2);
tree.addBlock(Block.OAK_LEAVES, 2, 2, 2);
tree.addBlock(Block.OAK_LEAVES, 0, 2, 2);
tree.addBlock(Block.OAK_LEAVES, -1, 2, 2);
tree.addBlock(Block.OAK_LEAVES, -2, 2, 2);
tree.addBlock(Block.OAK_LEAVES, 1, 2, -1);
tree.addBlock(Block.OAK_LEAVES, 2, 2, -1);
tree.addBlock(Block.OAK_LEAVES, 0, 2, -1);
tree.addBlock(Block.OAK_LEAVES, -1, 2, -1);
tree.addBlock(Block.OAK_LEAVES, -2, 2, -1);
tree.addBlock(Block.OAK_LEAVES, 1, 2, -1);
tree.addBlock(Block.OAK_LEAVES, 2, 2, -1);
tree.addBlock(Block.OAK_LEAVES, 0, 2, -1);
tree.addBlock(Block.OAK_LEAVES, -1, 2, -1);
tree.addBlock(Block.OAK_LEAVES, -2, 2, -1);
tree.addBlock(Block.OAK_LEAVES, 1, 2, -2);
tree.addBlock(Block.OAK_LEAVES, 2, 2, -2);
tree.addBlock(Block.OAK_LEAVES, 0, 2, -2);
tree.addBlock(Block.OAK_LEAVES, -1, 2, -2);
tree.addBlock(Block.OAK_LEAVES, -2, 2, -2);
tree.addBlock(Block.OAK_LEAVES, 1, 2, -2);
tree.addBlock(Block.OAK_LEAVES, 2, 2, -2);
tree.addBlock(Block.OAK_LEAVES, 0, 2, -2);
tree.addBlock(Block.OAK_LEAVES, -1, 2, -2);
tree.addBlock(Block.OAK_LEAVES, -2, 2, -2);
tree.addBlock(Block.OAK_LEAVES, 1, 3, 0);
tree.addBlock(Block.OAK_LEAVES, -1, 3, 0);
tree.addBlock(Block.OAK_LEAVES, 1, 3, 0);
tree.addBlock(Block.OAK_LEAVES, -1, 3, 0);
tree.addBlock(Block.OAK_LEAVES, 1, 3, 1);
tree.addBlock(Block.OAK_LEAVES, 0, 3, 1);
tree.addBlock(Block.OAK_LEAVES, -1, 3, 1);
tree.addBlock(Block.OAK_LEAVES, 1, 3, 1);
tree.addBlock(Block.OAK_LEAVES, 0, 3, 1);
tree.addBlock(Block.OAK_LEAVES, -1, 3, 1);
tree.addBlock(Block.OAK_LEAVES, 1, 3, -1);
tree.addBlock(Block.OAK_LEAVES, 0, 3, -1);
tree.addBlock(Block.OAK_LEAVES, -1, 3, -1);
tree.addBlock(Block.OAK_LEAVES, 1, 3, -1);
tree.addBlock(Block.OAK_LEAVES, 0, 3, -1);
tree.addBlock(Block.OAK_LEAVES, -1, 3, -1);
tree.addBlock(Block.OAK_LEAVES, 1, 4, 0);
tree.addBlock(Block.OAK_LEAVES, 0, 4, 0);
tree.addBlock(Block.OAK_LEAVES, -1, 4, 0);
tree.addBlock(Block.OAK_LEAVES, 1, 4, 0);
tree.addBlock(Block.OAK_LEAVES, 0, 4, 0);
tree.addBlock(Block.OAK_LEAVES, -1, 4, 0);
tree.addBlock(Block.OAK_LEAVES, 0, 4, 1);
tree.addBlock(Block.OAK_LEAVES, 0, 4, 1);
tree.addBlock(Block.OAK_LEAVES, 0, 4, -1);
tree.addBlock(Block.OAK_LEAVES, -1, 4, -1);
}
tree.addBlock(Block.OAK_LEAVES, 0, 4, -1);
tree.addBlock(Block.OAK_LEAVES, -1, 4, -1);
}
//todo improve
@Override
public void populateChunk(ChunkBatch batch, Chunk chunk) {
for (int i = -2; i < 18; i++) {
for (int j = -2; j < 18; j++) {
if (jNoise2.getNoise(i + chunk.getChunkX() * 16, j + chunk.getChunkZ() * 16) > 0.75) {
//todo improve
@Override
public void populateChunk(ChunkBatch batch, Chunk chunk) {
for (int i = -2; i < 18; i++) {
for (int j = -2; j < 18; j++) {
if (jNoise2.getNoise(i + chunk.getChunkX() * 16, j + chunk.getChunkZ() * 16) > 0.75) {
int y = getHeight(i + chunk.getChunkX() * 16, j + chunk.getChunkZ() * 16);
tree.build(batch, new BlockPosition(i, y, j));
}
}
}
}
}
}
}
}
}
}
}

View File

@ -1,9 +1,16 @@
package net.minestom.server.data;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import it.unimi.dsi.fastutil.objects.Object2ShortMap;
import it.unimi.dsi.fastutil.objects.Object2ShortOpenHashMap;
import net.minestom.server.MinecraftServer;
import net.minestom.server.reader.DataReader;
import net.minestom.server.utils.PrimitiveConversion;
import net.minestom.server.utils.binary.BinaryReader;
import net.minestom.server.utils.binary.BinaryWriter;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class SerializableData extends Data {
@ -44,32 +51,105 @@ public class SerializableData extends Data {
/**
* Serialize the data into an array of bytes
* <p>
* Use {@link net.minestom.server.reader.DataReader#readData(byte[])}
* to convert it back
* Use {@link DataReader#readIndexedData(BinaryReader)} if {@code indexed} is true,
* {@link DataReader#readData(Object2ShortMap, BinaryReader)} otherwise with the index map
* to convert it back to a {@link SerializableData}
*
* @param typeToIndexMap the type to index map, will create entries if new types are discovered.
* The map is not thread-safe
* @param indexed true to add the types index in the header
* @return the array representation of this data object
*/
public byte[] getSerializedData() {
public byte[] getSerializedData(Object2ShortMap<String> typeToIndexMap, boolean indexed) {
// Get the current max index, it supposes that the index keep being incremented by 1
short lastIndex = (short) typeToIndexMap.size();
// Main buffer containing the data
BinaryWriter binaryWriter = new BinaryWriter();
for (Map.Entry<String, Object> entry : data.entrySet()) {
final String key = entry.getKey();
final Object value = entry.getValue();
data.forEach((key, value) -> {
final Class type = dataType.get(key);
final DataType dataType = DATA_MANAGER.getDataType(type);
final short typeIndex;
{
// Find the type name
final String encodedType = PrimitiveConversion.getObjectClassString(type.getName()); // Data type (fix for primitives)
// Write the data type
final String encodedType = PrimitiveConversion.getObjectClassString(type.getName()); // Data type (fix for primitives)
binaryWriter.writeSizedString(encodedType);
// Find the type index
if (typeToIndexMap.containsKey(encodedType)) {
// Get index
typeIndex = typeToIndexMap.getShort(encodedType);
} else {
// Create new index
typeToIndexMap.put(encodedType, ++lastIndex);
// Set index
typeIndex = lastIndex;
}
}
// Write the data type index
binaryWriter.writeShort(typeIndex);
// Write the data key
binaryWriter.writeSizedString(key);
// Write the data (no length)
final DataType dataType = DATA_MANAGER.getDataType(type);
dataType.encode(binaryWriter, value);
});
}
binaryWriter.writeVarInt(0); // End of data object
binaryWriter.writeShort((short) 0); // End of data object
// Header for type indexes
if (indexed) {
// The buffer containing all the index info (class name to class index)
BinaryWriter indexWriter = new BinaryWriter();
writeDataIndexHeader(indexWriter, typeToIndexMap);
// Merge the index buffer & the main data buffer
final ByteBuf finalBuffer = Unpooled.wrappedBuffer(indexWriter.getBuffer(), binaryWriter.getBuffer());
// Change the main writer buffer, so it contains both the indexes and the data
binaryWriter.setBuffer(finalBuffer);
}
return binaryWriter.toByteArray();
}
/**
* Serialize the data into an array of bytes
* <p>
* Use {@link net.minestom.server.reader.DataReader#readIndexedData(BinaryReader)}
* to convert it back to a {@link SerializableData}
* <p>
* This will create a type index map which will be present in the header
*
* @return the array representation of this data object
*/
public byte[] getIndexedSerializedData() {
return getSerializedData(new Object2ShortOpenHashMap<>(), true);
}
/**
* Get the index info (class name -> class index)
* <p>
* Sized by a var-int
*
* @param typeToIndexMap the data index map
*/
public static void writeDataIndexHeader(BinaryWriter indexWriter, Object2ShortMap<String> typeToIndexMap) {
// Write the size of the following index list (class name-> class index)
indexWriter.writeVarInt(typeToIndexMap.size());
for (Object2ShortMap.Entry<String> entry : typeToIndexMap.object2ShortEntrySet()) {
final String className = entry.getKey();
final short classIndex = entry.getShortValue();
// Write className -> class index
indexWriter.writeSizedString(className);
indexWriter.writeShort(classIndex);
}
}
}

View File

@ -11,11 +11,11 @@ public class SerializableDataData extends DataType<SerializableData> {
@Override
public void encode(BinaryWriter writer, SerializableData value) {
writer.writeBytes(value.getSerializedData());
writer.writeBytes(value.getIndexedSerializedData());
}
@Override
public SerializableData decode(BinaryReader reader) {
return DataReader.readData(reader);
return DataReader.readIndexedData(reader);
}
}

View File

@ -1,7 +1,11 @@
package net.minestom.server.instance;
import com.extollit.gaming.ai.path.model.ColumnarOcclusionFieldList;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2ShortMap;
import it.unimi.dsi.fastutil.objects.Object2ShortOpenHashMap;
import net.minestom.server.data.Data;
import net.minestom.server.data.SerializableData;
import net.minestom.server.entity.pathfinding.PFBlockDescription;
@ -145,6 +149,10 @@ public class DynamicChunk extends Chunk {
@Override
protected byte[] getSerializedData() {
// Used for blocks data
Object2ShortMap<String> typeToIndexMap = new Object2ShortOpenHashMap<>();
BinaryWriter binaryWriter = new BinaryWriter();
// Write the biomes id
@ -179,13 +187,28 @@ public class DynamicChunk extends Chunk {
final boolean hasData = data instanceof SerializableData;
binaryWriter.writeBoolean(hasData);
if (hasData) {
final byte[] serializedData = ((SerializableData) data).getSerializedData();
// Get the un-indexed data
final byte[] serializedData = ((SerializableData) data).getSerializedData(typeToIndexMap, false);
binaryWriter.writeBytes(serializedData);
}
}
}
}
// If the chunk data contains SerializableData type, it needs to be added in the header
BinaryWriter indexWriter = new BinaryWriter();
final boolean hasIndex = !typeToIndexMap.isEmpty();
indexWriter.writeBoolean(hasIndex);
if (hasIndex) {
// Get the index buffer (prefixed by true to say that the chunk contains data indexes)
SerializableData.writeDataIndexHeader(indexWriter, typeToIndexMap);
}
// Create the final buffer (data index buffer followed by the chunk buffer)
final ByteBuf finalBuffer = Unpooled.wrappedBuffer(indexWriter.getBuffer(), binaryWriter.getBuffer());
// Change the main writer buffer
binaryWriter.setBuffer(finalBuffer);
return binaryWriter.toByteArray();
}

View File

@ -1,5 +1,6 @@
package net.minestom.server.reader;
import it.unimi.dsi.fastutil.objects.Object2ShortMap;
import net.minestom.server.MinecraftServer;
import net.minestom.server.data.Data;
import net.minestom.server.instance.Chunk;
@ -19,9 +20,17 @@ public class ChunkReader {
public static void readChunk(byte[] b, Instance instance, int chunkX, int chunkZ, Consumer<Chunk> callback) {
BinaryReader binaryReader = new BinaryReader(b);
// Used for blocks data
Object2ShortMap<String> typeToIndexMap = null;
ChunkBatch chunkBatch = null;
try {
final boolean hasIndex = binaryReader.readBoolean();
if (hasIndex) {
typeToIndexMap = DataReader.readDataIndexes(binaryReader);
}
Biome[] biomes = new Biome[Chunk.BIOME_COUNT];
for (int i = 0; i < biomes.length; i++) {
final byte id = binaryReader.readByte();
@ -48,7 +57,8 @@ public class ChunkReader {
final boolean hasData = binaryReader.readBoolean();
// Data deserializer
if (hasData) {
data = DataReader.readData(binaryReader);
// Read the data with the deserialized index map
data = DataReader.readData(typeToIndexMap, binaryReader);
}
}

View File

@ -1,10 +1,16 @@
package net.minestom.server.reader;
import it.unimi.dsi.fastutil.objects.Object2ShortMap;
import it.unimi.dsi.fastutil.objects.Object2ShortOpenHashMap;
import it.unimi.dsi.fastutil.shorts.Short2ObjectMap;
import it.unimi.dsi.fastutil.shorts.Short2ObjectOpenHashMap;
import net.minestom.server.MinecraftServer;
import net.minestom.server.data.DataManager;
import net.minestom.server.data.SerializableData;
import net.minestom.server.utils.binary.BinaryReader;
import java.util.concurrent.ConcurrentHashMap;
/**
* Class used to convert an array of bytes to a {@link SerializableData}
* <p>
@ -14,60 +20,93 @@ public class DataReader {
private static final DataManager DATA_MANAGER = MinecraftServer.getDataManager();
private static ConcurrentHashMap<String, Class> nameToClassMap = new ConcurrentHashMap<>();
/**
* Convert a buffer into a {@link SerializableData}
* Convert a buffer into a {@link SerializableData}, this will not read the data index header.
* Use {@link #readIndexedData(BinaryReader)} to read the whole data object (if your data contains the indexes)
* <p>
* WARNING: the {@link DataManager} needs to have all the required types as the {@link SerializableData} has
*
* @param reader the reader
* @param typeToIndexMap the map which index all the type contained in the data (className->classIndex)
* @param reader the reader
* @return a {@link SerializableData} based on the data input
*/
public static SerializableData readData(BinaryReader reader) {
SerializableData data = new SerializableData();
try {
while (true) {
final int typeLength = reader.readVarInt();
if (typeLength == 0) {
// End of data
break;
}
// Get the class type
final Class type;
{
final byte[] typeCache = reader.readBytes(typeLength);
final String className = new String(typeCache);
type = Class.forName(className);
}
// Get the key
final String name = reader.readSizedString();
// Get the data
final Object value = DATA_MANAGER.getDataType(type).decode(reader);
// Set the data
data.set(name, value, type);
public static SerializableData readData(Object2ShortMap<String> typeToIndexMap, BinaryReader reader) {
final Short2ObjectMap<String> indexToTypeMap = new Short2ObjectOpenHashMap<>(typeToIndexMap.size());
{
// Fill the indexToType map
for (Object2ShortMap.Entry<String> entry : typeToIndexMap.object2ShortEntrySet()) {
final String type = entry.getKey();
final short index = entry.getShortValue();
indexToTypeMap.put(index, type);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
SerializableData data = new SerializableData();
while (true) {
// Get the class index
final short typeIndex = reader.readShort();
if (typeIndex == 0) {
// End of data
break;
}
final Class type;
{
final String className = indexToTypeMap.get(typeIndex);
type = nameToClassMap.computeIfAbsent(className, s -> {
try {
return Class.forName(className);
} catch (ClassNotFoundException e) {
e.printStackTrace();
return null;
}
});
}
// Get the key
final String name = reader.readSizedString();
// Get the data
final Object value = DATA_MANAGER.getDataType(type).decode(reader);
// Set the data
data.set(name, value, type);
}
return data;
}
/**
* Convert a bytes array to a {@link SerializableData}
* Read the indexes of the data + the data
*
* @param data the data
* @return a {@link SerializableData} based on the data input
* @see #readData(BinaryReader)
* @param reader the reader
* @return the deserialized {@link SerializableData}
*/
public static SerializableData readData(byte[] data) {
return readData(new BinaryReader(data));
public static SerializableData readIndexedData(BinaryReader reader) {
final Object2ShortMap<String> typeToIndexMap = readDataIndexes(reader);
return readData(typeToIndexMap, reader);
}
/**
* Get a map containing the indexes of your data (type name -> type index)
*
* @param binaryReader the reader
* @return a map containing the indexes of your data
*/
public static Object2ShortMap<String> readDataIndexes(BinaryReader binaryReader) {
Object2ShortMap<String> typeToIndexMap = new Object2ShortOpenHashMap<>();
{
final int dataIndexSize = binaryReader.readVarInt();
for (int i = 0; i < dataIndexSize; i++) {
final String className = binaryReader.readSizedString();
final short classIndex = binaryReader.readShort();
typeToIndexMap.put(className, classIndex);
}
}
return typeToIndexMap;
}
}

View File

@ -96,7 +96,7 @@ public class StorageFolder {
SerializableData data;
if (bytes != null) {
data = DataReader.readData(new BinaryReader(bytes));
data = DataReader.readIndexedData(new BinaryReader(bytes));
} else {
data = new SerializableData();
}
@ -129,7 +129,7 @@ public class StorageFolder {
SerializableData data;
if (bytes != null) {
data = DataReader.readData(new BinaryReader(bytes));
data = DataReader.readIndexedData(new BinaryReader(bytes));
} else {
data = new SerializableData();
}
@ -153,7 +153,7 @@ public class StorageFolder {
return;
// Save the data
set(key, serializableData.getSerializedData());
set(key, serializableData.getIndexedSerializedData());
// Remove from map
this.cachedData.remove(key);
@ -166,7 +166,7 @@ public class StorageFolder {
public void saveCachedData() {
synchronized (cachedData) {
cachedData.forEach((key, data) -> {
set(key, data.getSerializedData());
set(key, data.getIndexedSerializedData());
});
}
}
@ -179,7 +179,7 @@ public class StorageFolder {
public void saveCachedData(String key) {
synchronized (cachedData) {
final SerializableData data = cachedData.get(key);
set(key, data.getSerializedData());
set(key, data.getIndexedSerializedData());
}
}

View File

@ -18,7 +18,7 @@ import java.util.function.Consumer;
public class BinaryWriter extends OutputStream {
private final ByteBuf buffer;
private ByteBuf buffer;
private final NBTWriter nbtWriter = new NBTWriter(this, false);
/**
@ -261,6 +261,15 @@ public class BinaryWriter extends OutputStream {
return buffer;
}
/**
* Change the buffer used by this binary writer
*
* @param buffer the new buffer used by this binary writer
*/
public void setBuffer(ByteBuf buffer) {
this.buffer = buffer;
}
@Override
public void write(int b) {
writeByte((byte) b);