Rework Block implementation (BlockTest)

This commit is contained in:
TheMode 2021-06-14 13:31:14 +02:00
parent adba6c3d40
commit 9c77ab267e
5 changed files with 240 additions and 123 deletions

View File

@ -22,32 +22,56 @@ import java.util.function.BiPredicate;
*/
public interface Block extends ProtocolObject, TagReadable, BlockConstants {
<T> @NotNull Block withProperty(@NotNull BlockProperty<T> property, @NotNull T value);
@NotNull Block withProperty(@NotNull String property, @NotNull String value);
default <T> @NotNull Block withProperty(@NotNull BlockProperty<T> property, @NotNull T value) {
return withProperty(property.getName(), value.toString());
}
<T> @NotNull Block withTag(@NotNull Tag<T> tag, @Nullable T value);
@NotNull Block withNbt(@Nullable NBTCompound compound);
@NotNull Block withHandler(@Nullable BlockHandler handler);
<T> @NotNull T getProperty(@NotNull BlockProperty<T> property);
@NotNull String getProperty(@NotNull String property);
default <T> @NotNull String getProperty(@NotNull BlockProperty<T> property) {
return getProperty(property.getName());
}
@Nullable NBTCompound getNbt();
@Nullable BlockHandler getHandler();
@NotNull Block getDefaultBlock();
@NotNull Map<String, String> createPropertiesMap();
short getStateId();
@NotNull Registry.BlockEntry registry();
default @NotNull Registry.BlockEntry registry() {
return Registry.block(this);
@Override
default @NotNull NamespaceID getNamespaceId() {
return NamespaceID.from(registry().namespace());
}
@Override
default int getId() {
return registry().id();
}
default short getStateId() {
return (short) registry().stateId();
}
default boolean isAir() {
return registry().isAir();
}
default boolean isSolid() {
return registry().isSolid();
}
default boolean isLiquid() {
return registry().isLiquid();
}
default boolean compare(@NotNull Block block, @NotNull Comparator comparator) {
@ -80,19 +104,6 @@ public interface Block extends ProtocolObject, TagReadable, BlockConstants {
BlockRegistry.register(namespaceID, block, range, blockSupplier);
}
default boolean isAir() {
return registry().isAir();
}
default boolean isSolid() {
return registry().isSolid();
}
default boolean isLiquid() {
return registry().isLiquid();
}
@FunctionalInterface
interface Comparator extends BiPredicate<Block, Block> {
Comparator IDENTITY = (b1, b2) -> b1 == b2;

View File

@ -1,5 +1,6 @@
package net.minestom.server.instance.block;
import net.minestom.server.registry.Registry;
import net.minestom.server.tag.Tag;
import net.minestom.server.utils.NamespaceID;
import net.minestom.server.utils.math.IntRange;
@ -9,6 +10,7 @@ import org.jglrxavpok.hephaistos.nbt.NBTCompound;
import java.util.*;
@Deprecated
class BlockImpl implements Block {
private NamespaceID namespaceID;
@ -45,36 +47,6 @@ class BlockImpl implements Block {
this(namespaceID, blockId, minStateId, stateId, properties, propertiesMap, null);
}
@Override
public @NotNull <T> Block withProperty(@NotNull BlockProperty<T> property, @NotNull T value) {
if (properties.isEmpty()) {
// This block doesn't have any state
return this;
}
final int index = properties.indexOf(property);
if (index == -1) {
// Invalid state
return this;
}
// Find properties map
LinkedHashMap<BlockProperty<?>, Object> map;
if (propertiesMap == null) {
// Represents the first id, create a new map
map = new LinkedHashMap<>();
properties.forEach(prop -> map.put(prop, prop.equals(property) ? value : null));
} else {
// Change property
map = (LinkedHashMap<BlockProperty<?>, Object>) propertiesMap.clone();
map.put(property, value);
}
var block = shallowClone();
block.stateId = computeId(minStateId, properties, map);
block.propertiesMap = map;
return block;
}
@Override
public @NotNull Block withProperty(@NotNull String property, @NotNull String value) {
// TODO
@ -121,11 +93,6 @@ class BlockImpl implements Block {
return block;
}
@Override
public <T> @NotNull T getProperty(@NotNull BlockProperty<T> property) {
return (T) propertiesMap.get(property);
}
@Override
public @NotNull String getProperty(@NotNull String property) {
// TODO
@ -137,11 +104,6 @@ class BlockImpl implements Block {
return compound != null ? compound.deepClone() : null;
}
@Override
public @NotNull Block getDefaultBlock() {
return original;
}
@Override
public @NotNull NamespaceID getNamespaceId() {
return namespaceID;
@ -155,13 +117,8 @@ class BlockImpl implements Block {
}
@Override
public int getId() {
return blockId;
}
@Override
public short getStateId() {
return stateId;
public @NotNull Registry.BlockEntry registry() {
return BlockRegistry.getState(stateId).registry(); // TODO
}
@Override

View File

@ -23,20 +23,35 @@ class BlockRegistry {
// Property name -> values
private static final Map<String, List<String>> PROPERTIES_MAP = new ConcurrentHashMap<>();
// Unique name -> IG key
private static final Map<String, String> PROPERTIES_NAME_MAP = new ConcurrentHashMap<>();
// Block namespace -> registry data
private static final Map<String, JsonObject> BLOCK_MAP = new ConcurrentHashMap<>();
private static final Map<String, Block> NAMESPACE_MAP = new ConcurrentHashMap<>();
// Block id -> registry data
private static final Map<Integer, Block> BLOCK_ID_MAP = new ConcurrentHashMap<>();
// Block state -> block object
private static final Map<Integer, Block> BLOCK_STATE_MAP = new ConcurrentHashMap<>();
// Block namespace -> properties map to block access
private static final Map<String, PropertyEntry> BLOCK_PROPERTY_MAP = new ConcurrentHashMap<>();
private static final Map<NamespaceID, Block> namespaceMap = new HashMap<>();
private static final Int2ObjectSortedMap<Block> blockSet = new Int2ObjectAVLTreeMap<>();
private static final Short2ObjectSortedMap<Block.Supplier> stateSet = new Short2ObjectAVLTreeMap<>();
private static class PropertyEntry {
private final Map<Map<String, String>, Block> propertyMap = new ConcurrentHashMap<>();
}
static {
// Load data from file
// Block properties
JsonObject properties = Registry.load(Registry.Resource.BLOCK_PROPERTY);
properties.keySet().forEach(propertyName -> {
final JsonObject propertyObject = properties.getAsJsonObject(propertyName);
properties.entrySet().forEach(entry -> {
final String propertyName = entry.getKey();
final JsonObject propertyObject = entry.getValue().getAsJsonObject();
final String key = propertyObject.get("key").getAsString();
final JsonArray values = propertyObject.getAsJsonArray("values");
@ -44,23 +59,85 @@ class BlockRegistry {
values.forEach(jsonElement -> stringValues.add(jsonElement.toString()));
PROPERTIES_MAP.put(key, stringValues);
PROPERTIES_NAME_MAP.put(propertyName, key);
});
// Blocks
JsonObject blocks = Registry.load(Registry.Resource.BLOCK);
blocks.keySet().forEach(blockNamespace -> {
final JsonObject blockObject = properties.getAsJsonObject(blockNamespace);
BLOCK_MAP.put(blockNamespace, blockObject);
final JsonObject propertiesObject = blockObject.getAsJsonObject("properties");
final JsonArray statesObject = blockObject.getAsJsonArray("states");
{
// To do not be cloned over and over
blockObject.remove("properties");
blockObject.remove("states");
}
blocks.entrySet().forEach(entry -> {
final String blockNamespace = entry.getKey();
final JsonObject blockObject = entry.getValue().getAsJsonObject();
retrieveState(blockNamespace, blockObject);
final int defaultState = blockObject.get("defaultStateId").getAsInt();
final Block defaultBlock = getState(defaultState);
final int id = blockObject.get("id").getAsInt();
BLOCK_ID_MAP.put(id, defaultBlock);
NAMESPACE_MAP.put(blockNamespace, defaultBlock);
});
}
private static void retrieveState(String namespace, JsonObject object) {
final JsonObject states = object.getAsJsonObject("states");
PropertyEntry propertyEntry = new PropertyEntry();
states.entrySet().forEach(stateEntry -> {
final String query = stateEntry.getKey();
JsonObject stateObject = object.deepCopy();
stateObject.remove("states");
stateObject.remove("properties");
JsonObject stateOverride = stateEntry.getValue().getAsJsonObject();
stateOverride.entrySet().forEach(entry -> stateObject.add(entry.getKey(), entry.getValue()));
final int stateId = stateOverride.get("stateId").getAsInt();
final var propertyMap = getPropertyMap(query);
final Block block = new BlockTest(stateObject);
BLOCK_STATE_MAP.put(stateId, block);
propertyEntry.propertyMap.put(propertyMap, block);
});
BLOCK_PROPERTY_MAP.put(namespace, propertyEntry);
}
private static Map<String, String> getPropertyMap(String query) {
Map<String, String> result = new HashMap<>();
final String propertiesString = query.substring(1, query.length() - 1);
StringBuilder keyBuilder = new StringBuilder();
StringBuilder valueBuilder = new StringBuilder();
StringBuilder builder = keyBuilder;
for (int i = 0; i < propertiesString.length(); i++) {
final char c = propertiesString.charAt(i);
if (c == '=') {
// Switch to value builder
builder = valueBuilder;
} else if (c == ',') {
// Append current text
result.put(keyBuilder.toString(), valueBuilder.toString());
keyBuilder = new StringBuilder();
valueBuilder = new StringBuilder();
builder = keyBuilder;
} else if (c != ' ') {
builder.append(c);
}
}
return result;
}
public static synchronized @Nullable Block get(@NotNull String namespace) {
return NAMESPACE_MAP.get(namespace);
}
public static @Nullable Block getState(int stateId) {
return BLOCK_STATE_MAP.get(stateId);
}
public static @Nullable Block getProperties(String namespace, Map<String, String> properties) {
final var entry = BLOCK_PROPERTY_MAP.get(namespace);
return entry.propertyMap.get(properties);
}
public static synchronized @Nullable Block fromNamespaceId(@NotNull NamespaceID namespaceID) {
return namespaceMap.get(namespaceID);
}

View File

@ -0,0 +1,97 @@
package net.minestom.server.instance.block;
import com.google.gson.JsonObject;
import net.minestom.server.registry.Registry;
import net.minestom.server.tag.Tag;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jglrxavpok.hephaistos.nbt.NBTCompound;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
class BlockTest implements Block {
private final Registry.BlockEntry registry;
private final Map<String, String> properties = new HashMap<>();
private NBTCompound compound;
private BlockHandler handler;
BlockTest(Registry.BlockEntry registry) {
this.registry = registry;
}
BlockTest(JsonObject jsonObject) {
this(Registry.block(jsonObject));
}
@Override
public @NotNull Block withProperty(@NotNull String property, @NotNull String value) {
var properties = new HashMap<>(this.properties);
properties.put(property, value);
return Objects.requireNonNull(BlockRegistry.getProperties(getNamespaceId().asString(), properties));
}
@Override
public @NotNull <T> Block withTag(@NotNull Tag<T> tag, @Nullable T value) {
var clone = shallowClone();
clone.compound = Objects.requireNonNullElseGet(clone.compound, NBTCompound::new);
tag.write(clone.compound, value);
return clone;
}
@Override
public @NotNull Block withNbt(@Nullable NBTCompound compound) {
var clone = shallowClone();
clone.compound = compound;
return clone;
}
@Override
public @NotNull Block withHandler(@Nullable BlockHandler handler) {
var clone = shallowClone();
clone.handler = handler;
return clone;
}
@Override
public @NotNull String getProperty(@NotNull String property) {
return properties.get(property);
}
@Override
public @Nullable NBTCompound getNbt() {
return compound.deepClone();
}
@Override
public @Nullable BlockHandler getHandler() {
return handler;
}
@Override
public @NotNull Map<String, String> createPropertiesMap() {
return new HashMap<>(properties);
}
@Override
public @NotNull Registry.BlockEntry registry() {
return registry;
}
@Override
public <T> @Nullable T getTag(@NotNull Tag<T> tag) {
return tag.read(compound);
}
@Override
public boolean hasTag(@NotNull Tag<?> tag) {
return compound.containsKey(tag.getKey());
}
private @NotNull BlockTest shallowClone() {
return new BlockTest(registry);
}
}

View File

@ -4,32 +4,22 @@ import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import net.minestom.server.MinecraftServer;
import net.minestom.server.instance.block.Block;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import java.io.InputStreamReader;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
@ApiStatus.Internal
public class Registry {
private static final Loader LOADER = new Loader();
protected static final Gson GSON = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create();
public static BlockEntry block(@NotNull Block block) {
return loader().block(block.getName());
public static BlockEntry block(@NotNull JsonObject jsonObject) {
return new BlockEntry(jsonObject);
}
@ApiStatus.Internal
public static @NotNull Loader loader() {
return LOADER;
}
@ApiStatus.Internal
public static JsonObject load(Resource resource) {
final String path = String.format("/%s_%s.json", MinecraftServer.VERSION_NAME_UNDERSCORED, resource.name);
final String path = String.format("/%s.json", resource.name);
final var resourceStream = Registry.class.getResourceAsStream(path);
return GSON.fromJson(new InputStreamReader(resourceStream), JsonObject.class);
}
@ -50,6 +40,18 @@ public class Registry {
super(json);
}
public String namespace() {
return getString("namespace");
}
public int id() {
return getInt("id");
}
public int stateId() {
return getInt("stateId");
}
public float destroySpeed() {
return getFloat("destroySpeed");
}
@ -110,31 +112,4 @@ public class Registry {
return json.get(name);
}
}
public static class Loader {
private final RegistryMap<BlockEntry> blockRegistry = new RegistryMap<>(BlockEntry::new);
public void loadBlocks(@NotNull JsonObject blocks) {
loadRegistry(blockRegistry, blocks);
}
public BlockEntry block(String name) {
return blockRegistry.get(name);
}
private <T extends Entry> void loadRegistry(RegistryMap<T> map, JsonObject data) {
data.keySet().forEach(namespace -> {
final JsonObject value = data.get(namespace).getAsJsonObject();
map.put(namespace, map.function.apply(value));
});
}
}
private static class RegistryMap<T extends Entry> extends ConcurrentHashMap<String, T> {
private final Function<JsonObject, T> function;
private RegistryMap(Function<JsonObject, T> function) {
this.function = function;
}
}
}