diff --git a/Spigot-Server-Patches/0004-MC-Utils.patch b/Spigot-Server-Patches/0004-MC-Utils.patch index 2a98b16b77..60e3b62952 100644 --- a/Spigot-Server-Patches/0004-MC-Utils.patch +++ b/Spigot-Server-Patches/0004-MC-Utils.patch @@ -3265,14 +3265,15 @@ index 75308712d0642d5ab168de653023349df8aee5ed..aa7501d366b15e7f7f64b7d98a1dccff // CraftBukkit end diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java new file mode 100644 -index 0000000000000000000000000000000000000000..b40cd1fad5c9e2f0f85c87a559caf2b780814017 +index 0000000000000000000000000000000000000000..aaa6e33b0e5df2549e4f989501bacfd1ab4ad063 --- /dev/null +++ b/src/main/java/net/minecraft/server/MCUtil.java -@@ -0,0 +1,487 @@ +@@ -0,0 +1,532 @@ +package net.minecraft.server; + +import com.destroystokyo.paper.block.TargetBlockInfo; +import com.google.common.util.concurrent.ThreadFactoryBuilder; ++import org.apache.commons.lang.exception.ExceptionUtils; +import org.bukkit.Location; +import org.bukkit.block.BlockFace; +import org.bukkit.craftbukkit.CraftWorld; @@ -3290,6 +3291,7 @@ index 0000000000000000000000000000000000000000..b40cd1fad5c9e2f0f85c87a559caf2b7 +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; ++import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Supplier; + @@ -3317,6 +3319,12 @@ index 0000000000000000000000000000000000000000..b40cd1fad5c9e2f0f85c87a559caf2b7 + }; + } + ++ public static Runnable once(List list, Consumer cb) { ++ return once(() -> { ++ list.forEach(cb); ++ }); ++ } ++ + private static Runnable makeCleanerCallback(Runnable run) { + return once(() -> cleanerExecutor.execute(run)); + } @@ -3384,19 +3392,16 @@ index 0000000000000000000000000000000000000000..b40cd1fad5c9e2f0f85c87a559caf2b7 + return list; + } + -+ public static long getCoordinateKey(final BlockPosition blockPos) { -+ return ((long)(blockPos.getZ() >> 4) << 32) | ((blockPos.getX() >> 4) & 0xFFFFFFFFL); -+ } -+ -+ public static long getCoordinateKey(final Entity entity) { -+ return ((long)(MCUtil.fastFloor(entity.locZ()) >> 4) << 32) | ((MCUtil.fastFloor(entity.locX()) >> 4) & 0xFFFFFFFFL); -+ } -+ + public static int fastFloor(double x) { + int truncated = (int)x; + return x < (double)truncated ? truncated - 1 : truncated; + } + ++ public static int fastFloor(float x) { ++ int truncated = (int)x; ++ return x < (double)truncated ? truncated - 1 : truncated; ++ } ++ + public static float normalizeYaw(float f) { + float f1 = f % 360.0F; + @@ -3411,9 +3416,31 @@ index 0000000000000000000000000000000000000000..b40cd1fad5c9e2f0f85c87a559caf2b7 + return f1; + } + -+ public static int fastFloor(float x) { -+ int truncated = (int)x; -+ return x < (double)truncated ? truncated - 1 : truncated; ++ /** ++ * Quickly generate a stack trace for current location ++ * ++ * @return Stacktrace ++ */ ++ public static String stack() { ++ return ExceptionUtils.getFullStackTrace(new Throwable()); ++ } ++ ++ /** ++ * Quickly generate a stack trace for current location with message ++ * ++ * @param str ++ * @return Stacktrace ++ */ ++ public static String stack(String str) { ++ return ExceptionUtils.getFullStackTrace(new Throwable(str)); ++ } ++ ++ public static long getCoordinateKey(final BlockPosition blockPos) { ++ return ((long)(blockPos.getZ() >> 4) << 32) | ((blockPos.getX() >> 4) & 0xFFFFFFFFL); ++ } ++ ++ public static long getCoordinateKey(final Entity entity) { ++ return ((long)(MCUtil.fastFloor(entity.locZ()) >> 4) << 32) | ((MCUtil.fastFloor(entity.locX()) >> 4) & 0xFFFFFFFFL); + } + + public static long getCoordinateKey(final ChunkCoordIntPair pair) { @@ -3466,6 +3493,24 @@ index 0000000000000000000000000000000000000000..b40cd1fad5c9e2f0f85c87a559caf2b7 + + private MCUtil() {} + ++ public static final java.util.concurrent.Executor MAIN_EXECUTOR = (run) -> { ++ if (!isMainThread()) { ++ MinecraftServer.getServer().execute(run); ++ } else { ++ run.run(); ++ } ++ }; ++ ++ public static CompletableFuture ensureMain(CompletableFuture future) { ++ return future.thenApplyAsync(r -> r, MAIN_EXECUTOR); ++ } ++ ++ public static void thenOnMain(CompletableFuture future, Consumer consumer) { ++ future.thenAcceptAsync(consumer, MAIN_EXECUTOR); ++ } ++ public static void thenOnMain(CompletableFuture future, BiConsumer consumer) { ++ future.whenCompleteAsync(consumer, MAIN_EXECUTOR); ++ } + + public static boolean isMainThread() { + return MinecraftServer.getServer().isMainThread(); diff --git a/Spigot-Server-Patches/0133-String-based-Action-Bar-API.patch b/Spigot-Server-Patches/0133-String-based-Action-Bar-API.patch index 2cbb8d05de..c1bcf54be7 100644 --- a/Spigot-Server-Patches/0133-String-based-Action-Bar-API.patch +++ b/Spigot-Server-Patches/0133-String-based-Action-Bar-API.patch @@ -4,43 +4,6 @@ Date: Tue, 27 Dec 2016 15:02:42 -0500 Subject: [PATCH] String based Action Bar API -diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java -index b40cd1fad5c9e2f0f85c87a559caf2b780814017..f9a7a1e9eea67bafb85c0ed88e96abb8e45f6c81 100644 ---- a/src/main/java/net/minecraft/server/MCUtil.java -+++ b/src/main/java/net/minecraft/server/MCUtil.java -@@ -2,6 +2,7 @@ package net.minecraft.server; - - import com.destroystokyo.paper.block.TargetBlockInfo; - import com.google.common.util.concurrent.ThreadFactoryBuilder; -+import org.apache.commons.lang.exception.ExceptionUtils; - import org.bukkit.Location; - import org.bukkit.block.BlockFace; - import org.bukkit.craftbukkit.CraftWorld; -@@ -195,6 +196,24 @@ public final class MCUtil { - - private MCUtil() {} - -+ /** -+ * Quickly generate a stack trace for current location -+ * -+ * @return Stacktrace -+ */ -+ public static String stack() { -+ return ExceptionUtils.getFullStackTrace(new Throwable()); -+ } -+ -+ /** -+ * Quickly generate a stack trace for current location with message -+ * -+ * @param str -+ * @return Stacktrace -+ */ -+ public static String stack(String str) { -+ return ExceptionUtils.getFullStackTrace(new Throwable(str)); -+ } - - public static boolean isMainThread() { - return MinecraftServer.getServer().isMainThread(); diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java index 764a8ef952b6f3a38ae8430c0648ad1694aa89b9..d349f0c87bfad19cf0bddb4709f1d7b0dd4b4a36 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java diff --git a/Spigot-Server-Patches/0154-Basic-PlayerProfile-API.patch b/Spigot-Server-Patches/0154-Basic-PlayerProfile-API.patch index 747c485c4f..2f7e36cb9f 100644 --- a/Spigot-Server-Patches/0154-Basic-PlayerProfile-API.patch +++ b/Spigot-Server-Patches/0154-Basic-PlayerProfile-API.patch @@ -403,21 +403,22 @@ index 0000000000000000000000000000000000000000..3aceb0ea8a1a3ed94dd8a9e954c52ecd + } +} diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java -index f9a7a1e9eea67bafb85c0ed88e96abb8e45f6c81..8ca1a4719d934db31d57b34cf7050acc5a1a7048 100644 +index aaa6e33b0e5df2549e4f989501bacfd1ab4ad063..8ebe5a2e2678fccb17aced57f6fd1e52c17935db 100644 --- a/src/main/java/net/minecraft/server/MCUtil.java +++ b/src/main/java/net/minecraft/server/MCUtil.java -@@ -1,7 +1,10 @@ +@@ -1,8 +1,11 @@ package net.minecraft.server; import com.destroystokyo.paper.block.TargetBlockInfo; +import com.destroystokyo.paper.profile.CraftPlayerProfile; +import com.destroystokyo.paper.profile.PlayerProfile; import com.google.common.util.concurrent.ThreadFactoryBuilder; -+import com.mojang.authlib.GameProfile; import org.apache.commons.lang.exception.ExceptionUtils; ++import com.mojang.authlib.GameProfile; import org.bukkit.Location; import org.bukkit.block.BlockFace; -@@ -329,6 +332,10 @@ public final class MCUtil { + import org.bukkit.craftbukkit.CraftWorld; +@@ -355,6 +358,10 @@ public final class MCUtil { return run.get(); } diff --git a/Spigot-Server-Patches/0301-Catch-JsonParseException-in-Entity-and-TE-names.patch b/Spigot-Server-Patches/0301-Catch-JsonParseException-in-Entity-and-TE-names.patch index a598dc8d7b..234005a4d7 100644 --- a/Spigot-Server-Patches/0301-Catch-JsonParseException-in-Entity-and-TE-names.patch +++ b/Spigot-Server-Patches/0301-Catch-JsonParseException-in-Entity-and-TE-names.patch @@ -39,10 +39,10 @@ index 0f74ec89b3e85c918c95f9d8fef6d68403ed1107..4609e402b419ed21e17ad34d02dca55b this.setCustomNameVisible(nbttagcompound.getBoolean("CustomNameVisible")); diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java -index 8ca1a4719d934db31d57b34cf7050acc5a1a7048..206d04dcce1d7d074cf7151a083bdc626b0b8e07 100644 +index 8ebe5a2e2678fccb17aced57f6fd1e52c17935db..45179aec0d91149ba9cc4d95e65c489ce695053d 100644 --- a/src/main/java/net/minecraft/server/MCUtil.java +++ b/src/main/java/net/minecraft/server/MCUtil.java -@@ -510,4 +510,19 @@ public final class MCUtil { +@@ -536,4 +536,19 @@ public final class MCUtil { return null; } } diff --git a/Spigot-Server-Patches/0377-Chunk-debug-command.patch b/Spigot-Server-Patches/0377-Chunk-debug-command.patch index 8ec744d77f..26c5526246 100644 --- a/Spigot-Server-Patches/0377-Chunk-debug-command.patch +++ b/Spigot-Server-Patches/0377-Chunk-debug-command.patch @@ -198,13 +198,13 @@ index 8c6550433c20c54cbe390219821ce393c5720da8..e6d08756f76360b29b29f18305e5ec84 public final ChunkGenerator chunkGenerator; private final WorldServer world; diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java -index 206d04dcce1d7d074cf7151a083bdc626b0b8e07..f75f48a3d0b0bc1da3c5ae3b3cf20b64f1e8288a 100644 +index 45179aec0d91149ba9cc4d95e65c489ce695053d..20f54baacebe98435539d4cbef41f182040db2e9 100644 --- a/src/main/java/net/minecraft/server/MCUtil.java +++ b/src/main/java/net/minecraft/server/MCUtil.java -@@ -4,7 +4,13 @@ import com.destroystokyo.paper.block.TargetBlockInfo; - import com.destroystokyo.paper.profile.CraftPlayerProfile; +@@ -5,7 +5,13 @@ import com.destroystokyo.paper.profile.CraftPlayerProfile; import com.destroystokyo.paper.profile.PlayerProfile; import com.google.common.util.concurrent.ThreadFactoryBuilder; + import org.apache.commons.lang.exception.ExceptionUtils; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.google.gson.internal.Streams; @@ -212,9 +212,9 @@ index 206d04dcce1d7d074cf7151a083bdc626b0b8e07..f75f48a3d0b0bc1da3c5ae3b3cf20b64 import com.mojang.authlib.GameProfile; +import com.mojang.datafixers.util.Either; +import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; - import org.apache.commons.lang.exception.ExceptionUtils; import org.bukkit.Location; import org.bukkit.block.BlockFace; + import org.bukkit.craftbukkit.CraftWorld; @@ -14,8 +20,11 @@ import org.spigotmc.AsyncCatcher; import javax.annotation.Nonnull; @@ -227,7 +227,7 @@ index 206d04dcce1d7d074cf7151a083bdc626b0b8e07..f75f48a3d0b0bc1da3c5ae3b3cf20b64 import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.LinkedBlockingQueue; -@@ -525,4 +534,170 @@ public final class MCUtil { +@@ -551,4 +560,170 @@ public final class MCUtil { return null; } diff --git a/Spigot-Server-Patches/0390-Asynchronous-chunk-IO-and-loading.patch b/Spigot-Server-Patches/0390-Asynchronous-chunk-IO-and-loading.patch index 779f30faae..fc427562fd 100644 --- a/Spigot-Server-Patches/0390-Asynchronous-chunk-IO-and-loading.patch +++ b/Spigot-Server-Patches/0390-Asynchronous-chunk-IO-and-loading.patch @@ -1847,10 +1847,10 @@ index 0000000000000000000000000000000000000000..1dfa8abfd869ca97e4cc566d44e509b4 +} diff --git a/src/main/java/com/destroystokyo/paper/io/chunk/ChunkTaskManager.java b/src/main/java/com/destroystokyo/paper/io/chunk/ChunkTaskManager.java new file mode 100644 -index 0000000000000000000000000000000000000000..a5f4cdaf06bfbb0dd957db9a1335c17b073d646d +index 0000000000000000000000000000000000000000..b5c2e1f4a2b5fdcaa6bb01f4b3b6847cd5b73ae8 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/io/chunk/ChunkTaskManager.java -@@ -0,0 +1,509 @@ +@@ -0,0 +1,511 @@ +package com.destroystokyo.paper.io.chunk; + +import com.destroystokyo.paper.io.PaperFileIOThread; @@ -1929,22 +1929,20 @@ index 0000000000000000000000000000000000000000..a5f4cdaf06bfbb0dd957db9a1335c17b + } + } + -+ public static String getChunkWaitInfo() { ++ private static ChunkInfo[] getChunkInfos() { ++ ChunkInfo[] chunks; + synchronized (WAITING_CHUNKS) { -+ return WAITING_CHUNKS.toString(); ++ chunks = WAITING_CHUNKS.toArray(new ChunkInfo[0]); + } ++ return chunks; + } + + public static void dumpAllChunkLoadInfo() { -+ synchronized (WAITING_CHUNKS) { -+ if (WAITING_CHUNKS.isEmpty()) { -+ return; -+ } -+ ++ ChunkInfo[] chunks = getChunkInfos(); ++ if (chunks.length > 0) { + PaperFileIOThread.LOGGER.log(Level.ERROR, "Chunk wait task info below: "); -+ Set seenChunks = new HashSet<>(); + -+ for (final ChunkInfo chunkInfo : WAITING_CHUNKS) { ++ for (final ChunkInfo chunkInfo : chunks) { + final long key = IOUtil.getCoordinateKey(chunkInfo.chunkX, chunkInfo.chunkZ); + final ChunkLoadTask loadTask = chunkInfo.world.asyncChunkTaskManager.chunkLoadTasks.get(key); + final ChunkSaveTask saveTask = chunkInfo.world.asyncChunkTaskManager.chunkSaveTasks.get(key); @@ -1955,18 +1953,22 @@ index 0000000000000000000000000000000000000000..a5f4cdaf06bfbb0dd957db9a1335c17b + // log current status of chunk to indicate whether we're waiting on generation or loading + net.minecraft.server.PlayerChunk chunkHolder = chunkInfo.world.getChunkProvider().playerChunkMap.getVisibleChunk(key); + -+ dumpChunkInfo(seenChunks, chunkHolder, chunkInfo.chunkX, chunkInfo.chunkZ); ++ dumpChunkInfo(new HashSet<>(), chunkHolder, chunkInfo.chunkX, chunkInfo.chunkZ); + } + } + } + + static void dumpChunkInfo(Set seenChunks, PlayerChunk chunkHolder, int x, int z) { -+ dumpChunkInfo(seenChunks, chunkHolder, x, z, 0); ++ dumpChunkInfo(seenChunks, chunkHolder, x, z, 0, 1); + } -+ static void dumpChunkInfo(Set seenChunks, PlayerChunk chunkHolder, int x, int z, int indent) { ++ ++ static void dumpChunkInfo(Set seenChunks, PlayerChunk chunkHolder, int x, int z, int indent, int maxDepth) { + if (seenChunks.contains(chunkHolder)) { + return; + } ++ if (indent > maxDepth) { ++ return; ++ } + seenChunks.add(chunkHolder); + String indentStr = StringUtils.repeat(" ", indent); + if (chunkHolder == null) { @@ -2987,10 +2989,10 @@ index 2f95174fcc467908808ed3f2dc956bdcafdc3558..134c76065bf382912e6c28d15449db3f +// Paper end } diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java -index f75f48a3d0b0bc1da3c5ae3b3cf20b64f1e8288a..0e01e5c2c008823355e370d0c9ced79130e5fb92 100644 +index 20f54baacebe98435539d4cbef41f182040db2e9..9f8c0e10e42d233a8b74ee5a71fb8fb6ea8e7480 100644 --- a/src/main/java/net/minecraft/server/MCUtil.java +++ b/src/main/java/net/minecraft/server/MCUtil.java -@@ -700,4 +700,9 @@ public final class MCUtil { +@@ -726,4 +726,9 @@ public final class MCUtil { out.print(fileData); } } diff --git a/Spigot-Server-Patches/0456-Optimize-PlayerChunkMap-memory-use-for-visibleChunks.patch b/Spigot-Server-Patches/0456-Optimize-PlayerChunkMap-memory-use-for-visibleChunks.patch index d522a30840..ab53ade9d8 100644 --- a/Spigot-Server-Patches/0456-Optimize-PlayerChunkMap-memory-use-for-visibleChunks.patch +++ b/Spigot-Server-Patches/0456-Optimize-PlayerChunkMap-memory-use-for-visibleChunks.patch @@ -70,10 +70,10 @@ index d53b34ba552771bf271131ce0a56ebb992ccc84c..a1b5e6b90fc93f83186cf3ebf3e15876 if (optional.isPresent()) { diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java -index 0e01e5c2c008823355e370d0c9ced79130e5fb92..d129c7f54d9f65fff6f512d8ff5f1c3866632603 100644 +index 9f8c0e10e42d233a8b74ee5a71fb8fb6ea8e7480..0d1065688b19ceca9440bc8bf2bf65910f03fa46 100644 --- a/src/main/java/net/minecraft/server/MCUtil.java +++ b/src/main/java/net/minecraft/server/MCUtil.java -@@ -602,7 +602,7 @@ public final class MCUtil { +@@ -628,7 +628,7 @@ public final class MCUtil { WorldServer world = ((org.bukkit.craftbukkit.CraftWorld)bukkitWorld).getHandle(); PlayerChunkMap chunkMap = world.getChunkProvider().playerChunkMap; diff --git a/Spigot-Server-Patches/0530-Implement-Chunk-Priority-Urgency-System-for-Chunks.patch b/Spigot-Server-Patches/0529-Implement-Chunk-Priority-Urgency-System-for-Chunks.patch similarity index 77% rename from Spigot-Server-Patches/0530-Implement-Chunk-Priority-Urgency-System-for-Chunks.patch rename to Spigot-Server-Patches/0529-Implement-Chunk-Priority-Urgency-System-for-Chunks.patch index 0a7b5d6341..470bcee2a1 100644 --- a/Spigot-Server-Patches/0530-Implement-Chunk-Priority-Urgency-System-for-Chunks.patch +++ b/Spigot-Server-Patches/0529-Implement-Chunk-Priority-Urgency-System-for-Chunks.patch @@ -23,7 +23,7 @@ Chunks in front of the player have higher priority, to help with fast traveling players keep up with their movement. diff --git a/src/main/java/com/destroystokyo/paper/io/chunk/ChunkTaskManager.java b/src/main/java/com/destroystokyo/paper/io/chunk/ChunkTaskManager.java -index a5f4cdaf06bfbb0dd957db9a1335c17b073d646d..3a4e7d8ce0a4591f56ec08ebe1c3bbb4f046b128 100644 +index b5c2e1f4a2b5fdcaa6bb01f4b3b6847cd5b73ae8..6209b33d8497ec56bbde507e523db0649c66f590 100644 --- a/src/main/java/com/destroystokyo/paper/io/chunk/ChunkTaskManager.java +++ b/src/main/java/com/destroystokyo/paper/io/chunk/ChunkTaskManager.java @@ -4,7 +4,10 @@ import com.destroystokyo.paper.io.PaperFileIOThread; @@ -37,45 +37,48 @@ index a5f4cdaf06bfbb0dd957db9a1335c17b073d646d..3a4e7d8ce0a4591f56ec08ebe1c3bbb4 import net.minecraft.server.IAsyncTaskHandler; import net.minecraft.server.IChunkAccess; import net.minecraft.server.MinecraftServer; -@@ -125,6 +128,36 @@ public final class ChunkTaskManager { +@@ -106,7 +109,7 @@ public final class ChunkTaskManager { + } + + static void dumpChunkInfo(Set seenChunks, PlayerChunk chunkHolder, int x, int z) { +- dumpChunkInfo(seenChunks, chunkHolder, x, z, 0, 1); ++ dumpChunkInfo(seenChunks, chunkHolder, x, z, 0, 4); + } + + static void dumpChunkInfo(Set seenChunks, PlayerChunk chunkHolder, int x, int z, int indent, int maxDepth) { +@@ -127,6 +130,30 @@ public final class ChunkTaskManager { PaperFileIOThread.LOGGER.log(Level.ERROR, indentStr + "Chunk Status - " + ((chunk == null) ? "null chunk" : chunk.getChunkStatus().toString())); PaperFileIOThread.LOGGER.log(Level.ERROR, indentStr + "Chunk Ticket Status - " + PlayerChunk.getChunkStatus(chunkHolder.getTicketLevel())); PaperFileIOThread.LOGGER.log(Level.ERROR, indentStr + "Chunk Holder Status - " + ((holderStatus == null) ? "null" : holderStatus.toString())); + PaperFileIOThread.LOGGER.log(Level.ERROR, indentStr + "Chunk Holder Priority - " + chunkHolder.getCurrentPriority()); -+ synchronized (chunkHolder.neighborPriorities) { -+ if (!chunkHolder.neighborPriorities.isEmpty()) { -+ PaperFileIOThread.LOGGER.log(Level.ERROR, indentStr + "Neighbors Requested Priority: "); -+ for (Long2ObjectMap.Entry entry : chunkHolder.neighborPriorities.long2ObjectEntrySet()) { -+ ChunkCoordIntPair r = new ChunkCoordIntPair(entry.getLongKey()); -+ PaperFileIOThread.LOGGER.log(Level.ERROR, indentStr + " (" + r.x + "," + r.z + "): " + entry.getValue()); ++ ++ if (!chunkHolder.neighbors.isEmpty()) { ++ if (indent >= maxDepth) { ++ PaperFileIOThread.LOGGER.log(Level.ERROR, indentStr + "Chunk Neighbors: (Can't show, too deeply nested)"); ++ return; ++ } ++ PaperFileIOThread.LOGGER.log(Level.ERROR, indentStr + "Chunk Neighbors: "); ++ for (PlayerChunk neighbor : chunkHolder.neighbors.keySet()) { ++ ChunkStatus status = neighbor.getChunkHolderStatus(); ++ if (status != null && status.isAtLeastStatus(PlayerChunk.getChunkStatus(neighbor.getTicketLevel()))) { ++ continue; + } ++ int nx = neighbor.location.x; ++ int nz = neighbor.location.z; ++ if (seenChunks.contains(neighbor)) { ++ PaperFileIOThread.LOGGER.log(Level.ERROR, indentStr + " " + nx + "," + nz + " in " + chunkHolder.getWorld().getWorld().getName() + " (CIRCULAR)"); ++ continue; ++ } ++ PaperFileIOThread.LOGGER.log(Level.ERROR, indentStr + " " + nx + "," + nz + " in " + chunkHolder.getWorld().getWorld().getName() + ":"); ++ dumpChunkInfo(seenChunks, neighbor, nx, nz, indent + 1, maxDepth); + } + } + -+ synchronized (chunkHolder.neighbors) { -+ if (!chunkHolder.neighbors.isEmpty()) { -+ PaperFileIOThread.LOGGER.log(Level.ERROR, indentStr + "Chunk Neighbors: "); -+ for (PlayerChunk neighbor : chunkHolder.neighbors.keySet()) { -+ ChunkStatus status = neighbor.getChunkHolderStatus(); -+ if (status != null && status.isAtLeastStatus(PlayerChunk.getChunkStatus(neighbor.getTicketLevel()))) { -+ continue; -+ } -+ int nx = neighbor.location.x; -+ int nz = neighbor.location.z; -+ if (seenChunks.contains(neighbor)) { -+ PaperFileIOThread.LOGGER.log(Level.ERROR, indentStr + " " + nx + "," + nz + " in " + chunkHolder.getWorld().getWorld().getName() + " (CIRCULAR)"); -+ continue; -+ } -+ PaperFileIOThread.LOGGER.log(Level.ERROR, indentStr + " " + nx + "," + nz + " in " + chunkHolder.getWorld().getWorld().getName() + ":"); -+ dumpChunkInfo(seenChunks, neighbor, nx, nz, indent + 1); -+ } -+ } -+ } } } diff --git a/src/main/java/net/minecraft/server/ChunkMapDistance.java b/src/main/java/net/minecraft/server/ChunkMapDistance.java -index 586a20fe5c77c2ad5fa26f337a94a16e21d8b5e2..6e422836f5a013d946965a2bb807c862cfc53912 100644 +index 586a20fe5c77c2ad5fa26f337a94a16e21d8b5e2..771d879711988bbece02c8b375db786b733925b5 100644 --- a/src/main/java/net/minecraft/server/ChunkMapDistance.java +++ b/src/main/java/net/minecraft/server/ChunkMapDistance.java @@ -23,6 +23,7 @@ import java.util.concurrent.Executor; @@ -94,7 +97,15 @@ index 586a20fe5c77c2ad5fa26f337a94a16e21d8b5e2..6e422836f5a013d946965a2bb807c862 return !arraysetsorted.isEmpty() ? ((Ticket) arraysetsorted.b()).b() : PlayerChunkMap.GOLDEN_TICKET + 1; } -@@ -107,11 +109,13 @@ public abstract class ChunkMapDistance { +@@ -97,6 +99,7 @@ public abstract class ChunkMapDistance { + + public boolean a(PlayerChunkMap playerchunkmap) { + //this.f.a(); // Paper - no longer used ++ AsyncCatcher.catchOp("DistanceManagerTick"); + this.g.a(); + int i = Integer.MAX_VALUE - this.e.a(Integer.MAX_VALUE); + boolean flag = i != 0; +@@ -107,11 +110,13 @@ public abstract class ChunkMapDistance { // Paper start if (!this.pendingChunkUpdates.isEmpty()) { @@ -108,7 +119,7 @@ index 586a20fe5c77c2ad5fa26f337a94a16e21d8b5e2..6e422836f5a013d946965a2bb807c862 // Paper end return true; } else { -@@ -147,14 +151,16 @@ public abstract class ChunkMapDistance { +@@ -147,14 +152,16 @@ public abstract class ChunkMapDistance { return flag; } } @@ -126,7 +137,7 @@ index 586a20fe5c77c2ad5fa26f337a94a16e21d8b5e2..6e422836f5a013d946965a2bb807c862 this.e.b(i, ticket.b(), true); } -@@ -162,6 +168,7 @@ public abstract class ChunkMapDistance { +@@ -162,6 +169,7 @@ public abstract class ChunkMapDistance { } private boolean removeTicket(long i, Ticket ticket) { // CraftBukkit - void -> boolean @@ -134,11 +145,12 @@ index 586a20fe5c77c2ad5fa26f337a94a16e21d8b5e2..6e422836f5a013d946965a2bb807c862 ArraySetSorted> arraysetsorted = this.e(i); boolean removed = false; // CraftBukkit -@@ -182,6 +189,59 @@ public abstract class ChunkMapDistance { +@@ -182,6 +190,82 @@ public abstract class ChunkMapDistance { this.addTicketAtLevel(tickettype, chunkcoordintpair, i, t0); } + // Paper start ++ public static final int PRIORITY_TICKET_LEVEL = 33; + public boolean markUrgent(ChunkCoordIntPair coords) { + return addPriorityTicket(coords, TicketType.URGENT, 30); + } @@ -150,16 +162,38 @@ index 586a20fe5c77c2ad5fa26f337a94a16e21d8b5e2..6e422836f5a013d946965a2bb807c862 + private boolean addPriorityTicket(ChunkCoordIntPair coords, TicketType ticketType, int priority) { + AsyncCatcher.catchOp("ChunkMapDistance::addPriorityTicket"); + long pair = coords.pair(); -+ Ticket ticket = new Ticket(ticketType, 34, coords); -+ ticket.priority = priority; + -+ this.removeTicket(pair, ticket); -+ boolean added = this.addTicket(pair, ticket); -+ PlayerChunk updatingChunk = chunkMap.getUpdatingChunk(pair); -+ if (updatingChunk != null) { -+ chunkMap.queueHolderUpdate(updatingChunk); ++ boolean success; ++ if (!(success = updatePriorityTicket(coords, ticketType, priority))) { ++ Ticket ticket = new Ticket(ticketType, PRIORITY_TICKET_LEVEL, coords); ++ ticket.priority = priority; ++ success = this.addTicket(pair, ticket); + } -+ return added; ++ ++ chunkMap.world.getChunkProvider().tickDistanceManager(); ++ PlayerChunk updatingChunk = chunkMap.getUpdatingChunk(pair); ++ if (updatingChunk != null && updatingChunk.priorityBoost < priority) { ++ // May not be enqueued, enqueue it if not and tick distance manager ++ chunkMap.queueHolderUpdate(updatingChunk); ++ chunkMap.world.getChunkProvider().tickDistanceManager(); ++ } ++ return success; ++ } ++ ++ private boolean updatePriorityTicket(ChunkCoordIntPair coords, TicketType type, int priority) { ++ ArraySetSorted> tickets = this.tickets.get(coords.pair()); ++ if (tickets == null) { ++ return false; ++ } ++ for (Ticket ticket : tickets) { ++ if (ticket.getTicketType() == type) { ++ // We only support increasing, not decreasing, too complicated ++ ticket.priority = Math.max(ticket.priority, priority); ++ return true; ++ } ++ } ++ ++ return false; + } + + public int getChunkPriority(ChunkCoordIntPair coords) { @@ -183,18 +217,18 @@ index 586a20fe5c77c2ad5fa26f337a94a16e21d8b5e2..6e422836f5a013d946965a2bb807c862 + + public void clearPriorityTickets(ChunkCoordIntPair coords) { + AsyncCatcher.catchOp("ChunkMapDistance::clearPriority"); -+ this.removeTicket(coords.pair(), new Ticket(TicketType.PRIORITY, 34, coords)); ++ this.removeTicket(coords.pair(), new Ticket(TicketType.PRIORITY, PRIORITY_TICKET_LEVEL, coords)); + } + + public void clearUrgent(ChunkCoordIntPair coords) { + AsyncCatcher.catchOp("ChunkMapDistance::clearUrgent"); -+ this.removeTicket(coords.pair(), new Ticket(TicketType.URGENT, 34, coords)); ++ this.removeTicket(coords.pair(), new Ticket(TicketType.URGENT, PRIORITY_TICKET_LEVEL, coords)); + } + // Paper end public boolean addTicketAtLevel(TicketType ticketType, ChunkCoordIntPair chunkcoordintpair, int level, T identifier) { return this.addTicket(chunkcoordintpair.pair(), new Ticket<>(ticketType, level, identifier)); // CraftBukkit end -@@ -397,12 +457,14 @@ public abstract class ChunkMapDistance { +@@ -397,12 +481,14 @@ public abstract class ChunkMapDistance { }); }, i, () -> { @@ -297,10 +331,10 @@ index 07a6fc3d88e7d44bfab7f3d6a0eef7dc132ab422..d60f659b368500e3a8c3305f99e60ffc for (int i = 0; i < this.inventory.getSize(); ++i) { ItemStack itemstack = this.inventory.getItem(i); diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java -index d129c7f54d9f65fff6f512d8ff5f1c3866632603..9b9536fba4a62c0153b921e678e6a9683bf2e37f 100644 +index 0d1065688b19ceca9440bc8bf2bf65910f03fa46..8a349964578e07e5ed13f801c57de68459220da9 100644 --- a/src/main/java/net/minecraft/server/MCUtil.java +++ b/src/main/java/net/minecraft/server/MCUtil.java -@@ -658,6 +658,7 @@ public final class MCUtil { +@@ -684,6 +684,7 @@ public final class MCUtil { chunkData.addProperty("x", playerChunk.location.x); chunkData.addProperty("z", playerChunk.location.z); chunkData.addProperty("ticket-level", playerChunk.getTicketLevel()); @@ -309,7 +343,7 @@ index d129c7f54d9f65fff6f512d8ff5f1c3866632603..9b9536fba4a62c0153b921e678e6a968 chunkData.addProperty("queued-for-unload", chunkMap.unloadQueue.contains(playerChunk.location.pair())); chunkData.addProperty("status", status == null ? "unloaded" : status.toString()); diff --git a/src/main/java/net/minecraft/server/PlayerChunk.java b/src/main/java/net/minecraft/server/PlayerChunk.java -index aeca6b2b9d5d73aeb6dc639b5cad2f2533a2de44..ee70efd4910fbbc489d4eb41342ece44f898c284 100644 +index aeca6b2b9d5d73aeb6dc639b5cad2f2533a2de44..b8fe42e8123e972b1ec97b048c35d90118076e66 100644 --- a/src/main/java/net/minecraft/server/PlayerChunk.java +++ b/src/main/java/net/minecraft/server/PlayerChunk.java @@ -26,8 +26,8 @@ public class PlayerChunk { @@ -345,7 +379,7 @@ index aeca6b2b9d5d73aeb6dc639b5cad2f2533a2de44..ee70efd4910fbbc489d4eb41342ece44 + int priority = neighborPriority; // if we have a neighbor priority, use it + int myPriority = getMyPriority(); + -+ if (priority == -1 || priority > myPriority) { ++ if (priority == -1 || (ticketLevel <= 33 && priority > myPriority)) { + priority = myPriority; + } + @@ -357,7 +391,7 @@ index aeca6b2b9d5d73aeb6dc639b5cad2f2533a2de44..ee70efd4910fbbc489d4eb41342ece44 + return 1; // Urgent - ticket level isn't always 31 so 33-30 = 3 + } + int basePriority = ticketLevel - priorityBoost; -+ if (ticketLevel >= 34 && priorityBoost == 0 && neighborPriorities.isEmpty()) { ++ if (ticketLevel >= 33 && priorityBoost == 0 && (neighborPriority >= 34 || neighborPriorities.isEmpty())) { + basePriority += 5; + } + return basePriority; @@ -460,7 +494,7 @@ index aeca6b2b9d5d73aeb6dc639b5cad2f2533a2de44..ee70efd4910fbbc489d4eb41342ece44 public PlayerChunk(ChunkCoordIntPair chunkcoordintpair, int i, LightEngine lightengine, PlayerChunk.c playerchunk_c, PlayerChunk.d playerchunk_d) { this.statusFutures = new AtomicReferenceArray(PlayerChunk.CHUNK_STATUSES.size()); -@@ -165,6 +288,12 @@ public class PlayerChunk { +@@ -165,6 +288,15 @@ public class PlayerChunk { } return null; } @@ -469,11 +503,14 @@ index aeca6b2b9d5d73aeb6dc639b5cad2f2533a2de44..ee70efd4910fbbc489d4eb41342ece44 + return status; + } + return CHUNK_STATUSES.get(status.getStatusIndex() + 1); ++ } ++ public CompletableFuture> getStatusFutureUncheckedMain(ChunkStatus chunkstatus) { ++ return MCUtil.ensureMain(getStatusFutureUnchecked(chunkstatus)); + } // Paper end public CompletableFuture> getStatusFutureUnchecked(ChunkStatus chunkstatus) { -@@ -418,6 +547,7 @@ public class PlayerChunk { +@@ -418,6 +550,7 @@ public class PlayerChunk { return this.n; } @@ -481,7 +518,23 @@ index aeca6b2b9d5d73aeb6dc639b5cad2f2533a2de44..ee70efd4910fbbc489d4eb41342ece44 private void d(int i) { this.n = i; } -@@ -507,6 +637,7 @@ public class PlayerChunk { +@@ -436,7 +569,7 @@ public class PlayerChunk { + // CraftBukkit start + // ChunkUnloadEvent: Called before the chunk is unloaded: isChunkLoaded is still true and chunk can still be modified by plugins. + if (playerchunk_state.isAtLeast(PlayerChunk.State.BORDER) && !playerchunk_state1.isAtLeast(PlayerChunk.State.BORDER)) { +- this.getStatusFutureUnchecked(ChunkStatus.FULL).thenAccept((either) -> { ++ this.getStatusFutureUncheckedMain(ChunkStatus.FULL).thenAccept((either) -> { // Paper - ensure main + Chunk chunk = (Chunk)either.left().orElse(null); + if (chunk != null) { + playerchunkmap.callbackExecutor.execute(() -> { +@@ -501,12 +634,13 @@ public class PlayerChunk { + if (!flag2 && flag3) { + // Paper start - cache ticking ready status + int expectCreateCount = ++this.fullChunkCreateCount; +- this.fullChunkFuture = playerchunkmap.b(this); this.fullChunkFuture.thenAccept((either) -> { ++ this.fullChunkFuture = playerchunkmap.b(this); MCUtil.ensureMain(this.fullChunkFuture).thenAccept((either) -> { // Paper - ensure main + if (either.left().isPresent() && PlayerChunk.this.fullChunkCreateCount == expectCreateCount) { + // note: Here is a very good place to add callbacks to logic waiting on this. Chunk fullChunk = either.left().get(); PlayerChunk.this.isFullChunkReady = true; fullChunk.playerChunk = PlayerChunk.this; @@ -489,7 +542,25 @@ index aeca6b2b9d5d73aeb6dc639b5cad2f2533a2de44..ee70efd4910fbbc489d4eb41342ece44 } -@@ -581,8 +712,22 @@ public class PlayerChunk { +@@ -531,7 +665,7 @@ public class PlayerChunk { + + if (!flag4 && flag5) { + // Paper start - cache ticking ready status +- this.tickingFuture = playerchunkmap.a(this); this.tickingFuture.thenAccept((either) -> { ++ this.tickingFuture = playerchunkmap.a(this); MCUtil.ensureMain(this.tickingFuture).thenAccept((either) -> { // Paper - ensure main + if (either.left().isPresent()) { + // note: Here is a very good place to add callbacks to logic waiting on this. + Chunk tickingChunk = either.left().get(); +@@ -562,7 +696,7 @@ public class PlayerChunk { + } + + // Paper start - cache ticking ready status +- this.entityTickingFuture = playerchunkmap.b(this.location); this.entityTickingFuture.thenAccept((either) -> { ++ this.entityTickingFuture = playerchunkmap.b(this.location); MCUtil.ensureMain(this.entityTickingFuture).thenAccept((either) -> { // Paper ensureMain + if (either.left().isPresent()) { + // note: Here is a very good place to add callbacks to logic waiting on this. + Chunk entityTickingChunk = either.left().get(); +@@ -581,13 +715,29 @@ public class PlayerChunk { this.entityTickingFuture.complete(PlayerChunk.UNLOADED_CHUNK); this.isEntityTickingReady = false; // Paper - cache chunk ticking stage this.entityTickingFuture = PlayerChunk.UNLOADED_CHUNK_FUTURE; } @@ -507,14 +578,22 @@ index aeca6b2b9d5d73aeb6dc639b5cad2f2533a2de44..ee70efd4910fbbc489d4eb41342ece44 + } + chunkMap.world.asyncChunkTaskManager.raisePriority(location.x, location.z, ioPriority); + } -+ this.w.a(this.location, this::getCurrentPriority, priority, this::setPriority); // use preferred priority -+ int neighborsPriority = getNeighborsPriority(); -+ this.neighbors.forEach((neighbor, neighborDesired) -> neighbor.setNeighborPriority(this, neighborsPriority)); ++ if (getCurrentPriority() != priority) { ++ this.w.a(this.location, this::getCurrentPriority, priority, this::setPriority); // use preferred priority ++ int neighborsPriority = getNeighborsPriority(); ++ this.neighbors.forEach((neighbor, neighborDesired) -> neighbor.setNeighborPriority(this, neighborsPriority)); ++ } + // Paper end this.oldTicketLevel = this.ticketLevel; // CraftBukkit start // ChunkLoadEvent: Called after the chunk is loaded: isChunkLoaded returns true and chunk is ready to be modified by plugins. -@@ -669,6 +814,7 @@ public class PlayerChunk { + if (!playerchunk_state.isAtLeast(PlayerChunk.State.BORDER) && playerchunk_state1.isAtLeast(PlayerChunk.State.BORDER)) { +- this.getStatusFutureUnchecked(ChunkStatus.FULL).thenAccept((either) -> { ++ this.getStatusFutureUncheckedMain(ChunkStatus.FULL).thenAccept((either) -> { // Paper - ensure main + Chunk chunk = (Chunk)either.left().orElse(null); + if (chunk != null) { + playerchunkmap.callbackExecutor.execute(() -> { +@@ -669,6 +819,7 @@ public class PlayerChunk { public interface c { @@ -523,10 +602,34 @@ index aeca6b2b9d5d73aeb6dc639b5cad2f2533a2de44..ee70efd4910fbbc489d4eb41342ece44 } diff --git a/src/main/java/net/minecraft/server/PlayerChunkMap.java b/src/main/java/net/minecraft/server/PlayerChunkMap.java -index f1c3cb3ff8961bc688a1d38cd79b999e539cf866..7b4700bdef3a4dc89fd8ba0c98d76c63ad18d5de 100644 +index f1c3cb3ff8961bc688a1d38cd79b999e539cf866..8fa1d5f7af6920a5fc63347c3a5dcfc5948d50c4 100644 --- a/src/main/java/net/minecraft/server/PlayerChunkMap.java +++ b/src/main/java/net/minecraft/server/PlayerChunkMap.java -@@ -375,6 +375,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { +@@ -50,6 +50,7 @@ import org.apache.commons.lang3.mutable.MutableBoolean; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; + import org.bukkit.entity.Player; // CraftBukkit ++import org.spigotmc.AsyncCatcher; + + public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + +@@ -123,6 +124,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + + @Override + public void execute(Runnable runnable) { ++ AsyncCatcher.catchOp("Callback Executor execute"); + if (queued == null) { + queued = new java.util.ArrayDeque<>(); + } +@@ -131,6 +133,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + + @Override + public void run() { ++ AsyncCatcher.catchOp("Callback Executor run"); + if (queued == null) { + return; + } +@@ -375,6 +378,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { this.playerViewDistanceTickMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets, (EntityPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState) -> { @@ -534,7 +637,7 @@ index f1c3cb3ff8961bc688a1d38cd79b999e539cf866..7b4700bdef3a4dc89fd8ba0c98d76c63 if (newState.size() != 1) { return; } -@@ -393,7 +394,8 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { +@@ -393,7 +397,8 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { } ChunkCoordIntPair chunkPos = new ChunkCoordIntPair(rangeX, rangeZ); PlayerChunkMap.this.world.getChunkProvider().removeTicketAtLevel(TicketType.PLAYER, chunkPos, 31, chunkPos); // entity ticking level, TODO check on update @@ -544,20 +647,28 @@ index f1c3cb3ff8961bc688a1d38cd79b999e539cf866..7b4700bdef3a4dc89fd8ba0c98d76c63 this.playerViewDistanceNoTickMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets); this.playerViewDistanceBroadcastMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets, (EntityPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, -@@ -410,6 +412,77 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { +@@ -410,6 +415,85 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { }); // Paper end - no-tick view distance } + // Paper start - Chunk Prioritization + private static final int[][] neighborMatrix = {{-1, 0}, {0, -1}, {0, 1}, {1, 0}}; + public void queueHolderUpdate(PlayerChunk playerchunk) { -+ executor.execute(() -> { -+ if (isUnloading(playerchunk)) return; // unloaded ++ Runnable runnable = () -> { ++ if (isUnloading(playerchunk)) { ++ return; // unloaded ++ } + chunkDistanceManager.pendingChunkUpdates.add(playerchunk); + if (!chunkDistanceManager.pollingPendingChunkUpdates) { + world.getChunkProvider().tickDistanceManager(); + } -+ }); ++ }; ++ if (MCUtil.isMainThread()) { ++ // We can't use executor here because it will not execute tasks if its currently in the middle of executing tasks... ++ runnable.run(); ++ } else { ++ executor.execute(runnable); ++ } + } + + public boolean isUnloading(PlayerChunk playerchunk) { @@ -622,7 +733,7 @@ index f1c3cb3ff8961bc688a1d38cd79b999e539cf866..7b4700bdef3a4dc89fd8ba0c98d76c63 public void updatePlayerMobTypeMap(Entity entity) { if (!this.world.paperConfig.perPlayerMobSpawns) { -@@ -539,6 +612,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { +@@ -539,6 +623,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { List>> list = Lists.newArrayList(); int j = chunkcoordintpair.x; int k = chunkcoordintpair.z; @@ -630,7 +741,7 @@ index f1c3cb3ff8961bc688a1d38cd79b999e539cf866..7b4700bdef3a4dc89fd8ba0c98d76c63 for (int l = -i; l <= i; ++l) { for (int i1 = -i; i1 <= i; ++i1) { -@@ -557,6 +631,14 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { +@@ -557,6 +642,14 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { ChunkStatus chunkstatus = (ChunkStatus) intfunction.apply(j1); CompletableFuture> completablefuture = playerchunk.a(chunkstatus, this); @@ -645,7 +756,7 @@ index f1c3cb3ff8961bc688a1d38cd79b999e539cf866..7b4700bdef3a4dc89fd8ba0c98d76c63 list.add(completablefuture); } -@@ -1020,14 +1102,22 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { +@@ -1020,14 +1113,22 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { }; CompletableFuture chunkSaveFuture = this.world.asyncChunkTaskManager.getChunkSaveFuture(chunkcoordintpair.x, chunkcoordintpair.z); @@ -673,7 +784,7 @@ index f1c3cb3ff8961bc688a1d38cd79b999e539cf866..7b4700bdef3a4dc89fd8ba0c98d76c63 return ret; // Paper end } -@@ -1156,7 +1246,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { +@@ -1156,7 +1257,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { long i = playerchunk.i().pair(); playerchunk.getClass();