From 4d38ee111f6a2b9c18f284aab38157c7c03c7936 Mon Sep 17 00:00:00 2001 From: Aikar Date: Fri, 22 May 2020 00:46:44 -0400 Subject: [PATCH] Many fixes and improvements to chunk prioritization I believe this brings us back to stable. A lot of complexity was learned about juggling priorities. We were essentially promoting more chunks to urgent than really needed to be urgent. So this commit adds a lot more logic to juggle neighbor priorities and demote their priority once they meet the requirements needed of them. This greatly improves the performance of "urgent" chunks". Fixes #3410 Fixes #3426 Fixes #3425 Fixes #3416 --- ...90-Asynchronous-chunk-IO-and-loading.patch | 34 +- .../0392-Reduce-sync-loads.patch | 4 +- ...ement-optional-per-player-mob-spawns.patch | 6 +- ...e-getChunkAt-calls-for-loaded-chunks.patch | 6 +- ...hunkMap-memory-use-for-visibleChunks.patch | 4 +- ...asks-Speed-up-processing-of-chunk-lo.patch | 12 +- ...oviderServer-s-chunk-level-checking-.patch | 4 +- ...-Chunk-Post-Processing-deadlock-risk.patch | 4 +- ...-isOutsideRange-to-use-distance-maps.patch | 8 +- ...-Priority-Urgency-System-for-Chunks.patch} | 454 +++++++++++------- scripts/testServer.sh | 12 +- 11 files changed, 328 insertions(+), 220 deletions(-) rename Spigot-Server-Patches/{0529-Implement-Chunk-Priority-Urgency-System-for-Chunks.patch => 0530-Implement-Chunk-Priority-Urgency-System-for-Chunks.patch} (66%) 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 74853d4bd..779f30faa 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..d9580eb998801edd34c610ced3f82f9627c6685b +index 0000000000000000000000000000000000000000..a5f4cdaf06bfbb0dd957db9a1335c17b073d646d --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/io/chunk/ChunkTaskManager.java -@@ -0,0 +1,502 @@ +@@ -0,0 +1,509 @@ +package com.destroystokyo.paper.io.chunk; + +import com.destroystokyo.paper.io.PaperFileIOThread; @@ -1870,6 +1870,8 @@ index 0000000000000000000000000000000000000000..d9580eb998801edd34c610ced3f82f96 +import org.spigotmc.AsyncCatcher; + +import java.util.ArrayDeque; ++import java.util.HashSet; ++import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; @@ -1940,6 +1942,7 @@ index 0000000000000000000000000000000000000000..d9580eb998801edd34c610ced3f82f96 + } + + PaperFileIOThread.LOGGER.log(Level.ERROR, "Chunk wait task info below: "); ++ Set seenChunks = new HashSet<>(); + + for (final ChunkInfo chunkInfo : WAITING_CHUNKS) { + final long key = IOUtil.getCoordinateKey(chunkInfo.chunkX, chunkInfo.chunkZ); @@ -1952,15 +1955,19 @@ index 0000000000000000000000000000000000000000..d9580eb998801edd34c610ced3f82f96 + // 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(chunkHolder, chunkInfo.chunkX, chunkInfo.chunkZ); ++ dumpChunkInfo(seenChunks, chunkHolder, chunkInfo.chunkX, chunkInfo.chunkZ); + } + } + } + -+ static void dumpChunkInfo(PlayerChunk chunkHolder, int x, int z) { -+ dumpChunkInfo(chunkHolder, x, z, 0); ++ static void dumpChunkInfo(Set seenChunks, PlayerChunk chunkHolder, int x, int z) { ++ dumpChunkInfo(seenChunks, chunkHolder, x, z, 0); + } -+ static void dumpChunkInfo(PlayerChunk chunkHolder, int x, int z, int indent) { ++ static void dumpChunkInfo(Set seenChunks, PlayerChunk chunkHolder, int x, int z, int indent) { ++ if (seenChunks.contains(chunkHolder)) { ++ return; ++ } ++ seenChunks.add(chunkHolder); + String indentStr = StringUtils.repeat(" ", indent); + if (chunkHolder == null) { + PaperFileIOThread.LOGGER.log(Level.ERROR, indentStr + "Chunk Holder - null for (" + x +"," + z +")"); @@ -2354,10 +2361,10 @@ index 0000000000000000000000000000000000000000..d9580eb998801edd34c610ced3f82f96 + +} diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java -index 4c9c8e483974f8869d6711626620cfd7d814d956..54325d9305953fa7520feec6c80e2931888141c2 100644 +index 4c9c8e483974f8869d6711626620cfd7d814d956..a88e8598aab55ac769a5f186507f362e4f99cef4 100644 --- a/src/main/java/net/minecraft/server/ChunkProviderServer.java +++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java -@@ -299,11 +299,137 @@ public class ChunkProviderServer extends IChunkProvider { +@@ -299,11 +299,138 @@ public class ChunkProviderServer extends IChunkProvider { return playerChunk.getAvailableChunkNow(); } @@ -2380,7 +2387,8 @@ index 4c9c8e483974f8869d6711626620cfd7d814d956..54325d9305953fa7520feec6c80e2931 + } + + if (!com.destroystokyo.paper.PaperConfig.asyncChunks) { -+ Chunk chunk = getChunkAt(x, z, gen); ++ world.getWorld().loadChunk(x, z, gen); ++ Chunk chunk = getChunkAtIfLoadedMainThread(x, z); + return CompletableFuture.completedFuture(chunk != null ? Either.left(chunk) : PlayerChunk.UNLOADED_CHUNK_ACCESS); + } + @@ -2419,7 +2427,7 @@ index 4c9c8e483974f8869d6711626620cfd7d814d956..54325d9305953fa7520feec6c80e2931 + IChunkAccess current = this.getChunkAtImmediately(x, z); // we want to bypass ticket restrictions + if (current != null) { + if (!(current instanceof ProtoChunkExtension) && !(current instanceof net.minecraft.server.Chunk)) { -+ return CompletableFuture.completedFuture(Either.left(null)); ++ return CompletableFuture.completedFuture(PlayerChunk.UNLOADED_CHUNK_ACCESS); + } + // we know the chunk is at full status here (either in read-only mode or the real thing) + return this.bringToFullStatusAsync(x, z, chunkPos, isUrgent); @@ -2495,7 +2503,7 @@ index 4c9c8e483974f8869d6711626620cfd7d814d956..54325d9305953fa7520feec6c80e2931 if (Thread.currentThread() != this.serverThread) { return (IChunkAccess) CompletableFuture.supplyAsync(() -> { return this.getChunkAt(i, j, chunkstatus, flag); -@@ -326,11 +452,16 @@ public class ChunkProviderServer extends IChunkProvider { +@@ -326,11 +453,16 @@ public class ChunkProviderServer extends IChunkProvider { } gameprofilerfiller.c("getChunkCacheMiss"); @@ -2513,7 +2521,7 @@ index 4c9c8e483974f8869d6711626620cfd7d814d956..54325d9305953fa7520feec6c80e2931 this.world.timings.syncChunkLoad.stopTiming(); // Paper } // Paper ichunkaccess = (IChunkAccess) ((Either) completablefuture.join()).map((ichunkaccess1) -> { -@@ -396,6 +527,11 @@ public class ChunkProviderServer extends IChunkProvider { +@@ -396,6 +528,11 @@ public class ChunkProviderServer extends IChunkProvider { } private CompletableFuture> getChunkFutureMainThread(int i, int j, ChunkStatus chunkstatus, boolean flag) { @@ -2525,7 +2533,7 @@ index 4c9c8e483974f8869d6711626620cfd7d814d956..54325d9305953fa7520feec6c80e2931 ChunkCoordIntPair chunkcoordintpair = new ChunkCoordIntPair(i, j); long k = chunkcoordintpair.pair(); int l = 33 + ChunkStatus.a(chunkstatus); -@@ -835,11 +971,12 @@ public class ChunkProviderServer extends IChunkProvider { +@@ -835,11 +972,12 @@ public class ChunkProviderServer extends IChunkProvider { protected boolean executeNext() { // CraftBukkit start - process pending Chunk loadCallback() and unloadCallback() after each run task try { diff --git a/Spigot-Server-Patches/0392-Reduce-sync-loads.patch b/Spigot-Server-Patches/0392-Reduce-sync-loads.patch index 5bcaddb38..e665656e2 100644 --- a/Spigot-Server-Patches/0392-Reduce-sync-loads.patch +++ b/Spigot-Server-Patches/0392-Reduce-sync-loads.patch @@ -286,10 +286,10 @@ index 0000000000000000000000000000000000000000..59aec103295f747793fdc0a52eb45f41 + } +} diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java -index 54325d9305953fa7520feec6c80e2931888141c2..32bea1dea9ebb05ed94f5b47e6ad2145f6319431 100644 +index a88e8598aab55ac769a5f186507f362e4f99cef4..21e444e6ad78081353a7330b60c74164e4596d61 100644 --- a/src/main/java/net/minecraft/server/ChunkProviderServer.java +++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java -@@ -459,6 +459,7 @@ public class ChunkProviderServer extends IChunkProvider { +@@ -460,6 +460,7 @@ public class ChunkProviderServer extends IChunkProvider { this.world.asyncChunkTaskManager.raisePriority(x, z, com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGHEST_PRIORITY); com.destroystokyo.paper.io.chunk.ChunkTaskManager.pushChunkWait(this.world, x, z); // Paper end diff --git a/Spigot-Server-Patches/0396-implement-optional-per-player-mob-spawns.patch b/Spigot-Server-Patches/0396-implement-optional-per-player-mob-spawns.patch index c1a26849a..fcb7d46af 100644 --- a/Spigot-Server-Patches/0396-implement-optional-per-player-mob-spawns.patch +++ b/Spigot-Server-Patches/0396-implement-optional-per-player-mob-spawns.patch @@ -545,10 +545,10 @@ index 0000000000000000000000000000000000000000..4f13d3ff8391793a99f067189f854078 + } +} diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java -index 32bea1dea9ebb05ed94f5b47e6ad2145f6319431..7a4e2c350e78b22dc035471ad0d7191dfd7afede 100644 +index 21e444e6ad78081353a7330b60c74164e4596d61..927abc78e973e6f0f87e12303409fd808b3cf6ab 100644 --- a/src/main/java/net/minecraft/server/ChunkProviderServer.java +++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java -@@ -746,7 +746,22 @@ public class ChunkProviderServer extends IChunkProvider { +@@ -747,7 +747,22 @@ public class ChunkProviderServer extends IChunkProvider { this.world.timings.countNaturalMobs.startTiming(); // Paper - timings int l = this.chunkMapDistance.b(); EnumCreatureType[] aenumcreaturetype = EnumCreatureType.values(); @@ -572,7 +572,7 @@ index 32bea1dea9ebb05ed94f5b47e6ad2145f6319431..7a4e2c350e78b22dc035471ad0d7191d this.world.timings.countNaturalMobs.stopTiming(); // Paper - timings this.world.getMethodProfiler().exit(); -@@ -814,8 +829,23 @@ public class ChunkProviderServer extends IChunkProvider { +@@ -815,8 +830,23 @@ public class ChunkProviderServer extends IChunkProvider { if (enumcreaturetype != EnumCreatureType.MISC && (!enumcreaturetype.c() || this.allowAnimals) && (enumcreaturetype.c() || this.allowMonsters) && (!enumcreaturetype.d() || flag2)) { int k1 = limit * l / ChunkProviderServer.b; // CraftBukkit - use per-world limits diff --git a/Spigot-Server-Patches/0431-Optimise-getChunkAt-calls-for-loaded-chunks.patch b/Spigot-Server-Patches/0431-Optimise-getChunkAt-calls-for-loaded-chunks.patch index 2f66ea350..daeb97a82 100644 --- a/Spigot-Server-Patches/0431-Optimise-getChunkAt-calls-for-loaded-chunks.patch +++ b/Spigot-Server-Patches/0431-Optimise-getChunkAt-calls-for-loaded-chunks.patch @@ -7,10 +7,10 @@ bypass the need to get a player chunk, then get the either, then unwrap it... diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java -index 7a4e2c350e78b22dc035471ad0d7191dfd7afede..4f65c3aca4e1c299114c03339605e0749a969653 100644 +index 927abc78e973e6f0f87e12303409fd808b3cf6ab..6688b1340e2cc8fb13a7e80c9b7c37b8822dcecd 100644 --- a/src/main/java/net/minecraft/server/ChunkProviderServer.java +++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java -@@ -435,6 +435,12 @@ public class ChunkProviderServer extends IChunkProvider { +@@ -436,6 +436,12 @@ public class ChunkProviderServer extends IChunkProvider { return this.getChunkAt(i, j, chunkstatus, flag); }, this.serverThreadQueue).join(); } else { @@ -23,7 +23,7 @@ index 7a4e2c350e78b22dc035471ad0d7191dfd7afede..4f65c3aca4e1c299114c03339605e074 GameProfilerFiller gameprofilerfiller = this.world.getMethodProfiler(); gameprofilerfiller.c("getChunk"); -@@ -485,39 +491,7 @@ public class ChunkProviderServer extends IChunkProvider { +@@ -486,39 +492,7 @@ public class ChunkProviderServer extends IChunkProvider { if (Thread.currentThread() != this.serverThread) { return null; } else { 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 285a5819e..d522a3084 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 @@ -57,10 +57,10 @@ index 0000000000000000000000000000000000000000..f6ff4d8132a95895680f5bc81f8f873e + } +} diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java -index fd998e4fb1534690a2ef8c1bca55e0ae9fe855f9..8f849d83d08b39f1cd9184f484a2089a7a3124ef 100644 +index d53b34ba552771bf271131ce0a56ebb992ccc84c..a1b5e6b90fc93f83186cf3ebf3e158767008c69a 100644 --- a/src/main/java/net/minecraft/server/ChunkProviderServer.java +++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java -@@ -755,7 +755,7 @@ public class ChunkProviderServer extends IChunkProvider { +@@ -756,7 +756,7 @@ public class ChunkProviderServer extends IChunkProvider { entityPlayer.playerNaturallySpawnedEvent.callEvent(); }; // Paper end diff --git a/Spigot-Server-Patches/0459-Mid-Tick-Chunk-Tasks-Speed-up-processing-of-chunk-lo.patch b/Spigot-Server-Patches/0459-Mid-Tick-Chunk-Tasks-Speed-up-processing-of-chunk-lo.patch index 3acefa77c..d8b1ecb97 100644 --- a/Spigot-Server-Patches/0459-Mid-Tick-Chunk-Tasks-Speed-up-processing-of-chunk-lo.patch +++ b/Spigot-Server-Patches/0459-Mid-Tick-Chunk-Tasks-Speed-up-processing-of-chunk-lo.patch @@ -56,10 +56,10 @@ index f1b41e16c8ce8323a896339c5d822f8ff7d8f7e6..f8f225e18fa38cad917f52a379233e0a + } } diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java -index 8f849d83d08b39f1cd9184f484a2089a7a3124ef..377d554553ce81f66207541d963f826867e66592 100644 +index a1b5e6b90fc93f83186cf3ebf3e158767008c69a..2ef8506f10426b8a5877e30986c105c0d95be774 100644 --- a/src/main/java/net/minecraft/server/ChunkProviderServer.java +++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java -@@ -688,6 +688,7 @@ public class ChunkProviderServer extends IChunkProvider { +@@ -689,6 +689,7 @@ public class ChunkProviderServer extends IChunkProvider { this.world.getMethodProfiler().enter("purge"); this.world.timings.doChunkMap.startTiming(); // Spigot this.chunkMapDistance.purgeTickets(); @@ -67,7 +67,7 @@ index 8f849d83d08b39f1cd9184f484a2089a7a3124ef..377d554553ce81f66207541d963f8268 this.tickDistanceManager(); this.world.timings.doChunkMap.stopTiming(); // Spigot this.world.getMethodProfiler().exitEnter("chunks"); -@@ -697,6 +698,7 @@ public class ChunkProviderServer extends IChunkProvider { +@@ -698,6 +699,7 @@ public class ChunkProviderServer extends IChunkProvider { this.world.timings.doChunkUnload.startTiming(); // Spigot this.world.getMethodProfiler().exitEnter("unload"); this.playerChunkMap.unloadChunks(booleansupplier); @@ -75,7 +75,7 @@ index 8f849d83d08b39f1cd9184f484a2089a7a3124ef..377d554553ce81f66207541d963f8268 this.world.timings.doChunkUnload.stopTiming(); // Spigot this.world.getMethodProfiler().exit(); this.clearCache(); -@@ -755,7 +757,7 @@ public class ChunkProviderServer extends IChunkProvider { +@@ -756,7 +758,7 @@ public class ChunkProviderServer extends IChunkProvider { entityPlayer.playerNaturallySpawnedEvent.callEvent(); }; // Paper end @@ -84,7 +84,7 @@ index 8f849d83d08b39f1cd9184f484a2089a7a3124ef..377d554553ce81f66207541d963f8268 Optional optional = ((Either) playerchunk.b().getNow(PlayerChunk.UNLOADED_CHUNK)).left(); if (optional.isPresent()) { -@@ -838,6 +840,7 @@ public class ChunkProviderServer extends IChunkProvider { +@@ -839,6 +841,7 @@ public class ChunkProviderServer extends IChunkProvider { this.world.timings.chunkTicks.startTiming(); // Spigot // Paper this.world.a(chunk, k); this.world.timings.chunkTicks.stopTiming(); // Spigot // Paper @@ -92,7 +92,7 @@ index 8f849d83d08b39f1cd9184f484a2089a7a3124ef..377d554553ce81f66207541d963f8268 } } }); -@@ -979,6 +982,41 @@ public class ChunkProviderServer extends IChunkProvider { +@@ -980,6 +983,41 @@ public class ChunkProviderServer extends IChunkProvider { super.executeTask(runnable); } diff --git a/Spigot-Server-Patches/0478-Optimize-ChunkProviderServer-s-chunk-level-checking-.patch b/Spigot-Server-Patches/0478-Optimize-ChunkProviderServer-s-chunk-level-checking-.patch index 6e73daa7a..e8a65459c 100644 --- a/Spigot-Server-Patches/0478-Optimize-ChunkProviderServer-s-chunk-level-checking-.patch +++ b/Spigot-Server-Patches/0478-Optimize-ChunkProviderServer-s-chunk-level-checking-.patch @@ -9,10 +9,10 @@ so inline where possible, and avoid the abstraction of the Either class. diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java -index 377d554553ce81f66207541d963f826867e66592..9afbec260a1d586152073b2adda32959453ab8c9 100644 +index 2ef8506f10426b8a5877e30986c105c0d95be774..722db939971fe395d8250c388fbd7f3b5e87804d 100644 --- a/src/main/java/net/minecraft/server/ChunkProviderServer.java +++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java -@@ -606,27 +606,37 @@ public class ChunkProviderServer extends IChunkProvider { +@@ -607,27 +607,37 @@ public class ChunkProviderServer extends IChunkProvider { public final boolean isInEntityTickingChunk(Entity entity) { return this.a(entity); } // Paper - OBFHELPER @Override public boolean a(Entity entity) { diff --git a/Spigot-Server-Patches/0481-Fix-Chunk-Post-Processing-deadlock-risk.patch b/Spigot-Server-Patches/0481-Fix-Chunk-Post-Processing-deadlock-risk.patch index 908e91f5d..525cd0e69 100644 --- a/Spigot-Server-Patches/0481-Fix-Chunk-Post-Processing-deadlock-risk.patch +++ b/Spigot-Server-Patches/0481-Fix-Chunk-Post-Processing-deadlock-risk.patch @@ -25,10 +25,10 @@ This successfully fixed a reoccurring and highly reproduceable crash for heightmaps. diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java -index 9afbec260a1d586152073b2adda32959453ab8c9..e89683b4f1e3cac60b88a5c7317e525c46950b17 100644 +index 722db939971fe395d8250c388fbd7f3b5e87804d..ba99e9949c6fdfc4f49b6b6716100eb51697227d 100644 --- a/src/main/java/net/minecraft/server/ChunkProviderServer.java +++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java -@@ -1039,6 +1039,7 @@ public class ChunkProviderServer extends IChunkProvider { +@@ -1040,6 +1040,7 @@ public class ChunkProviderServer extends IChunkProvider { return super.executeNext() || execChunkTask; // Paper } } finally { diff --git a/Spigot-Server-Patches/0503-Optimize-isOutsideRange-to-use-distance-maps.patch b/Spigot-Server-Patches/0503-Optimize-isOutsideRange-to-use-distance-maps.patch index b9cb3b52e..dae46d9a8 100644 --- a/Spigot-Server-Patches/0503-Optimize-isOutsideRange-to-use-distance-maps.patch +++ b/Spigot-Server-Patches/0503-Optimize-isOutsideRange-to-use-distance-maps.patch @@ -77,10 +77,10 @@ index 4e0ea454f00c69f03023f01c1d4bd2eda5553a02..353b186060b2c0417a49ab3865ea5972 public String c() { diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java -index e89683b4f1e3cac60b88a5c7317e525c46950b17..0a99b347d8497f097ef1da6560a5d0adc1374f25 100644 +index ba99e9949c6fdfc4f49b6b6716100eb51697227d..7a275bf3260f9fbefc41883c5ebdc1eb2196daf0 100644 --- a/src/main/java/net/minecraft/server/ChunkProviderServer.java +++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java -@@ -724,6 +724,36 @@ public class ChunkProviderServer extends IChunkProvider { +@@ -725,6 +725,36 @@ public class ChunkProviderServer extends IChunkProvider { boolean flag1 = this.world.getGameRules().getBoolean(GameRules.DO_MOB_SPAWNING) && !world.getPlayers().isEmpty(); // CraftBukkit if (!flag) { @@ -117,7 +117,7 @@ index e89683b4f1e3cac60b88a5c7317e525c46950b17..0a99b347d8497f097ef1da6560a5d0ad this.world.getMethodProfiler().enter("pollingChunks"); int k = this.world.getGameRules().getInt(GameRules.RANDOM_TICK_SPEED); BlockPosition blockposition = this.world.getSpawn(); -@@ -758,15 +788,7 @@ public class ChunkProviderServer extends IChunkProvider { +@@ -759,15 +789,7 @@ public class ChunkProviderServer extends IChunkProvider { this.world.timings.countNaturalMobs.stopTiming(); // Paper - timings this.world.getMethodProfiler().exit(); @@ -134,7 +134,7 @@ index e89683b4f1e3cac60b88a5c7317e525c46950b17..0a99b347d8497f097ef1da6560a5d0ad final int[] chunksTicked = {0}; this.playerChunkMap.forEachVisibleChunk((playerchunk) -> { // Paper - safe iterator incase chunk loads, also no wrapping Optional optional = ((Either) playerchunk.b().getNow(PlayerChunk.UNLOADED_CHUNK)).left(); -@@ -780,10 +802,10 @@ public class ChunkProviderServer extends IChunkProvider { +@@ -781,10 +803,10 @@ public class ChunkProviderServer extends IChunkProvider { this.world.getMethodProfiler().exit(); ChunkCoordIntPair chunkcoordintpair = playerchunk.i(); diff --git a/Spigot-Server-Patches/0529-Implement-Chunk-Priority-Urgency-System-for-Chunks.patch b/Spigot-Server-Patches/0530-Implement-Chunk-Priority-Urgency-System-for-Chunks.patch similarity index 66% rename from Spigot-Server-Patches/0529-Implement-Chunk-Priority-Urgency-System-for-Chunks.patch rename to Spigot-Server-Patches/0530-Implement-Chunk-Priority-Urgency-System-for-Chunks.patch index 4ef15b052..0a7b5d634 100644 --- a/Spigot-Server-Patches/0529-Implement-Chunk-Priority-Urgency-System-for-Chunks.patch +++ b/Spigot-Server-Patches/0530-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 d9580eb998801edd34c610ced3f82f9627c6685b..537a22b72aa2c90f0102b035843e09c54e5ae39f 100644 +index a5f4cdaf06bfbb0dd957db9a1335c17b073d646d..3a4e7d8ce0a4591f56ec08ebe1c3bbb4f046b128 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,7 +37,7 @@ index d9580eb998801edd34c610ced3f82f9627c6685b..537a22b72aa2c90f0102b035843e09c5 import net.minecraft.server.IAsyncTaskHandler; import net.minecraft.server.IChunkAccess; import net.minecraft.server.MinecraftServer; -@@ -118,6 +121,32 @@ public final class ChunkTaskManager { +@@ -125,6 +128,36 @@ 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())); @@ -62,8 +62,12 @@ index d9580eb998801edd34c610ced3f82f9627c6685b..537a22b72aa2c90f0102b035843e09c5 + } + 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(neighbor, nx, nz, indent + 1); ++ dumpChunkInfo(seenChunks, neighbor, nx, nz, indent + 1); + } + } + } @@ -71,137 +75,191 @@ index d9580eb998801edd34c610ced3f82f9627c6685b..537a22b72aa2c90f0102b035843e09c5 } diff --git a/src/main/java/net/minecraft/server/ChunkMapDistance.java b/src/main/java/net/minecraft/server/ChunkMapDistance.java -index 586a20fe5c77c2ad5fa26f337a94a16e21d8b5e2..58407f488bbd8bda8781959d7c9da5d09f2a3cc4 100644 +index 586a20fe5c77c2ad5fa26f337a94a16e21d8b5e2..6e422836f5a013d946965a2bb807c862cfc53912 100644 --- a/src/main/java/net/minecraft/server/ChunkMapDistance.java +++ b/src/main/java/net/minecraft/server/ChunkMapDistance.java -@@ -154,7 +154,7 @@ public abstract class ChunkMapDistance { +@@ -23,6 +23,7 @@ import java.util.concurrent.Executor; + import javax.annotation.Nullable; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; ++import org.spigotmc.AsyncCatcher; // Paper + + public abstract class ChunkMapDistance { + +@@ -84,6 +85,7 @@ public abstract class ChunkMapDistance { + } + + private static int a(ArraySetSorted> arraysetsorted) { ++ AsyncCatcher.catchOp("ChunkMapDistance::getHighestTicketLevel"); // Paper + return !arraysetsorted.isEmpty() ? ((Ticket) arraysetsorted.b()).b() : PlayerChunkMap.GOLDEN_TICKET + 1; + } + +@@ -107,11 +109,13 @@ public abstract class ChunkMapDistance { + + // Paper start + if (!this.pendingChunkUpdates.isEmpty()) { ++ this.pollingPendingChunkUpdates = true; + while(!this.pendingChunkUpdates.isEmpty()) { + PlayerChunk remove = this.pendingChunkUpdates.remove(); + remove.isUpdateQueued = false; + remove.a(playerchunkmap); + } ++ this.pollingPendingChunkUpdates = false; + // Paper end + return true; + } else { +@@ -147,14 +151,16 @@ public abstract class ChunkMapDistance { + return flag; + } + } ++ boolean pollingPendingChunkUpdates = false; // Paper + + private boolean addTicket(long i, Ticket ticket) { // CraftBukkit - void -> boolean ++ AsyncCatcher.catchOp("ChunkMapDistance::addTicket"); // Paper + ArraySetSorted> arraysetsorted = this.e(i); + int j = a(arraysetsorted); Ticket ticket1 = (Ticket) arraysetsorted.a(ticket); // CraftBukkit - decompile error ticket1.a(this.currentTick); - if (ticket.b() < j) { -+ if (ticket.b() < j || (ticket.getTicketType() == TicketType.PRIORITY && (30 - ticket.priority) < j)) { // Paper - check priority tickets too ++ if (ticket.getTicketLevel() < j || ticket.getTicketType() == TicketType.URGENT || (ticket.getTicketType() == TicketType.PRIORITY && ticket.getTicketLevel() - ticket.priority < j)) { // Paper - check priority tickets too this.e.b(i, ticket.b(), true); } -@@ -182,6 +182,54 @@ public abstract class ChunkMapDistance { +@@ -162,6 +168,7 @@ public abstract class ChunkMapDistance { + } + + private boolean removeTicket(long i, Ticket ticket) { // CraftBukkit - void -> boolean ++ AsyncCatcher.catchOp("ChunkMapDistance::removeTicket"); // Paper + ArraySetSorted> arraysetsorted = this.e(i); + + boolean removed = false; // CraftBukkit +@@ -182,6 +189,59 @@ public abstract class ChunkMapDistance { this.addTicketAtLevel(tickettype, chunkcoordintpair, i, t0); } + // Paper start + public boolean markUrgent(ChunkCoordIntPair coords) { -+ return this.markHighPriority(coords, 30); ++ return addPriorityTicket(coords, TicketType.URGENT, 30); + } + public boolean markHighPriority(ChunkCoordIntPair coords, int priority) { -+ priority = Math.min(30, Math.max(1, priority)); -+ long pair = coords.pair(); -+ int currentPriority = getChunkPriority(coords); -+ if (currentPriority > priority) { -+ return false; -+ } -+ Ticket ticket = new Ticket(TicketType.PRIORITY, 31, 0); -+ ticket.priority = priority; -+ this.removeTicket(pair, ticket); -+ return this.addTicket(pair, ticket); ++ priority = Math.min(28, Math.max(1, priority)); ++ return addPriorityTicket(coords, TicketType.PRIORITY, priority); + } ++ ++ 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); ++ } ++ return added; ++ } ++ + public int getChunkPriority(ChunkCoordIntPair coords) { -+ int priority = 0; ++ AsyncCatcher.catchOp("ChunkMapDistance::getChunkPriority"); + ArraySetSorted> tickets = this.tickets.get(coords.pair()); + if (tickets == null) { -+ return priority; ++ return 0; ++ } ++ for (Ticket ticket : tickets) { ++ if (ticket.getTicketType() == TicketType.URGENT) { ++ return 30; ++ } + } + for (Ticket ticket : tickets) { + if (ticket.getTicketType() == TicketType.PRIORITY && ticket.priority > 0) { + return ticket.priority; + } + } -+ return priority; ++ return 0; + } + -+ public void refreshUrgentTicket(ChunkCoordIntPair coords) { -+ ArraySetSorted> tickets = this.tickets.get(coords.pair()); -+ if (tickets == null) { -+ markUrgent(coords); -+ return; -+ } -+ for (Ticket ticket : tickets) { -+ if (ticket.getTicketType() == TicketType.PRIORITY) { -+ ticket.setCurrentTick(this.currentTick); -+ return; -+ } -+ } -+ -+ } + public void clearPriorityTickets(ChunkCoordIntPair coords) { -+ this.removeTicket(coords.pair(), new Ticket(TicketType.PRIORITY, 31, 0)); ++ AsyncCatcher.catchOp("ChunkMapDistance::clearPriority"); ++ this.removeTicket(coords.pair(), new Ticket(TicketType.PRIORITY, 34, coords)); ++ } ++ ++ public void clearUrgent(ChunkCoordIntPair coords) { ++ AsyncCatcher.catchOp("ChunkMapDistance::clearUrgent"); ++ this.removeTicket(coords.pair(), new Ticket(TicketType.URGENT, 34, 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,7 +445,8 @@ public abstract class ChunkMapDistance { +@@ -397,12 +457,14 @@ public abstract class ChunkMapDistance { }); }, i, () -> { - return j; -+ PlayerChunk chunk = chunkMap.getUpdatingChunk(i); // Paper -+ return chunk != null && chunk.getCurrentPriority() < j ? chunk.getCurrentPriority() : j; // Paper ++ return Math.min(PlayerChunkMap.GOLDEN_TICKET, j + 15); // Paper - this is based on distance to player for priority, ++ // ensure new no tick tickets arent higher priority than high priority tickets... })); } else { ChunkMapDistance.this.k.a(ChunkTaskQueueSorter.a(() -> { // CraftBukkit - decompile error + ChunkMapDistance.this.m.execute(() -> { + ChunkMapDistance.this.removeTicket(i, ticket); ++ ChunkMapDistance.this.clearPriorityTickets(new ChunkCoordIntPair(i)); // Paper + }); + }, i, true)); + } diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java -index 0a99b347d8497f097ef1da6560a5d0adc1374f25..e57e4c739b86646ef148c1f8e06ca160dbc778a1 100644 +index 7a275bf3260f9fbefc41883c5ebdc1eb2196daf0..54e89c9cc6c47ff2c4f4dd5d4c22a391f8a3d6e0 100644 --- a/src/main/java/net/minecraft/server/ChunkProviderServer.java +++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java -@@ -431,6 +431,16 @@ public class ChunkProviderServer extends IChunkProvider { +@@ -432,6 +432,18 @@ public class ChunkProviderServer extends IChunkProvider { public void removeTicketAtLevel(TicketType ticketType, ChunkCoordIntPair chunkPos, int ticketLevel, T identifier) { this.chunkMapDistance.removeTicketAtLevel(ticketType, chunkPos, ticketLevel, identifier); } + + public boolean markUrgent(ChunkCoordIntPair coords) { -+ return chunkMapDistance.markUrgent(coords); ++ return this.chunkMapDistance.markUrgent(coords); + } ++ + public boolean markHighPriority(ChunkCoordIntPair coords, int priority) { -+ return chunkMapDistance.markHighPriority(coords, priority); ++ return this.chunkMapDistance.markHighPriority(coords, priority); + } ++ + public void clearPriorityTickets(ChunkCoordIntPair coords) { + this.chunkMapDistance.clearPriorityTickets(coords); + } // Paper end @Nullable -@@ -469,14 +479,22 @@ public class ChunkProviderServer extends IChunkProvider { +@@ -470,6 +482,8 @@ public class ChunkProviderServer extends IChunkProvider { if (!completablefuture.isDone()) { // Paper // Paper start - async chunk io/loading + ChunkCoordIntPair pair = new ChunkCoordIntPair(x, z); -+ this.markUrgent(pair); ++ this.chunkMapDistance.markUrgent(pair); this.world.asyncChunkTaskManager.raisePriority(x, z, com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGHEST_PRIORITY); com.destroystokyo.paper.io.chunk.ChunkTaskManager.pushChunkWait(this.world, x, z); // Paper end - com.destroystokyo.paper.io.SyncLoadFinder.logSyncLoad(this.world, x, z); // Paper - sync load info - this.world.timings.syncChunkLoad.startTiming(); // Paper -- this.serverThreadQueue.awaitTasks(completablefuture::isDone); -+ // Paper start - keep priority ticket refreshed -+ this.serverThreadQueue.awaitTasks(() -> { -+ this.chunkMapDistance.refreshUrgentTicket(pair); -+ return completablefuture.isDone(); -+ }); -+ // PAper end +@@ -478,6 +492,8 @@ public class ChunkProviderServer extends IChunkProvider { + this.serverThreadQueue.awaitTasks(completablefuture::isDone); com.destroystokyo.paper.io.chunk.ChunkTaskManager.popChunkWait(); // Paper - async chunk debug this.world.timings.syncChunkLoad.stopTiming(); // Paper -+ this.clearPriorityTickets(pair); // Paper ++ this.chunkMapDistance.clearPriorityTickets(pair); // Paper ++ this.chunkMapDistance.clearUrgent(pair); // Paper } // Paper ichunkaccess = (IChunkAccess) ((Either) completablefuture.join()).map((ichunkaccess1) -> { return ichunkaccess1; -@@ -529,6 +547,7 @@ public class ChunkProviderServer extends IChunkProvider { +@@ -530,6 +546,7 @@ public class ChunkProviderServer extends IChunkProvider { if (flag && !currentlyUnloading) { // CraftBukkit end this.chunkMapDistance.a(TicketType.UNKNOWN, chunkcoordintpair, l, chunkcoordintpair); -+ if (isUrgent) this.markUrgent(chunkcoordintpair); // Paper ++ if (isUrgent) this.chunkMapDistance.markUrgent(chunkcoordintpair); // Paper if (this.a(playerchunk, l)) { GameProfilerFiller gameprofilerfiller = this.world.getMethodProfiler(); -@@ -541,8 +560,13 @@ public class ChunkProviderServer extends IChunkProvider { +@@ -542,8 +559,13 @@ public class ChunkProviderServer extends IChunkProvider { } } } @@ -210,13 +268,22 @@ index 0a99b347d8497f097ef1da6560a5d0adc1374f25..e57e4c739b86646ef148c1f8e06ca160 + // Paper start + CompletableFuture> future = this.a(playerchunk, l) ? PlayerChunk.UNLOADED_CHUNK_ACCESS_FUTURE : playerchunk.a(chunkstatus, this.playerChunkMap); + if (isUrgent) { -+ future.thenAccept(either -> this.clearPriorityTickets(chunkcoordintpair)); ++ future.thenAccept(either -> this.chunkMapDistance.clearUrgent(chunkcoordintpair)); + } + return future; + // Paper end } private boolean a(@Nullable PlayerChunk playerchunk, int i) { +@@ -593,7 +615,7 @@ public class ChunkProviderServer extends IChunkProvider { + return this.serverThreadQueue.executeNext(); + } + +- private boolean tickDistanceManager() { ++ public boolean tickDistanceManager() { // Paper - public + boolean flag = this.chunkMapDistance.a(this.playerChunkMap); + boolean flag1 = this.playerChunkMap.b(); + diff --git a/src/main/java/net/minecraft/server/EntityPlayer.java b/src/main/java/net/minecraft/server/EntityPlayer.java index 07a6fc3d88e7d44bfab7f3d6a0eef7dc132ab422..d60f659b368500e3a8c3305f99e60ffc643e2fbd 100644 --- a/src/main/java/net/minecraft/server/EntityPlayer.java @@ -242,7 +309,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..46462298c7d02fcf31bb8da502a3ee5d98fe7905 100644 +index aeca6b2b9d5d73aeb6dc639b5cad2f2533a2de44..ee70efd4910fbbc489d4eb41342ece44f898c284 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 { @@ -264,61 +331,84 @@ index aeca6b2b9d5d73aeb6dc639b5cad2f2533a2de44..46462298c7d02fcf31bb8da502a3ee5d long lastAutoSaveTime; // Paper - incremental autosave long inactiveTimeStart; // Paper - incremental autosave -@@ -67,6 +68,92 @@ public class PlayerChunk { +@@ -67,6 +68,128 @@ public class PlayerChunk { return null; } // Paper end - no-tick view distance + // Paper start - Chunk gen/load priority system + volatile int neighborPriority = -1; ++ volatile int priorityBoost = 0; + public final java.util.concurrent.ConcurrentHashMap neighbors = new java.util.concurrent.ConcurrentHashMap<>(); + public final it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap neighborPriorities = new it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap<>(); + -+ public int getPreferredPriority() { ++ private int getDemandedPriority() { + int priority = neighborPriority; // if we have a neighbor priority, use it -+ int priorityBoost = chunkMap.chunkDistanceManager.getChunkPriority(location); -+ int basePriority = ticketLevel - priorityBoost; ++ int myPriority = getMyPriority(); + -+ if (priority == -1 || priority > basePriority) { -+ if (priorityBoost > 0) { -+ //System.out.println(location + " boost " + (basePriority) + " = " + ticketLevel + " - " + priorityBoost); -+ } -+ priority = basePriority; -+ if (ticketLevel >= 34 && priorityBoost == 0) { -+ priority += 5; -+ } ++ if (priority == -1 || priority > myPriority) { ++ priority = myPriority; + } + -+ + return Math.max(1, Math.min(PlayerChunkMap.GOLDEN_TICKET, priority)); + } -+ public void onNeighborRequest(PlayerChunk neighbor, ChunkStatus status) { -+ int priority = getCurrentPriority() + 1; -+ if (!neighborPriorities.containsKey(neighbor.location.pair()) && (neighbor.neighborPriority == -1 || neighbor.neighborPriority > priority)) { -+ this.neighbors.put(neighbor, status); -+ neighbor.setNeighborPriority(this, Math.max(1, priority)); ++ ++ private int getMyPriority() { ++ if (priorityBoost == 30) { ++ return 1; // Urgent - ticket level isn't always 31 so 33-30 = 3 + } ++ int basePriority = ticketLevel - priorityBoost; ++ if (ticketLevel >= 34 && priorityBoost == 0 && neighborPriorities.isEmpty()) { ++ basePriority += 5; ++ } ++ return basePriority; + } + ++ private int getNeighborsPriority() { ++ return neighborPriorities.isEmpty() ? getMyPriority() : getDemandedPriority(); ++ } ++ ++ public void onNeighborRequest(PlayerChunk neighbor, ChunkStatus status) { ++ neighbor.setNeighborPriority(this, getNeighborsPriority()); ++ this.neighbors.compute(neighbor, (playerChunk, currentWantedStatus) -> { ++ if (currentWantedStatus == null || !currentWantedStatus.isAtLeastStatus(status)) { ++ //System.out.println(this + " request " + neighbor + " at " + status + " currently " + currentWantedStatus); ++ return status; ++ } else { ++ //System.out.println(this + " requested " + neighbor + " at " + status + " but thats lower than other wanted status " + currentWantedStatus); ++ return currentWantedStatus; ++ } ++ }); ++ ++ } ++ ++ public void onNeighborDone(PlayerChunk neighbor, ChunkStatus chunkstatus, IChunkAccess chunk) { ++ this.neighbors.compute(neighbor, (playerChunk, wantedStatus) -> { ++ if (wantedStatus != null && chunkstatus.isAtLeastStatus(wantedStatus)) { ++ //System.out.println(this + " neighbor done at " + neighbor + " for status " + chunkstatus + " wanted " + wantedStatus); ++ neighbor.removeNeighborPriority(this); ++ return null; ++ } else { ++ //System.out.println(this + " neighbor finished our previous request at " + neighbor + " for status " + chunkstatus + " but we now want instead " + wantedStatus); ++ return wantedStatus; ++ } ++ }); ++ } ++ ++ private void removeNeighborPriority(PlayerChunk requester) { ++ synchronized (neighborPriorities) { ++ neighborPriorities.remove(requester.location.pair()); ++ recalcNeighborPriority(); ++ } ++ checkPriority(); ++ } ++ ++ + private void setNeighborPriority(PlayerChunk requester, int priority) { -+ if (priority < neighborPriority || neighborPriority == -1) { -+ synchronized (neighborPriorities) { -+ if (priority < neighborPriority || neighborPriority == -1) { -+ neighborPriority = priority; -+ neighborPriorities.put(requester.location.pair(), Integer.valueOf(priority)); -+ } -+ } -+ } -+ } -+ -+ public void onNeighborsDone() { -+ java.util.List neighbors = new java.util.ArrayList<>(this.neighbors.keySet()); -+ this.neighbors.clear(); -+ for (PlayerChunk neighbor : neighbors) { -+ synchronized (neighbor.neighborPriorities) { -+ neighbor.neighborPriorities.remove(location.pair()); -+ neighbor.recalcNeighborPriority(); -+ } ++ synchronized (neighborPriorities) { ++ neighborPriorities.put(requester.location.pair(), Integer.valueOf(priority)); ++ recalcNeighborPriority(); + } ++ checkPriority(); + } + + private void recalcNeighborPriority() { @@ -333,6 +423,9 @@ index aeca6b2b9d5d73aeb6dc639b5cad2f2533a2de44..46462298c7d02fcf31bb8da502a3ee5d + } + } + } ++ private void checkPriority() { ++ if (getCurrentPriority() != getDemandedPriority()) this.chunkMap.queueHolderUpdate(this); ++ } + + public final double getDistanceFromPointInFront(EntityPlayer player, int dist) { + int inFront = dist * 16; @@ -353,11 +446,21 @@ index aeca6b2b9d5d73aeb6dc639b5cad2f2533a2de44..46462298c7d02fcf31bb8da502a3ee5d + final double z = location.z - cz; + return (x * x) + (z * z); + } ++ @Override ++ public String toString() { ++ return "PlayerChunk{" + ++ "location=" + location + ++ ", ticketLevel=" + ticketLevel + "/" + getChunkStatus(this.ticketLevel) + ++ ", chunkHolderStatus=" + getChunkHolderStatus() + ++ ", neighborPriority=" + getNeighborsPriority() + ++ ", priority=(" + ticketLevel + " - " + priorityBoost +" vs N " + neighborPriority + ") = " + getDemandedPriority() + " A " + getCurrentPriority() + ++ '}'; ++ } + // Paper end 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 +252,12 @@ public class PlayerChunk { +@@ -165,6 +288,12 @@ public class PlayerChunk { } return null; } @@ -370,7 +473,7 @@ index aeca6b2b9d5d73aeb6dc639b5cad2f2533a2de44..46462298c7d02fcf31bb8da502a3ee5d // Paper end public CompletableFuture> getStatusFutureUnchecked(ChunkStatus chunkstatus) { -@@ -418,6 +511,7 @@ public class PlayerChunk { +@@ -418,6 +547,7 @@ public class PlayerChunk { return this.n; } @@ -378,7 +481,7 @@ index aeca6b2b9d5d73aeb6dc639b5cad2f2533a2de44..46462298c7d02fcf31bb8da502a3ee5d private void d(int i) { this.n = i; } -@@ -507,6 +601,7 @@ public class PlayerChunk { +@@ -507,6 +637,7 @@ public class PlayerChunk { Chunk fullChunk = either.left().get(); PlayerChunk.this.isFullChunkReady = true; fullChunk.playerChunk = PlayerChunk.this; @@ -386,14 +489,15 @@ index aeca6b2b9d5d73aeb6dc639b5cad2f2533a2de44..46462298c7d02fcf31bb8da502a3ee5d } -@@ -581,8 +676,30 @@ public class PlayerChunk { +@@ -581,8 +712,22 @@ public class PlayerChunk { this.entityTickingFuture.complete(PlayerChunk.UNLOADED_CHUNK); this.isEntityTickingReady = false; // Paper - cache chunk ticking stage this.entityTickingFuture = PlayerChunk.UNLOADED_CHUNK_FUTURE; } - - this.w.a(this.location, this::k, this.ticketLevel, this::d); + // Paper start - raise IO/load priority if priority changes, use our preferred priority -+ int priority = getPreferredPriority(); ++ priorityBoost = chunkMap.chunkDistanceManager.getChunkPriority(location); ++ int priority = getDemandedPriority(); + if (getCurrentPriority() > priority) { + int ioPriority = com.destroystokyo.paper.io.PrioritizedTaskQueue.NORMAL_PRIORITY; + if (priority <= 10) { @@ -404,22 +508,13 @@ index aeca6b2b9d5d73aeb6dc639b5cad2f2533a2de44..46462298c7d02fcf31bb8da502a3ee5d + chunkMap.world.asyncChunkTaskManager.raisePriority(location.x, location.z, ioPriority); + } + this.w.a(this.location, this::getCurrentPriority, priority, this::setPriority); // use preferred priority -+ this.neighbors.forEach((neighbor, neighborDesired) -> { -+ ChunkStatus neighborCurrent = neighbor.getChunkHolderStatus(); -+ if (neighborCurrent == null || !neighborCurrent.isAtLeastStatus(neighborDesired)) { -+ if (neighbor.getCurrentPriority() > priority + 1 && neighbor.neighborPriority > priority + 1) { -+ neighbor.setNeighborPriority(this, priority + 1); -+ // Pending chunk update will run this same code here for the neighbor to update their priority -+ // And since we are in the poll loop when this method runs, it should happen immediately after this. -+ chunkMap.chunkDistanceManager.pendingChunkUpdates.add(neighbor); -+ } -+ } -+ }); ++ 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 +786,7 @@ public class PlayerChunk { +@@ -669,6 +814,7 @@ public class PlayerChunk { public interface c { @@ -428,7 +523,7 @@ index aeca6b2b9d5d73aeb6dc639b5cad2f2533a2de44..46462298c7d02fcf31bb8da502a3ee5d } diff --git a/src/main/java/net/minecraft/server/PlayerChunkMap.java b/src/main/java/net/minecraft/server/PlayerChunkMap.java -index f1c3cb3ff8961bc688a1d38cd79b999e539cf866..8f1bb2048f271f6a873b683b0be4b0a6f71a7ee1 100644 +index f1c3cb3ff8961bc688a1d38cd79b999e539cf866..7b4700bdef3a4dc89fd8ba0c98d76c63ad18d5de 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 { @@ -449,17 +544,31 @@ index f1c3cb3ff8961bc688a1d38cd79b999e539cf866..8f1bb2048f271f6a873b683b0be4b0a6 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,62 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { +@@ -410,6 +412,77 @@ 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 ++ chunkDistanceManager.pendingChunkUpdates.add(playerchunk); ++ if (!chunkDistanceManager.pollingPendingChunkUpdates) { ++ world.getChunkProvider().tickDistanceManager(); ++ } ++ }); ++ } ++ ++ public boolean isUnloading(PlayerChunk playerchunk) { ++ return playerchunk == null || MCUtil.getChunkStatus(playerchunk) == null || unloadQueue.contains(playerchunk.location.pair()); ++ } ++ + public void checkHighPriorityChunks(EntityPlayer player) { + MCUtil.getSpiralOutChunks(new BlockPosition(player), Math.min(7, getLoadViewDistance())).forEach(coord -> { + PlayerChunk chunk = getUpdatingChunk(coord.pair()); + if (chunk == null || chunk.isFullChunkReady() || chunk.getTicketLevel() >= 34 || -+ !world.getWorldBorder().isInBounds(coord) ++ !world.getWorldBorder().isInBounds(coord) || isUnloading(chunk) + ) { + return; + } @@ -467,11 +576,32 @@ index f1c3cb3ff8961bc688a1d38cd79b999e539cf866..8f1bb2048f271f6a873b683b0be4b0a6 + double dist = chunk.getDistance(player); + // Prioritize immediate + if (dist <= 5) { -+ chunkDistanceManager.markHighPriority(coord, (int) (29 - dist)); ++ chunkDistanceManager.markHighPriority(coord, (int) (28 - dist)); + return; + } ++ // Prioritize Frustum near ++ double distFront1 = chunk.getDistanceFromPointInFront(player, 2); ++ if (distFront1 <= (4*4)) { ++ if (distFront1 <= (3 * 3)) { ++ chunkDistanceManager.markHighPriority(coord, 24); ++ } else { ++ chunkDistanceManager.markHighPriority(coord, 22); ++ } ++ return; ++ } ++ // Prioritize Frustum far ++ double distFront2 = chunk.getDistanceFromPointInFront(player, 5); ++ if (distFront2 <= (4*4)) { ++ if (distFront2 <= (3 * 3)) { ++ chunkDistanceManager.markHighPriority(coord, 23); ++ } else { ++ chunkDistanceManager.markHighPriority(coord, 20); ++ } ++ return; ++ } ++ + boolean hasNeighbor = false; -+ for (int[] matrix : neighborMatrix) { ++ /*for (int[] matrix : neighborMatrix) { + long neighborKey = MCUtil.getCoordinateKey(coord.x + matrix[0], coord.x + matrix[1]); + PlayerChunk neighbor = getUpdatingChunk(neighborKey); + if (neighbor != null && neighbor.isFullChunkReady()) { @@ -481,27 +611,7 @@ index f1c3cb3ff8961bc688a1d38cd79b999e539cf866..8f1bb2048f271f6a873b683b0be4b0a6 + } + if (!hasNeighbor) { + return; -+ } -+ // Prioritize Frustum near -+ double distFront1 = chunk.getDistanceFromPointInFront(player, 2); -+ if (distFront1 <= (4*4)) { -+ if (distFront1 <= (2 * 2)) { -+ chunkDistanceManager.markHighPriority(coord, 24); -+ } else { -+ chunkDistanceManager.markHighPriority(coord, 22); -+ } -+ return; -+ } -+ // Prioritize Frustum far -+ double distFront2 = chunk.getDistanceFromPointInFront(player, 4); -+ if (distFront2 <= (3*3)) { -+ if (distFront2 <= (2 * 2)) { -+ chunkDistanceManager.markHighPriority(coord, 23); -+ } else { -+ chunkDistanceManager.markHighPriority(coord, 20); -+ } -+ return; -+ } ++ }*/ + // Prioritize nearby chunks + if (dist <= (5*5)) { + chunkDistanceManager.markHighPriority(coord, (int) (16 - Math.sqrt(dist*(4D/5D)))); @@ -512,7 +622,7 @@ index f1c3cb3ff8961bc688a1d38cd79b999e539cf866..8f1bb2048f271f6a873b683b0be4b0a6 public void updatePlayerMobTypeMap(Entity entity) { if (!this.world.paperConfig.perPlayerMobSpawns) { -@@ -539,6 +597,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { +@@ -539,6 +612,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { List>> list = Lists.newArrayList(); int j = chunkcoordintpair.x; int k = chunkcoordintpair.z; @@ -520,15 +630,22 @@ index f1c3cb3ff8961bc688a1d38cd79b999e539cf866..8f1bb2048f271f6a873b683b0be4b0a6 for (int l = -i; l <= i; ++l) { for (int i1 = -i; i1 <= i; ++i1) { -@@ -556,6 +615,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { - } +@@ -557,6 +631,14 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { ChunkStatus chunkstatus = (ChunkStatus) intfunction.apply(j1); -+ if (requestingNeighbor != null && requestingNeighbor != playerchunk) requestingNeighbor.onNeighborRequest(playerchunk, chunkstatus); // Paper CompletableFuture> completablefuture = playerchunk.a(chunkstatus, this); ++ // Paper start ++ if (requestingNeighbor != null && requestingNeighbor != playerchunk && !completablefuture.isDone()) { ++ requestingNeighbor.onNeighborRequest(playerchunk, chunkstatus); ++ completablefuture.thenAccept(either -> { ++ requestingNeighbor.onNeighborDone(playerchunk, chunkstatus, either.left().orElse(null)); ++ }); ++ } ++ // Paper end list.add(completablefuture); -@@ -1020,14 +1080,22 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + } +@@ -1020,14 +1102,22 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { }; CompletableFuture chunkSaveFuture = this.world.asyncChunkTaskManager.getChunkSaveFuture(chunkcoordintpair.x, chunkcoordintpair.z); @@ -556,25 +673,17 @@ index f1c3cb3ff8961bc688a1d38cd79b999e539cf866..8f1bb2048f271f6a873b683b0be4b0a6 return ret; // Paper end } -@@ -1064,6 +1132,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { - return CompletableFuture.completedFuture(Either.right(playerchunk_failure)); - }); - }, (runnable) -> { -+ playerchunk.onNeighborsDone(); // Paper - this.mailboxWorldGen.a(ChunkTaskQueueSorter.a(playerchunk, runnable)); // CraftBukkit - decompile error - }); - } -@@ -1156,7 +1225,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { +@@ -1156,7 +1246,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { long i = playerchunk.i().pair(); playerchunk.getClass(); - mailbox.a(ChunkTaskQueueSorter.a(runnable, i, playerchunk::getTicketLevel)); // CraftBukkit - decompile error -+ mailbox.a(ChunkTaskQueueSorter.a(runnable, i, playerchunk::getCurrentPriority)); // CraftBukkit - decompile error // Paper - use priority not ticket level.... ++ mailbox.a(ChunkTaskQueueSorter.a(runnable, i, () -> 1)); // CraftBukkit - decompile error // Paper - final loads are always urgent! }); } diff --git a/src/main/java/net/minecraft/server/Ticket.java b/src/main/java/net/minecraft/server/Ticket.java -index 7a8397815a5b7f79f3e3a0348aeedf63fe879f8f..a7cd67b0d5e49a4492dc14ec80e442a0f32671d3 100644 +index 7a8397815a5b7f79f3e3a0348aeedf63fe879f8f..0d6e0f2ddaa85c04e626980591e9a78ac27fb42d 100644 --- a/src/main/java/net/minecraft/server/Ticket.java +++ b/src/main/java/net/minecraft/server/Ticket.java @@ -8,6 +8,7 @@ public final class Ticket implements Comparable> { @@ -585,44 +694,31 @@ index 7a8397815a5b7f79f3e3a0348aeedf63fe879f8f..a7cd67b0d5e49a4492dc14ec80e442a0 protected Ticket(TicketType tickettype, int i, T t0) { this.a = tickettype; -@@ -56,6 +57,7 @@ public final class Ticket implements Comparable> { - return this.b; - } - -+ protected final void setCurrentTick(long i) { a(i); } // Paper - OBFHELPER - protected void a(long i) { - this.d = i; - } diff --git a/src/main/java/net/minecraft/server/TicketType.java b/src/main/java/net/minecraft/server/TicketType.java -index 8055f5998213ab1c6c10d03d88d2b14d220a5e40..4913205c15a2b6d5ea058890b02090b494e9c177 100644 +index 8055f5998213ab1c6c10d03d88d2b14d220a5e40..24ec5d77ca7fdf12585c1bb7442554380f0c1918 100644 --- a/src/main/java/net/minecraft/server/TicketType.java +++ b/src/main/java/net/minecraft/server/TicketType.java -@@ -23,6 +23,7 @@ public class TicketType { +@@ -23,6 +23,8 @@ public class TicketType { public static final TicketType PLUGIN_TICKET = a("plugin_ticket", (plugin1, plugin2) -> plugin1.getClass().getName().compareTo(plugin2.getClass().getName())); // CraftBukkit public static final TicketType FUTURE_AWAIT = a("future_await", Long::compareTo); // Paper public static final TicketType ASYNC_LOAD = a("async_load", Long::compareTo); // Paper -+ public static final TicketType PRIORITY = a("priority", Integer::compareTo, 300); // Paper ++ public static final TicketType PRIORITY = a("priority", Comparator.comparingLong(ChunkCoordIntPair::pair), 300); // Paper ++ public static final TicketType URGENT = a("urgent", Comparator.comparingLong(ChunkCoordIntPair::pair), 300); // Paper public static TicketType a(String s, Comparator comparator) { return new TicketType<>(s, comparator, 0L); diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index bbfbfeed12890c9d20d78a9661ab172c901f008c..589926d6029ca2a4aeb4f2c7903a5f9517deebef 100644 +index bbfbfeed12890c9d20d78a9661ab172c901f008c..7230ddbf6eb765390543bdb3ff8c08d383bb2666 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -2472,10 +2472,15 @@ public class CraftWorld implements World { +@@ -2472,6 +2472,10 @@ public class CraftWorld implements World { } } -- return this.world.getChunkProvider().getChunkAtAsynchronously(x, z, gen, urgent).thenComposeAsync((either) -> { -+ CompletableFuture future = this.world.getChunkProvider().getChunkAtAsynchronously(x, z, gen, urgent).thenComposeAsync((either) -> { ++ if (!urgent) { ++ // if not urgent, at least use a slightly boosted priority ++ world.getChunkProvider().markHighPriority(new ChunkCoordIntPair(x, z), 1); ++ } + return this.world.getChunkProvider().getChunkAtAsynchronously(x, z, gen, urgent).thenComposeAsync((either) -> { net.minecraft.server.Chunk chunk = (net.minecraft.server.Chunk) either.left().orElse(null); return CompletableFuture.completedFuture(chunk == null ? null : chunk.getBukkitChunk()); - }, MinecraftServer.getServer()); -+ if (urgent) { -+ world.asyncChunkTaskManager.raisePriority(x, z, com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGHEST_PRIORITY); -+ } -+ return future; -+ - } - // Paper end - diff --git a/scripts/testServer.sh b/scripts/testServer.sh index a6dea0e0d..502e7a8e3 100755 --- a/scripts/testServer.sh +++ b/scripts/testServer.sh @@ -65,6 +65,9 @@ fi folder="$basedir/Paper-Server" jar="$folder/target/paper-${minecraftversion}.jar" +if [ ! -z "$PAPER_JAR" ]; then + jar="$PAPER_JAR" +fi if [ ! -d "$folder" ]; then ( echo "Building Patched Repo" @@ -73,14 +76,13 @@ if [ ! -d "$folder" ]; then ) fi -if [ ! -f "$jar" ] || [ "$2" == "build" ] || [ "$3" == "build" ]; then +if [ "$2" == "build" ] || [ "$3" == "build" ]; then ( echo "Building Paper" cd "$basedir" mvn package ) fi - # # JVM FLAGS # @@ -102,7 +104,9 @@ tmux_command="tmux new-session -A -s Paper -n 'Paper Test' -c '$(pwd)' '$cmd'" multiplex=${PAPER_TEST_MULTIPLEXER} -if [ "$multiplex" == "screen" ]; then +if [ ! -z "$PAPER_NO_MULTIPLEX" ]; then + cmd="$cmd" +elif [ "$multiplex" == "screen" ]; then if command -v "screen" >/dev/null 2>&1 ; then cmd="$screen_command" else @@ -136,6 +140,6 @@ if [ ! -z "$PAPER_TEST_COMMAND_WRAPPER" ]; then else echo "Running command: $cmd" echo "In directory: $(pwd)" - sleep 1 + #sleep 1 /usr/bin/env bash -c "$cmd" fi