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
This commit is contained in:
Aikar 2020-05-22 00:46:44 -04:00
parent f04209258d
commit ad73c0eb38
3 changed files with 268 additions and 160 deletions

View file

@ -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<PlayerChunk> 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<PlayerChunk> 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<PlayerChunk> 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);

View file

@ -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<Ticket<?>> 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<Ticket<?>> 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<Ticket<?>> 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<Integer> ticket = new Ticket<Integer>(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<ChunkCoordIntPair> ticketType, int priority) {
+ AsyncCatcher.catchOp("ChunkMapDistance::addPriorityTicket");
+ long pair = coords.pair();
+ Ticket<ChunkCoordIntPair> ticket = new Ticket<ChunkCoordIntPair>(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<Ticket<?>> 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<Ticket<?>> 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<Integer>(TicketType.PRIORITY, 31, 0));
+ AsyncCatcher.catchOp("ChunkMapDistance::clearPriority");
+ this.removeTicket(coords.pair(), new Ticket<ChunkCoordIntPair>(TicketType.PRIORITY, 34, coords));
+ }
+
+ public void clearUrgent(ChunkCoordIntPair coords) {
+ AsyncCatcher.catchOp("ChunkMapDistance::clearUrgent");
+ this.removeTicket(coords.pair(), new Ticket<ChunkCoordIntPair>(TicketType.URGENT, 34, coords));
+ }
+ // Paper end
public <T> boolean addTicketAtLevel(TicketType<T> 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<Either<IChunkAccess, PlayerChunk.Failure>> 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<PlayerChunk, ChunkStatus> neighbors = new java.util.concurrent.ConcurrentHashMap<>();
+ public final it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap<Integer> 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<PlayerChunk> 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<Either<IChunkAccess, PlayerChunk.Failure>> 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<T> tickettype, int i, T t0) {
this.a = tickettype;
@@ -0,0 +0,0 @@ public final class Ticket<T> implements Comparable<Ticket<?>> {
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<org.bukkit.plugin.Plugin> PLUGIN_TICKET = a("plugin_ticket", (plugin1, plugin2) -> plugin1.getClass().getName().compareTo(plugin2.getClass().getName())); // CraftBukkit
public static final TicketType<Long> FUTURE_AWAIT = a("future_await", Long::compareTo); // Paper
public static final TicketType<Long> ASYNC_LOAD = a("async_load", Long::compareTo); // Paper
+ public static final TicketType<Integer> PRIORITY = a("priority", Integer::compareTo, 300); // Paper
+ public static final TicketType<ChunkCoordIntPair> PRIORITY = a("priority", Comparator.comparingLong(ChunkCoordIntPair::pair), 300); // Paper
+ public static final TicketType<ChunkCoordIntPair> URGENT = a("urgent", Comparator.comparingLong(ChunkCoordIntPair::pair), 300); // Paper
public static <T> TicketType<T> a(String s, Comparator<T> 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<Chunk> 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

View file

@ -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