mirror of
https://github.com/Winds-Studio/Leaf.git
synced 2025-12-27 19:09:22 +00:00
ClassInstanceMultiMap belongs to Minecraft vanilla entity storage. And is unused, since replaced by spottedleaf's entity storage (rewrite chunk system). However these patches might be useful for vanilla entity storage if is used.
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 4d06df242ab73411bdefc4770e131b27a6ea668a..fb936344ba363490f2255682678f08e219fdec3d 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()
|
|
);
|