mirror of
https://github.com/Minestom/Minestom.git
synced 2024-12-26 02:57:37 +01:00
Improve block properties performance
Signed-off-by: TheMode <themode@outlook.fr>
This commit is contained in:
parent
2d305cb0f0
commit
30cf6e8e4f
@ -7,7 +7,7 @@ adventure = "4.9.3"
|
||||
kotlin = "1.6.10"
|
||||
hydrazine = "1.7.2"
|
||||
dependencyGetter = "v1.0.1"
|
||||
minestomData = "801b8007cf"
|
||||
minestomData = "c7caadddbf"
|
||||
hephaistos = "2.4.1"
|
||||
jetbrainsAnnotations = "23.0.0"
|
||||
|
||||
|
@ -2,6 +2,8 @@ package net.minestom.server.instance.block;
|
||||
|
||||
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 net.minestom.server.registry.Registry;
|
||||
import net.minestom.server.tag.Tag;
|
||||
import net.minestom.server.utils.ArrayUtils;
|
||||
@ -9,46 +11,71 @@ import net.minestom.server.utils.ObjectArray;
|
||||
import net.minestom.server.utils.block.BlockUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.Unmodifiable;
|
||||
import org.jglrxavpok.hephaistos.nbt.NBTCompound;
|
||||
import org.jglrxavpok.hephaistos.nbt.mutable.MutableNBTCompound;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
|
||||
record BlockImpl(@NotNull Registry.BlockEntry registry,
|
||||
@NotNull Map<String, String> properties,
|
||||
@NotNull String[] propertiesArray,
|
||||
@Nullable NBTCompound nbt,
|
||||
@Nullable BlockHandler handler) implements Block {
|
||||
// Block state -> block object
|
||||
private static final ObjectArray<Block> BLOCK_STATE_MAP = new ObjectArray<>();
|
||||
// Block id -> Map<Properties, Block>
|
||||
private static final ObjectArray<Map<Map<String, String>, Block>> POSSIBLE_STATES = new ObjectArray<>();
|
||||
// Block id -> valid property keys (order is important for lookup)
|
||||
private static final ObjectArray<String[]> PROPERTIES_KEYS = new ObjectArray<>();
|
||||
// Block id -> Map<PropertiesValues, Block>
|
||||
private static final ObjectArray<Map<PropertiesHolder, BlockImpl>> POSSIBLE_STATES = new ObjectArray<>();
|
||||
private static final Registry.Container<Block> CONTAINER = Registry.createContainer(Registry.Resource.BLOCKS,
|
||||
(namespace, object) -> {
|
||||
final int blockId = ((Number) object.get("id")).intValue();
|
||||
final var stateObject = (Map<String, Object>) object.get("states");
|
||||
// Retrieve the block states
|
||||
|
||||
// Retrieve properties
|
||||
String[] possibleProperties = new String[0];
|
||||
{
|
||||
var properties = (Map<String, Object>) object.get("properties");
|
||||
if (properties != null) {
|
||||
possibleProperties = new String[properties.size()];
|
||||
int i = 0;
|
||||
for (var entry : properties.entrySet()) {
|
||||
possibleProperties[i++] = entry.getKey();
|
||||
}
|
||||
}
|
||||
}
|
||||
PROPERTIES_KEYS.set(blockId, possibleProperties);
|
||||
|
||||
// Retrieve block states
|
||||
{
|
||||
final var stateEntries = stateObject.entrySet();
|
||||
final int propertiesCount = stateEntries.size();
|
||||
Map<String, String>[] propertiesKeys = new Map[propertiesCount];
|
||||
Block[] blocksValues = new Block[propertiesCount];
|
||||
PropertiesHolder[] propertiesKeys = new PropertiesHolder[propertiesCount];
|
||||
BlockImpl[] blocksValues = new BlockImpl[propertiesCount];
|
||||
int propertiesOffset = 0;
|
||||
for (var stateEntry : stateEntries) {
|
||||
final String query = stateEntry.getKey();
|
||||
final var stateOverride = (Map<String, Object>) stateEntry.getValue();
|
||||
final var propertyMap = BlockUtils.parseProperties(query);
|
||||
final Block block = new BlockImpl(Registry.block(namespace, object, stateOverride),
|
||||
propertyMap, null, null);
|
||||
|
||||
String[] propertiesArray = new String[possibleProperties.length];
|
||||
int i = 0;
|
||||
for (var entry : propertyMap.entrySet()) {
|
||||
propertiesArray[i++] = entry.getValue();
|
||||
}
|
||||
|
||||
final BlockImpl block = new BlockImpl(Registry.block(namespace, object, stateOverride),
|
||||
propertiesArray, null, null);
|
||||
BLOCK_STATE_MAP.set(block.stateId(), block);
|
||||
propertiesKeys[propertiesOffset] = propertyMap;
|
||||
propertiesKeys[propertiesOffset] = new PropertiesHolder(propertiesArray);
|
||||
blocksValues[propertiesOffset++] = block;
|
||||
}
|
||||
POSSIBLE_STATES.set(((Number) object.get("id")).intValue(),
|
||||
ArrayUtils.toMap(propertiesKeys, blocksValues, propertiesOffset));
|
||||
POSSIBLE_STATES.set(blockId, ArrayUtils.toMap(propertiesKeys, blocksValues, propertiesOffset));
|
||||
}
|
||||
// Register default state
|
||||
final int defaultState = ((Number) object.get("defaultStateId")).intValue();
|
||||
@ -84,28 +111,32 @@ record BlockImpl(@NotNull Registry.BlockEntry registry,
|
||||
return CONTAINER.values();
|
||||
}
|
||||
|
||||
public BlockImpl {
|
||||
properties = Map.copyOf(properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Block withProperty(@NotNull String property, @NotNull String value) {
|
||||
var properties = new HashMap<>(this.properties);
|
||||
final String oldProperty = properties.replace(property, value);
|
||||
if (oldProperty == null)
|
||||
throw new IllegalArgumentException("Property " + property + " does not exist");
|
||||
final String[] keys = PROPERTIES_KEYS.get(id());
|
||||
final int index = ArrayUtils.indexOf(keys, property);
|
||||
if (index == -1) {
|
||||
throw new IllegalArgumentException("Property " + property + " is not valid for block " + this);
|
||||
}
|
||||
var properties = this.propertiesArray.clone();
|
||||
properties[index] = value;
|
||||
return compute(properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Block withProperties(@NotNull Map<@NotNull String, @NotNull String> properties) {
|
||||
if (properties.isEmpty()) return this;
|
||||
if (this.properties.size() == properties.size()) {
|
||||
return compute(properties); // Map should be complete
|
||||
final String[] keys = PROPERTIES_KEYS.get(id());
|
||||
assert keys != null;
|
||||
String[] result = this.propertiesArray.clone();
|
||||
for (var entry : properties.entrySet()) {
|
||||
final int index = ArrayUtils.indexOf(keys, entry.getKey());
|
||||
if (index == -1) {
|
||||
throw new IllegalArgumentException("Property " + entry.getKey() + " is not valid for block " + this);
|
||||
}
|
||||
result[index] = entry.getValue();
|
||||
}
|
||||
var newProperties = new HashMap<>(this.properties);
|
||||
newProperties.putAll(properties);
|
||||
return compute(newProperties);
|
||||
return compute(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -113,17 +144,25 @@ record BlockImpl(@NotNull Registry.BlockEntry registry,
|
||||
var temporaryNbt = new MutableNBTCompound(Objects.requireNonNullElse(nbt, NBTCompound.EMPTY));
|
||||
tag.write(temporaryNbt, value);
|
||||
final var finalNbt = temporaryNbt.getSize() > 0 ? NBT_CACHE.get(temporaryNbt.toCompound(), Function.identity()) : null;
|
||||
return new BlockImpl(registry, properties, finalNbt, handler);
|
||||
return new BlockImpl(registry, propertiesArray, finalNbt, handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Block withHandler(@Nullable BlockHandler handler) {
|
||||
return new BlockImpl(registry, properties, nbt, handler);
|
||||
return new BlockImpl(registry, propertiesArray, nbt, handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Unmodifiable @NotNull Map<String, String> properties() {
|
||||
final String[] keys = PROPERTIES_KEYS.get(id());
|
||||
assert keys != null;
|
||||
var map = new Object2ObjectArrayMap<>(keys, propertiesArray, keys.length);
|
||||
return Map.class.cast(Object2ObjectMaps.unmodifiable(map));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Collection<@NotNull Block> possibleStates() {
|
||||
return possibleProperties().values();
|
||||
return Collection.class.cast(possibleProperties().values());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -131,20 +170,54 @@ record BlockImpl(@NotNull Registry.BlockEntry registry,
|
||||
return tag.read(Objects.requireNonNullElse(nbt, NBTCompound.EMPTY));
|
||||
}
|
||||
|
||||
private Map<Map<String, String>, Block> possibleProperties() {
|
||||
private Map<PropertiesHolder, BlockImpl> possibleProperties() {
|
||||
return POSSIBLE_STATES.get(id());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("%s{properties=%s, nbt=%s, handler=%s}", name(), properties, nbt, handler);
|
||||
return String.format("%s{properties=%s, nbt=%s, handler=%s}", name(), properties(), nbt, handler);
|
||||
}
|
||||
|
||||
private Block compute(Map<String, String> properties) {
|
||||
if (this.properties.equals(properties)) return this;
|
||||
Block block = possibleProperties().get(properties);
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof BlockImpl block)) return false;
|
||||
return stateId() == block.stateId() && Objects.equals(nbt, block.nbt) && Objects.equals(handler, block.handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(stateId(), nbt, handler);
|
||||
}
|
||||
|
||||
private Block compute(String[] properties) {
|
||||
if (Arrays.equals(propertiesArray, properties)) return this;
|
||||
BlockImpl block = possibleProperties().get(new PropertiesHolder(properties));
|
||||
if (block == null)
|
||||
throw new IllegalArgumentException("Invalid properties: " + properties + " for block " + this);
|
||||
return nbt == null && handler == null ? block : new BlockImpl(block.registry(), block.properties(), nbt, handler);
|
||||
throw new IllegalArgumentException("Invalid properties: " + Arrays.toString(properties) + " for block " + this);
|
||||
return nbt == null && handler == null ? block : new BlockImpl(block.registry(), block.propertiesArray, nbt, handler);
|
||||
}
|
||||
|
||||
private static final class PropertiesHolder {
|
||||
private final String[] properties;
|
||||
private final int hashCode;
|
||||
|
||||
public PropertiesHolder(String[] 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -64,4 +64,33 @@ public final class ArrayUtils {
|
||||
list.getElements(0, array, 0, array.length);
|
||||
return array;
|
||||
}
|
||||
|
||||
private static final int INDEX_NOT_FOUND = -1;
|
||||
|
||||
public static int indexOf(final Object[] array, final Object objectToFind) {
|
||||
return indexOf(array, objectToFind, 0);
|
||||
}
|
||||
|
||||
public static int indexOf(final Object[] array, final Object objectToFind, int startIndex) {
|
||||
if (array == null) {
|
||||
return INDEX_NOT_FOUND;
|
||||
}
|
||||
if (startIndex < 0) {
|
||||
startIndex = 0;
|
||||
}
|
||||
if (objectToFind == null) {
|
||||
for (int i = startIndex; i < array.length; i++) {
|
||||
if (array[i] == null) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int i = startIndex; i < array.length; i++) {
|
||||
if (objectToFind.equals(array[i])) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
return INDEX_NOT_FOUND;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user