From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: JellySquid <jellysquid+atwork@protonmail.com>
Date: Wed, 20 Jan 2021 20:14:55 +0100
Subject: [PATCH] Port hydrogen

Original code by JellySquid, licensed under GNU Lesser General Public License v3.0
you can find the original code on https://github.com/CaffeineMC/hydrogen-fabric/ (Yarn mappings)

diff --git a/src/main/java/me/jellysquid/mods/lithium/common/cache/StatePropertyTableCache.java b/src/main/java/me/jellysquid/mods/lithium/common/cache/StatePropertyTableCache.java
new file mode 100644
index 0000000000000000000000000000000000000000..dd18bb8b3550d0fe9ba2589151baceb9100ae6f2
--- /dev/null
+++ b/src/main/java/me/jellysquid/mods/lithium/common/cache/StatePropertyTableCache.java
@@ -0,0 +1,38 @@
+package me.jellysquid.mods.lithium.common.cache;
+
+import me.jellysquid.mods.lithium.common.collections.FastImmutableTableCache;
+import net.minecraft.server.Block;
+import net.minecraft.server.Fluid;
+import net.minecraft.server.FluidType;
+import net.minecraft.server.IBlockData;
+import net.minecraft.server.IBlockState;
+
+/**
+ * Many of the column and row key arrays in block state tables will be duplicated, leading to an unnecessary waste of
+ * memory. Since we have very limited options for trying to construct more optimized table types without throwing
+ * maintainability or mod compatibility out the window, this class acts as a dirty way to find and de-duplicate arrays
+ * after we construct our table types.
+ * <p>
+ * While this global cache does not provide the ability to remove or clear entries from it, the reality is that it
+ * shouldn't matter because block state tables are only initialized once and remain loaded for the entire lifetime of
+ * the game. Even in the event of classloader pre-boot shenanigans, we still shouldn't leak memory as our cache will be
+ * dropped along with the rest of the loaded classes when the class loader is reaped.
+ */
+public class StatePropertyTableCache {
+    public static final FastImmutableTableCache<IBlockState<?>, Comparable<?>, IBlockData> BLOCK_STATE_TABLE =
+            new FastImmutableTableCache<>();
+
+    public static final FastImmutableTableCache<IBlockState<?>, Comparable<?>, Fluid> FLUID_STATE_TABLE =
+            new FastImmutableTableCache<>();
+
+    @SuppressWarnings("unchecked")
+    public static <S, O> FastImmutableTableCache<IBlockState<?>, Comparable<?>, S> getTableCache(O owner) {
+        if (owner instanceof Block) {
+            return (FastImmutableTableCache<IBlockState<?>, Comparable<?>, S>) BLOCK_STATE_TABLE;
+        } else if (owner instanceof FluidType) {
+            return (FastImmutableTableCache<IBlockState<?>, Comparable<?>, S>) FLUID_STATE_TABLE;
+        } else {
+            throw new IllegalArgumentException("");
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/me/jellysquid/mods/lithium/common/collections/FastImmutableTable.java b/src/main/java/me/jellysquid/mods/lithium/common/collections/FastImmutableTable.java
new file mode 100644
index 0000000000000000000000000000000000000000..9aa45ecdff223258c4f3252a61b9ccbbed6151d2
--- /dev/null
+++ b/src/main/java/me/jellysquid/mods/lithium/common/collections/FastImmutableTable.java
@@ -0,0 +1,227 @@
+package me.jellysquid.mods.lithium.common.collections;
+
+import com.google.common.collect.Table;
+import it.unimi.dsi.fastutil.Hash;
+import it.unimi.dsi.fastutil.HashCommon;
+import org.apache.commons.lang3.ArrayUtils;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+
+import static it.unimi.dsi.fastutil.HashCommon.arraySize;
+
+public class FastImmutableTable<R, C, V> implements Table<R, C, V> {
+    private R[] rowKeys;
+    private int[] rowIndices;
+    private final int rowMask;
+    private final int rowCount;
+
+    private C[] colKeys;
+    private int[] colIndices;
+    private final int colMask;
+    private final int colCount;
+
+    private V[] values;
+    private final int size;
+
+    @SuppressWarnings("unchecked")
+    public FastImmutableTable(Table<R, C, V> table, FastImmutableTableCache<R, C, V> cache) {
+        if (cache == null) {
+            throw new IllegalArgumentException("Cache must not be null");
+        }
+
+        float loadFactor = Hash.DEFAULT_LOAD_FACTOR;
+
+        Set<R> rowKeySet = table.rowKeySet();
+        Set<C> colKeySet = table.columnKeySet();
+
+        this.rowCount = rowKeySet.size();
+        this.colCount = colKeySet.size();
+
+        int rowN = arraySize(this.rowCount, loadFactor);
+        int colN = arraySize(this.colCount, loadFactor);
+
+        this.rowMask = rowN - 1;
+        this.rowKeys = (R[]) new Object[rowN];
+        this.rowIndices = new int[rowN];
+
+        this.colMask = colN - 1;
+        this.colKeys = (C[]) new Object[colN];
+        this.colIndices = new int[colN];
+
+        this.createIndex(this.colKeys, this.colIndices, this.colMask, colKeySet);
+        this.createIndex(this.rowKeys, this.rowIndices, this.rowMask, rowKeySet);
+
+        this.values = (V[]) new Object[this.rowCount * this.colCount];
+
+        for (Cell<R, C, V> cell : table.cellSet()) {
+            int colIdx = this.getIndex(this.colKeys, this.colIndices, this.colMask, cell.getColumnKey());
+            int rowIdx = this.getIndex(this.rowKeys, this.rowIndices, this.rowMask, cell.getRowKey());
+
+            if (colIdx < 0 || rowIdx < 0) {
+                throw new IllegalStateException("Missing index for " + cell);
+            }
+
+            this.values[this.colCount * rowIdx + colIdx] = cell.getValue();
+        }
+
+        this.size = table.size();
+
+        this.rowKeys = cache.dedupRows(this.rowKeys);
+        this.rowIndices = cache.dedupIndices(this.rowIndices);
+
+        this.colIndices = cache.dedupIndices(this.colIndices);
+        this.colKeys = cache.dedupColumns(this.colKeys);
+
+        this.values = cache.dedupValues(this.values);
+    }
+
+    private <T> void createIndex(T[] keys, int[] indices, int mask, Collection<T> iterable) {
+        int index = 0;
+
+        for (T obj : iterable) {
+            int i = this.find(keys, mask, obj);
+
+            if (i < 0) {
+                int pos = -i - 1;
+
+                keys[pos] = obj;
+                indices[pos] = index++;
+            }
+        }
+    }
+
+    private <T> int getIndex(T[] keys, int[] indices, int mask, T key) {
+        int pos = this.find(keys, mask, key);
+
+        if (pos < 0) {
+            return -1;
+        }
+
+        return indices[pos];
+    }
+
+    @Override
+    public boolean contains(Object rowKey, Object columnKey) {
+        return this.get(rowKey, columnKey) != null;
+    }
+
+    @Override
+    public boolean containsRow(Object rowKey) {
+        return this.find(this.rowKeys, this.rowMask, rowKey) >= 0;
+    }
+
+    @Override
+    public boolean containsColumn(Object columnKey) {
+        return this.find(this.colKeys, this.colMask, columnKey) >= 0;
+    }
+
+    @Override
+    public boolean containsValue(Object value) {
+        return ArrayUtils.contains(this.values, value);
+    }
+
+    @Override
+    public V get(Object rowKey, Object columnKey) {
+        final int row = this.getIndex(this.rowKeys, this.rowIndices, this.rowMask, rowKey);
+        final int col = this.getIndex(this.colKeys, this.colIndices, this.colMask, columnKey);
+
+        if (row < 0 || col < 0) {
+            return null;
+        }
+
+        return this.values[this.colCount * row + col];
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return this.size() == 0;
+    }
+
+    @Override
+    public int size() {
+        return this.size;
+    }
+
+    @Override
+    public void clear() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public V put(R rowKey, C columnKey, V val) {
+        throw new UnsupportedOperationException();
+    }
+
+    private <T> int find(T[] key, int mask, T value) {
+        T curr;
+        int pos;
+        // The starting point.
+        if ((curr = key[pos = HashCommon.mix(value.hashCode()) & mask]) == null) {
+            return -(pos + 1);
+        }
+        if (value.equals(curr)) {
+            return pos;
+        }
+        // There's always an unused entry.
+        while (true) {
+            if ((curr = key[pos = pos + 1 & mask]) == null) {
+                return -(pos + 1);
+            }
+            if (value.equals(curr)) {
+                return pos;
+            }
+        }
+    }
+
+    @Override
+    public void putAll(Table<? extends R, ? extends C, ? extends V> table) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public V remove(Object rowKey, Object columnKey) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Map<C, V> row(R rowKey) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Map<R, V> column(C columnKey) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Set<Cell<R, C, V>> cellSet() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Set<R> rowKeySet() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Set<C> columnKeySet() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Collection<V> values() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Map<R, Map<C, V>> rowMap() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Map<C, Map<R, V>> columnMap() {
+        throw new UnsupportedOperationException();
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/me/jellysquid/mods/lithium/common/collections/FastImmutableTableCache.java b/src/main/java/me/jellysquid/mods/lithium/common/collections/FastImmutableTableCache.java
new file mode 100644
index 0000000000000000000000000000000000000000..a5314a0396f4a8f373d855e873820ddddd635a4a
--- /dev/null
+++ b/src/main/java/me/jellysquid/mods/lithium/common/collections/FastImmutableTableCache.java
@@ -0,0 +1,44 @@
+package me.jellysquid.mods.lithium.common.collections;
+
+import it.unimi.dsi.fastutil.Hash;
+import it.unimi.dsi.fastutil.ints.IntArrays;
+import it.unimi.dsi.fastutil.objects.ObjectArrays;
+import it.unimi.dsi.fastutil.objects.ObjectOpenCustomHashSet;
+
+/**
+ * @param <R> The type used by the
+ * @param <C>
+ * @param <V>
+ */
+public class FastImmutableTableCache<R, C, V> {
+    private final ObjectOpenCustomHashSet<R[]> rows;
+    private final ObjectOpenCustomHashSet<C[]> columns;
+    private final ObjectOpenCustomHashSet<V[]> values;
+
+    private final ObjectOpenCustomHashSet<int[]> indices;
+
+    @SuppressWarnings("unchecked")
+    public FastImmutableTableCache() {
+        this.rows = new ObjectOpenCustomHashSet<>((Hash.Strategy<R[]>) ObjectArrays.HASH_STRATEGY);
+        this.columns = new ObjectOpenCustomHashSet<>((Hash.Strategy<C[]>) ObjectArrays.HASH_STRATEGY);
+        this.values = new ObjectOpenCustomHashSet<>((Hash.Strategy<V[]>) ObjectArrays.HASH_STRATEGY);
+
+        this.indices = new ObjectOpenCustomHashSet<>(IntArrays.HASH_STRATEGY);
+    }
+
+    public synchronized V[] dedupValues(V[] values) {
+        return this.values.addOrGet(values);
+    }
+
+    public synchronized R[] dedupRows(R[] rows) {
+        return this.rows.addOrGet(rows);
+    }
+
+    public synchronized C[] dedupColumns(C[] columns) {
+        return this.columns.addOrGet(columns);
+    }
+
+    public synchronized int[] dedupIndices(int[] ints) {
+        return this.indices.addOrGet(ints);
+    }
+}
diff --git a/src/main/java/net/minecraft/server/Chunk.java b/src/main/java/net/minecraft/server/Chunk.java
index 0924f6b484468f3cf3c2d405101c0158c12d69e6..bcf94f13414b1e8cb7438af81448e74f42a24768 100644
--- a/src/main/java/net/minecraft/server/Chunk.java
+++ b/src/main/java/net/minecraft/server/Chunk.java
@@ -298,6 +298,14 @@ public class Chunk implements IChunkAccess {
 
         // CraftBukkit start
         this.bukkitChunk = new org.bukkit.craftbukkit.CraftChunk(this);
+        // Yatopia start - Port hydrogen
+        // Upgrading a ProtoChunk to a Chunk might result in empty sections being copied over
+        // These simply waste memory, and the Chunk will return air blocks for any absent section without issue.
+        for (int i2 = 0; i2 < this.sections.length; i2++) {
+            if (ChunkSection.isEmpty(this.sections[i2])) {
+                this.sections[i2] = null;
+            }
+        } // Yatopia end
         this.entitySlicesManager = new com.tuinity.tuinity.world.ChunkEntitySlices(this.world, this.loc.x, this.loc.z, 0, 15); // TODO update for 1.17 // Tuinity
         this.lightningTick = this.world.random.nextInt(100000) << 1; // Airplane - initialize lightning tick
     }
diff --git a/src/main/java/net/minecraft/server/ChunkRegionLoader.java b/src/main/java/net/minecraft/server/ChunkRegionLoader.java
index e77da341b765725771726283d3a8249b514b40da..c44333ec5b0c1914f7cb9f4b3b39626069136c22 100644
--- a/src/main/java/net/minecraft/server/ChunkRegionLoader.java
+++ b/src/main/java/net/minecraft/server/ChunkRegionLoader.java
@@ -70,8 +70,10 @@ public class ChunkRegionLoader {
     private static final String UNINITIALISED_SKYLIGHT_TAG = "starlight.skylight_uninit";
     private static final String STARLIGHT_VERSION_TAG = "starlight.light_version";
     // Tuinity end - rewrite light engine
+    private static final ThreadLocal<NBTTagCompound> CAPTURED_TAGS = new ThreadLocal<>(); // Yatopia - Port Hydrogen
 
     public static InProgressChunkHolder loadChunk(WorldServer worldserver, DefinedStructureManager definedstructuremanager, VillagePlace villageplace, ChunkCoordIntPair chunkcoordintpair, NBTTagCompound nbttagcompound, boolean distinguish) {
+        CAPTURED_TAGS.set(nbttagcompound); // Yatopia
         ArrayDeque<Runnable> tasksToExecuteOnMain = new ArrayDeque<>();
         // Paper end
         ChunkGenerator chunkgenerator = worldserver.getChunkProvider().getChunkGenerator();
@@ -190,10 +192,22 @@ public class ChunkRegionLoader {
             } else {
                 object2 = protochunkticklist1;
             }
+            // Yatopia start
+            NBTTagCompound strippedTag = new NBTTagCompound();
+            strippedTag.set("Entities", nbttagcompound1.getList("Entities", 10));
+            strippedTag.set("TileEntities", nbttagcompound1.getList("TileEntities", 10));
+            strippedTag.set("ChunkBukkitValues", nbttagcompound1.get("ChunkBukkitValues"));
+            // Yatopia end
 
             object = new Chunk(worldserver.getMinecraftWorld(), chunkcoordintpair, biomestorage, chunkconverter, (TickList) object1, (TickList) object2, j, achunksection, // Paper start - fix massive nbt memory leak due to lambda. move lambda into a container method to not leak scope. Only clone needed NBT keys.
+                    // Yatopia start - Port hydrogen
+                    /*
                 createLoadEntitiesConsumer(new SafeNBTCopy(nbttagcompound1, "TileEntities", "Entities", "ChunkBukkitValues")) // Paper - move CB Chunk PDC into here
+                     */
+                    createLoadEntitiesConsumer(strippedTag)
+                    // Yatopia end
             );// Paper end
+            CAPTURED_TAGS.remove(); // Yatopia
             ((Chunk)object).setBlockNibbles(blockNibbles); // Tuinity - replace light impl
             ((Chunk)object).setSkyNibbles(skyNibbles); // Tuinity - replace light impl
         } else {
diff --git a/src/main/java/net/minecraft/server/DataBits.java b/src/main/java/net/minecraft/server/DataBits.java
index f0c9009fb808ca664a7c3ebaeb8cfa8e2ba7b97e..40e3001bc1742231dcd02cee4423c57d0fd9a991 100644
--- a/src/main/java/net/minecraft/server/DataBits.java
+++ b/src/main/java/net/minecraft/server/DataBits.java
@@ -64,6 +64,7 @@ public class DataBits {
         return j1;
     }
 
+    public final void set(int i, int j){ b(i, j); } // Yatopia - OBFHELPER
     public final void b(int i, int j) { // Paper - make final for inline
         //Validate.inclusiveBetween(0L, (long) (this.e - 1), (long) i); // Paper
         //Validate.inclusiveBetween(0L, this.d, (long) j); // Paper
@@ -74,6 +75,7 @@ public class DataBits {
         this.b[k] = l & ~(this.d << i1) | ((long) j & this.d) << i1;
     }
 
+    public final int get(int i) { return this.a(i); } // Yatopia - OBFHELPER
     public final int a(int i) { // Paper - make final for inline
         //Validate.inclusiveBetween(0L, (long) (this.e - 1), (long) i); // Paper
         int j = this.b(i);
@@ -88,6 +90,7 @@ public class DataBits {
         return this.b;
     }
 
+    public int getSize(){ return b(); } // Yatopia - OBFHELPER
     public int b() {
         return this.e;
     }
diff --git a/src/main/java/net/minecraft/server/IBlockDataHolder.java b/src/main/java/net/minecraft/server/IBlockDataHolder.java
index b19c694cf01bc868dd7c4ec6432b613d19f2ca40..865fd6ef879943634ee2861c6da21ead31306eb2 100644
--- a/src/main/java/net/minecraft/server/IBlockDataHolder.java
+++ b/src/main/java/net/minecraft/server/IBlockDataHolder.java
@@ -8,6 +8,10 @@ import com.google.common.collect.Table;
 import com.google.common.collect.UnmodifiableIterator;
 import com.mojang.serialization.Codec;
 import com.mojang.serialization.MapCodec;
+// Yatopia start
+import me.jellysquid.mods.lithium.common.cache.StatePropertyTableCache;
+import me.jellysquid.mods.lithium.common.collections.FastImmutableTable;
+// Yatopia end
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Iterator;
@@ -143,7 +147,12 @@ public abstract class IBlockDataHolder<O, S> {
                 }
             }
 
+            // Yatopia start - Port hydrogen
+            /*
             this.e = (Table) (table.isEmpty() ? table : ArrayTable.create(table));
+             */
+            this.e = new FastImmutableTable<>((table.isEmpty() ? table : ArrayTable.create(table)), StatePropertyTableCache.getTableCache(this.c));
+            // Yatopia end
         }
     }
 
diff --git a/src/main/java/net/minecraft/server/NBTTagCompound.java b/src/main/java/net/minecraft/server/NBTTagCompound.java
index 06d5acab794e3ee139a11f9b068e8a359c46db2c..2bc71833fea91f1b7b35a431b1d6905a5738e83c 100644
--- a/src/main/java/net/minecraft/server/NBTTagCompound.java
+++ b/src/main/java/net/minecraft/server/NBTTagCompound.java
@@ -20,6 +20,7 @@ import java.util.Set;
 import java.util.UUID;
 import java.util.regex.Pattern;
 import javax.annotation.Nullable;
+import it.unimi.dsi.fastutil.objects.Object2ObjectMap; // Yatopia
 import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; // Paper
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
@@ -74,7 +75,12 @@ public class NBTTagCompound implements NBTBase {
     public final Map<String, NBTBase> map; // Paper
 
     protected NBTTagCompound(Map<String, NBTBase> map) {
+        // Yatopia start - Port hydrogen
+        /*
         this.map = map;
+         */
+        this.map = map instanceof Object2ObjectMap ? map : new Object2ObjectOpenHashMap<>(map);
+        // Yatopia end
     }
 
     public NBTTagCompound() {
