From edad8be41f5f4790afc4233f11749e7bc970dc83 Mon Sep 17 00:00:00 2001 From: Spottedleaf Date: Sat, 10 Aug 2024 14:15:56 -0700 Subject: [PATCH] Drastically reduce memory footprint of ZeroCollidingReferenceStateTable We can linearize the table lookup by generating an index per state using the property values. --- ConcurrentUtil | 2 +- .../bitstorage/SimpleBitStorageMixin.java | 5 +- .../BooleanPropertyMixin.java | 32 ++- .../EnumPropertyMixin.java | 41 ++-- .../IntegerPropertyMixin.java | 38 +++- .../PropertyMixin.java | 60 +++--- .../StateHolderMixin.java | 47 +++-- .../PropertyAccess.java | 5 +- .../PropertyAccessStateHolder.java | 7 + .../ZeroCollidingReferenceStateTable.java | 197 ++++++++---------- 10 files changed, 233 insertions(+), 201 deletions(-) create mode 100644 src/main/java/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/PropertyAccessStateHolder.java diff --git a/ConcurrentUtil b/ConcurrentUtil index f6facb74..08d3ca32 160000 --- a/ConcurrentUtil +++ b/ConcurrentUtil @@ -1 +1 @@ -Subproject commit f6facb7403cca2e573053e48d643343d133134b1 +Subproject commit 08d3ca3241084c045d9dfe407b699cd4503ffbc7 diff --git a/src/main/java/ca/spottedleaf/moonrise/mixin/bitstorage/SimpleBitStorageMixin.java b/src/main/java/ca/spottedleaf/moonrise/mixin/bitstorage/SimpleBitStorageMixin.java index 3720e8e0..54be4b4c 100644 --- a/src/main/java/ca/spottedleaf/moonrise/mixin/bitstorage/SimpleBitStorageMixin.java +++ b/src/main/java/ca/spottedleaf/moonrise/mixin/bitstorage/SimpleBitStorageMixin.java @@ -1,5 +1,6 @@ package ca.spottedleaf.moonrise.mixin.bitstorage; +import ca.spottedleaf.concurrentutil.util.IntegerUtil; import net.minecraft.util.BitStorage; import net.minecraft.util.SimpleBitStorage; import org.spongepowered.asm.mixin.Final; @@ -10,8 +11,6 @@ import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import java.lang.invoke.MethodHandles; -import java.lang.invoke.VarHandle; @Mixin(SimpleBitStorage.class) abstract class SimpleBitStorageMixin implements BitStorage { @@ -45,7 +44,7 @@ abstract class SimpleBitStorageMixin implements BitStorage { // since index is always [0, 4095] (i.e 12 bits), multiplication by a magic value here (20 bits) // fits exactly in an int and allows us to use integer arithmetic for (int bits = 1; bits < BETTER_MAGIC.length; ++bits) { - BETTER_MAGIC[bits] = (0xFFFFF / (64 / bits)) + 1; + BETTER_MAGIC[bits] = (int)IntegerUtil.getUnsignedDivisorMagic(64L / bits, 20); } } diff --git a/src/main/java/ca/spottedleaf/moonrise/mixin/blockstate_propertyaccess/BooleanPropertyMixin.java b/src/main/java/ca/spottedleaf/moonrise/mixin/blockstate_propertyaccess/BooleanPropertyMixin.java index baafd6a9..23c5e085 100644 --- a/src/main/java/ca/spottedleaf/moonrise/mixin/blockstate_propertyaccess/BooleanPropertyMixin.java +++ b/src/main/java/ca/spottedleaf/moonrise/mixin/blockstate_propertyaccess/BooleanPropertyMixin.java @@ -6,7 +6,10 @@ import net.minecraft.world.level.block.state.properties.BooleanProperty; import net.minecraft.world.level.block.state.properties.Property; import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Constant; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Mixin(BooleanProperty.class) abstract class BooleanPropertyMixin extends Property implements PropertyAccess { @@ -14,6 +17,25 @@ protected BooleanPropertyMixin(String string, Class class_) { super(string, class_); } + @Override + public final int moonrise$getIdFor(final Boolean value) { + return value.booleanValue() ? 1 : 0; + } + + /** + * @reason Hook into constructor to init fields + * @author Spottedleaf + */ + @Inject( + method = "", + at = @At( + value = "RETURN" + ) + ) + private void init(final CallbackInfo ci) { + this.moonrise$setById(new Boolean[]{ Boolean.FALSE, Boolean.TRUE }); + } + /** * This skips all ops after the identity comparison in the original code. * @@ -30,14 +52,4 @@ protected BooleanPropertyMixin(String string, Class class_) { private boolean skipFurtherComparison(final Object obj, final Operation orig) { return false; } - - @Override - public final boolean moonrise$requiresDefaultImpl() { - return false; - } - - @Override - public final int moonrise$getIdFor(final Boolean value) { - return value.booleanValue() ? 1 : 0; - } } diff --git a/src/main/java/ca/spottedleaf/moonrise/mixin/blockstate_propertyaccess/EnumPropertyMixin.java b/src/main/java/ca/spottedleaf/moonrise/mixin/blockstate_propertyaccess/EnumPropertyMixin.java index 2b5041a0..6f0e6c34 100644 --- a/src/main/java/ca/spottedleaf/moonrise/mixin/blockstate_propertyaccess/EnumPropertyMixin.java +++ b/src/main/java/ca/spottedleaf/moonrise/mixin/blockstate_propertyaccess/EnumPropertyMixin.java @@ -6,14 +6,21 @@ import net.minecraft.world.level.block.state.properties.Property; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Overwrite; +import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import java.lang.reflect.Array; +import java.util.Arrays; +import java.util.Collection; @Mixin(EnumProperty.class) abstract class EnumPropertyMixin & StringRepresentable> extends Property implements PropertyAccess { + @Shadow + public abstract Collection getPossibleValues(); + @Unique private int[] idLookupTable; @@ -21,6 +28,12 @@ protected EnumPropertyMixin(String string, Class class_) { super(string, class_); } + @Override + public final int moonrise$getIdFor(final T value) { + final Class target = this.getValueClass(); + return ((value.getClass() != target && value.getDeclaringClass() != target)) ? -1 : this.idLookupTable[value.ordinal()]; + } + /** * @reason Hook into constructor to init fields * @author Spottedleaf @@ -32,12 +45,21 @@ protected EnumPropertyMixin(String string, Class class_) { ) ) private void init(final CallbackInfo ci) { + final Collection values = this.getPossibleValues(); + final Class clazz = this.getValueClass(); + int id = 0; - this.idLookupTable = new int[getValueClass().getEnumConstants().length]; - java.util.Arrays.fill(this.idLookupTable, -1); - for (final T value : this.getPossibleValues()) { - this.idLookupTable[value.ordinal()] = id++; + this.idLookupTable = new int[clazz.getEnumConstants().length]; + Arrays.fill(this.idLookupTable, -1); + final T[] byId = (T[])Array.newInstance(clazz, values.size()); + + for (final T value : values) { + final int valueId = id++; + this.idLookupTable[value.ordinal()] = valueId; + byId[valueId] = value; } + + this.moonrise$setById(byId); } /** @@ -49,15 +71,4 @@ private void init(final CallbackInfo ci) { public boolean equals(final Object obj) { return this == obj; } - - @Override - public final boolean moonrise$requiresDefaultImpl() { - return false; - } - - @Override - public final int moonrise$getIdFor(final T value) { - final Class target = this.getValueClass(); - return ((value.getClass() != target && value.getDeclaringClass() != target)) ? -1 : this.idLookupTable[value.ordinal()]; - } } diff --git a/src/main/java/ca/spottedleaf/moonrise/mixin/blockstate_propertyaccess/IntegerPropertyMixin.java b/src/main/java/ca/spottedleaf/moonrise/mixin/blockstate_propertyaccess/IntegerPropertyMixin.java index 3153e6ae..630a5d77 100644 --- a/src/main/java/ca/spottedleaf/moonrise/mixin/blockstate_propertyaccess/IntegerPropertyMixin.java +++ b/src/main/java/ca/spottedleaf/moonrise/mixin/blockstate_propertyaccess/IntegerPropertyMixin.java @@ -7,6 +7,9 @@ import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Overwrite; import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Mixin(IntegerProperty.class) abstract class IntegerPropertyMixin extends Property implements PropertyAccess { @@ -23,6 +26,14 @@ protected IntegerPropertyMixin(String string, Class class_) { super(string, class_); } + @Override + public final int moonrise$getIdFor(final Integer value) { + final int val = value.intValue(); + final int ret = val - this.min; + + return ret | ((this.max - ret) >> 31); + } + /** * @reason Properties are identity comparable * @author Spottedleaf @@ -33,16 +44,25 @@ public boolean equals(final Object obj) { return this == obj; } - @Override - public final boolean moonrise$requiresDefaultImpl() { - return false; - } + /** + * @reason Hook into constructor to init fields + * @author Spottedleaf + */ + @Inject( + method = "", + at = @At( + value = "RETURN" + ) + ) + private void init(final CallbackInfo ci) { + final int min = this.min; + final int max = this.max; - @Override - public final int moonrise$getIdFor(final Integer value) { - final int val = value.intValue(); - final int ret = val - this.min; + final Integer[] byId = new Integer[max - min + 1]; + for (int i = min; i <= max; ++i) { + byId[i - min] = Integer.valueOf(i); + } - return ret | ((this.max - ret) >> 31); + this.moonrise$setById(byId); } } diff --git a/src/main/java/ca/spottedleaf/moonrise/mixin/blockstate_propertyaccess/PropertyMixin.java b/src/main/java/ca/spottedleaf/moonrise/mixin/blockstate_propertyaccess/PropertyMixin.java index 2b152ece..e999f021 100644 --- a/src/main/java/ca/spottedleaf/moonrise/mixin/blockstate_propertyaccess/PropertyMixin.java +++ b/src/main/java/ca/spottedleaf/moonrise/mixin/blockstate_propertyaccess/PropertyMixin.java @@ -1,24 +1,18 @@ package ca.spottedleaf.moonrise.mixin.blockstate_propertyaccess; import ca.spottedleaf.moonrise.patches.blockstate_propertyaccess.PropertyAccess; -import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import net.minecraft.world.level.block.state.properties.Property; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Overwrite; -import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import java.util.Collection; import java.util.concurrent.atomic.AtomicInteger; @Mixin(Property.class) abstract class PropertyMixin> implements PropertyAccess { - @Shadow - public abstract Collection getPossibleValues(); - @Unique private static final AtomicInteger ID_GENERATOR = new AtomicInteger(); @@ -26,36 +20,42 @@ abstract class PropertyMixin> implements PropertyAccess< private int id; @Unique - private Object2IntOpenHashMap defaultById; + private T[] byId; + + @Override + public final int moonrise$getId() { + return this.id; + } + + @Override + public final T moonrise$getById(final int id) { + final T[] byId = this.byId; + return id < 0 || id >= byId.length ? null : this.byId[id]; + } + + @Override + public final void moonrise$setById(final T[] byId) { + if (this.byId != null) { + throw new IllegalStateException(); + } + this.byId = byId; + } + + @Override + public abstract int moonrise$getIdFor(final T value); /** * @reason Hook into constructor to init fields * @author Spottedleaf */ @Inject( - method = "", - at = @At( - value = "RETURN" - ) + method = "", + at = @At( + value = "RETURN" + ) ) private void initId(final CallbackInfo ci) { this.id = ID_GENERATOR.getAndIncrement(); - - final Collection values = this.getPossibleValues(); - - if (this.moonrise$requiresDefaultImpl()) { - this.defaultById = new Object2IntOpenHashMap<>(values.size()); - - int id = 0; - for (final T value : values) { - this.defaultById.put(value, id++); - } - } - } - - @Override - public final int moonrise$getId() { - return this.id; } /** @@ -67,10 +67,4 @@ private void initId(final CallbackInfo ci) { public boolean equals(final Object obj) { return this == obj; } - - // add default implementation in case mods create their own Properties - @Override - public int moonrise$getIdFor(final T value) { - return this.defaultById.getOrDefault(value, -1); - } } diff --git a/src/main/java/ca/spottedleaf/moonrise/mixin/blockstate_propertyaccess/StateHolderMixin.java b/src/main/java/ca/spottedleaf/moonrise/mixin/blockstate_propertyaccess/StateHolderMixin.java index eb829851..a2c4699d 100644 --- a/src/main/java/ca/spottedleaf/moonrise/mixin/blockstate_propertyaccess/StateHolderMixin.java +++ b/src/main/java/ca/spottedleaf/moonrise/mixin/blockstate_propertyaccess/StateHolderMixin.java @@ -1,7 +1,7 @@ package ca.spottedleaf.moonrise.mixin.blockstate_propertyaccess; +import ca.spottedleaf.moonrise.patches.blockstate_propertyaccess.PropertyAccessStateHolder; import ca.spottedleaf.moonrise.patches.blockstate_propertyaccess.util.ZeroCollidingReferenceStateTable; -import com.google.common.collect.Table; import it.unimi.dsi.fastutil.objects.Reference2ObjectArrayMap; import net.minecraft.world.level.block.state.StateHolder; import net.minecraft.world.level.block.state.properties.Property; @@ -17,10 +17,7 @@ import java.util.Optional; @Mixin(StateHolder.class) -abstract class StateHolderMixin { - - @Shadow - private Table, Comparable, S> neighbours; +abstract class StateHolderMixin implements PropertyAccessStateHolder { @Shadow @Final @@ -31,7 +28,15 @@ abstract class StateHolderMixin { private Reference2ObjectArrayMap, Comparable> values; @Unique - protected ZeroCollidingReferenceStateTable optimisedTable; + protected ZeroCollidingReferenceStateTable optimisedTable; + + @Unique + protected long tableIndex; + + @Override + public final long moonrise$getTableIndex() { + return this.tableIndex; + } /** * @reason Hook into constructor to init fields @@ -44,7 +49,8 @@ abstract class StateHolderMixin { ) ) private void init(final CallbackInfo ci) { - this.optimisedTable = new ZeroCollidingReferenceStateTable((StateHolder)(Object)this, this.values); // Paper - optimise state lookup + this.optimisedTable = new ZeroCollidingReferenceStateTable<>(this.values.keySet()); + this.tableIndex = this.optimisedTable.getIndex((StateHolder)(Object)this); } /** @@ -53,12 +59,24 @@ private void init(final CallbackInfo ci) { */ @Inject( method = "populateNeighbours", + cancellable = true, at = @At( - value = "RETURN" + value = "HEAD" ) ) private void loadTable(final Map, Comparable>, S> map, final CallbackInfo ci) { - this.optimisedTable.loadInTable((Table, Comparable, StateHolder>)this.neighbours, this.values); + if (this.optimisedTable.isLoaded()) { + ci.cancel(); + return; + } + this.optimisedTable.loadInTable(map); + + // de-duplicate the tables + for (final S value : map.values()) { + ((StateHolderMixin)(Object)(StateHolder)value).optimisedTable = this.optimisedTable; + } + + ci.cancel(); } /** @@ -67,7 +85,7 @@ private void loadTable(final Map, Comparable>, S> map, final */ @Overwrite public , V extends T> S setValue(final Property property, final V value) { - final S ret = (S)this.optimisedTable.get(property, value); + final S ret = this.optimisedTable.set(this.tableIndex, property, value); if (ret == null) { throw new IllegalArgumentException("Cannot set property " + property + " to " + value + " on " + this.owner + ", it is not an allowed value"); } @@ -80,8 +98,7 @@ public , V extends T> S setValue(final Property prope */ @Overwrite public > Optional getOptionalValue(final Property property) { - final Comparable ret = this.optimisedTable.get(property); - return ret == null ? Optional.empty() : Optional.of((T)ret); + return Optional.ofNullable(this.optimisedTable.get(this.tableIndex, property)); } /** @@ -90,11 +107,11 @@ public > Optional getOptionalValue(final Property */ @Overwrite public > T getValue(final Property property) { - final Comparable ret = this.optimisedTable.get(property); + final T ret = this.optimisedTable.get(this.tableIndex, property); if (ret == null) { throw new IllegalArgumentException("Cannot get property " + property + " as it does not exist in " + this.owner); } else { - return (T)ret; + return ret; } } @@ -104,6 +121,6 @@ public > T getValue(final Property property) { */ @Overwrite public > boolean hasProperty(final Property property) { - return this.optimisedTable.get(property) != null; + return this.optimisedTable.hasProperty(property); } } diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/PropertyAccess.java b/src/main/java/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/PropertyAccess.java index 77a037a0..89e75b45 100644 --- a/src/main/java/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/PropertyAccess.java +++ b/src/main/java/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/PropertyAccess.java @@ -6,8 +6,7 @@ public interface PropertyAccess { public int moonrise$getIdFor(final T value); - public default boolean moonrise$requiresDefaultImpl() { - return true; - } + public T moonrise$getById(final int id); + public void moonrise$setById(final T[] values); } diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/PropertyAccessStateHolder.java b/src/main/java/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/PropertyAccessStateHolder.java new file mode 100644 index 00000000..01da52b9 --- /dev/null +++ b/src/main/java/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/PropertyAccessStateHolder.java @@ -0,0 +1,7 @@ +package ca.spottedleaf.moonrise.patches.blockstate_propertyaccess; + +public interface PropertyAccessStateHolder { + + public long moonrise$getTableIndex(); + +} diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/util/ZeroCollidingReferenceStateTable.java b/src/main/java/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/util/ZeroCollidingReferenceStateTable.java index 06798a8c..d275c602 100644 --- a/src/main/java/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/util/ZeroCollidingReferenceStateTable.java +++ b/src/main/java/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/util/ZeroCollidingReferenceStateTable.java @@ -1,161 +1,134 @@ package ca.spottedleaf.moonrise.patches.blockstate_propertyaccess.util; +import ca.spottedleaf.concurrentutil.util.IntegerUtil; import ca.spottedleaf.moonrise.patches.blockstate_propertyaccess.PropertyAccess; -import com.google.common.collect.Table; +import ca.spottedleaf.moonrise.patches.blockstate_propertyaccess.PropertyAccessStateHolder; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import net.minecraft.world.level.block.state.StateHolder; import net.minecraft.world.level.block.state.properties.Property; +import java.util.ArrayList; import java.util.Collection; -import java.util.HashSet; +import java.util.List; import java.util.Map; -import java.util.Set; -public final class ZeroCollidingReferenceStateTable { +public final class ZeroCollidingReferenceStateTable { - // upper 32 bits: starting index - // lower 32 bits: bitset for contained ids - private final long[] this_index_table; - private final Comparable[] this_table; - private final StateHolder this_state; + private final Int2ObjectOpenHashMap propertyToIndexer; + private S[] lookup; - private long[] index_table; - private StateHolder[][] value_table; + public ZeroCollidingReferenceStateTable(final Collection> properties) { + this.propertyToIndexer = new Int2ObjectOpenHashMap<>(properties.size()); - private boolean inited; + final List> sortedProperties = new ArrayList<>(properties); - public ZeroCollidingReferenceStateTable(final StateHolder state, final Map, Comparable> this_map) { - this.this_state = state; - this.this_index_table = create_table(this_map.keySet()); + // important that each table sees the same property order given the same _set_ of properties, + // as each table will calculate the index for the block state + sortedProperties.sort((final Property p1, final Property p2) -> { + return Integer.compare( + ((PropertyAccess)p1).moonrise$getId(), + ((PropertyAccess)p2).moonrise$getId() + ); + }); - 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; - } - } + int currentMultiple = 1; + for (final Property property : sortedProperties) { + final int totalValues = property.getPossibleValues().size(); - this.this_table = new Comparable[max_id + 1]; - for (final Map.Entry, Comparable> entry : this_map.entrySet()) { - this.this_table[lookup_vindex(entry.getKey(), this.this_index_table)] = entry.getValue(); + this.propertyToIndexer.put( + ((PropertyAccess)property).moonrise$getId(), + new Indexer( + totalValues, + currentMultiple, + IntegerUtil.getUnsignedDivisorMagic((long)currentMultiple, 32), + IntegerUtil.getUnsignedDivisorMagic((long)totalValues, 32) + ) + ); + + currentMultiple *= totalValues; } } - public void loadInTable(final Table, Comparable, StateHolder> table, - final Map, Comparable> this_map) { - if (this.inited) { - throw new IllegalStateException(); - } - this.inited = true; - final Set> combined = new HashSet<>(table.rowKeySet()); - combined.addAll(this_map.keySet()); + public > boolean hasProperty(final Property property) { + return this.propertyToIndexer.containsKey(((PropertyAccess)property).moonrise$getId()); + } - this.index_table = create_table(combined); + public long getIndex(final StateHolder stateHolder) { + long ret = 0L; - 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; - } - } + for (final Map.Entry, Comparable> entry : stateHolder.getValues().entrySet()) { + final Property property = entry.getKey(); + final Comparable value = entry.getValue(); - this.value_table = new StateHolder[max_id + 1][]; + final Indexer indexer = this.propertyToIndexer.get(((PropertyAccess)property).moonrise$getId()); - final Map, Map, StateHolder>> map = table.rowMap(); - for (final Property property : map.keySet()) { - final Map, StateHolder> propertyMap = map.get(property); + ret += (((PropertyAccess)property).moonrise$getIdFor(value)) * indexer.multiple; + } - final int id = lookup_vindex(property, this.index_table); - final StateHolder[] states = this.value_table[id] = new StateHolder[property.getPossibleValues().size()]; + return ret; + } - for (final Map.Entry, StateHolder> entry : propertyMap.entrySet()) { - if (entry.getValue() == null) { - continue; - } + public boolean isLoaded() { + return this.lookup != null; + } - states[((PropertyAccess)property).moonrise$getIdFor(entry.getKey())] = entry.getValue(); - } + public void loadInTable(final Map, Comparable>, S> universe) { + if (this.lookup != null) { + throw new IllegalStateException(); } + this.lookup = (S[])new StateHolder[universe.size()]; - for (final Map.Entry, 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()]; + for (final S value : universe.values()) { + if (value == null) { + continue; } - - this.value_table[index][((PropertyAccess)property).moonrise$getIdFor(entry.getValue())] = this.this_state; + this.lookup[(int)((PropertyAccessStateHolder)(StateHolder)value).moonrise$getTableIndex()] = value; } - } - private static long[] create_table(final Collection> collection) { - int max_id = -1; - for (final Property property : collection) { - final int id = ((PropertyAccess)property).moonrise$getId(); - if (id > max_id) { - max_id = id; + for (final S value : this.lookup) { + if (value == null) { + throw new IllegalStateException(); } } + } - final long[] ret = new long[((max_id + 1) + 31) >>> 5]; // ceil((max_id + 1) / 32) - - for (final Property property : collection) { - final int id = ((PropertyAccess)property).moonrise$getId(); - - ret[id >>> 5] |= (1L << (id & 31)); + public > T get(final long index, final Property property) { + final Indexer indexer = this.propertyToIndexer.get(((PropertyAccess)property).moonrise$getId()); + if (indexer == null) { + return null; } - int total = 0; - for (int i = 1, len = ret.length; i < len; ++i) { - ret[i] |= (long)(total += Long.bitCount(ret[i - 1] & 0xFFFFFFFFL)) << 32; - } + final long divided = (index * indexer.multipleDivMagic) >>> 32; + final long modded = (((divided * indexer.modMagic) & 0xFFFFFFFFL) * indexer.totalValues) >>> 32; + // equiv to: divided = index / multiple + // modded = divided % totalValues - return ret; + return ((PropertyAccess)property).moonrise$getById((int)modded); } - 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) { + public > S set(final long index, final Property property, final T with) { + final int newValueId = ((PropertyAccess)property).moonrise$getIdFor(with); + if (newValueId < 0) { return null; } - return table[index]; - } - public StateHolder get(final Property property, final Comparable with) { - final int index = lookup_vindex(property, this.index_table); - final StateHolder[][] table = this.value_table; - if (index < 0 || index >= table.length) { + final Indexer indexer = this.propertyToIndexer.get(((PropertyAccess)property).moonrise$getId()); + if (indexer == null) { return null; } - final StateHolder[] values = table[index]; + final long divided = (index * indexer.multipleDivMagic) >>> 32; + final long modded = (((divided * indexer.modMagic) & 0xFFFFFFFFL) * indexer.totalValues) >>> 32; + // equiv to: divided = index / multiple + // modded = divided % totalValues - final int withId = ((PropertyAccess)property).moonrise$getIdFor(with); - if (withId < 0 || withId >= values.length) { - return null; - } + // subtract out the old value, add in the new + final long newIndex = (((long)newValueId - modded) * indexer.multiple) + index; - return values[withId]; + return this.lookup[(int)newIndex]; } - private static int lookup_vindex(final Property property, final long[] index_table) { - final int id = ((PropertyAccess)property).moonrise$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); - } + private static final record Indexer( + int totalValues, int multiple, long multipleDivMagic, long modMagic + ) {} }