From 7c001d64a54ccb5c8840fffbeeb4be28fdd4fe70 Mon Sep 17 00:00:00 2001 From: Aikar Date: Fri, 22 May 2020 19:03:48 -0400 Subject: [PATCH] More Improvements to Chunks Fixed issues where urgent and prioritized chunks didn't actually always get their priority boosted correctly.... Properly deprioritize non ticking chunks. Limit recursion on watchdog prints to stop flooding as much Remove neighbor priorities from watchdog to reduce information reduce synchronization duration so that watch dog won't block main should main actually wake up probably fixed a deadlock risk in watchdog printing also that was leading to crashes fixed chunk holder enqueues not being processed correctly added async catchers in some locations that should not be ran async Fixed upstream bug where VITAL callbacks that must run on main actually could sometimes run on the server thread pool causing alot of these nasty bugs we've seen lately! This build will provide massive improvements to stability as well as even faster sync chunk load/gens now that priority is correctly set. Fixes #3435 --- Spigot-Server-Patches/0004-MC-Utils.patch | 71 ++++- .../0133-String-based-Action-Bar-API.patch | 37 --- .../0154-Basic-PlayerProfile-API.patch | 9 +- ...arseException-in-Entity-and-TE-names.patch | 4 +- .../0377-Chunk-debug-command.patch | 10 +- ...90-Asynchronous-chunk-IO-and-loading.patch | 34 +-- ...hunkMap-memory-use-for-visibleChunks.patch | 4 +- ...-Priority-Urgency-System-for-Chunks.patch} | 245 +++++++++++++----- 8 files changed, 268 insertions(+), 146 deletions(-) rename Spigot-Server-Patches/{0530-Implement-Chunk-Priority-Urgency-System-for-Chunks.patch => 0529-Implement-Chunk-Priority-Urgency-System-for-Chunks.patch} (77%) 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();