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..a0a0ec8747cf6477df8943d2268dece8e064cb33 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); // DivineMC - lithium: faster chunk serialization } diff --git a/net/minecraft/util/SimpleBitStorage.java b/net/minecraft/util/SimpleBitStorage.java index e6306a68c8652d4c5d22d5ecb1416f5f931f76ee..1d8125e23e34a929da6fb4e361eae3ccbaeabce9 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) { + 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)) + 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..6e264b311894f510112beb996190f5ff6943e5e8 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) { } // DivineMC - lithium: faster chunk serialization } diff --git a/net/minecraft/world/level/chunk/PalettedContainer.java b/net/minecraft/world/level/chunk/PalettedContainer.java index a251ba67644cd02a0b00d7c8b0e2c64aa5e26291..9d892c1c3890e0aaf13fd5cd7b7d138afeaad260 100644 --- a/net/minecraft/world/level/chunk/PalettedContainer.java +++ b/net/minecraft/world/level/chunk/PalettedContainer.java @@ -32,6 +32,23 @@ public class PalettedContainer implements PaletteResize, PalettedContainer private final PalettedContainer.Strategy strategy; //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 } @@ -343,28 +360,49 @@ public class PalettedContainer implements PaletteResize, PalettedContainer public synchronized PalettedContainerRO.PackedData pack(IdMap registry, PalettedContainer.Strategy strategy) { // Paper - synchronize this.acquire(); - PalettedContainerRO.PackedData var12; + // DivineMC start - lithium: faster chunk serialization + Optional data = Optional.empty(); + List elements = null; try { - HashMapPalette hashMapPalette = new HashMapPalette<>(registry, this.data.storage.getBits(), this.dummyPaletteResize); - int size = strategy.size(); - int[] ints = new int[size]; - this.data.storage.unpack(ints); - swapPalette(ints, id -> hashMapPalette.idFor(this.data.palette.valueFor(id))); - int i = strategy.calculateBitsForSerialization(registry, hashMapPalette.getSize()); - Optional optional; - if (i != 0) { - SimpleBitStorage simpleBitStorage = new SimpleBitStorage(i, size, 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; } - var12 = new PalettedContainerRO.PackedData<>(hashMapPalette.getEntries(), optional); + if (elements == null) { + net.caffeinemc.mods.lithium.common.world.chunk.LithiumHashPalette compactedPalette = new net.caffeinemc.mods.lithium.common.world.chunk.LithiumHashPalette<>(registry, storage.getBits(), this.dummyPaletteResize); + short[] array = this.getOrCreate(strategy.size()); + + storage.compact(this.data.palette(), compactedPalette, array); + + if (hashPalette != null && hashPalette.getSize() == compactedPalette.getSize() && storage.getBits() == strategy.calculateBitsForSerialization(registry, hashPalette.getSize())) { // 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.calculateBitsForSerialization(registry, compactedPalette.getSize()); + 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 var12; + return new PalettedContainerRO.PackedData<>(elements, data); + // DivineMC end - lithium: faster chunk serialization } private static void swapPalette(int[] bits, IntUnaryOperator operator) { @@ -404,13 +442,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 } record Configuration(Palette.Factory factory, int bits) {