From ad73c0eb382b8316514bfb226354e8c9c43a3a9a 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 --- .../Asynchronous-chunk-IO-and-loading.patch | 20 +- ...k-Priority-Urgency-System-for-Chunks.patch | 396 +++++++++++------- scripts/testServer.sh | 12 +- 3 files changed, 268 insertions(+), 160 deletions(-) diff --git a/Spigot-Server-Patches/Asynchronous-chunk-IO-and-loading.patch b/Spigot-Server-Patches/Asynchronous-chunk-IO-and-loading.patch index 47e75ce696..1ae2641828 100644 --- a/Spigot-Server-Patches/Asynchronous-chunk-IO-and-loading.patch +++ b/Spigot-Server-Patches/Asynchronous-chunk-IO-and-loading.patch @@ -1870,6 +1870,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 +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..00000000000000000000000000000000 + } + + 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..00000000000000000000000000000000 + // 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 +")"); @@ -2380,7 +2387,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + + 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 0000000000000000000000000000000000000000..00000000000000000000000000000000 + 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); diff --git a/Spigot-Server-Patches/Implement-Chunk-Priority-Urgency-System-for-Chunks.patch b/Spigot-Server-Patches/Implement-Chunk-Priority-Urgency-System-for-Chunks.patch index 6335cac6be..c9be3f60e5 100644 --- a/Spigot-Server-Patches/Implement-Chunk-Priority-Urgency-System-for-Chunks.patch +++ b/Spigot-Server-Patches/Implement-Chunk-Priority-Urgency-System-for-Chunks.patch @@ -62,8 +62,12 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + 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); + } + } + } @@ -74,65 +78,117 @@ diff --git a/src/main/java/net/minecraft/server/ChunkMapDistance.java b/src/main index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/server/ChunkMapDistance.java +++ b/src/main/java/net/minecraft/server/ChunkMapDistance.java +@@ -0,0 +0,0 @@ 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 { + @@ -0,0 +0,0 @@ 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; + } + +@@ -0,0 +0,0 @@ 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 { +@@ -0,0 +0,0 @@ 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); } +@@ -0,0 +0,0 @@ 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 @@ -0,0 +0,0 @@ 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) { @@ -143,11 +199,17 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 }); }, 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/server/ChunkProviderServer.java @@ -158,11 +220,13 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 } + + 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); + } @@ -174,22 +238,16 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 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 +@@ -0,0 +0,0 @@ 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; @@ -197,7 +255,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 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(); @@ -210,13 +268,22 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + // 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) { +@@ -0,0 +0,0 @@ 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/server/EntityPlayer.java @@ -270,55 +337,78 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 // 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 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + } + } ++ private void checkPriority() { ++ if (getCurrentPriority() != getDemandedPriority()) this.chunkMap.queueHolderUpdate(this); ++ } + + public final double getDistanceFromPointInFront(EntityPlayer player, int dist) { + int inFront = dist * 16; @@ -353,6 +446,16 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + 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) { @@ -393,7 +496,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 - - 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,17 +508,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + 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 @@ -455,11 +550,25 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 } + // 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 0000000000000000000000000000000000000000..00000000000000000000000000000000 + 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 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + 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)))); @@ -521,13 +631,20 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 for (int l = -i; l <= i; ++l) { for (int i1 = -i; i1 <= i; ++i1) { @@ -0,0 +0,0 @@ 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); + } @@ -0,0 +0,0 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { }; @@ -556,20 +673,12 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 return ret; // Paper end } -@@ -0,0 +0,0 @@ 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 - }); - } @@ -0,0 +0,0 @@ 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! }); } @@ -585,14 +694,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 protected Ticket(TicketType tickettype, int i, T t0) { this.a = tickettype; -@@ -0,0 +0,0 @@ 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/server/TicketType.java @@ -601,7 +702,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 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); @@ -613,16 +715,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 } } -- 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 a6dea0e0db..502e7a8e3f 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