Make Data and SerializableData interfaces

This commit is contained in:
Nesaak 2020-09-02 12:31:33 -04:00
parent 6e9e6be1fe
commit dd66699a20
8 changed files with 197 additions and 137 deletions

View File

@ -4,6 +4,7 @@ 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.data.SerializableDataImpl;
import net.minestom.server.instance.Chunk;
import net.minestom.server.instance.ChunkGenerator;
import net.minestom.server.instance.ChunkPopulator;
@ -46,7 +47,7 @@ public class NoiseTestGenerator implements ChunkGenerator {
batch.setBlock(x, y, z, Block.GRASS_BLOCK);
} else if (y > height - 7) {
// Data for debugging purpose
SerializableData serializableData = new SerializableData();
SerializableData serializableData = new SerializableDataImpl();
serializableData.set("test", 55, Integer.class);
batch.setBlockStateId(x, y, z, Block.DIRT.getBlockId(), serializableData);
} else {

View File

@ -2,14 +2,12 @@ package net.minestom.server.data;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
public class Data {
public interface Data {
public static final Data EMPTY = new Data() {
Data EMPTY = new Data() {
@Override
public <T> void set(String key, T value, Class<T> type) {
}
public <T> void set(String key, T value, Class<T> type) { }
@Override
public <T> T get(String key) {
@ -21,14 +19,27 @@ public class Data {
return false;
}
@Override
public Set<String> getKeys() {
return Collections.EMPTY_SET;
}
@Override
public boolean isEmpty() {
return true;
}
@Override
public Data clone() {
return this;
}
@Override
public <T> T getOrDefault(String key, T defaultValue) {
return defaultValue;
}
};
protected final ConcurrentHashMap<String, Object> data = new ConcurrentHashMap<>();
/**
* Set a value to a specific key
*
@ -37,21 +48,16 @@ public class Data {
* @param type the value type
* @param <T> the value generic
*/
public <T> void set(String key, T value, Class<T> type) {
this.data.put(key, value);
}
<T> void set(String key, T value, Class<T> type);
/**
* Retrieve a value based on its key
*
* @param key the key
* @param <T> the value type
* @return the data associated with the key
* @throws NullPointerException if the key is not found
* @return the data associated with the key or null
*/
public <T> T get(String key) {
return (T) data.get(key);
}
<T> T get(String key);
/**
* Retrieve a value based on its key, give a default value if not found
@ -61,9 +67,7 @@ public class Data {
* @param <T> the value type
* @return {@link #get(String)} if found, {@code defaultValue} otherwise
*/
public <T> T getOrDefault(String key, T defaultValue) {
return (T) data.getOrDefault(key, defaultValue);
}
<T> T getOrDefault(String key, T defaultValue);
/**
* Get if the data has a key
@ -71,37 +75,27 @@ public class Data {
* @param key the key to check
* @return true if the data contains the key
*/
public boolean hasKey(String key) {
return data.containsKey(key);
}
boolean hasKey(String key);
/**
* Get the list of data keys
*
* @return an unmodifiable set containing all keys
*/
public Set<String> getKeys() {
return Collections.unmodifiableSet(data.keySet());
}
Set<String> getKeys();
/**
* Get if the data is empty or not
*
* @return true if the data does not contain anything, false otherwise
*/
public boolean isEmpty() {
return data.isEmpty();
}
boolean isEmpty();
/**
* Clone this data
*
* @return a cloned data object
*/
public Data clone() {
Data data = new Data();
data.data.putAll(this.data);
return data;
}
Data clone();
}

View File

@ -0,0 +1,48 @@
package net.minestom.server.data;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
public class DataImpl implements Data {
protected final ConcurrentHashMap<String, Object> data = new ConcurrentHashMap<>();
@Override
public <T> void set(String key, T value, Class<T> type) {
this.data.put(key, value);
}
@Override
public <T> T get(String key) {
return (T) data.get(key);
}
@Override
public <T> T getOrDefault(String key, T defaultValue) {
return (T) data.getOrDefault(key, defaultValue);
}
@Override
public boolean hasKey(String key) {
return data.containsKey(key);
}
@Override
public Set<String> getKeys() {
return Collections.unmodifiableSet(data.keySet());
}
@Override
public boolean isEmpty() {
return data.isEmpty();
}
@Override
public Data clone() {
DataImpl data = new DataImpl();
data.data.putAll(this.data);
return data;
}
}

View File

@ -1,52 +1,14 @@
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 interface SerializableData extends Data {
public class SerializableData extends Data {
private static final DataManager DATA_MANAGER = MinecraftServer.getDataManager();
private ConcurrentHashMap<String, Class> dataType = new ConcurrentHashMap<>();
/**
* Set a value to a specific key
* <p>
* WARNING: the type needs to be registered in {@link DataManager}
*
* @param key the key
* @param value the value object
* @param type the value type
* @param <T> the value generic
* @throws UnsupportedOperationException if {@code type} is not registered in {@link DataManager}
*/
@Override
public <T> void set(String key, T value, Class<T> type) {
if (DATA_MANAGER.getDataType(type) == null) {
throw new UnsupportedOperationException("Type " + type.getName() + " hasn't been registered in DataManager#registerType");
}
super.set(key, value, type);
this.dataType.put(key, type);
}
@Override
public Data clone() {
SerializableData data = new SerializableData();
data.data.putAll(this.data);
data.dataType.putAll(this.dataType);
return data;
}
DataManager DATA_MANAGER = MinecraftServer.getDataManager();
/**
* Serialize the data into an array of bytes
@ -60,61 +22,7 @@ public class SerializableData extends Data {
* @param indexed true to add the types index in the header
* @return the array representation of this data object
*/
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();
final Class type = dataType.get(key);
final short typeIndex;
{
// Find the type name
final String encodedType = PrimitiveConversion.getObjectClassString(type.getName()); // Data type (fix for primitives)
// 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.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();
}
byte[] getSerializedData(Object2ShortMap<String> typeToIndexMap, boolean indexed);
/**
* Serialize the data into an array of bytes
@ -126,9 +34,7 @@ public class SerializableData extends Data {
*
* @return the array representation of this data object
*/
public byte[] getIndexedSerializedData() {
return getSerializedData(new Object2ShortOpenHashMap<>(), true);
}
byte[] getIndexedSerializedData();
/**
* Get the index info (class name -> class index)
@ -137,7 +43,7 @@ public class SerializableData extends Data {
*
* @param typeToIndexMap the data index map
*/
public static void writeDataIndexHeader(BinaryWriter indexWriter, Object2ShortMap<String> typeToIndexMap) {
static void writeDataIndexHeader(BinaryWriter indexWriter, Object2ShortMap<String> typeToIndexMap) {
// Write the size of the following index list (class name-> class index)
indexWriter.writeVarInt(typeToIndexMap.size());

View File

@ -0,0 +1,108 @@
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.utils.PrimitiveConversion;
import net.minestom.server.utils.binary.BinaryWriter;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class SerializableDataImpl extends DataImpl implements SerializableData {
private ConcurrentHashMap<String, Class> dataType = new ConcurrentHashMap<>();
/**
* Set a value to a specific key
* <p>
* WARNING: the type needs to be registered in {@link DataManager}
*
* @param key the key
* @param value the value object
* @param type the value type
* @param <T> the value generic
* @throws UnsupportedOperationException if {@code type} is not registered in {@link DataManager}
*/
@Override
public <T> void set(String key, T value, Class<T> type) {
if (DATA_MANAGER.getDataType(type) == null) {
throw new UnsupportedOperationException("Type " + type.getName() + " hasn't been registered in DataManager#registerType");
}
super.set(key, value, type);
this.dataType.put(key, type);
}
@Override
public Data clone() {
SerializableDataImpl data = new SerializableDataImpl();
data.data.putAll(this.data);
data.dataType.putAll(this.dataType);
return data;
}
@Override
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();
final Class type = dataType.get(key);
final short typeIndex;
{
// Find the type name
final String encodedType = PrimitiveConversion.getObjectClassString(type.getName()); // Data type (fix for primitives)
// 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.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();
SerializableData.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();
}
@Override
public byte[] getIndexedSerializedData() {
return getSerializedData(new Object2ShortOpenHashMap<>(), true);
}
}

View File

@ -7,6 +7,7 @@ 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.data.SerializableDataImpl;
import net.minestom.server.utils.binary.BinaryReader;
import java.util.concurrent.ConcurrentHashMap;
@ -43,7 +44,7 @@ public class DataReader {
}
}
SerializableData data = new SerializableData();
SerializableData data = new SerializableDataImpl();
while (true) {
// Get the class index
final short typeIndex = reader.readShort();

View File

@ -5,6 +5,7 @@ import net.minestom.server.data.DataContainer;
import net.minestom.server.data.DataManager;
import net.minestom.server.data.DataType;
import net.minestom.server.data.SerializableData;
import net.minestom.server.data.SerializableDataImpl;
import net.minestom.server.reader.DataReader;
import net.minestom.server.utils.binary.BinaryReader;
import net.minestom.server.utils.binary.BinaryWriter;
@ -152,7 +153,7 @@ public class StorageLocation {
if (bytes != null) {
data = DataReader.readIndexedData(new BinaryReader(bytes));
} else {
data = new SerializableData();
data = new SerializableDataImpl();
}
dataContainer.setData(data);
@ -183,7 +184,7 @@ public class StorageLocation {
if (bytes != null) {
data = DataReader.readIndexedData(new BinaryReader(bytes));
} else {
data = new SerializableData();
data = new SerializableDataImpl();
}
dataContainer.setData(data);

View File

@ -1,6 +1,7 @@
package loottables;
import net.minestom.server.data.Data;
import net.minestom.server.data.DataImpl;
import net.minestom.server.gamedata.conditions.SurvivesExplosionCondition;
import net.minestom.server.gamedata.loottables.LootTable;
import net.minestom.server.gamedata.loottables.LootTableManager;
@ -94,7 +95,7 @@ public class TestLootTables {
@Test
public void simpleGenerate() throws FileNotFoundException {
LootTable lootTable = tableManager.load(NamespaceID.from("blocks/acacia_button"));
Data arguments = new Data();
Data arguments = new DataImpl();
List<ItemStack> stacks = lootTable.generate(arguments);
Assertions.assertEquals(1, stacks.size());
Assertions.assertEquals(Material.ACACIA_BUTTON, stacks.get(0).getMaterial());
@ -103,7 +104,7 @@ public class TestLootTables {
@Test
public void testExplosion() throws FileNotFoundException {
LootTable lootTable = tableManager.load(NamespaceID.from("blocks/acacia_button"));
Data arguments = new Data();
Data arguments = new DataImpl();
// negative value will force the condition to fail
arguments.set("explosionPower", -1.0, Double.class);
List<ItemStack> stacks = lootTable.generate(arguments);