From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Taiyou06 Date: Mon, 14 Apr 2025 20:07:52 +0200 Subject: [PATCH] Optimise chunkUnloads 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/storage/SerializableChunkData.java b/net/minecraft/world/level/chunk/storage/SerializableChunkData.java index 08b98e60518027a6d0aa99d03ba02982355ffa76..bbb871f011e947e426f8be32f0ab71aded4e0980 100644 --- a/net/minecraft/world/level/chunk/storage/SerializableChunkData.java +++ b/net/minecraft/world/level/chunk/storage/SerializableChunkData.java @@ -493,14 +493,16 @@ public record SerializableChunkData( throw new IllegalArgumentException("Chunk can't be serialized: " + chunk); } else { ChunkPos pos = chunk.getPos(); - List list = new ArrayList<>(); final List sectionsList = list; // Paper - starlight - OBFHELPER - LevelChunkSection[] sections = chunk.getSections(); - LevelLightEngine lightEngine = level.getChunkSource().getLightEngine(); // Paper start - starlight final int minLightSection = ca.spottedleaf.moonrise.common.util.WorldUtil.getMinLightSection(level); final int maxLightSection = ca.spottedleaf.moonrise.common.util.WorldUtil.getMaxLightSection(level); final int minBlockSection = ca.spottedleaf.moonrise.common.util.WorldUtil.getMinSection(level); + // Leaf start - Optimize chunkUnload + // Pre-allocate with correct capacity to avoid resizing + final int expectedSectionCount = maxLightSection - minLightSection + 1; + List list = new ArrayList<>(expectedSectionCount); + // Leaf end - Optimize chunkUnload final LevelChunkSection[] chunkSections = chunk.getSections(); final ca.spottedleaf.moonrise.patches.starlight.light.SWMRNibbleArray[] blockNibbles = ((ca.spottedleaf.moonrise.patches.starlight.chunk.StarlightChunk)chunk).starlight$getBlockNibbles(); @@ -518,10 +520,20 @@ public record SerializableChunkData( continue; } + // Leaf start - Optimize chunkUnload + DataLayer blockDataLayer = null; + if (blockNibble != null && blockNibble.data != null) { + blockDataLayer = new DataLayer(blockNibble.data); + } + + DataLayer skyDataLayer = null; + if (skyNibble != null && skyNibble.data != null) { + skyDataLayer = new DataLayer(skyNibble.data); + } + // Leaf end - Optimize chunkUnload + final SerializableChunkData.SectionData sectionData = new SerializableChunkData.SectionData( - lightSection, chunkSection, - blockNibble == null ? null : (blockNibble.data == null ? null : new DataLayer(blockNibble.data)), - skyNibble == null ? null : (skyNibble.data == null ? null : new DataLayer(skyNibble.data)) + lightSection, chunkSection, blockDataLayer, skyDataLayer // Leaf - Optimize chunkUnload ); if (blockNibble != null) { @@ -532,12 +544,16 @@ public record SerializableChunkData( ((ca.spottedleaf.moonrise.patches.starlight.storage.StarlightSectionData)(Object)sectionData).starlight$setSkyLightState(skyNibble.state); } - sectionsList.add(sectionData); + list.add(sectionData); // Leaf - Optimize chunkUnload } // Paper end - starlight - List list1 = new ArrayList<>(chunk.getBlockEntitiesPos().size()); + // Leaf start - Optimize chunkUnload + // Pre-allocate block entities list with exact size needed + final int blockEntityCount = chunk.getBlockEntitiesPos().size(); + List list1 = blockEntityCount > 0 ? new ArrayList<>(blockEntityCount) : java.util.Collections.emptyList(); + if (blockEntityCount > 0) // Leaf - Optimize chunkUnload for (BlockPos blockPos : chunk.getBlockEntitiesPos()) { CompoundTag blockEntityNbtForSaving = chunk.getBlockEntityNbtForSaving(blockPos, level.registryAccess()); if (blockEntityNbtForSaving != null) { @@ -545,15 +561,27 @@ public record SerializableChunkData( } } - List list2 = new ArrayList<>(); + // Leaf start - Optimize chunkUnload + // For entities, use an initial estimated capacity if it's a ProtoChunk + List list2; long[] longs = null; if (chunk.getPersistedStatus().getChunkType() == ChunkType.PROTOCHUNK) { ProtoChunk protoChunk = (ProtoChunk)chunk; - list2.addAll(protoChunk.getEntities()); + // Leaf start - Optimize chunkUnload + int entitySize = protoChunk.getEntities().size(); + if (entitySize > 0) { + list2 = new ArrayList<>(Math.max(16, entitySize)); + list2.addAll(protoChunk.getEntities()); + } else { + list2 = java.util.Collections.emptyList(); + } + // Leaf end - Optimize chunkUnload CarvingMask carvingMask = protoChunk.getCarvingMask(); if (carvingMask != null) { longs = carvingMask.toArray(); } + } else { + list2 = java.util.Collections.emptyList(); // Leaf - Optimize chunkUnload } Map map = new EnumMap<>(Heightmap.Types.class); @@ -561,14 +589,26 @@ public record SerializableChunkData( for (Entry entry : chunk.getHeightmaps()) { if (chunk.getPersistedStatus().heightmapsAfter().contains(entry.getKey())) { long[] rawData = entry.getValue().getRawData(); - map.put(entry.getKey(), (long[])rawData.clone()); + map.put(entry.getKey(), Arrays.copyOf(rawData, rawData.length)); // Leaf - Optimize chunkUnload } } ChunkAccess.PackedTicks ticksForSerialization = chunk.getTicksForSerialization(level.getGameTime()); - ShortList[] lists = Arrays.stream(chunk.getPostProcessing()) - .map(list3 -> list3 != null ? new ShortArrayList(list3) : null) - .toArray(ShortList[]::new); + // Leaf start - Optimize chunkUnload - remove stream + ShortList[] postProcessing = chunk.getPostProcessing(); + ShortList[] lists = new ShortList[postProcessing.length]; + for (int i = 0; i < postProcessing.length; i++) { + ShortList source = postProcessing[i]; + // Only create a new list if there's actual data to copy + if (source != null) { + int size = source.size(); + if (size > 0) { + lists[i] = new ShortArrayList(size); + lists[i].addAll(source); + } + } + } + // Leaf end - Optimize chunkUnload - remove stream CompoundTag compoundTag = packStructureData( StructurePieceSerializationContext.fromLevel(level), pos, chunk.getAllStarts(), chunk.getAllReferences() );