Faster chunk serialization
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: kidofcubes <kidofcubes@gmail.com>
|
||||
From: Bacteriawa <A3167717663@hotmail.com>
|
||||
Date: Fri, 8 Nov 2024 00:22:44 +0800
|
||||
Subject: [PATCH] Lithium: Skip unnecessary calculations if player is not
|
||||
flying or swing
|
||||
|
||||
@@ -0,0 +1,216 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Bacteriawa <A3167717663@hotmail.com>
|
||||
Date: Wed, 30 Nov 2022 21:51:16 +0100
|
||||
Subject: [PATCH] Gale: Faster chunk serialization
|
||||
|
||||
|
||||
diff --git a/net/minecraft/util/BitStorage.java b/net/minecraft/util/BitStorage.java
|
||||
index 02502d50f0255f5bbcc0ecb965abb48cc1a112da..322a1ba06d6aed44ec67dc3f1831ac6b05c82fe0 100644
|
||||
--- a/net/minecraft/util/BitStorage.java
|
||||
+++ b/net/minecraft/util/BitStorage.java
|
||||
@@ -21,6 +21,8 @@ public interface BitStorage extends ca.spottedleaf.moonrise.patches.block_counti
|
||||
|
||||
BitStorage copy();
|
||||
|
||||
+ <T> void compact(net.minecraft.world.level.chunk.Palette<T> srcPalette, net.minecraft.world.level.chunk.Palette<T> dstPalette, short[] out); // Gale - Lithium - faster chunk serialization
|
||||
+
|
||||
// Paper start - block counting
|
||||
// provide default impl in case mods implement this...
|
||||
@Override
|
||||
diff --git a/net/minecraft/util/SimpleBitStorage.java b/net/minecraft/util/SimpleBitStorage.java
|
||||
index e6306a68c8652d4c5d22d5ecb1416f5f931f76ee..2a3d34733b61c73729daa4da61f33e2c2e7b6c72 100644
|
||||
--- a/net/minecraft/util/SimpleBitStorage.java
|
||||
+++ b/net/minecraft/util/SimpleBitStorage.java
|
||||
@@ -465,4 +465,36 @@ public class SimpleBitStorage implements BitStorage {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
+
|
||||
+ // Gale start - Lithium - faster chunk serialization
|
||||
+ @Override
|
||||
+ public <T> void compact(net.minecraft.world.level.chunk.Palette<T> srcPalette, net.minecraft.world.level.chunk.Palette<T> 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;
|
||||
+ }
|
||||
+ }
|
||||
+ } // Gale end - Lithium - faster chunk serialization
|
||||
}
|
||||
diff --git a/net/minecraft/util/ZeroBitStorage.java b/net/minecraft/util/ZeroBitStorage.java
|
||||
index 09fd99c9cbd23b5f3c899bfb00c9b89651948ed8..90a85a00c6208d2db65cafb164cd95e6128b6dc4 100644
|
||||
--- a/net/minecraft/util/ZeroBitStorage.java
|
||||
+++ b/net/minecraft/util/ZeroBitStorage.java
|
||||
@@ -19,6 +19,8 @@ public class ZeroBitStorage implements BitStorage {
|
||||
return 0;
|
||||
}
|
||||
|
||||
+ @Override public <T> void compact(net.minecraft.world.level.chunk.Palette<T> srcPalette, net.minecraft.world.level.chunk.Palette<T> dstPalette, short[] out) {} // Gale - Lithium - faster chunk serialization
|
||||
+
|
||||
@Override
|
||||
public final void set(int index, int value) { // Paper - Perf: Optimize SimpleBitStorage
|
||||
//Validate.inclusiveBetween(0L, (long)(this.size - 1), (long)index); // Paper - Perf: Optimize SimpleBitStorage
|
||||
diff --git a/net/minecraft/world/level/chunk/PaletteResize.java b/net/minecraft/world/level/chunk/PaletteResize.java
|
||||
index c723606fa0be811e580ba47de8c9c575583cc930..60d3176477c201643e1657751fcffad511b2994f 100644
|
||||
--- a/net/minecraft/world/level/chunk/PaletteResize.java
|
||||
+++ b/net/minecraft/world/level/chunk/PaletteResize.java
|
||||
@@ -1,5 +1,5 @@
|
||||
package net.minecraft.world.level.chunk;
|
||||
|
||||
-interface PaletteResize<T> {
|
||||
+public interface PaletteResize<T> { // Gale - Lithium - faster chunk serialization - package -> public
|
||||
int onResize(int bits, T objectAdded);
|
||||
}
|
||||
diff --git a/net/minecraft/world/level/chunk/PalettedContainer.java b/net/minecraft/world/level/chunk/PalettedContainer.java
|
||||
index f5da433050fd3060e0335d4002d520ebe8cd691f..5a6e699df0a177ea6a919cad609a508678bff823 100644
|
||||
--- a/net/minecraft/world/level/chunk/PalettedContainer.java
|
||||
+++ b/net/minecraft/world/level/chunk/PalettedContainer.java
|
||||
@@ -25,6 +25,21 @@ import net.minecraft.util.ThreadingDetector;
|
||||
import net.minecraft.util.ZeroBitStorage;
|
||||
|
||||
public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainerRO<T> {
|
||||
+ // Gale start - Lithium - faster chunk serialization
|
||||
+ private static final ThreadLocal<short[]> CACHED_ARRAY_4096 = ThreadLocal.withInitial(() -> new short[4096]);
|
||||
+ private static final ThreadLocal<short[]> CACHED_ARRAY_64 = ThreadLocal.withInitial(() -> new short[64]);
|
||||
+ private Optional<LongStream> 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];
|
||||
+ };
|
||||
+ }
|
||||
+ // Gale end - Lithium - faster chunk serialization
|
||||
+
|
||||
private static final int MIN_PALETTE_BITS = 0;
|
||||
private final PaletteResize<T> dummyPaletteResize = (bits, objectAdded) -> 0;
|
||||
public final IdMap<T> registry;
|
||||
@@ -344,28 +359,53 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
|
||||
public synchronized PalettedContainerRO.PackedData<T> pack(IdMap<T> registry, PalettedContainer.Strategy strategy) { // Paper - synchronize
|
||||
this.acquire();
|
||||
|
||||
- PalettedContainerRO.PackedData var12;
|
||||
+ // Gale start - Lithium - faster chunk serialization
|
||||
+ Optional<LongStream> data = Optional.empty();
|
||||
+ List<T> elements = null;
|
||||
try {
|
||||
- HashMapPalette<T> 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<LongStream> optional;
|
||||
- if (i != 0) {
|
||||
- SimpleBitStorage simpleBitStorage = new SimpleBitStorage(i, size, ints);
|
||||
- optional = Optional.of(Arrays.stream(simpleBitStorage.getRaw()));
|
||||
- } else {
|
||||
- optional = Optional.empty();
|
||||
+ // The palette that will be serialized
|
||||
+ net.caffeinemc.mods.lithium.common.world.chunk.LithiumHashPalette<T> hashPalette = null;
|
||||
+
|
||||
+ final Palette<T> palette = this.data.palette();
|
||||
+ final BitStorage storage = this.data.storage();
|
||||
+ if (storage instanceof ZeroBitStorage || palette.getSize() == 1) {
|
||||
+ // If the palette only contains one entry, don't attempt to repack it.
|
||||
+ elements = List.of(palette.valueFor(0));
|
||||
+ } else if (palette instanceof net.caffeinemc.mods.lithium.common.world.chunk.LithiumHashPalette<T> lithiumHashPalette) {
|
||||
+ hashPalette = lithiumHashPalette;
|
||||
}
|
||||
|
||||
- var12 = new PalettedContainerRO.PackedData<>(hashMapPalette.getEntries(), optional);
|
||||
+ if (elements == null) {
|
||||
+ var 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 the palette didn't change during compaction, do a simple copy of the data 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) {
|
||||
+ // Re-pack the integer array as the palette has changed size
|
||||
+ SimpleBitStorage copy = new SimpleBitStorage(bits, array.length);
|
||||
+ for (int i = 0; i < array.length; ++i)
|
||||
+ copy.set(i, array[i]);
|
||||
+
|
||||
+ // We don't need to clone the data array as we are the sole owner of it
|
||||
+ data = this.asOptional(copy.getRaw());
|
||||
+ }
|
||||
+
|
||||
+ elements = compactedPalette.getElements();
|
||||
+ }
|
||||
+ }
|
||||
} finally {
|
||||
this.release();
|
||||
}
|
||||
|
||||
- return var12;
|
||||
+ return new PalettedContainerRO.PackedData<>(elements, data);
|
||||
+ // Gale end - Lithium - faster chunk serialization
|
||||
}
|
||||
|
||||
private static <T> void swapPalette(int[] bits, IntUnaryOperator operator) {
|
||||
@@ -405,13 +445,30 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
|
||||
|
||||
@Override
|
||||
public void count(PalettedContainer.CountConsumer<T> 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()));
|
||||
+ // Gale start - Lithium - faster chunk serialization
|
||||
+ int len = this.data.palette().getSize();
|
||||
+
|
||||
+ // Do not allocate huge arrays if we're using a large palette
|
||||
+ if (len > 4096) {
|
||||
+ // VanillaCopy
|
||||
+ 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]);
|
||||
}
|
||||
+ // Gale end - Lithium - faster chunk serialization
|
||||
}
|
||||
|
||||
record Configuration<T>(Palette.Factory factory, int bits) {
|
||||
@@ -0,0 +1,212 @@
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/net/caffeinemc/mods/lithium/common/world/chunk/LithiumHashPalette.java
|
||||
@@ -1,0 +_,209 @@
|
||||
+// Lithium - faster chunk serialization
|
||||
+
|
||||
+package net.caffeinemc.mods.lithium.common.world.chunk;
|
||||
+
|
||||
+import it.unimi.dsi.fastutil.HashCommon;
|
||||
+import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
|
||||
+import net.minecraft.CrashReport;
|
||||
+import net.minecraft.CrashReportCategory;
|
||||
+import net.minecraft.ReportedException;
|
||||
+import net.minecraft.core.IdMap;
|
||||
+import net.minecraft.network.FriendlyByteBuf;
|
||||
+import net.minecraft.network.VarInt;
|
||||
+import net.minecraft.world.level.chunk.MissingPaletteEntryException;
|
||||
+import net.minecraft.world.level.chunk.Palette;
|
||||
+import net.minecraft.world.level.chunk.PaletteResize;
|
||||
+import org.jetbrains.annotations.NotNull;
|
||||
+
|
||||
+import java.util.Arrays;
|
||||
+import java.util.List;
|
||||
+import java.util.function.Predicate;
|
||||
+
|
||||
+import static it.unimi.dsi.fastutil.Hash.FAST_LOAD_FACTOR;
|
||||
+
|
||||
+/**
|
||||
+ * Generally provides better performance over the vanilla {@link net.minecraft.world.level.chunk.HashMapPalette} when calling
|
||||
+ * {@link LithiumHashPalette#idFor(Object)} through using a faster backing map and reducing pointer chasing.
|
||||
+ */
|
||||
+public class LithiumHashPalette<T> implements Palette<T> {
|
||||
+ private static final int ABSENT_VALUE = -1;
|
||||
+
|
||||
+ private final IdMap<T> idList;
|
||||
+ private final PaletteResize<T> resizeHandler;
|
||||
+ private final int indexBits;
|
||||
+
|
||||
+ private final Reference2IntOpenHashMap<T> table;
|
||||
+ private T[] entries;
|
||||
+ private int size = 0;
|
||||
+
|
||||
+ private LithiumHashPalette(IdMap<T> idList, PaletteResize<T> resizeHandler, int indexBits, T[] entries, Reference2IntOpenHashMap<T> table, int size) {
|
||||
+ this.idList = idList;
|
||||
+ this.resizeHandler = resizeHandler;
|
||||
+ this.indexBits = indexBits;
|
||||
+ this.entries = entries;
|
||||
+ this.table = table;
|
||||
+ this.size = size;
|
||||
+ }
|
||||
+
|
||||
+ public LithiumHashPalette(IdMap<T> idList, int bits, PaletteResize<T> resizeHandler, List<T> list) {
|
||||
+ this(idList, bits, resizeHandler);
|
||||
+
|
||||
+ for (T t : list) {
|
||||
+ this.addEntry(t);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ @SuppressWarnings("unchecked")
|
||||
+ public LithiumHashPalette(IdMap<T> idList, int bits, PaletteResize<T> resizeHandler) {
|
||||
+ this.idList = idList;
|
||||
+ this.indexBits = bits;
|
||||
+ this.resizeHandler = resizeHandler;
|
||||
+
|
||||
+ int capacity = 1 << bits;
|
||||
+
|
||||
+ this.entries = (T[]) new Object[capacity];
|
||||
+ this.table = new Reference2IntOpenHashMap<>(capacity, FAST_LOAD_FACTOR);
|
||||
+ this.table.defaultReturnValue(ABSENT_VALUE);
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public int idFor(@NotNull T obj) {
|
||||
+ int id = this.table.getInt(obj);
|
||||
+
|
||||
+ if (id == ABSENT_VALUE) {
|
||||
+ id = this.computeEntry(obj);
|
||||
+ }
|
||||
+
|
||||
+ return id;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public boolean maybeHas(@NotNull Predicate<T> predicate) {
|
||||
+ for (int i = 0; i < this.size; ++i) {
|
||||
+ if (predicate.test(this.entries[i])) {
|
||||
+ return true;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ return false;
|
||||
+ }
|
||||
+
|
||||
+ private int computeEntry(T obj) {
|
||||
+ int id = this.addEntry(obj);
|
||||
+
|
||||
+ if (id >= 1 << this.indexBits) {
|
||||
+ if (this.resizeHandler == null) {
|
||||
+ throw new IllegalStateException("Cannot grow");
|
||||
+ } else {
|
||||
+ id = this.resizeHandler.onResize(this.indexBits + 1, obj);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ return id;
|
||||
+ }
|
||||
+
|
||||
+ private int addEntry(T obj) {
|
||||
+ int nextId = this.size;
|
||||
+
|
||||
+ if (nextId >= this.entries.length) {
|
||||
+ this.resize(this.size);
|
||||
+ }
|
||||
+
|
||||
+ this.table.put(obj, nextId);
|
||||
+ this.entries[nextId] = obj;
|
||||
+
|
||||
+ this.size++;
|
||||
+
|
||||
+ return nextId;
|
||||
+ }
|
||||
+
|
||||
+ private void resize(int neededCapacity) {
|
||||
+ this.entries = Arrays.copyOf(this.entries, HashCommon.nextPowerOfTwo(neededCapacity + 1));
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public @NotNull T valueFor(int id) {
|
||||
+ T[] entries = this.entries;
|
||||
+
|
||||
+ T entry = null;
|
||||
+ if (id >= 0 && id < entries.length) {
|
||||
+ entry = entries[id];
|
||||
+ }
|
||||
+
|
||||
+ if (entry != null) {
|
||||
+ return entry;
|
||||
+ } else {
|
||||
+ throw this.missingPaletteEntryCrash(id);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ private ReportedException missingPaletteEntryCrash(int id) {
|
||||
+ try {
|
||||
+ throw new MissingPaletteEntryException(id);
|
||||
+ } catch (MissingPaletteEntryException e) {
|
||||
+ CrashReport crashReport = CrashReport.forThrowable(e, "[Lithium] Getting Palette Entry");
|
||||
+ CrashReportCategory crashReportCategory = crashReport.addCategory("Chunk section");
|
||||
+ crashReportCategory.setDetail("IndexBits", this.indexBits);
|
||||
+ crashReportCategory.setDetail("Entries", this.entries.length + " Elements: " + Arrays.toString(this.entries));
|
||||
+ crashReportCategory.setDetail("Table", this.table.size() + " Elements: " + this.table);
|
||||
+ return new ReportedException(crashReport);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void read(FriendlyByteBuf buf) {
|
||||
+ this.clear();
|
||||
+
|
||||
+ int entryCount = buf.readVarInt();
|
||||
+
|
||||
+ for (int i = 0; i < entryCount; ++i) {
|
||||
+ this.addEntry(this.idList.byIdOrThrow(buf.readVarInt()));
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void write(FriendlyByteBuf buf) {
|
||||
+ int size = this.size;
|
||||
+ buf.writeVarInt(size);
|
||||
+
|
||||
+ for (int i = 0; i < size; ++i) {
|
||||
+ buf.writeVarInt(this.idList.getId(this.valueFor(i)));
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public int getSerializedSize() {
|
||||
+ int size = VarInt.getByteSize(this.size);
|
||||
+
|
||||
+ for (int i = 0; i < this.size; ++i) {
|
||||
+ size += VarInt.getByteSize(this.idList.getId(this.valueFor(i)));
|
||||
+ }
|
||||
+
|
||||
+ return size;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public int getSize() {
|
||||
+ return this.size;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public @NotNull Palette<T> copy(@NotNull PaletteResize<T> resizeHandler) {
|
||||
+ return new LithiumHashPalette<>(this.idList, resizeHandler, this.indexBits, this.entries.clone(), this.table.clone(), this.size);
|
||||
+ }
|
||||
+
|
||||
+ private void clear() {
|
||||
+ Arrays.fill(this.entries, null);
|
||||
+ this.table.clear();
|
||||
+ this.size = 0;
|
||||
+ }
|
||||
+
|
||||
+ public List<T> getElements() {
|
||||
+ T[] copy = Arrays.copyOf(this.entries, this.size);
|
||||
+ return Arrays.asList(copy);
|
||||
+ }
|
||||
+
|
||||
+ public static <A> Palette<A> create(int bits, IdMap<A> idList, PaletteResize<A> listener, List<A> list) {
|
||||
+ return new LithiumHashPalette<>(idList, bits, listener, list);
|
||||
+ }
|
||||
+}
|
||||
Reference in New Issue
Block a user