Selaa lähdekoodia

Implement alternative indexing schemes for fast maps
Make FC immutable maps smaller

malte0811 4 vuotta sitten
vanhempi
sitoutus
169f22ea04

+ 13 - 17
common/src/main/java/com/google/common/collect/FerriteCoreEntrySet.java

@@ -5,30 +5,26 @@ import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 import java.util.Map;
-import java.util.function.Function;
-import java.util.function.IntFunction;
-import java.util.function.IntSupplier;
-
-public class FerriteCoreEntrySet<K, V, F extends Function<Object, V> & IntFunction<Map.Entry<K, V>> & IntSupplier>
-        extends ImmutableSet<Map.Entry<K, V>> {
-    // Function<Object, V>: Map#get
-    // IntFunction<Entry<K, V>>: get i-th entry of the map
-    // IntSupplier: Map#size
-    private final F access;
-
-    public FerriteCoreEntrySet(F access) {
-        this.access = access;
+
+public class FerriteCoreEntrySet<K> extends ImmutableSet<Map.Entry<K, Comparable<?>>> {
+    private final Object viewedState;
+
+    public FerriteCoreEntrySet(Object viewedState) {
+        this.viewedState = viewedState;
     }
 
     @Override
     @NotNull
-    public UnmodifiableIterator<Map.Entry<K, V>> iterator() {
-        return new FerriteCoreIterator<>(access, size());
+    public UnmodifiableIterator<Map.Entry<K, Comparable<?>>> iterator() {
+        return new FerriteCoreIterator<>(
+                i -> (Map.Entry<K, Comparable<?>>) FerriteCoreImmutableMap.entryByStateAndIndex.apply(viewedState, i),
+                size()
+        );
     }
 
     @Override
     public int size() {
-        return access.getAsInt();
+        return FerriteCoreImmutableMap.numProperties.applyAsInt(viewedState);
     }
 
     @Override
@@ -40,7 +36,7 @@ public class FerriteCoreEntrySet<K, V, F extends Function<Object, V> & IntFuncti
         if (!(entry.getKey() instanceof Property<?>)) {
             return false;
         }
-        Object valueInMap = access.apply(entry.getKey());
+        Object valueInMap = FerriteCoreImmutableMap.getByStateAndKey.apply(viewedState, entry.getKey());
         return valueInMap != null && valueInMap.equals(((Map.Entry<?, ?>) object).getValue());
     }
 

+ 28 - 19
common/src/main/java/com/google/common/collect/FerriteCoreImmutableMap.java

@@ -1,37 +1,46 @@
 package com.google.common.collect;
 
+import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 import java.util.Map;
-import java.util.function.Function;
-import java.util.function.IntFunction;
-import java.util.function.IntSupplier;
-
-public class FerriteCoreImmutableMap<K, V, F extends Function<Object, V> & IntFunction<Map.Entry<K, V>> & IntSupplier>
-        extends ImmutableMap<K, V> {
-    // This is a quite inconvenient "handle" on a FastMap, but we need classloader separation
-    // Function<Object, V>: Map#get
-    // IntFunction<Entry<K, V>>: get i-th entry of the map
-    // IntSupplier: Map#size
-    private final F access;
-
-    public FerriteCoreImmutableMap(F access) {
-        this.access = access;
+import java.util.function.BiFunction;
+import java.util.function.ToIntFunction;
+
+public class FerriteCoreImmutableMap<K> extends ImmutableMap<K, Comparable<?>> {
+    // This is a very inconvenient "handle" on a FastMap, but
+    // a) we need classloader separation
+    // b) by keeping the functions static we can keep the size of the object down
+    public static ToIntFunction<Object> numProperties;
+    public static BiFunction<Object, Object, Comparable<?>> getByStateAndKey;
+    public static BiFunction<Object, Integer, Entry<?, Comparable<?>>> entryByStateAndIndex;
+
+    // Actually a FastMapStateHolder, but classloader separation…
+    private final Object viewedState;
+
+    public FerriteCoreImmutableMap(Object viewedState) {
+        this.viewedState = viewedState;
     }
 
     @Override
     public int size() {
-        return access.getAsInt();
+        return numProperties.applyAsInt(viewedState);
     }
 
     @Override
-    public V get(@Nullable Object key) {
-        return access.apply(key);
+    public Comparable<?> get(@Nullable Object key) {
+        return getByStateAndKey.apply(viewedState, key);
     }
 
     @Override
-    ImmutableSet<Map.Entry<K, V>> createEntrySet() {
-        return new FerriteCoreEntrySet<>(access);
+    ImmutableSet<Map.Entry<K, Comparable<?>>> createEntrySet() {
+        return new FerriteCoreEntrySet<>(viewedState);
+    }
+
+    @Override
+    @NotNull
+    public ImmutableSet<Entry<K, Comparable<?>>> entrySet() {
+        return new FerriteCoreEntrySet<>(viewedState);
     }
 
     @Override

+ 0 - 53
common/src/main/java/malte0811/ferritecore/classloading/ClassDefiner.java

@@ -1,53 +0,0 @@
-package malte0811.ferritecore.classloading;
-
-import com.google.common.base.Preconditions;
-import com.google.common.collect.ImmutableMap;
-import net.minecraft.util.LazyValue;
-
-import java.io.InputStream;
-import java.lang.invoke.MethodHandle;
-import java.lang.invoke.MethodHandles;
-import java.lang.invoke.MethodType;
-import java.lang.reflect.Method;
-import java.util.Map;
-import java.util.function.Function;
-import java.util.function.IntFunction;
-import java.util.function.IntSupplier;
-
-public class ClassDefiner {
-    private static final LazyValue<MethodHandle> MAKE_IMMUTABLE_FAST_MAP = new LazyValue<>(() -> {
-        try {
-            define("com.google.common.collect.FerriteCoreIterator");
-            define("com.google.common.collect.FerriteCoreEntrySet");
-            Class<?> map = define("com.google.common.collect.FerriteCoreImmutableMap");
-            MethodHandles.Lookup lookup = MethodHandles.lookup();
-            // Function is:
-            // Function<Object, V>: Map#get
-            // IntFunction<Entry<K, V>>: get i-th entry of the map
-            // IntSupplier: Map#size
-            return lookup.findConstructor(map, MethodType.methodType(
-                    void.class, Function.class
-            ));
-        } catch (Exception x) {
-            throw new RuntimeException(x);
-        }
-    });
-
-    public static <K, V, F extends Function<Object, V> & IntFunction<Map.Entry<K, V>> & IntSupplier>
-    ImmutableMap<K, V> makeMap(F mapAccess) throws Throwable {
-        return (ImmutableMap<K, V>) MAKE_IMMUTABLE_FAST_MAP.getValue().invoke(mapAccess);
-    }
-
-    private static Class<?> define(String name) throws Exception {
-        ClassLoader loaderToUse = ImmutableMap.class.getClassLoader();
-        InputStream byteInput = ClassDefiner.class.getResourceAsStream('/' + name.replace('.', '/') + ".class");
-        byte[] classBytes = new byte[byteInput.available()];
-        final int bytesRead = byteInput.read(classBytes);
-        Preconditions.checkState(bytesRead == classBytes.length);
-        Method defineClass = ClassLoader.class.getDeclaredMethod(
-                "defineClass", String.class, byte[].class, int.class, int.class
-        );
-        defineClass.setAccessible(true);
-        return (Class<?>) defineClass.invoke(loaderToUse, name, classBytes, 0, classBytes.length);
-    }
-}

+ 80 - 0
common/src/main/java/malte0811/ferritecore/classloading/FastImmutableMapDefiner.java

@@ -0,0 +1,80 @@
+package malte0811.ferritecore.classloading;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableMap;
+import malte0811.ferritecore.ducks.FastMapStateHolder;
+import net.minecraft.state.Property;
+import net.minecraft.util.LazyValue;
+
+import java.io.InputStream;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.lang.reflect.Method;
+import java.util.Map;
+import java.util.function.BiFunction;
+import java.util.function.ToIntFunction;
+
+public class FastImmutableMapDefiner {
+    private static final LazyValue<Method> DEFINE_CLASS = new LazyValue<>(() -> {
+        try {
+            Method defineClass = ClassLoader.class.getDeclaredMethod(
+                    "defineClass", String.class, byte[].class, int.class, int.class
+            );
+            defineClass.setAccessible(true);
+            return defineClass;
+        } catch (Exception x) {
+            throw new RuntimeException(x);
+        }
+    });
+    private static final LazyValue<MethodHandle> MAKE_IMMUTABLE_FAST_MAP = new LazyValue<>(() -> {
+        try {
+            define("com.google.common.collect.FerriteCoreIterator");
+            define("com.google.common.collect.FerriteCoreEntrySet");
+            Class<?> map = define("com.google.common.collect.FerriteCoreImmutableMap");
+            map.getField("numProperties").set(
+                    null, (ToIntFunction<Object>) o -> ((FastMapStateHolder<?>) o).getStateMap().numProperties()
+            );
+            map.getField("getByStateAndKey").set(
+                    null, (BiFunction<Object, Object, Comparable<?>>) (o, key) -> {
+                        FastMapStateHolder<?> stateHolder = (FastMapStateHolder<?>) o;
+                        return stateHolder.getStateMap().getValue(stateHolder.getStateIndex(), (Property<?>) key);
+                    }
+            );
+            map.getField("entryByStateAndIndex").set(
+                    null, (BiFunction<Object, Integer, Map.Entry<?, Comparable<?>>>) (o, key) -> {
+                        FastMapStateHolder<?> stateHolder = (FastMapStateHolder<?>) o;
+                        return stateHolder.getStateMap().getEntry(key, stateHolder.getStateIndex());
+                    }
+            );
+            MethodHandles.Lookup lookup = MethodHandles.lookup();
+            return lookup.findConstructor(map, MethodType.methodType(
+                    void.class, Object.class
+            ));
+        } catch (Exception x) {
+            throw new RuntimeException(x);
+        }
+    });
+
+    public static ImmutableMap<Property<?>, Comparable<?>> makeMap(FastMapStateHolder<?> state) {
+        try {
+            return (ImmutableMap<Property<?>, Comparable<?>>) MAKE_IMMUTABLE_FAST_MAP.getValue().invoke(state);
+        } catch (Error e) {
+            throw e;
+        } catch (Throwable x) {
+            throw new RuntimeException(x);
+        }
+    }
+
+    private static Class<?> define(String name) throws Exception {
+        ClassLoader loaderToUse = ImmutableMap.class.getClassLoader();
+        InputStream byteInput = FastImmutableMapDefiner.class.getResourceAsStream('/' + name.replace(
+                '.',
+                '/'
+        ) + ".class");
+        byte[] classBytes = new byte[byteInput.available()];
+        final int bytesRead = byteInput.read(classBytes);
+        Preconditions.checkState(bytesRead == classBytes.length);
+        return (Class<?>) DEFINE_CLASS.getValue().invoke(loaderToUse, name, classBytes, 0, classBytes.length);
+    }
+}

+ 50 - 0
common/src/main/java/malte0811/ferritecore/fastmap/BinaryFastMapKey.java

@@ -0,0 +1,50 @@
+package malte0811.ferritecore.fastmap;
+
+import com.google.common.base.Preconditions;
+import net.minecraft.state.Property;
+import net.minecraft.util.math.MathHelper;
+
+public class BinaryFastMapKey<T extends Comparable<T>> extends FastMapKey<T> {
+    private final byte firstBitInValue;
+    private final byte firstBitAfterValue;
+
+    public BinaryFastMapKey(Property<T> property, int mapFactor) {
+        super(property);
+        Preconditions.checkArgument(MathHelper.isPowerOfTwo(mapFactor));
+        final int addedFactor = MathHelper.smallestEncompassingPowerOfTwo(numValues());
+        Preconditions.checkState(numValues() <= addedFactor);
+        Preconditions.checkState(addedFactor < 2 * numValues());
+        final int setBitInBaseFactor = MathHelper.log2(mapFactor);
+        final int setBitInAddedFactor = MathHelper.log2(addedFactor);
+        //TODO off-by-one errors?
+        Preconditions.checkState(setBitInBaseFactor + setBitInAddedFactor < 32);
+        firstBitInValue = (byte) setBitInBaseFactor;
+        firstBitAfterValue = (byte) (setBitInBaseFactor + setBitInAddedFactor);
+    }
+
+    @Override
+    public T getValue(int mapIndex) {
+        final int clearAbove = mapIndex & ~lowestNBits(firstBitAfterValue);
+        return byInternalIndex(clearAbove >>> firstBitInValue);
+    }
+
+    @Override
+    public int replaceIn(int mapIndex, T newValue) {
+        final int keepMask = ~lowestNBits(firstBitAfterValue) | lowestNBits(firstBitInValue);
+        return (keepMask & mapIndex) | toPartialMapIndex(newValue);
+    }
+
+    @Override
+    public int toPartialMapIndex(Comparable<?> value) {
+        return getInternalIndex(value) << firstBitInValue;
+    }
+
+    @Override
+    public int getFactorToNext() {
+        return 1 << (firstBitAfterValue - firstBitInValue);
+    }
+
+    private int lowestNBits(byte n) {
+        return (1 << n) - 1;
+    }
+}

+ 41 - 0
common/src/main/java/malte0811/ferritecore/fastmap/CompactFastMapKey.java

@@ -0,0 +1,41 @@
+package malte0811.ferritecore.fastmap;
+
+import net.minecraft.state.Property;
+
+public class CompactFastMapKey<T extends Comparable<T>> extends FastMapKey<T> {
+    private final int mapFactor;
+
+    CompactFastMapKey(Property<T> property, int mapFactor) {
+        super(property);
+        this.mapFactor = mapFactor;
+    }
+
+    @Override
+    public T getValue(int mapIndex) {
+        int index = (mapIndex / mapFactor) % numValues();
+        return byInternalIndex(index);
+    }
+
+    @Override
+    public int replaceIn(int mapIndex, T newValue) {
+        final int lowerData = mapIndex % mapFactor;
+        final int upperFactor = mapFactor * numValues();
+        final int upperData = mapIndex - mapIndex % upperFactor;
+        int internalIndex = getInternalIndex(newValue);
+        if (internalIndex < 0) {
+            return -1;
+        } else {
+            return lowerData + mapFactor * internalIndex + upperData;
+        }
+    }
+
+    @Override
+    public int toPartialMapIndex(Comparable<?> value) {
+        return mapFactor * getInternalIndex(value);
+    }
+
+    @Override
+    public int getFactorToNext() {
+        return numValues();
+    }
+}

+ 7 - 36
common/src/main/java/malte0811/ferritecore/fastmap/FastMap.java

@@ -2,14 +2,10 @@ package malte0811.ferritecore.fastmap;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
-import malte0811.ferritecore.classloading.ClassDefiner;
 import net.minecraft.state.Property;
 
 import javax.annotation.Nullable;
 import java.util.*;
-import java.util.function.Function;
-import java.util.function.IntFunction;
-import java.util.function.IntSupplier;
 
 public class FastMap<Value> {
     private final List<FastMapKey<?>> keys;
@@ -25,8 +21,9 @@ public class FastMap<Value> {
         ImmutableMap.Builder<Property<?>, Integer> toKeyIndex = ImmutableMap.builder();
         for (Property<?> prop : properties) {
             toKeyIndex.put(prop, keys.size());
-            keys.add(new FastMapKey<>(prop, factorUpTo));
-            factorUpTo *= prop.getAllowedValues().size();
+            FastMapKey<?> nextKey = new CompactFastMapKey<>(prop, factorUpTo);
+            keys.add(nextKey);
+            factorUpTo *= nextKey.getFactorToNext();
         }
         this.keys = ImmutableList.copyOf(keys);
         this.toKeyIndex = toKeyIndex.build();
@@ -73,36 +70,10 @@ public class FastMap<Value> {
         return propId.getValue(stateIndex);
     }
 
-    public ImmutableMap<Property<?>, Comparable<?>> makeValuesFor(int index) {
-        try {
-            class MapAccessor implements Function<Object, Comparable<?>>,
-                    IntFunction<Map.Entry<Property<?>, Comparable<?>>>, IntSupplier {
-                @Override
-                public Comparable<?> apply(Object obj) {
-                    if (obj instanceof Property<?>) {
-                        return getValue(index, (Property<?>) obj);
-                    } else {
-                        return null;
-                    }
-                }
-
-                @Override
-                public Map.Entry<Property<?>, Comparable<?>> apply(int subIndex) {
-                    return new AbstractMap.SimpleImmutableEntry<>(
-                            getKey(subIndex).getProperty(), getKey(subIndex).getValue(index)
-                    );
-                }
-
-                @Override
-                public int getAsInt() {
-                    return numProperties();
-                }
-            }
-            MapAccessor func = new MapAccessor();
-            return ClassDefiner.makeMap(func);
-        } catch (Throwable throwable) {
-            throw new RuntimeException(throwable);
-        }
+    public Map.Entry<Property<?>, Comparable<?>> getEntry(int propertyIndex, int stateIndex) {
+        return new AbstractMap.SimpleImmutableEntry<>(
+                getKey(propertyIndex).getProperty(), getKey(propertyIndex).getValue(stateIndex)
+        );
     }
 
     public <T extends Comparable<T>>

+ 16 - 28
common/src/main/java/malte0811/ferritecore/fastmap/FastMapKey.java

@@ -1,47 +1,35 @@
 package malte0811.ferritecore.fastmap;
 
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
 import net.minecraft.state.Property;
 
-import java.util.List;
-import java.util.Map;
-
-class FastMapKey<T extends Comparable<T>> {
-    private final int mapFactor;
+public abstract class FastMapKey<T extends Comparable<T>> {
     private final PropertyIndexer<T> indexer;
 
-    FastMapKey(Property<T> property, int mapFactor) {
+    protected FastMapKey(Property<T> property) {
         this.indexer = PropertyIndexer.makeIndexer(property);
-        this.mapFactor = mapFactor;
     }
 
-    T getValue(int mapIndex) {
-        int index = (mapIndex / mapFactor) % indexer.size();
-        return indexer.byIndex(index);
-    }
+    abstract T getValue(int mapIndex);
+
+    abstract int replaceIn(int mapIndex, T newValue);
+
+    abstract int toPartialMapIndex(Comparable<?> value);
 
-    int replaceIn(int mapIndex, T newValue) {
-        final int lowerData = mapIndex % mapFactor;
-        final int upperFactor = mapFactor * indexer.size();
-        final int upperData = mapIndex - mapIndex % upperFactor;
-        int internalIndex = getInternalIndex(newValue);
-        if (internalIndex < 0) {
-            return -1;
-        } else {
-            return lowerData + mapFactor * internalIndex + upperData;
-        }
+    abstract int getFactorToNext();
+
+    protected final int numValues() {
+        return indexer.numValues();
     }
 
-    Property<T> getProperty() {
+    protected final Property<T> getProperty() {
         return indexer.getProperty();
     }
 
-    int toPartialMapIndex(Comparable<?> value) {
-        return mapFactor * getInternalIndex(value);
+    protected final int getInternalIndex(Comparable<?> value) {
+        return indexer.toIndex((T) value);
     }
 
-    private int getInternalIndex(Comparable<?> value) {
-        return indexer.toIndex((T) value);
+    protected final T byInternalIndex(int internalIndex) {
+        return indexer.byIndex(internalIndex);
     }
 }

+ 26 - 22
common/src/main/java/malte0811/ferritecore/fastmap/PropertyIndexer.java

@@ -3,6 +3,7 @@ package malte0811.ferritecore.fastmap;
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
+import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
 import net.minecraft.state.BooleanProperty;
 import net.minecraft.state.EnumProperty;
 import net.minecraft.state.IntegerProperty;
@@ -10,7 +11,6 @@ import net.minecraft.state.Property;
 import net.minecraft.state.properties.BlockStateProperties;
 import net.minecraft.util.Direction;
 import net.minecraft.util.IStringSerializable;
-import org.apache.logging.log4j.LogManager;
 
 import java.util.Collection;
 import java.util.Comparator;
@@ -18,36 +18,45 @@ import java.util.List;
 import java.util.Map;
 
 public abstract class PropertyIndexer<T extends Comparable<T>> {
+    private static final Map<Property<?>, PropertyIndexer<?>> KNOWN_INDEXERS = new Object2ObjectOpenHashMap<>();
+
     private final Property<T> property;
+    private final int numValues;
 
     public static <T extends Comparable<T>> PropertyIndexer<T> makeIndexer(Property<T> prop) {
-        PropertyIndexer<?> result = null;
-        if (prop instanceof BooleanProperty) {
-            result = new BoolIndexer((BooleanProperty) prop);
-        } else if (prop instanceof IntegerProperty) {
-            result = new IntIndexer((IntegerProperty) prop);
-        } else if (prop == BlockStateProperties.FACING) {
-            result = new WeirdVanillaDirectionIndexer();
-        } else if (prop instanceof EnumProperty<?>) {
-            result = new EnumIndexer<>((EnumProperty<?>) prop);
-        }
-        if (result == null || !result.isValid()) {
-            return new GenericIndexer<>(prop);
-        } else {
-            return (PropertyIndexer<T>) result;
+        synchronized (KNOWN_INDEXERS) {
+            PropertyIndexer<?> unchecked = KNOWN_INDEXERS.computeIfAbsent(prop, propInner -> {
+                PropertyIndexer<?> result = null;
+                if (propInner instanceof BooleanProperty) {
+                    result = new BoolIndexer((BooleanProperty) propInner);
+                } else if (propInner instanceof IntegerProperty) {
+                    result = new IntIndexer((IntegerProperty) propInner);
+                } else if (propInner == BlockStateProperties.FACING) {
+                    result = new WeirdVanillaDirectionIndexer();
+                } else if (propInner instanceof EnumProperty<?>) {
+                    result = new EnumIndexer<>((EnumProperty<?>) propInner);
+                }
+                if (result == null || !result.isValid()) {
+                    return new GenericIndexer<>(propInner);
+                } else {
+                    return result;
+                }
+            });
+            return (PropertyIndexer<T>) unchecked;
         }
     }
 
     protected PropertyIndexer(Property<T> property) {
         this.property = property;
+        this.numValues = property.getAllowedValues().size();
     }
 
     public Property<T> getProperty() {
         return property;
     }
 
-    public int size() {
-        return property.getAllowedValues().size();
+    public int numValues() {
+        return numValues;
     }
 
     public abstract T byIndex(int index);
@@ -59,11 +68,6 @@ public abstract class PropertyIndexer<T extends Comparable<T>> {
         int index = 0;
         for (T val : allowed) {
             if (toIndex(val) != index || !val.equals(byIndex(index))) {
-                //TODO remove
-                LogManager.getLogger().info(
-                        "Property {} will use generic indexer, specialized is inconsistent at {}, {}",
-                        getProperty(), val, index
-                );
                 return false;
             }
             ++index;

+ 0 - 7
common/src/main/java/malte0811/ferritecore/impl/BlockStateCacheImpl.java

@@ -16,8 +16,6 @@ import net.minecraft.util.math.shapes.VoxelShape;
 import net.minecraft.util.math.shapes.VoxelShapeArray;
 import net.minecraft.util.math.shapes.VoxelShapes;
 import net.minecraft.world.IBlockReader;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
 
 import java.util.function.Function;
 
@@ -38,11 +36,6 @@ public class BlockStateCacheImpl {
     public static int projectCalls = 0;
 
     public static void resetCaches() {
-        //TODO remove
-        Logger logger = LogManager.getLogger();
-        logger.info("Collide stats: Cache size: {}, calls: {}", CACHE_COLLIDE.size(), collideCalls);
-        logger.info("Project stats: Cache size: {}, calls: {}", CACHE_PROJECT.size(), projectCalls);
-
         CACHE_COLLIDE.clear();
         CACHE_COLLIDE.trim();
         collideCalls = 0;

+ 2 - 1
common/src/main/java/malte0811/ferritecore/impl/StateHolderImpl.java

@@ -1,5 +1,6 @@
 package malte0811.ferritecore.impl;
 
+import malte0811.ferritecore.classloading.FastImmutableMapDefiner;
 import malte0811.ferritecore.ducks.FastMapStateHolder;
 import malte0811.ferritecore.fastmap.FastMap;
 import malte0811.ferritecore.mixin.config.FerriteConfig;
@@ -27,7 +28,7 @@ public class StateHolderImpl {
         int index = holder.getStateMap().getIndexOf(holder.getVanillaPropertyMap());
         holder.setStateIndex(index);
         if (FerriteConfig.PROPERTY_MAP.isEnabled()) {
-            holder.replacePropertyMap(holder.getStateMap().makeValuesFor(holder.getStateIndex()));
+            holder.replacePropertyMap(FastImmutableMapDefiner.makeMap(holder));
         }
     }
 }

+ 0 - 1
settings.gradle

@@ -1,6 +1,5 @@
 pluginManagement {
     repositories {
-        jcenter()
         maven { url "https://maven.fabricmc.net/" }
         maven { url "https://dl.bintray.com/shedaniel/cloth" }
         maven { url "https://files.minecraftforge.net/maven/" }

+ 38 - 16
summary.md

@@ -48,21 +48,16 @@ Side: both
 Mixin subpackage: `fastmap`  
 
 ### 3. BlockState property storage
-Each blockstate stores its properties as an `ImmutableMap<Property<?>, Comparable<?>>`,
-which takes around 170 MB in total. Most operations do not actually require this map, they
-can be implemented with similar speed using the `FastMap` from the previous point.  There
-is one problematic exception: `getValues`. This is a simple getter for the property map in
-vanilla, if the map is no longer stored it needs to be created on the fly. The method
-returns an `ImmutableMap`, which can't be easily extended due to package-private methods.
-Otherwise it would be possible to return a `Map`-implementation that only requires a
-`FastMap` and the index in that `FastMap`. The current approach to this is to implement a
-second version of `getValues` which returns such a custom map, and to replace calls to the
-old one with the new implementation where possible.
+Each blockstate stores its properties as an `ImmutableMap<Property<?>, Comparable<?>>`, which takes around 170 MB in
+total. Replacing this `ImmutableMap` by a custom implementation based on the `FastMap` from the previous point (loaded
+with some classloader trickery) removes most of that memory usage.
 
 Saved memory: Around 170 MB  
-CPU impact: unclear (see second paragraph)  
+CPU impact: probably around zero  
 Side: both  
-Mixin subpackage: `nopropertymap`  
+Mixin subpackage: None (implemented as part of the `fastmap` code)  
+Notes: If this is ever included in Forge the custom `ImmutableMap` should probably be replaced by a regular map, and a
+new `getValues` method returning a `map` rather than an `ImmutableMap` should be added
 
 ### 4. Multipart model predicate caching
 Each multipart model stores a number of predicates to determine which parts to show under
@@ -102,12 +97,39 @@ first part would require changing what constructor the constructor in question r
 
 ### 6. Multipart model instances
 By default every blockstate using a multipart model gets its own instance of that multipart model. Since multipart
-models are generally used for blocks with a lot of states this means a lot of instances, and a lot of wasted memory.
-The only input data for a multipart model is a `List<Pair<Predicate<BlockState>, IBakedModel>>`. The predicate is
-already deduplicated by point 4, so it is very easy to use the same instance for equivalent lists. This reduces the
-number of instance from about 200k to 1.5k (DW20 1.2.0).
+models are generally used for blocks with a lot of states this means a lot of instances, and a lot of wasted memory. The
+only input data for a multipart model is a `List<Pair<Predicate<BlockState>, IBakedModel>>`. The predicate is already
+deduplicated by point 4, so it is very easy to use the same instance for equivalent lists. This reduces the number of
+instance from about 200k to 1.5k (DW20 1.2.0).
 
 Saved memory: Close to 200 MB  
 CPU impact: Slight during loading, zero at runtime  
 Side: client  
 Mixin subpackage: `dedupmultipart`
+
+### 7. Blockstate cache deduplication
+
+Blockstates that are not marked as "variable opacity" cache their collision and render shapes. This uses around 200 MB,
+mostly just because there are a lot of blockstates. Many blocks have the same shapes, so in a lot of cases the existing
+instances can be reused. There is an additional problem with this: Many mods cache their own render/collision shapes
+internally, even if they're cached by the vanilla cache. This can be worked around by replacing the "internals" of the
+voxel shape returned by the block with the internals of the "canonical" instance of that shape. Vanilla assumes that
+shapes are immutable once created, so this should be safe.
+
+Saved memory: Around 200 MB  
+CPU impact: Some during loading and joining (<1 second with the DW20 pack), none afterwards  
+Side: both  
+Mixin subpackage: `blockstatecache`
+
+### 8. Quad deduplication
+
+Baked quads (especially their `int[]`s storing vertex data) account for around 340 MB in total, a lot of which is just
+necessary to store the models. But it is also common for multiple quads to have the same data. Using the same `int[]`
+instance for quads with the same data reduces the amount of memory used by quads to around 195 MB. This is not
+technically 100% safe to do, since the `int[]` can theoretically be modified at any time. Only applying the optimization
+to quads used in `SimpleBakedModel`s is probably a good compromise for this.
+
+Saved memory: Close to 150 MB  
+CPU impact: Some during model loading, none afterwards  
+Side: client  
+Mixin subpackage: `bakedquad`