From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Taiyou06 Date: Fri, 28 Feb 2025 01:35:49 +0100 Subject: [PATCH] Optimize chunkUnload diff --git a/ca/spottedleaf/moonrise/patches/starlight/light/SWMRNibbleArray.java b/ca/spottedleaf/moonrise/patches/starlight/light/SWMRNibbleArray.java index 4ca68a903e67606fc4ef0bfa9862a73797121c8b..bed3a64388bb43e47c2ba4e67f7dde5b990d9578 100644 --- a/ca/spottedleaf/moonrise/patches/starlight/light/SWMRNibbleArray.java +++ b/ca/spottedleaf/moonrise/patches/starlight/light/SWMRNibbleArray.java @@ -22,12 +22,24 @@ public final class SWMRNibbleArray { protected static final int INIT_STATE_INIT = 2; // initialised protected static final int INIT_STATE_HIDDEN = 3; // initialised, but conversion to Vanilla data should be treated as if NULL + // Leaf start - Optimize chunkUnload + private volatile boolean cachedIsAllZero = false; + private boolean cachedIsAllZeroValid = false; + + private static final ThreadLocal SAVE_STATE_CACHE = ThreadLocal.withInitial(() -> new SaveState[4]); + public static final int ARRAY_SIZE = 16 * 16 * 16 / (8/4); // blocks / bytes per block // this allows us to maintain only 1 byte array when we're not updating - static final ThreadLocal> WORKING_BYTES_POOL = ThreadLocal.withInitial(ArrayDeque::new); + static final ThreadLocal> WORKING_BYTES_POOL = ThreadLocal.withInitial(() -> { + return new ArrayDeque<>(8); // Limit pool size to avoid memory leaks + }); + // Leaf end - Optimize chunkUnload private static byte[] allocateBytes() { - final byte[] inPool = WORKING_BYTES_POOL.get().pollFirst(); + // Leaf start - Optimize chunkUnload + final ArrayDeque queue = WORKING_BYTES_POOL.get(); + final byte[] inPool = queue.pollFirst(); + // Leaf end - Optimize chunkUnload if (inPool != null) { return inPool; } @@ -36,7 +48,12 @@ public final class SWMRNibbleArray { } private static void freeBytes(final byte[] bytes) { - WORKING_BYTES_POOL.get().addFirst(bytes); + // Leaf start - Optimize chunkUnload + final ArrayDeque queue = WORKING_BYTES_POOL.get(); + if (queue.size() < 8) { // Limit pool size to prevent memory leaks + queue.addFirst(bytes); + } + // Leaf end - Optimize chunkUnload } public static SWMRNibbleArray fromVanilla(final DataLayer nibble) { @@ -131,15 +148,44 @@ public final class SWMRNibbleArray { public SaveState getSaveState() { synchronized (this) { final int state = this.stateVisible; - final byte[] data = this.storageVisible; if (state == INIT_STATE_NULL) { return null; } if (state == INIT_STATE_UNINIT) { - return new SaveState(null, state); + // Leaf start - Optimize chunkUnload + // Use array-based cache instead of WeakHashMap + SaveState[] cache = SAVE_STATE_CACHE.get(); + SaveState cachedState = cache[INIT_STATE_UNINIT]; + if (cachedState == null) { + cachedState = new SaveState(null, state); + cache[INIT_STATE_UNINIT] = cachedState; + } + return cachedState; + } + + // Check if we need to test for all zeros + final byte[] data = this.storageVisible; + boolean zero; + if (cachedIsAllZeroValid) { + zero = cachedIsAllZero; + } else { + zero = isAllZero(data); + cachedIsAllZero = zero; + cachedIsAllZeroValid = true; } - final boolean zero = isAllZero(data); if (zero) { + // Use array-based cache instead of WeakHashMap + SaveState[] cache = SAVE_STATE_CACHE.get(); + int cacheKey = state == INIT_STATE_INIT ? INIT_STATE_UNINIT : -1; + if (cacheKey >= 0) { + SaveState cachedState = cache[cacheKey]; + if (cachedState == null) { + cachedState = new SaveState(null, cacheKey); + cache[cacheKey] = cachedState; + } + return cachedState; + } + // Leaf end - Optimize chunkUnload return state == INIT_STATE_INIT ? new SaveState(null, INIT_STATE_UNINIT) : null; } else { return new SaveState(data.clone(), state); @@ -148,17 +194,28 @@ public final class SWMRNibbleArray { } protected static boolean isAllZero(final byte[] data) { - for (int i = 0; i < (ARRAY_SIZE >>> 4); ++i) { - byte whole = data[i << 4]; - - for (int k = 1; k < (1 << 4); ++k) { - whole |= data[(i << 4) | k]; + // Leaf start - Optimize chunkUnload + // Check in 8-byte chunks + final int longLength = ARRAY_SIZE >>> 3; + for (int i = 0; i < longLength; i++) { + long value = 0; + final int baseIndex = i << 3; + // Combine 8 bytes into a long + for (int j = 0; j < 8; j++) { + value |= ((long) (data[baseIndex + j] & 0xFF)) << (j << 3); + } + if (value != 0) { + return false; } + } - if (whole != 0) { + // Check remaining bytes + for (int i = longLength << 3; i < ARRAY_SIZE; i++) { + if (data[i] != 0) { return false; } } + // Leaf end - Optimize chunkUnload return true; } @@ -349,6 +406,7 @@ public final class SWMRNibbleArray { } this.updatingDirty = false; this.stateVisible = this.stateUpdating; + this.cachedIsAllZeroValid = false; // Leaf - Optimize chunkUnload - Invalidate cache on update } return true; @@ -424,7 +482,16 @@ public final class SWMRNibbleArray { final int shift = (index & 1) << 2; final int i = index >>> 1; - this.storageUpdating[i] = (byte)((this.storageUpdating[i] & (0xF0 >>> shift)) | (value << shift)); + // Leaf start - Optimize chunkUnload + byte oldValue = this.storageUpdating[i]; + byte newValue = (byte)((oldValue & (0xF0 >>> shift)) | (value << shift)); + + // Only invalidate cache if the value actually changes + if (oldValue != newValue) { + this.storageUpdating[i] = newValue; + this.cachedIsAllZeroValid = false; + } + // Leaf end - Optimize chunkUnload } public static final class SaveState { diff --git a/net/minecraft/world/level/chunk/LevelChunkSection.java b/net/minecraft/world/level/chunk/LevelChunkSection.java index b8ac6a9ba7b56ccd034757f7d135d272b8e69e90..dc158e981199b507531af810ff9ced3ca717e39e 100644 --- a/net/minecraft/world/level/chunk/LevelChunkSection.java +++ b/net/minecraft/world/level/chunk/LevelChunkSection.java @@ -24,6 +24,7 @@ public class LevelChunkSection implements ca.spottedleaf.moonrise.patches.block_ private boolean isRandomlyTickingBlocksStatus; // Leaf - Cache random tick block status public final PalettedContainer states; private PalettedContainer> biomes; // CraftBukkit - read/write + private boolean modified = false; // Leaf - Optimize chunkUnload // Paper start - block counting private static final it.unimi.dsi.fastutil.shorts.ShortArrayList FULL_LIST = new it.unimi.dsi.fastutil.shorts.ShortArrayList(16*16*16); @@ -135,6 +136,7 @@ public class LevelChunkSection implements ca.spottedleaf.moonrise.patches.block_ // Paper end - block counting public BlockState setBlockState(int x, int y, int z, BlockState state, boolean useLocks) { + this.modified = true; // Leaf - Optimize chunkUnload BlockState blockState; if (useLocks) { blockState = this.states.getAndSet(x, y, z, state); @@ -328,7 +330,32 @@ public class LevelChunkSection implements ca.spottedleaf.moonrise.patches.block_ this.biomes = palettedContainer; } + // Leaf start - Optimize chunkUnload + private LevelChunkSection(short nonEmptyBlockCount, short tickingBlockCount, short tickingFluidCount, + PalettedContainer states, PalettedContainer> biomes) { + this.nonEmptyBlockCount = nonEmptyBlockCount; + this.tickingBlockCount = tickingBlockCount; + this.tickingFluidCount = tickingFluidCount; + this.states = states; + this.biomes = biomes; + this.isRandomlyTickingBlocksStatus = this.tickingBlockCount > 0; // Leaf - Cache random tick block status + } + // Leaf end - Optimize chunkUnload + public LevelChunkSection copy() { + // Leaf - Optimize chunkUnload + // If the section hasn't been modified and no random ticking blocks/fluids, + // return a lightweight copy that shares palette data + if (!this.modified && this.tickingBlockCount == 0 && this.tickingFluidCount == 0) { + return new LevelChunkSection( + this.nonEmptyBlockCount, + this.tickingBlockCount, + this.tickingFluidCount, + this.states, // Share reference instead of copying + this.biomes // Share reference instead of copying + ); + } + // Leaf end - Optimize chunkUnload return new LevelChunkSection(this); } }