From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> Date: Sun, 6 Jul 2025 02:23:03 +0300 Subject: [PATCH] lithium: faster chunk serialization This patch is based on the following mixins and classes: * "net/caffeinemc/mods/lithium/common/world/chunk/CompactingPackedIntegerArray.java" * "net/caffeinemc/mods/lithium/common/world/chunk/LithiumHashPalette.java" * "net/caffeinemc/mods/lithium/mixin/chunk/serialization/SimpleBitStorageMixin.java" * "net/caffeinemc/mods/lithium/mixin/chunk/serialization/PalettedContainerMixin.java" By: Angeline As part of: Lithium (https://github.com/CaffeineMC/lithium) Licensed under: LGPL-3.0 (https://www.gnu.org/licenses/lgpl-3.0.html) diff --git a/net/minecraft/util/BitStorage.java b/net/minecraft/util/BitStorage.java index 02502d50f0255f5bbcc0ecb965abb48cc1a112da..89c65b8e4b99b78ec847f0ef958166a645ed4324 100644 --- a/net/minecraft/util/BitStorage.java +++ b/net/minecraft/util/BitStorage.java @@ -38,4 +38,6 @@ public interface BitStorage extends ca.spottedleaf.moonrise.patches.block_counti return ret; } // Paper end - block counting + + void compact(net.minecraft.world.level.chunk.Palette srcPalette, net.minecraft.world.level.chunk.Palette dstPalette, short[] out, net.minecraft.world.level.chunk.PalettedContainer resizeHandler); // DivineMC - lithium: faster chunk serialization } diff --git a/net/minecraft/util/SimpleBitStorage.java b/net/minecraft/util/SimpleBitStorage.java index e6306a68c8652d4c5d22d5ecb1416f5f931f76ee..536cf90d2c4bccabe67bffde6bc4fa644a9e7d63 100644 --- a/net/minecraft/util/SimpleBitStorage.java +++ b/net/minecraft/util/SimpleBitStorage.java @@ -465,4 +465,44 @@ public class SimpleBitStorage implements BitStorage { super(message); } } + + // DivineMC start - lithium: faster chunk serialization + @Override + public void compact(net.minecraft.world.level.chunk.Palette srcPalette, net.minecraft.world.level.chunk.Palette dstPalette, short[] out, net.minecraft.world.level.chunk.PalettedContainer resizeHandler) { + if (this.size >= Short.MAX_VALUE) { + throw new IllegalStateException("Array too large"); + } + + if (this.size != out.length) { + throw new IllegalStateException("Array size mismatch"); + } + + short[] mappings = new short[(int) (this.mask + 1)]; + + int idx = 0; + + for (long word : this.data) { + long bits = word; + + for (int elementIdx = 0; elementIdx < this.valuesPerLong; ++elementIdx) { + int value = (int) (bits & this.mask); + int remappedId = mappings[value]; + + if (remappedId == 0) { + remappedId = dstPalette.idFor(srcPalette.valueFor(value), resizeHandler) + 1; + mappings[value] = (short) remappedId; + } + + out[idx] = (short) (remappedId - 1); + bits >>= this.bits; + + ++idx; + + if (idx >= this.size) { + return; + } + } + } + } + // DivineMC end - lithium: faster chunk serialization } diff --git a/net/minecraft/util/ZeroBitStorage.java b/net/minecraft/util/ZeroBitStorage.java index 09fd99c9cbd23b5f3c899bfb00c9b89651948ed8..6fdd51c767399bce29dce1ecea2e13072a6f4d00 100644 --- a/net/minecraft/util/ZeroBitStorage.java +++ b/net/minecraft/util/ZeroBitStorage.java @@ -80,4 +80,6 @@ public class ZeroBitStorage implements BitStorage { return ret; } // Paper end - block counting + + @Override public void compact(net.minecraft.world.level.chunk.Palette srcPalette, net.minecraft.world.level.chunk.Palette dstPalette, short[] out, net.minecraft.world.level.chunk.PalettedContainer resizeHandler) { } // DivineMC - lithium: faster chunk serialization } diff --git a/net/minecraft/world/level/chunk/PalettedContainer.java b/net/minecraft/world/level/chunk/PalettedContainer.java index 54d71bd666a946bdf6eece520e080c3f96003452..51ece91c956fd4d55eaef34a534fdb38c6d24446 100644 --- a/net/minecraft/world/level/chunk/PalettedContainer.java +++ b/net/minecraft/world/level/chunk/PalettedContainer.java @@ -29,6 +29,23 @@ public class PalettedContainer implements PaletteResize, PalettedContainer private final T @org.jetbrains.annotations.Nullable [] presetValues; // Paper - Anti-Xray - Add preset values //private final ThreadingDetector threadingDetector = new ThreadingDetector("PalettedContainer"); // Paper - unused + // DivineMC start - lithium: faster chunk serialization + private static final ThreadLocal CACHED_ARRAY_4096 = ThreadLocal.withInitial(() -> new short[4096]); + private static final ThreadLocal CACHED_ARRAY_64 = ThreadLocal.withInitial(() -> new short[64]); + + private Optional asOptional(long[] data) { + return Optional.of(Arrays.stream(data)); + } + + private short[] getOrCreate(int size) { + return switch (size) { + case 64 -> CACHED_ARRAY_64.get(); + case 4096 -> CACHED_ARRAY_4096.get(); + default -> new short[size]; + }; + } + // DivineMC end - lithium: faster chunk serialization + public void acquire() { // this.threadingDetector.checkAndLock(); // Paper - disable this - use proper synchronization } @@ -338,29 +355,49 @@ public class PalettedContainer implements PaletteResize, PalettedContainer public synchronized PalettedContainerRO.PackedData pack(Strategy strategy) { // Paper - synchronize this.acquire(); - PalettedContainerRO.PackedData var14; + // DivineMC start - lithium: faster chunk serialization + Optional data = Optional.empty(); + List elements = null; try { - BitStorage bitStorage = this.data.storage; - Palette palette = this.data.palette; - HashMapPalette hashMapPalette = new HashMapPalette<>(bitStorage.getBits()); - int entryCount = strategy.entryCount(); - int[] ints = reencodeContents(bitStorage, palette, hashMapPalette); - Configuration configurationForPaletteSize = strategy.getConfigurationForPaletteSize(hashMapPalette.getSize()); - int i = configurationForPaletteSize.bitsInStorage(); - Optional optional; - if (i != 0) { - SimpleBitStorage simpleBitStorage = new SimpleBitStorage(i, entryCount, ints); - optional = Optional.of(Arrays.stream(simpleBitStorage.getRaw())); - } else { - optional = Optional.empty(); + net.caffeinemc.mods.lithium.common.world.chunk.LithiumHashPalette hashPalette = null; + + final Palette palette = this.data.palette(); + final BitStorage storage = this.data.storage(); + if (storage instanceof ZeroBitStorage || palette.getSize() == 1) { + elements = List.of(palette.valueFor(0)); + } else if (palette instanceof net.caffeinemc.mods.lithium.common.world.chunk.LithiumHashPalette lithiumHashPalette) { + hashPalette = lithiumHashPalette; } - var14 = new PalettedContainerRO.PackedData<>(hashMapPalette.getEntries(), optional, i); + if (elements == null) { + net.caffeinemc.mods.lithium.common.world.chunk.LithiumHashPalette compactedPalette = new net.caffeinemc.mods.lithium.common.world.chunk.LithiumHashPalette<>(storage.getBits()); + short[] array = this.getOrCreate(strategy.entryCount()); + + storage.compact(this.data.palette(), compactedPalette, array, this); + + if (hashPalette != null && hashPalette.getSize() == compactedPalette.getSize() && storage.getBits() == strategy.getConfigurationForPaletteSize(hashPalette.getSize()).bitsInStorage()) { // paletteSize can de-sync from palette - see https://github.com/CaffeineMC/lithium-fabric/issues/279 + data = this.asOptional(storage.getRaw().clone()); + elements = hashPalette.getElements(); + } else { + int bits = strategy.getConfigurationForPaletteSize(compactedPalette.getSize()).bitsInStorage(); + if (bits != 0) { + SimpleBitStorage copy = new SimpleBitStorage(bits, array.length); + for (int i = 0; i < array.length; ++i) { + copy.set(i, array[i]); + } + + data = this.asOptional(copy.getRaw()); + } + + elements = compactedPalette.getElements(); + } + } } finally { this.release(); } - return var14; + return new PalettedContainerRO.PackedData<>(elements, data); + // DivineMC end - lithium: faster chunk serialization } private static int[] reencodeContents(BitStorage bitStorage, Palette oldPalette, Palette newPalette) { @@ -410,13 +447,31 @@ public class PalettedContainer implements PaletteResize, PalettedContainer @Override public void count(PalettedContainer.CountConsumer countConsumer) { - if (this.data.palette.getSize() == 1) { - countConsumer.accept(this.data.palette.valueFor(0), this.data.storage.getSize()); - } else { - Int2IntOpenHashMap map = new Int2IntOpenHashMap(); - this.data.storage.getAll(id -> map.addTo(id, 1)); - map.int2IntEntrySet().forEach(idEntry -> countConsumer.accept(this.data.palette.valueFor(idEntry.getIntKey()), idEntry.getIntValue())); + // DivineMC start - lithium: faster chunk serialization + int len = this.data.palette().getSize(); + + if (len > 4096) { + if (this.data.palette.getSize() == 1) { + countConsumer.accept(this.data.palette.valueFor(0), this.data.storage.getSize()); + } else { + Int2IntOpenHashMap map = new Int2IntOpenHashMap(); + this.data.storage.getAll(id -> map.addTo(id, 1)); + map.int2IntEntrySet().forEach(idEntry -> countConsumer.accept(this.data.palette.valueFor(idEntry.getIntKey()), idEntry.getIntValue())); + } + } + + short[] counts = new short[len]; + + this.data.storage().getAll(i -> counts[i]++); + + for (int i = 0; i < counts.length; i++) { + T obj = this.data.palette().valueFor(i); + + if (obj != null) { + countConsumer.accept(obj, counts[i]); + } } + // DivineMC end - lithium: faster chunk serialization } @FunctionalInterface