From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Paul Sauve <paul@technove.co>
Date: Thu, 4 Feb 2021 23:28:46 -0600
Subject: [PATCH] Cache palette array

Instead of allocating the 4KB for every chunk section, cache it locally and
reuse it for other chunk sections to save on allocations. These allocations add
up very quickly when saving chunks frequently.

1.18: PalettedContainer changed, mojang introduced a Codec into the mix to make things
more complicated. Needs a complete rework, but probably still viable as a perf improvement.

diff --git a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java
index c9e942669458668a184aaec3bc0a5509dd6ab5f0..ac84d49d7819666a89cacbe9ef1199cf22ac2ac3 100644
--- a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java
+++ b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java
@@ -263,13 +263,17 @@ public class PalettedContainer<T> implements PaletteResize<T> {
 
     }
 
+    // Paper start - allow reusing int array
     public synchronized void write(CompoundTag nbt, String paletteKey, String dataKey) { // Paper - synchronize
+        this.write(nbt, paletteKey, dataKey, new int[4096]);
+    }
+    public synchronized void write(CompoundTag nbt, String paletteKey, String dataKey, int[] is) { // Paper - synchronize // Paper end
         try {
             this.acquire();
             HashMapPalette<T> hashMapPalette = new HashMapPalette<>(this.registry, this.bits, this.dummyPaletteResize, this.reader, this.writer);
             T object = this.defaultValue;
             int i = hashMapPalette.idFor(this.defaultValue);
-            int[] is = new int[4096];
+            //int[] is = new int[4096]; // Paper - will now be pulled from ThreadLocal
 
             for(int j = 0; j < 4096; ++j) {
                 T object2 = this.get(j);
diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
index 7921ee2786d0d6a60d43786b20efc03a0f9178e3..10f15b8c7763a980aec47a4b4dda5686e60642ce 100644
--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
+++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
@@ -500,6 +500,7 @@ public class ChunkSerializer {
         return new AsyncSaveData(blockLight, skyLight, blockTickListSerialized, fluidTickListSerialized, blockEntitiesSerialized, world.getGameTime());
     }
 
+    private static final ThreadLocal<int[]> PALETTE_ARRAY = ThreadLocal.withInitial(() -> new int[4096]); // Paper - maintain a per-thread cache for saving palettes
     public static CompoundTag write(ServerLevel world, ChunkAccess chunk) {
         return saveChunk(world, chunk, null);
     }
@@ -533,6 +534,7 @@ public class ChunkSerializer {
         ThreadedLevelLightEngine lightenginethreaded = world.getChunkSource().getLightEngine();
         boolean flag = chunk.isLightCorrect();
 
+        int[] is = PALETTE_ARRAY.get(); // Paper - use cached
         for (int i = lightenginethreaded.getMinLightSection(); i < lightenginethreaded.getMaxLightSection(); ++i) {
             int finalI = i; // CraftBukkit - decompile errors
             LevelChunkSection chunksection = (LevelChunkSection) Arrays.stream(achunksection).filter((chunksection1) -> {
@@ -547,7 +549,7 @@ public class ChunkSerializer {
 
                 nbttagcompound2.putByte("Y", (byte) (i & 255));
                 if (chunksection != LevelChunk.EMPTY_SECTION) {
-                    chunksection.getStates().write(nbttagcompound2, "Palette", "BlockStates");
+                    chunksection.getStates().write(nbttagcompound2, "Palette", "BlockStates", is); // Paper - reuse array
                 }
 
                 // Paper start - replace light engine
