mirror of
https://github.com/Winds-Studio/Leaf.git
synced 2025-12-19 15:09:25 +00:00
342 lines
17 KiB
Diff
342 lines
17 KiB
Diff
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
|
From: Taiyou06 <kaandindar21@gmail.com>
|
|
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<SaveState[]> 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<ArrayDeque<byte[]>> WORKING_BYTES_POOL = ThreadLocal.withInitial(ArrayDeque::new);
|
|
+ static final ThreadLocal<ArrayDeque<byte[]>> 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<byte[]> 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<byte[]> 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 1cc33a038060aaf5258ee4f1deb19b4a1be59a29..a14247b043715de50b253ab1f10a9244003f8797 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<BlockState> states;
|
|
private PalettedContainer<Holder<Biome>> 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<BlockState> states, PalettedContainer<Holder<Biome>> 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);
|
|
}
|
|
}
|
|
diff --git a/net/minecraft/world/level/chunk/storage/SerializableChunkData.java b/net/minecraft/world/level/chunk/storage/SerializableChunkData.java
|
|
index ab1fc7ca0639f9e71933f623258bb8d123988262..781f9470dc0115a464ce4711aecafb0f11a016b7 100644
|
|
--- a/net/minecraft/world/level/chunk/storage/SerializableChunkData.java
|
|
+++ b/net/minecraft/world/level/chunk/storage/SerializableChunkData.java
|
|
@@ -495,14 +495,16 @@ public record SerializableChunkData(
|
|
throw new IllegalArgumentException("Chunk can't be serialized: " + chunk);
|
|
} else {
|
|
ChunkPos pos = chunk.getPos();
|
|
- List<SerializableChunkData.SectionData> list = new ArrayList<>(); final List<SerializableChunkData.SectionData> 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<SerializableChunkData.SectionData> 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();
|
|
@@ -520,10 +522,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) {
|
|
@@ -534,12 +546,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<CompoundTag> 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<CompoundTag> 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) {
|
|
@@ -547,15 +563,27 @@ public record SerializableChunkData(
|
|
}
|
|
}
|
|
|
|
- List<CompoundTag> list2 = new ArrayList<>();
|
|
+ // Leaf start - Optimize chunkUnload
|
|
+ // For entities, use an initial estimated capacity if it's a ProtoChunk
|
|
+ List<CompoundTag> 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<Heightmap.Types, long[]> map = new EnumMap<>(Heightmap.Types.class);
|
|
@@ -563,14 +591,26 @@ public record SerializableChunkData(
|
|
for (Entry<Heightmap.Types, Heightmap> 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()
|
|
);
|