Optimize block parsing

This commit is contained in:
themode 2022-01-31 21:03:28 +01:00
parent 4512cef7d9
commit 6a304969d4
4 changed files with 63 additions and 66 deletions

View File

@ -7,7 +7,7 @@ adventure = "4.9.3"
kotlin = "1.6.10"
hydrazine = "1.7.2"
dependencyGetter = "v1.0.1"
minestomData = "eadcb99e14"
minestomData = "9fa3da7a24"
hephaistos = "2.4.2"
jetbrainsAnnotations = "23.0.0"

View File

@ -4,6 +4,8 @@ import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap;
import it.unimi.dsi.fastutil.objects.Object2ObjectMaps;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import it.unimi.dsi.fastutil.objects.ObjectLists;
import net.minestom.server.registry.Registry;
import net.minestom.server.tag.Tag;
import net.minestom.server.utils.ArrayUtils;
@ -18,6 +20,7 @@ import org.jglrxavpok.hephaistos.nbt.mutable.MutableNBTCompound;
import java.time.Duration;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.Function;
record BlockImpl(@NotNull Registry.BlockEntry registry,
@ -31,7 +34,7 @@ record BlockImpl(@NotNull Registry.BlockEntry registry,
// Block id -> [property key -> values]
private static final ObjectArray<String[][]> PROPERTIES_VALUES = new ObjectArray<>();
// Block id -> Map<PropertiesValues, Block>
private static final ObjectArray<Map<PropertiesHolder, BlockImpl>> POSSIBLE_STATES = new ObjectArray<>();
private static final ObjectArray<BlockImpl[]> POSSIBLE_STATES = new ObjectArray<>();
private static final Registry.Container<Block> CONTAINER = Registry.createContainer(Registry.Resource.BLOCKS,
(namespace, properties) -> {
final int blockId = properties.getInt("id");
@ -60,38 +63,22 @@ record BlockImpl(@NotNull Registry.BlockEntry registry,
PROPERTIES_VALUES.set(blockId, values);
// Retrieve block states
{
final int propertiesCount = stateObject.size();
PropertiesHolder[] propertiesKeys = new PropertiesHolder[propertiesCount];
BlockImpl[] blocksValues = new BlockImpl[propertiesCount];
int propertiesOffset = 0;
for (var stateEntry : stateObject) {
final String query = stateEntry.getKey();
final var stateOverride = (Map<String, Object>) stateEntry.getValue();
final var propertyMap = BlockUtils.parseProperties(query);
assert keys.length == propertyMap.size();
byte[] propertiesArray = new byte[keys.length];
for (var entry : propertyMap.entrySet()) {
final int keyIndex = ArrayUtils.indexOf(keys, entry.getKey());
if (keyIndex == -1) {
throw new IllegalArgumentException("Unknown property key: " + entry.getKey());
}
final byte valueIndex = (byte) ArrayUtils.indexOf(values[keyIndex], entry.getValue());
if (valueIndex == -1) {
throw new IllegalArgumentException("Unknown property value: " + entry.getValue());
}
propertiesArray[keyIndex] = valueIndex;
}
String[][] finalValues = values;
int propertiesCount = 1;
for (var v : values) propertiesCount *= v.length;
BlockImpl[] possibleBlocks = new BlockImpl[propertiesCount];
final int minStateId = properties.getInt("minStateId");
forStates(finalValues, (propertiesArray, index) -> {
// TODO registry override
final int stateID = minStateId + index;
final BlockImpl block = new BlockImpl(Registry.block(namespace, stateID, properties),
propertiesArray, null, null);
BLOCK_STATE_MAP.set(stateID, block);
possibleBlocks[index] = block;
});
POSSIBLE_STATES.set(blockId, possibleBlocks);
var mainProperties = Registry.Properties.fromMap(new MergedMap<>(properties.asMap(), stateOverride));
final BlockImpl block = new BlockImpl(Registry.block(namespace, mainProperties),
propertiesArray, null, null);
BLOCK_STATE_MAP.set(block.stateId(), block);
propertiesKeys[propertiesOffset] = new PropertiesHolder(propertiesArray);
blocksValues[propertiesOffset++] = block;
}
POSSIBLE_STATES.set(blockId, ArrayUtils.toMap(propertiesKeys, blocksValues, propertiesOffset));
}
// Register default state
final int defaultState = properties.getInt("defaultStateId");
return getState(defaultState);
@ -144,7 +131,7 @@ record BlockImpl(@NotNull Registry.BlockEntry registry,
}
var properties = this.propertiesArray.clone();
properties[keyIndex] = valueIndex;
return compute(properties);
return compute(properties, values);
}
@Override
@ -166,7 +153,7 @@ record BlockImpl(@NotNull Registry.BlockEntry registry,
}
result[keyIndex] = valueIndex;
}
return compute(result);
return compute(result, values);
}
@Override
@ -197,7 +184,7 @@ record BlockImpl(@NotNull Registry.BlockEntry registry,
@Override
public @NotNull Collection<@NotNull Block> possibleStates() {
return Collection.class.cast(possibleProperties().values());
return ObjectLists.unmodifiable(ObjectArrayList.wrap(possibleProperties()));
}
@Override
@ -205,7 +192,7 @@ record BlockImpl(@NotNull Registry.BlockEntry registry,
return tag.read(Objects.requireNonNullElse(nbt, NBTCompound.EMPTY));
}
private Map<PropertiesHolder, BlockImpl> possibleProperties() {
private BlockImpl[] possibleProperties() {
return POSSIBLE_STATES.get(id());
}
@ -226,33 +213,42 @@ record BlockImpl(@NotNull Registry.BlockEntry registry,
return Objects.hash(stateId(), nbt, handler);
}
private Block compute(byte[] properties) {
private Block compute(byte[] properties, String[][] values) {
if (Arrays.equals(propertiesArray, properties)) return this;
BlockImpl block = possibleProperties().get(new PropertiesHolder(properties));
if (block == null)
throw new IllegalArgumentException("Invalid properties: " + Arrays.toString(properties) + " for block " + this);
int propertiesCount = 1;
int id = 0;
for (int i = properties.length - 1; i >= 0; i--) {
id += properties[i] * propertiesCount;
propertiesCount *= values[i].length;
}
BlockImpl block = possibleProperties()[id];
assert Arrays.equals(properties, block.propertiesArray) :
"""
Invalid properties compute
Expected: %s
Actual: %s
""".formatted(Arrays.toString(properties), Arrays.toString(block.propertiesArray));
return nbt == null && handler == null ? block : new BlockImpl(block.registry(), block.propertiesArray, nbt, handler);
}
private static final class PropertiesHolder {
private final byte[] properties;
private final int hashCode;
private static <T> void forStates(T[][] sets, BiConsumer<byte[], Integer> consumer) {
int count = 0;
while (true) {
int tmp = count;
byte[] value = new byte[sets.length];
for (int i = value.length - 1; i >= 0; i--) {
final T[] set = sets[i];
final int radix = set.length;
final int index = tmp % radix;
public PropertiesHolder(byte[] properties) {
this.properties = properties;
this.hashCode = Arrays.hashCode(properties);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof PropertiesHolder that)) return false;
return Arrays.equals(properties, that.properties);
}
@Override
public int hashCode() {
return hashCode;
value[i] = (byte) index;
tmp /= radix;
}
if (tmp != 0) {
// Overflow.
break;
}
consumer.accept(value, count++);
}
}
}

View File

@ -2,6 +2,7 @@ package net.minestom.server.registry;
import com.google.gson.ToNumberPolicy;
import com.google.gson.stream.JsonReader;
import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap;
import net.minestom.server.MinecraftServer;
import net.minestom.server.entity.EntitySpawnType;
import net.minestom.server.entity.EquipmentSlot;
@ -26,8 +27,8 @@ import java.util.function.Supplier;
*/
public final class Registry {
@ApiStatus.Internal
public static BlockEntry block(String namespace, @NotNull Properties main) {
return new BlockEntry(namespace, main, null);
public static BlockEntry block(String namespace, int stateId, @NotNull Properties main) {
return new BlockEntry(namespace, stateId, main);
}
@ApiStatus.Internal
@ -166,11 +167,11 @@ public final class Registry {
private final Supplier<Material> materialSupplier;
private final Properties custom;
private BlockEntry(String namespace, Properties main, Properties custom) {
private BlockEntry(String namespace, int stateId, Properties main, Properties custom) {
this.custom = custom;
this.namespace = NamespaceID.from(namespace);
this.id = main.getInt("id");
this.stateId = main.getInt("stateId");
this.stateId = stateId;
this.translationKey = main.getString("translationKey");
this.hardness = main.getDouble("hardness");
this.explosionResistance = main.getDouble("explosionResistance");
@ -418,7 +419,7 @@ public final class Registry {
yield list;
}
case BEGIN_OBJECT -> {
Map<String, Object> map = new HashMap<>();
Object2ObjectArrayMap<String, Object> map = new Object2ObjectArrayMap<>();
reader.beginObject();
while (reader.hasNext()) map.put(reader.nextName(), readObject(reader));
reader.endObject();

View File

@ -35,13 +35,13 @@ public class BlockTest {
@Test
public void testProperty() {
Block block = Block.CHEST;
assertEquals(block.properties(), Objects.requireNonNull(Block.fromBlockId(block.id())).properties());
// Default state may change, but the test is required to ensure the `properties` method is working
assertEquals(Map.of("facing", "north",
"type", "single",
"waterlogged", "false"), block.properties());
assertEquals(block.properties(), Objects.requireNonNull(Block.fromBlockId(block.id())).properties());
for (var possible : block.possibleStates()) {
assertEquals(possible, block.withProperties(possible.properties()));
}