From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: JellySquid Date: Wed, 20 Jan 2021 20:14:55 +0100 Subject: [PATCH] Port hydrogen 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/BiomeStorage.java b/src/main/java/net/minecraft/server/BiomeStorage.java index b7f5c932197529c0ea045cfe696fd5da1ccc7202..af6245ba0c6b1389703722596ab7aa50deaf9e9f 100644 --- a/src/main/java/net/minecraft/server/BiomeStorage.java +++ b/src/main/java/net/minecraft/server/BiomeStorage.java @@ -1,23 +1,32 @@ package net.minecraft.server; import javax.annotation.Nullable; + +import it.unimi.dsi.fastutil.objects.Reference2ShortMap; +import it.unimi.dsi.fastutil.objects.Reference2ShortOpenHashMap; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; public class BiomeStorage implements BiomeManager.Provider { private static final Logger LOGGER = LogManager.getLogger(); - private static final int e = (int) Math.round(Math.log(16.0D) / Math.log(2.0D)) - 2; + private static final int e = (int) Math.round(Math.log(16.0D) / Math.log(2.0D)) - 2; public static final int HORIZONTAL_SECTION_COUNT = e; // Yatopia - OBFHELPER private static final int f = (int) Math.round(Math.log(256.0D) / Math.log(2.0D)) - 2; public static final int a = 1 << BiomeStorage.e + BiomeStorage.e + BiomeStorage.f; - public static final int b = (1 << BiomeStorage.e) - 1; - public static final int c = (1 << BiomeStorage.f) - 1; + public static final int b = (1 << BiomeStorage.e) - 1; public static final int HORIZONTAL_BIT_MASK = b; // Yatopia - OBFHELPER + public static final int c = (1 << BiomeStorage.f) - 1; public static final int VERTICAL_BIT_MASK = c; // Yatopia - OBFHELPER public final Registry registry; - private final BiomeBase[] h; + private BiomeBase[] h; private BiomeBase[] getData() { return h; } // Yatopia - OBFHELPER , remove final + + // Yatopia start - Port hydrogen + private BiomeBase[] palette; + private DataBits intArray; + // Yatopia end public BiomeStorage(Registry registry, BiomeBase[] abiomebase) { this.registry = registry; this.h = abiomebase; + this.createCompact(); // Yatopia - Port hydrogen } private BiomeStorage(Registry registry) { @@ -37,6 +46,7 @@ public class BiomeStorage implements BiomeManager.Provider { this.h[k] = worldchunkmanager.getBiome(i + l, i1, j + j1); } + this.createCompact(); // Yatopia - Port hydrogen } public BiomeStorage(Registry registry, ChunkCoordIntPair chunkcoordintpair, WorldChunkManager worldchunkmanager, @Nullable int[] aint) { @@ -67,9 +77,11 @@ public class BiomeStorage implements BiomeManager.Provider { } } + this.createCompact(); // Yatopia - Port hydrogen } public int[] a() { + /* // Yatopia start - Port hydrogen int[] aint = new int[this.h.length]; for (int i = 0; i < this.h.length; ++i) { @@ -77,15 +89,33 @@ public class BiomeStorage implements BiomeManager.Provider { } return aint; + */ + int size = this.intArray.getSize(); + int[] array = new int[size]; + + for (int i = 0; i < size; ++i) { + array[i] = this.registry.a(this.palette[this.intArray.get(i)]); + } + + return array; + // Yatopia end } @Override public BiomeBase getBiome(int i, int j, int k) { + /* // Yatopia start - Port hydrogen int l = i & BiomeStorage.b; int i1 = MathHelper.clamp(j, 0, BiomeStorage.c); int j1 = k & BiomeStorage.b; return this.h[i1 << BiomeStorage.e + BiomeStorage.e | j1 << BiomeStorage.e | l]; + */ + int x = i & HORIZONTAL_BIT_MASK; + int y = MathHelper.clamp(j, 0, VERTICAL_BIT_MASK); + int z = k & HORIZONTAL_BIT_MASK; + + return this.palette[this.intArray.get(y << HORIZONTAL_SECTION_COUNT + HORIZONTAL_SECTION_COUNT | z << HORIZONTAL_SECTION_COUNT | x)]; + // Yatopia end } // CraftBukkit start @@ -94,7 +124,74 @@ public class BiomeStorage implements BiomeManager.Provider { int i1 = MathHelper.clamp(j, 0, BiomeStorage.c); int j1 = k & BiomeStorage.b; - this.h[i1 << BiomeStorage.e + BiomeStorage.e | j1 << BiomeStorage.e | l] = biome; + this.palette[this.intArray.get(l << HORIZONTAL_SECTION_COUNT + HORIZONTAL_SECTION_COUNT | i1 << HORIZONTAL_SECTION_COUNT | j1)] = biome; // Yatopia - Port Hydrogen } // CraftBukkit end + + + // Yatopia Start - Port Hydrogen + private void createCompact() { + if (this.intArray != null || this.getData()[0] == null) { + return; + } + + Reference2ShortOpenHashMap paletteTable = this.createPalette(); + BiomeBase[] paletteIndexed = new BiomeBase[paletteTable.size()]; + + for (Reference2ShortMap.Entry entry : paletteTable.reference2ShortEntrySet()) { + paletteIndexed[entry.getShortValue()] = entry.getKey(); + } + + int packedIntSize = Math.max(2, MathHelper.e(paletteTable.size())); + DataBits integerArray = new DataBits(packedIntSize, a); + + BiomeBase prevBiome = null; + short prevId = -1; + + for (int i = 0; i < this.getData().length; i++) { + BiomeBase biome = this.getData()[i]; + short id; + + if (prevBiome == biome) { + id = prevId; + } else { + id = paletteTable.getShort(biome); + + if (id < 0) { + throw new IllegalStateException("Palette is missing entry: " + biome); + } + + prevId = id; + prevBiome = biome; + } + + integerArray.set(i, id); + } + + this.palette = paletteIndexed; + this.intArray = integerArray; + this.h = null; + } + + private Reference2ShortOpenHashMap createPalette() { + Reference2ShortOpenHashMap map = new Reference2ShortOpenHashMap<>(); + map.defaultReturnValue(Short.MIN_VALUE); + + BiomeBase prevObj = null; + short id = 0; + + for (BiomeBase obj : this.getData()) { + if (obj == prevObj) { + continue; + } + + if (map.getShort(obj) < 0) { + map.put(obj, id++); + } + + prevObj = obj; + } + + return map; + } // Yatopia end } diff --git a/src/main/java/net/minecraft/server/Chunk.java b/src/main/java/net/minecraft/server/Chunk.java index 8b7fd21e6b366196fbc9cd44a340335c4cf9205f..8b8ecc2d8925b3c209d99e101f6e478b08d86db9 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() {