From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: JellySquid 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. + *

+ * 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, Comparable, IBlockData> BLOCK_STATE_TABLE = + new FastImmutableTableCache<>(); + + public static final FastImmutableTableCache, Comparable, Fluid> FLUID_STATE_TABLE = + new FastImmutableTableCache<>(); + + @SuppressWarnings("unchecked") + public static FastImmutableTableCache, Comparable, S> getTableCache(O owner) { + if (owner instanceof Block) { + return (FastImmutableTableCache, Comparable, S>) BLOCK_STATE_TABLE; + } else if (owner instanceof FluidType) { + return (FastImmutableTableCache, 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 implements Table { + 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 table, FastImmutableTableCache cache) { + if (cache == null) { + throw new IllegalArgumentException("Cache must not be null"); + } + + float loadFactor = Hash.DEFAULT_LOAD_FACTOR; + + Set rowKeySet = table.rowKeySet(); + Set 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 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 void createIndex(T[] keys, int[] indices, int mask, Collection 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 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 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 table) { + throw new UnsupportedOperationException(); + } + + @Override + public V remove(Object rowKey, Object columnKey) { + throw new UnsupportedOperationException(); + } + + @Override + public Map row(R rowKey) { + throw new UnsupportedOperationException(); + } + + @Override + public Map column(C columnKey) { + throw new UnsupportedOperationException(); + } + + @Override + public Set> cellSet() { + throw new UnsupportedOperationException(); + } + + @Override + public Set rowKeySet() { + throw new UnsupportedOperationException(); + } + + @Override + public Set columnKeySet() { + throw new UnsupportedOperationException(); + } + + @Override + public Collection values() { + throw new UnsupportedOperationException(); + } + + @Override + public Map> rowMap() { + throw new UnsupportedOperationException(); + } + + @Override + public Map> 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 The type used by the + * @param + * @param + */ +public class FastImmutableTableCache { + private final ObjectOpenCustomHashSet rows; + private final ObjectOpenCustomHashSet columns; + private final ObjectOpenCustomHashSet values; + + private final ObjectOpenCustomHashSet indices; + + @SuppressWarnings("unchecked") + public FastImmutableTableCache() { + this.rows = new ObjectOpenCustomHashSet<>((Hash.Strategy) ObjectArrays.HASH_STRATEGY); + this.columns = new ObjectOpenCustomHashSet<>((Hash.Strategy) ObjectArrays.HASH_STRATEGY); + this.values = new ObjectOpenCustomHashSet<>((Hash.Strategy) 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 7b8036840dcca16904b3063c209d5ff10ab8a6af..6bc5fedc4d8effa530799eac814f626dfeed2105 100644 --- a/src/main/java/net/minecraft/server/Chunk.java +++ b/src/main/java/net/minecraft/server/Chunk.java @@ -336,6 +336,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 } public org.bukkit.Chunk bukkitChunk; diff --git a/src/main/java/net/minecraft/server/ChunkRegionLoader.java b/src/main/java/net/minecraft/server/ChunkRegionLoader.java index 5f04591193d58ba7897194142da5efcbec3763dd..bf4f34b5f4f7473921589b2f001570a38e96c5ce 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 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 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 { } } + // 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 map; // Paper protected NBTTagCompound(Map map) { + // Yatopia start - Port hydrogen + /* this.map = map; + */ + this.map = map instanceof Object2ObjectMap ? map : new Object2ObjectOpenHashMap<>(map); + // Yatopia end } public NBTTagCompound() {