344 lines
15 KiB
Diff
344 lines
15 KiB
Diff
|
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||
|
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
|
||
|
Date: Thu, 11 Mar 2021 20:05:44 -0800
|
||
|
Subject: [PATCH] Custom table implementation for blockstate state lookups
|
||
|
|
||
|
Testing some redstone intensive machines showed to bring about a 10%
|
||
|
improvement.
|
||
|
|
||
|
diff --git a/src/main/java/io/papermc/paper/util/table/ZeroCollidingReferenceStateTable.java b/src/main/java/io/papermc/paper/util/table/ZeroCollidingReferenceStateTable.java
|
||
|
new file mode 100644
|
||
|
index 0000000000000000000000000000000000000000..57d0cd3ad6f972e986c72a57f1a6e36003f190c2
|
||
|
--- /dev/null
|
||
|
+++ b/src/main/java/io/papermc/paper/util/table/ZeroCollidingReferenceStateTable.java
|
||
|
@@ -0,0 +1,160 @@
|
||
|
+package io.papermc.paper.util.table;
|
||
|
+
|
||
|
+import com.google.common.collect.Table;
|
||
|
+import net.minecraft.world.level.block.state.StateHolder;
|
||
|
+import net.minecraft.world.level.block.state.properties.Property;
|
||
|
+import java.util.Collection;
|
||
|
+import java.util.HashSet;
|
||
|
+import java.util.Map;
|
||
|
+import java.util.Set;
|
||
|
+
|
||
|
+public final class ZeroCollidingReferenceStateTable {
|
||
|
+
|
||
|
+ // upper 32 bits: starting index
|
||
|
+ // lower 32 bits: bitset for contained ids
|
||
|
+ protected final long[] this_index_table;
|
||
|
+ protected final Comparable<?>[] this_table;
|
||
|
+ protected final StateHolder<?, ?> this_state;
|
||
|
+
|
||
|
+ protected long[] index_table;
|
||
|
+ protected StateHolder<?, ?>[][] value_table;
|
||
|
+
|
||
|
+ public ZeroCollidingReferenceStateTable(final StateHolder<?, ?> state, final Map<Property<?>, Comparable<?>> this_map) {
|
||
|
+ this.this_state = state;
|
||
|
+ this.this_index_table = this.create_table(this_map.keySet());
|
||
|
+
|
||
|
+ int max_id = -1;
|
||
|
+ for (final Property<?> property : this_map.keySet()) {
|
||
|
+ final int id = lookup_vindex(property, this.this_index_table);
|
||
|
+ if (id > max_id) {
|
||
|
+ max_id = id;
|
||
|
+ }
|
||
|
+ }
|
||
|
+
|
||
|
+ this.this_table = new Comparable[max_id + 1];
|
||
|
+ for (final Map.Entry<Property<?>, Comparable<?>> entry : this_map.entrySet()) {
|
||
|
+ this.this_table[lookup_vindex(entry.getKey(), this.this_index_table)] = entry.getValue();
|
||
|
+ }
|
||
|
+ }
|
||
|
+
|
||
|
+ public void loadInTable(final Table<Property<?>, Comparable<?>, StateHolder<?, ?>> table,
|
||
|
+ final Map<Property<?>, Comparable<?>> this_map) {
|
||
|
+ final Set<Property<?>> combined = new HashSet<>(table.rowKeySet());
|
||
|
+ combined.addAll(this_map.keySet());
|
||
|
+
|
||
|
+ this.index_table = this.create_table(combined);
|
||
|
+
|
||
|
+ int max_id = -1;
|
||
|
+ for (final Property<?> property : combined) {
|
||
|
+ final int id = lookup_vindex(property, this.index_table);
|
||
|
+ if (id > max_id) {
|
||
|
+ max_id = id;
|
||
|
+ }
|
||
|
+ }
|
||
|
+
|
||
|
+ this.value_table = new StateHolder[max_id + 1][];
|
||
|
+
|
||
|
+ final Map<Property<?>, Map<Comparable<?>, StateHolder<?, ?>>> map = table.rowMap();
|
||
|
+ for (final Property<?> property : map.keySet()) {
|
||
|
+ final Map<Comparable<?>, StateHolder<?, ?>> propertyMap = map.get(property);
|
||
|
+
|
||
|
+ final int id = lookup_vindex(property, this.index_table);
|
||
|
+ final StateHolder<?, ?>[] states = this.value_table[id] = new StateHolder[property.getPossibleValues().size()];
|
||
|
+
|
||
|
+ for (final Map.Entry<Comparable<?>, StateHolder<?, ?>> entry : propertyMap.entrySet()) {
|
||
|
+ if (entry.getValue() == null) {
|
||
|
+ // TODO what
|
||
|
+ continue;
|
||
|
+ }
|
||
|
+
|
||
|
+ states[((Property)property).getIdFor(entry.getKey())] = entry.getValue();
|
||
|
+ }
|
||
|
+ }
|
||
|
+
|
||
|
+
|
||
|
+ for (final Map.Entry<Property<?>, Comparable<?>> entry : this_map.entrySet()) {
|
||
|
+ final Property<?> property = entry.getKey();
|
||
|
+ final int index = lookup_vindex(property, this.index_table);
|
||
|
+
|
||
|
+ if (this.value_table[index] == null) {
|
||
|
+ this.value_table[index] = new StateHolder[property.getPossibleValues().size()];
|
||
|
+ }
|
||
|
+
|
||
|
+ this.value_table[index][((Property)property).getIdFor(entry.getValue())] = this.this_state;
|
||
|
+ }
|
||
|
+ }
|
||
|
+
|
||
|
+
|
||
|
+ protected long[] create_table(final Collection<Property<?>> collection) {
|
||
|
+ int max_id = -1;
|
||
|
+ for (final Property<?> property : collection) {
|
||
|
+ final int id = property.getId();
|
||
|
+ if (id > max_id) {
|
||
|
+ max_id = id;
|
||
|
+ }
|
||
|
+ }
|
||
|
+
|
||
|
+ final long[] ret = new long[((max_id + 1) + 31) >>> 5]; // ceil((max_id + 1) / 32)
|
||
|
+
|
||
|
+ for (final Property<?> property : collection) {
|
||
|
+ final int id = property.getId();
|
||
|
+
|
||
|
+ ret[id >>> 5] |= (1L << (id & 31));
|
||
|
+ }
|
||
|
+
|
||
|
+ int total = 0;
|
||
|
+ for (int i = 1, len = ret.length; i < len; ++i) {
|
||
|
+ ret[i] |= (long)(total += Long.bitCount(ret[i - 1] & 0xFFFFFFFFL)) << 32;
|
||
|
+ }
|
||
|
+
|
||
|
+ return ret;
|
||
|
+ }
|
||
|
+
|
||
|
+ public Comparable<?> get(final Property<?> state) {
|
||
|
+ final Comparable<?>[] table = this.this_table;
|
||
|
+ final int index = lookup_vindex(state, this.this_index_table);
|
||
|
+
|
||
|
+ if (index < 0 || index >= table.length) {
|
||
|
+ return null;
|
||
|
+ }
|
||
|
+ return table[index];
|
||
|
+ }
|
||
|
+
|
||
|
+ public StateHolder<?, ?> get(final Property<?> property, final Comparable<?> with) {
|
||
|
+ final int withId = ((Property)property).getIdFor(with);
|
||
|
+ if (withId < 0) {
|
||
|
+ return null;
|
||
|
+ }
|
||
|
+
|
||
|
+ final int index = lookup_vindex(property, this.index_table);
|
||
|
+ final StateHolder<?, ?>[][] table = this.value_table;
|
||
|
+ if (index < 0 || index >= table.length) {
|
||
|
+ return null;
|
||
|
+ }
|
||
|
+
|
||
|
+ final StateHolder<?, ?>[] values = table[index];
|
||
|
+
|
||
|
+ if (withId >= values.length) {
|
||
|
+ return null;
|
||
|
+ }
|
||
|
+
|
||
|
+ return values[withId];
|
||
|
+ }
|
||
|
+
|
||
|
+ protected static int lookup_vindex(final Property<?> property, final long[] index_table) {
|
||
|
+ final int id = property.getId();
|
||
|
+ final long bitset_mask = (1L << (id & 31));
|
||
|
+ final long lower_mask = bitset_mask - 1;
|
||
|
+ final int index = id >>> 5;
|
||
|
+ if (index >= index_table.length) {
|
||
|
+ return -1;
|
||
|
+ }
|
||
|
+ final long index_value = index_table[index];
|
||
|
+ final long contains_check = ((index_value & bitset_mask) - 1) >> (Long.SIZE - 1); // -1L if doesn't contain
|
||
|
+
|
||
|
+ // index = total bits set in lower table values (upper 32 bits of index_value) plus total bits set in lower indices below id
|
||
|
+ // contains_check is 0 if the bitset had id set, else it's -1: so index is unaffected if contains_check == 0,
|
||
|
+ // otherwise it comes out as -1.
|
||
|
+ return (int)(((index_value >>> 32) + Long.bitCount(index_value & lower_mask)) | contains_check);
|
||
|
+ }
|
||
|
+}
|
||
|
diff --git a/src/main/java/net/minecraft/world/level/block/state/StateHolder.java b/src/main/java/net/minecraft/world/level/block/state/StateHolder.java
|
||
|
index baf1cb77eb170a44d821eae572d059f18ea46d7e..11c05776c051ba6a7e8416fd8591babafda35a80 100644
|
||
|
--- a/src/main/java/net/minecraft/world/level/block/state/StateHolder.java
|
||
|
+++ b/src/main/java/net/minecraft/world/level/block/state/StateHolder.java
|
||
|
@@ -40,11 +40,13 @@ public abstract class StateHolder<O, S> {
|
||
|
private final ImmutableMap<Property<?>, Comparable<?>> values;
|
||
|
private Table<Property<?>, Comparable<?>, S> neighbours;
|
||
|
protected final MapCodec<S> propertiesCodec;
|
||
|
+ protected final io.papermc.paper.util.table.ZeroCollidingReferenceStateTable optimisedTable; // Paper - optimise state lookup
|
||
|
|
||
|
protected StateHolder(O owner, ImmutableMap<Property<?>, Comparable<?>> entries, MapCodec<S> codec) {
|
||
|
this.owner = owner;
|
||
|
this.values = entries;
|
||
|
this.propertiesCodec = codec;
|
||
|
+ this.optimisedTable = new io.papermc.paper.util.table.ZeroCollidingReferenceStateTable(this, entries); // Paper - optimise state lookup
|
||
|
}
|
||
|
|
||
|
public <T extends Comparable<T>> S cycle(Property<T> property) {
|
||
|
@@ -85,11 +87,11 @@ public abstract class StateHolder<O, S> {
|
||
|
}
|
||
|
|
||
|
public <T extends Comparable<T>> boolean hasProperty(Property<T> property) {
|
||
|
- return this.values.containsKey(property);
|
||
|
+ return this.optimisedTable.get(property) != null; // Paper - optimise state lookup
|
||
|
}
|
||
|
|
||
|
public <T extends Comparable<T>> T getValue(Property<T> property) {
|
||
|
- Comparable<?> comparable = this.values.get(property);
|
||
|
+ Comparable<?> comparable = this.optimisedTable.get(property); // Paper - optimise state lookup
|
||
|
if (comparable == null) {
|
||
|
throw new IllegalArgumentException("Cannot get property " + property + " as it does not exist in " + this.owner);
|
||
|
} else {
|
||
|
@@ -98,24 +100,18 @@ public abstract class StateHolder<O, S> {
|
||
|
}
|
||
|
|
||
|
public <T extends Comparable<T>> Optional<T> getOptionalValue(Property<T> property) {
|
||
|
- Comparable<?> comparable = this.values.get(property);
|
||
|
+ Comparable<?> comparable = this.optimisedTable.get(property); // Paper - optimise state lookup
|
||
|
return comparable == null ? Optional.empty() : Optional.of(property.getValueClass().cast(comparable));
|
||
|
}
|
||
|
|
||
|
public <T extends Comparable<T>, V extends T> S setValue(Property<T> property, V value) {
|
||
|
- Comparable<?> comparable = this.values.get(property);
|
||
|
- if (comparable == null) {
|
||
|
- throw new IllegalArgumentException("Cannot set property " + property + " as it does not exist in " + this.owner);
|
||
|
- } else if (comparable == value) {
|
||
|
- return (S)this;
|
||
|
- } else {
|
||
|
- S object = this.neighbours.get(property, value);
|
||
|
- if (object == null) {
|
||
|
- throw new IllegalArgumentException("Cannot set property " + property + " to " + value + " on " + this.owner + ", it is not an allowed value");
|
||
|
- } else {
|
||
|
- return object;
|
||
|
- }
|
||
|
+ // Paper start - optimise state lookup
|
||
|
+ final S ret = (S)this.optimisedTable.get(property, value);
|
||
|
+ if (ret == null) {
|
||
|
+ throw new IllegalArgumentException("Cannot set property " + property + " to " + value + " on " + this.owner + ", it is not an allowed value");
|
||
|
}
|
||
|
+ return ret;
|
||
|
+ // Paper end - optimise state lookup
|
||
|
}
|
||
|
|
||
|
public void populateNeighbours(Map<Map<Property<?>, Comparable<?>>, S> states) {
|
||
|
@@ -134,7 +130,7 @@ public abstract class StateHolder<O, S> {
|
||
|
}
|
||
|
}
|
||
|
|
||
|
- this.neighbours = (Table<Property<?>, Comparable<?>, S>)(table.isEmpty() ? table : ArrayTable.create(table));
|
||
|
+ this.neighbours = (Table<Property<?>, Comparable<?>, S>)(table.isEmpty() ? table : ArrayTable.create(table)); this.optimisedTable.loadInTable((Table)this.neighbours, this.values); // Paper - optimise state lookup
|
||
|
}
|
||
|
}
|
||
|
|
||
|
diff --git a/src/main/java/net/minecraft/world/level/block/state/properties/BooleanProperty.java b/src/main/java/net/minecraft/world/level/block/state/properties/BooleanProperty.java
|
||
|
index ff1a0d125edd2ea10c870cbb62ae9aa23644b6dc..233215280f8494dbc33a2fd0b14e37e59f1cb643 100644
|
||
|
--- a/src/main/java/net/minecraft/world/level/block/state/properties/BooleanProperty.java
|
||
|
+++ b/src/main/java/net/minecraft/world/level/block/state/properties/BooleanProperty.java
|
||
|
@@ -7,6 +7,13 @@ import java.util.Optional;
|
||
|
public class BooleanProperty extends Property<Boolean> {
|
||
|
private final ImmutableSet<Boolean> values = ImmutableSet.of(true, false);
|
||
|
|
||
|
+ // Paper start - optimise iblockdata state lookup
|
||
|
+ @Override
|
||
|
+ public final int getIdFor(final Boolean value) {
|
||
|
+ return value.booleanValue() ? 1 : 0;
|
||
|
+ }
|
||
|
+ // Paper end - optimise iblockdata state lookup
|
||
|
+
|
||
|
protected BooleanProperty(String name) {
|
||
|
super(name, Boolean.class);
|
||
|
}
|
||
|
diff --git a/src/main/java/net/minecraft/world/level/block/state/properties/EnumProperty.java b/src/main/java/net/minecraft/world/level/block/state/properties/EnumProperty.java
|
||
|
index bcf8b24e9f9e9870c1a1d27c721a6a433305d55a..6b05e766162b2cf8b499e5b0effb2cd15e57bea3 100644
|
||
|
--- a/src/main/java/net/minecraft/world/level/block/state/properties/EnumProperty.java
|
||
|
+++ b/src/main/java/net/minecraft/world/level/block/state/properties/EnumProperty.java
|
||
|
@@ -17,6 +17,15 @@ public class EnumProperty<T extends Enum<T> & StringRepresentable> extends Prope
|
||
|
private final ImmutableSet<T> values;
|
||
|
private final Map<String, T> names = Maps.newHashMap();
|
||
|
|
||
|
+ // Paper start - optimise iblockdata state lookup
|
||
|
+ private int[] idLookupTable;
|
||
|
+
|
||
|
+ @Override
|
||
|
+ public final int getIdFor(final T value) {
|
||
|
+ return this.idLookupTable[value.ordinal()];
|
||
|
+ }
|
||
|
+ // Paper end - optimise iblockdata state lookup
|
||
|
+
|
||
|
protected EnumProperty(String name, Class<T> type, Collection<T> values) {
|
||
|
super(name, type);
|
||
|
this.values = ImmutableSet.copyOf(values);
|
||
|
@@ -31,6 +40,14 @@ public class EnumProperty<T extends Enum<T> & StringRepresentable> extends Prope
|
||
|
this.names.put(string, enum_);
|
||
|
}
|
||
|
|
||
|
+ // Paper start - optimise iblockdata state lookup
|
||
|
+ int id = 0;
|
||
|
+ this.idLookupTable = new int[type.getEnumConstants().length];
|
||
|
+ java.util.Arrays.fill(this.idLookupTable, -1);
|
||
|
+ for (final T value : this.getPossibleValues()) {
|
||
|
+ this.idLookupTable[value.ordinal()] = id++;
|
||
|
+ }
|
||
|
+ // Paper end - optimise iblockdata state lookup
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
diff --git a/src/main/java/net/minecraft/world/level/block/state/properties/IntegerProperty.java b/src/main/java/net/minecraft/world/level/block/state/properties/IntegerProperty.java
|
||
|
index 72f508321ebffcca31240fbdd068b4d185454cbc..d16156f8a4a2507e114dc651fd0af9cdffb3c8e0 100644
|
||
|
--- a/src/main/java/net/minecraft/world/level/block/state/properties/IntegerProperty.java
|
||
|
+++ b/src/main/java/net/minecraft/world/level/block/state/properties/IntegerProperty.java
|
||
|
@@ -13,6 +13,16 @@ public class IntegerProperty extends Property<Integer> {
|
||
|
public final int min;
|
||
|
public final int max;
|
||
|
|
||
|
+ // Paper start - optimise iblockdata state lookup
|
||
|
+ @Override
|
||
|
+ public final int getIdFor(final Integer value) {
|
||
|
+ final int val = value.intValue();
|
||
|
+ final int ret = val - this.min;
|
||
|
+
|
||
|
+ return ret | ((this.max - ret) >> 31);
|
||
|
+ }
|
||
|
+ // Paper end - optimise iblockdata state lookup
|
||
|
+
|
||
|
protected IntegerProperty(String name, int min, int max) {
|
||
|
super(name, Integer.class);
|
||
|
this.min = min;
|
||
|
diff --git a/src/main/java/net/minecraft/world/level/block/state/properties/Property.java b/src/main/java/net/minecraft/world/level/block/state/properties/Property.java
|
||
|
index 81b43e0b0146729a8a1c6ade82634c86cde67857..44118dbd1ed212a0911f1244e7fea78cee275aad 100644
|
||
|
--- a/src/main/java/net/minecraft/world/level/block/state/properties/Property.java
|
||
|
+++ b/src/main/java/net/minecraft/world/level/block/state/properties/Property.java
|
||
|
@@ -20,6 +20,17 @@ public abstract class Property<T extends Comparable<T>> {
|
||
|
}, this::getName);
|
||
|
private final Codec<Property.Value<T>> valueCodec = this.codec.xmap(this::value, Property.Value::value);
|
||
|
|
||
|
+ // Paper start - optimise iblockdata state lookup
|
||
|
+ private static final java.util.concurrent.atomic.AtomicInteger ID_GENERATOR = new java.util.concurrent.atomic.AtomicInteger();
|
||
|
+ private final int id = ID_GENERATOR.getAndIncrement();
|
||
|
+
|
||
|
+ public final int getId() {
|
||
|
+ return this.id;
|
||
|
+ }
|
||
|
+
|
||
|
+ public abstract int getIdFor(final T value);
|
||
|
+ // Paper end - optimise state lookup
|
||
|
+
|
||
|
protected Property(String name, Class<T> type) {
|
||
|
this.clazz = type;
|
||
|
this.name = name;
|