This repository has been archived by the owner. It is now read-only.
Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Custom table implementation for blockstate state lookups
Testing some redstone intensive machines showed to bring about a 10% improvement.
- Loading branch information
Showing
with
348 additions
and 0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@@ -0,0 +1,348 @@ | ||
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/com/tuinity/tuinity/util/table/ZeroCollidingReferenceStateTable.java b/src/main/java/com/tuinity/tuinity/util/table/ZeroCollidingReferenceStateTable.java | ||
new file mode 100644 | ||
index 0000000000000000000000000000000000000000..298d09634effcb06cd2237a1f7f903e9e46ec78d | ||
--- /dev/null | ||
+++ b/src/main/java/com/tuinity/tuinity/util/table/ZeroCollidingReferenceStateTable.java | ||
@@ -0,0 +1,160 @@ | ||
+package com.tuinity.tuinity.util.table; | ||
+ | ||
+import com.google.common.collect.Table; | ||
+import net.minecraft.server.IBlockDataHolder; | ||
+import net.minecraft.server.IBlockState; | ||
+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 IBlockDataHolder<?, ?> this_state; | ||
+ | ||
+ protected long[] index_table; | ||
+ protected IBlockDataHolder<?, ?>[][] value_table; | ||
+ | ||
+ public ZeroCollidingReferenceStateTable(final IBlockDataHolder<?, ?> state, final Map<IBlockState<?>, Comparable<?>> this_map) { | ||
+ this.this_state = state; | ||
+ this.this_index_table = this.create_table(this_map.keySet()); | ||
+ | ||
+ int max_id = -1; | ||
+ for (final IBlockState<?> 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<IBlockState<?>, Comparable<?>> entry : this_map.entrySet()) { | ||
+ this.this_table[lookup_vindex(entry.getKey(), this.this_index_table)] = entry.getValue(); | ||
+ } | ||
+ } | ||
+ | ||
+ public void loadInTable(final Table<IBlockState<?>, Comparable<?>, IBlockDataHolder<?, ?>> table, | ||
+ final Map<IBlockState<?>, Comparable<?>> this_map) { | ||
+ final Set<IBlockState<?>> combined = new HashSet<>(table.rowKeySet()); | ||
+ combined.addAll(this_map.keySet()); | ||
+ | ||
+ this.index_table = this.create_table(combined); | ||
+ | ||
+ int max_id = -1; | ||
+ for (final IBlockState<?> property : combined) { | ||
+ final int id = lookup_vindex(property, this.index_table); | ||
+ if (id > max_id) { | ||
+ max_id = id; | ||
+ } | ||
+ } | ||
+ | ||
+ this.value_table = new IBlockDataHolder[max_id + 1][]; | ||
+ | ||
+ final Map<IBlockState<?>, Map<Comparable<?>, IBlockDataHolder<?, ?>>> map = table.rowMap(); | ||
+ for (final IBlockState<?> property : map.keySet()) { | ||
+ final Map<Comparable<?>, IBlockDataHolder<?, ?>> propertyMap = map.get(property); | ||
+ | ||
+ final int id = lookup_vindex(property, this.index_table); | ||
+ final IBlockDataHolder<?, ?>[] states = this.value_table[id] = new IBlockDataHolder[property.getValues().size()]; | ||
+ | ||
+ for (final Map.Entry<Comparable<?>, IBlockDataHolder<?, ?>> entry : propertyMap.entrySet()) { | ||
+ if (entry.getValue() == null) { | ||
+ // TODO what | ||
+ continue; | ||
+ } | ||
+ | ||
+ states[((IBlockState)property).getIdFor(entry.getKey())] = entry.getValue(); | ||
+ } | ||
+ } | ||
+ | ||
+ | ||
+ for (final Map.Entry<IBlockState<?>, Comparable<?>> entry : this_map.entrySet()) { | ||
+ final IBlockState<?> property = entry.getKey(); | ||
+ final int index = lookup_vindex(property, this.index_table); | ||
+ | ||
+ if (this.value_table[index] == null) { | ||
+ this.value_table[index] = new IBlockDataHolder[property.getValues().size()]; | ||
+ } | ||
+ | ||
+ this.value_table[index][((IBlockState)property).getIdFor(entry.getValue())] = this.this_state; | ||
+ } | ||
+ } | ||
+ | ||
+ | ||
+ protected long[] create_table(final Collection<IBlockState<?>> collection) { | ||
+ int max_id = -1; | ||
+ for (final IBlockState<?> 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 IBlockState<?> 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 IBlockState<?> 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 IBlockDataHolder<?, ?> get(final IBlockState<?> property, final Comparable<?> with) { | ||
+ final int withId = ((IBlockState)property).getIdFor(with); | ||
+ if (withId < 0) { | ||
+ return null; | ||
+ } | ||
+ | ||
+ final int index = lookup_vindex(property, this.index_table); | ||
+ final IBlockDataHolder<?, ?>[][] table = this.value_table; | ||
+ if (index < 0 || index >= table.length) { | ||
+ return null; | ||
+ } | ||
+ | ||
+ final IBlockDataHolder<?, ?>[] values = table[index]; | ||
+ | ||
+ if (withId >= values.length) { | ||
+ return null; | ||
+ } | ||
+ | ||
+ return values[withId]; | ||
+ } | ||
+ | ||
+ protected static int lookup_vindex(final IBlockState<?> 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/server/BlockStateBoolean.java b/src/main/java/net/minecraft/server/BlockStateBoolean.java | ||
index 4ca8db630434915de4eaeac6c4ecd60714d7f5d9..d6c8ae638b3993ce55be91087de09a300405e075 100644 | ||
--- a/src/main/java/net/minecraft/server/BlockStateBoolean.java | ||
+++ b/src/main/java/net/minecraft/server/BlockStateBoolean.java | ||
@@ -12,6 +12,13 @@ public class BlockStateBoolean extends IBlockState<Boolean> { | ||
super(s, Boolean.class); | ||
} | ||
|
||
+ // Tuinity start - optimise iblockdata state lookup | ||
+ @Override | ||
+ public final int getIdFor(final Boolean value) { | ||
+ return value.booleanValue() ? 1 : 0; | ||
+ } | ||
+ // Tuinity end - optimise iblockdata state lookup | ||
+ | ||
@Override | ||
public Collection<Boolean> getValues() { | ||
return this.a; | ||
diff --git a/src/main/java/net/minecraft/server/BlockStateEnum.java b/src/main/java/net/minecraft/server/BlockStateEnum.java | ||
index 8dc620b22bb904aa6a82e2127aa9da861986525c..e6663281481a48fe1b838339160565fbf4c6a65a 100644 | ||
--- a/src/main/java/net/minecraft/server/BlockStateEnum.java | ||
+++ b/src/main/java/net/minecraft/server/BlockStateEnum.java | ||
@@ -17,6 +17,15 @@ public class BlockStateEnum<T extends Enum<T> & INamable> extends IBlockState<T> | ||
private final ImmutableSet<T> a; | ||
private final Map<String, T> b = Maps.newHashMap(); | ||
|
||
+ // Tuinity start - optimise iblockdata state lookup | ||
+ private int[] idLookupTable; | ||
+ | ||
+ @Override | ||
+ public final int getIdFor(final T value) { | ||
+ return this.idLookupTable[value.ordinal()]; | ||
+ } | ||
+ // Tuinity end - optimise iblockdata state lookup | ||
+ | ||
protected BlockStateEnum(String s, Class<T> oclass, Collection<T> collection) { | ||
super(s, oclass); | ||
this.a = ImmutableSet.copyOf(collection); | ||
@@ -32,6 +41,14 @@ public class BlockStateEnum<T extends Enum<T> & INamable> extends IBlockState<T> | ||
|
||
this.b.put(s1, t0); | ||
} | ||
+ // Tuinity start - optimise iblockdata state lookup | ||
+ int id = 0; | ||
+ this.idLookupTable = new int[oclass.getEnumConstants().length]; | ||
+ java.util.Arrays.fill(this.idLookupTable, -1); | ||
+ for (final T value : this.getValues()) { | ||
+ this.idLookupTable[value.ordinal()] = id++; | ||
+ } | ||
+ // Tuinity end - optimise iblockdata state lookup | ||
|
||
} | ||
|
||
diff --git a/src/main/java/net/minecraft/server/BlockStateInteger.java b/src/main/java/net/minecraft/server/BlockStateInteger.java | ||
index 36b84446e96faefad3b783f73df74e0f3bce8255..acae5fe0bff44d2bc3921c8e078b2f261de3a035 100644 | ||
--- a/src/main/java/net/minecraft/server/BlockStateInteger.java | ||
+++ b/src/main/java/net/minecraft/server/BlockStateInteger.java | ||
@@ -13,6 +13,16 @@ public class BlockStateInteger extends IBlockState<Integer> { | ||
public final int min; | ||
public final int max; | ||
|
||
+ // Tuinity 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); | ||
+ } | ||
+ // Tuinity end - optimise iblockdata state lookup | ||
+ | ||
protected BlockStateInteger(String s, int i, int j) { | ||
super(s, Integer.class); | ||
this.min = i; | ||
diff --git a/src/main/java/net/minecraft/server/IBlockDataHolder.java b/src/main/java/net/minecraft/server/IBlockDataHolder.java | ||
index b19c694cf01bc868dd7c4ec6432b613d19f2ca40..06d2dd7584253a406aa77867fac5543aa01020fd 100644 | ||
--- a/src/main/java/net/minecraft/server/IBlockDataHolder.java | ||
+++ b/src/main/java/net/minecraft/server/IBlockDataHolder.java | ||
@@ -39,11 +39,13 @@ public abstract class IBlockDataHolder<O, S> { | ||
private final ImmutableMap<IBlockState<?>, Comparable<?>> b; | ||
private Table<IBlockState<?>, Comparable<?>, S> e; | ||
protected final MapCodec<S> d; | ||
+ protected com.tuinity.tuinity.util.table.ZeroCollidingReferenceStateTable optimisedTable; // Tuinity - optimise state lookup | ||
|
||
protected IBlockDataHolder(O o0, ImmutableMap<IBlockState<?>, Comparable<?>> immutablemap, MapCodec<S> mapcodec) { | ||
this.c = o0; | ||
this.b = immutablemap; | ||
this.d = mapcodec; | ||
+ this.optimisedTable = new com.tuinity.tuinity.util.table.ZeroCollidingReferenceStateTable(this, immutablemap); // Tuinity - optimise state lookup | ||
} | ||
|
||
public <T extends Comparable<T>> S a(IBlockState<T> iblockstate) { | ||
@@ -85,11 +87,11 @@ public abstract class IBlockDataHolder<O, S> { | ||
|
||
public <T extends Comparable<T>> boolean contains(IBlockState<T> iblockstate) { return this.b(iblockstate); } // Paper - OBFHELPER | ||
public <T extends Comparable<T>> boolean b(IBlockState<T> iblockstate) { | ||
- return this.b.containsKey(iblockstate); | ||
+ return this.optimisedTable.get(iblockstate) != null; // Tuinity - optimise state lookup | ||
} | ||
|
||
public <T extends Comparable<T>> T get(IBlockState<T> iblockstate) { | ||
- Comparable<?> comparable = (Comparable) this.b.get(iblockstate); | ||
+ final Comparable<?> comparable = this.optimisedTable.get(iblockstate); // Tuinity - optimise state lookup | ||
|
||
if (comparable == null) { | ||
throw new IllegalArgumentException("Cannot get property " + iblockstate + " as it does not exist in " + this.c); | ||
@@ -99,27 +101,21 @@ public abstract class IBlockDataHolder<O, S> { | ||
} | ||
|
||
public <T extends Comparable<T>> Optional<T> d(IBlockState<T> iblockstate) { | ||
- Comparable<?> comparable = (Comparable) this.b.get(iblockstate); | ||
+ final Comparable<?> comparable = this.optimisedTable.get(iblockstate); // Tuinity - optimise state lookup | ||
|
||
return comparable == null ? Optional.empty() : Optional.of(iblockstate.getType().cast(comparable)); | ||
} | ||
|
||
public <T extends Comparable<T>, V extends T> S set(IBlockState<T> iblockstate, V v0) { | ||
- Comparable<?> comparable = (Comparable) this.b.get(iblockstate); | ||
+ // Tuinity start - optimise state lookup | ||
+ final S ret = (S)this.optimisedTable.get(iblockstate, v0); | ||
|
||
- if (comparable == null) { | ||
- throw new IllegalArgumentException("Cannot set property " + iblockstate + " as it does not exist in " + this.c); | ||
- } else if (comparable == v0) { | ||
- return (S) this; // Paper - decompile error | ||
- } else { | ||
- S s0 = this.e.get(iblockstate, v0); | ||
- | ||
- if (s0 == null) { | ||
- throw new IllegalArgumentException("Cannot set property " + iblockstate + " to " + v0 + " on " + this.c + ", it is not an allowed value"); | ||
- } else { | ||
- return s0; | ||
- } | ||
+ if (ret == null) { | ||
+ throw new IllegalArgumentException("Cannot set property " + iblockstate + " to " + v0 + " on " + this.c + ", it is not an allowed value"); | ||
} | ||
+ | ||
+ return ret; | ||
+ // Tuinity end - optimise state lookup | ||
} | ||
|
||
public void a(Map<Map<IBlockState<?>, Comparable<?>>, S> map) { | ||
@@ -143,7 +139,8 @@ public abstract class IBlockDataHolder<O, S> { | ||
} | ||
} | ||
|
||
- this.e = (Table) (table.isEmpty() ? table : ArrayTable.create(table)); | ||
+ this.e = (Table) (table.isEmpty() ? table : ArrayTable.create(table)); this.optimisedTable.loadInTable((Table)this.e, this.b); // Tuinity - optimise state lookup | ||
+ | ||
} | ||
} | ||
|
||
diff --git a/src/main/java/net/minecraft/server/IBlockState.java b/src/main/java/net/minecraft/server/IBlockState.java | ||
index 6550b55067db31dbbc903fe17a13849383651c5a..30344d1e5703690d97ecb889af24fa5e7b35f895 100644 | ||
--- a/src/main/java/net/minecraft/server/IBlockState.java | ||
+++ b/src/main/java/net/minecraft/server/IBlockState.java | ||
@@ -15,6 +15,17 @@ public abstract class IBlockState<T extends Comparable<T>> { | ||
private final Codec<T> d; | ||
private final Codec<IBlockState.a<T>> e; | ||
|
||
+ // Tuinity 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); | ||
+ // Tuinity end - optimise state lookup | ||
+ | ||
protected IBlockState(String s, Class<T> oclass) { | ||
this.d = Codec.STRING.comapFlatMap((s1) -> this.b(s1).map(DataResult::success).orElseGet(() -> { // Paper - decompile error | ||
return DataResult.error("Unable to read property: " + this + " with value: " + s1); |