From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Aikar <aikar@aikar.co>
Date: Sat, 11 Apr 2020 03:56:07 -0400
Subject: [PATCH] Implement Chunk Priority / Urgency System for Chunks

Mark chunks that are blocking main thread for world generation as urgent

Implements a general priority system so that chunks that are sorted in
the generator queues can prioritize certain chunks over another.

Urgent chunks will jump to the front of the line, ensuring that a
sync chunk load on an ungenerated chunk does not lag the server for
a long period of time if the servers generator queues are filled with
lots of chunks already.

This massively reduces the lag spikes from sync chunk gens.

Then we further prioritize loading order so nearby chunks have higher
priority than distant chunks, reducing the pressure a high no tick
view distance holds on you.

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/net/minecraft/server/ChunkMapDistance.java b/src/main/java/net/minecraft/server/ChunkMapDistance.java
index 716d4eab382244ee9bc4855bf0f026e65ec057d6..20052dcbc71899165ac99801fa9f7753672ba997 100644
--- a/src/main/java/net/minecraft/server/ChunkMapDistance.java
+++ b/src/main/java/net/minecraft/server/ChunkMapDistance.java
@@ -143,7 +143,7 @@ public abstract class ChunkMapDistance {
         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 && ((Ticket<Integer>) ticket).getObjectReason() < j)) { // Paper - check priority tickets too
             this.e.b(i, ticket.b(), true);
         }
 
@@ -171,6 +171,48 @@ public abstract class ChunkMapDistance {
         this.addTicketAtLevel(tickettype, chunkcoordintpair, i, t0);
     }
 
+    // Paper start
+    public boolean markUrgent(ChunkCoordIntPair coords) {
+        return this.markHighPriority(coords, 30);
+    }
+    public boolean markHighPriority(ChunkCoordIntPair coords, int priority) {
+        priority = Math.min(30, Math.max(1, priority));
+        Ticket<Integer> ticket = new Ticket<Integer>(TicketType.PRIORITY, 31, priority);
+        return this.addTicket(coords.pair(), ticket);
+    }
+    public int getChunkPriority(ChunkCoordIntPair coords) {
+        int priority = 0;
+        ArraySetSorted<Ticket<?>> tickets = this.tickets.get(coords.pair());
+        if (tickets == null) {
+            return priority;
+        }
+        for (Ticket<?> ticket : tickets) {
+            if (ticket.getTicketType() != TicketType.PRIORITY) {
+                continue;
+            }
+            //noinspection unchecked
+            Ticket<Integer> prioTicket = (Ticket<Integer>) ticket;
+            if (prioTicket.getObjectReason() > priority) {
+                priority = prioTicket.getObjectReason();
+            }
+        }
+        return priority;
+    }
+    public void clearPriorityTickets(ChunkCoordIntPair coords) {
+        ArraySetSorted<Ticket<?>> tickets = this.tickets.get(coords.pair());
+        java.util.List<Ticket<?>> toRemove = new java.util.ArrayList<>();
+        if (tickets == null) return;
+        for (Ticket<?> ticket : tickets) {
+            if (ticket.getTicketType() == TicketType.PRIORITY) {
+                toRemove.add(ticket);
+            }
+        }
+        for (Ticket<?> ticket : toRemove) {
+            this.removeTicket(coords.pair(), ticket);
+        }
+
+    }
+    // Paper end
     public <T> boolean addTicketAtLevel(TicketType<T> ticketType, ChunkCoordIntPair chunkcoordintpair, int level, T identifier) {
         return this.addTicket(chunkcoordintpair.pair(), new Ticket<>(ticketType, level, identifier));
         // CraftBukkit end
@@ -386,7 +428,8 @@ public abstract class ChunkMapDistance {
 
                         });
                     }, i, () -> {
-                        return j;
+                        PlayerChunk chunk = chunkMap.getUpdatingChunk(i); // Paper
+                        return chunk != null && chunk.getCurrentPriority() < j ? chunk.getCurrentPriority() : j; // Paper
                     }));
                 } else {
                     ChunkMapDistance.this.k.a(ChunkTaskQueueSorter.a(() -> { // CraftBukkit - decompile error
diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java
index 0a99b347d8497f097ef1da6560a5d0adc1374f25..d6f629d45e05167c22b6cd08a9709809a32b15a1 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 {
     public <T> void removeTicketAtLevel(TicketType<T> ticketType, ChunkCoordIntPair chunkPos, int ticketLevel, T identifier) {
         this.chunkMapDistance.removeTicketAtLevel(ticketType, chunkPos, ticketLevel, identifier);
     }
+
+    public boolean markUrgent(ChunkCoordIntPair coords) {
+        return chunkMapDistance.markUrgent(coords);
+    }
+    public boolean markHighPriority(ChunkCoordIntPair coords, int priority) {
+        return chunkMapDistance.markHighPriority(coords, priority);
+    }
+    public void clearPriorityTickets(ChunkCoordIntPair coords) {
+        this.chunkMapDistance.clearPriorityTickets(coords);
+    }
     // Paper end
 
     @Nullable
@@ -469,6 +479,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.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
@@ -477,6 +489,7 @@ 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
             } // Paper
             ichunkaccess = (IChunkAccess) ((Either) completablefuture.join()).map((ichunkaccess1) -> {
                 return ichunkaccess1;
@@ -529,6 +542,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 (this.a(playerchunk, l)) {
                 GameProfilerFiller gameprofilerfiller = this.world.getMethodProfiler();
 
@@ -541,8 +555,13 @@ public class ChunkProviderServer extends IChunkProvider {
                 }
             }
         }
-
-        return this.a(playerchunk, l) ? PlayerChunk.UNLOADED_CHUNK_ACCESS_FUTURE : playerchunk.a(chunkstatus, this.playerChunkMap);
+        // 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));
+        }
+        return future;
+        // Paper end
     }
 
     private boolean a(@Nullable PlayerChunk playerchunk, int i) {
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
+++ b/src/main/java/net/minecraft/server/EntityPlayer.java
@@ -441,6 +441,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting {
             if (valid && (!this.isSpectator() || this.world.isLoaded(new BlockPosition(this)))) { // Paper - don't tick dead players that are not in the world currently (pending respawn)
                 super.tick();
             }
+            if (valid && isAlive() && this.ticksLived % 20 == 0) ((WorldServer)world).getChunkProvider().playerChunkMap.checkHighPriorityChunks(this); // Paper
 
             for (int i = 0; i < this.inventory.getSize(); ++i) {
                 ItemStack itemstack = this.inventory.getItem(i);
diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java
index d129c7f54d9f65fff6f512d8ff5f1c3866632603..9b9536fba4a62c0153b921e678e6a9683bf2e37f 100644
--- a/src/main/java/net/minecraft/server/MCUtil.java
+++ b/src/main/java/net/minecraft/server/MCUtil.java
@@ -658,6 +658,7 @@ public final class MCUtil {
                 chunkData.addProperty("x", playerChunk.location.x);
                 chunkData.addProperty("z", playerChunk.location.z);
                 chunkData.addProperty("ticket-level", playerChunk.getTicketLevel());
+                chunkData.addProperty("priority", playerChunk.getCurrentPriority());
                 chunkData.addProperty("state", PlayerChunk.getChunkState(playerChunk.getTicketLevel()).toString());
                 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 9e95c6b54f0855ddde6db6bd3e768e87fee6c21a..44721a4f446bdd5bf4575e4168a0570afd71c4d4 100644
--- a/src/main/java/net/minecraft/server/PlayerChunk.java
+++ b/src/main/java/net/minecraft/server/PlayerChunk.java
@@ -25,7 +25,7 @@ public class PlayerChunk {
     private CompletableFuture<IChunkAccess> chunkSave;
     public int oldTicketLevel;
     private int ticketLevel;
-    private int n;
+    private int n; public final int getCurrentPriority() { return n; } // Paper - OBFHELPER
     final ChunkCoordIntPair location; // Paper - private -> package
     private final short[] dirtyBlocks;
     private int dirtyCount;
@@ -68,6 +68,92 @@ public class PlayerChunk {
         return null;
     }
     // Paper end - no-tick view distance
+    // Paper start - Chunk gen/load priority system
+    volatile int neighborPriority = -1;
+    final java.util.concurrent.ConcurrentHashMap<PlayerChunk, Integer> neighbors = new java.util.concurrent.ConcurrentHashMap<>();
+    final it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap<Integer> neighborPriorities = new it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap<>();
+
+    public int getPreferredPriority() {
+        int priority = neighborPriority; // if we have a neighbor priority, use it
+        int priorityBoost = chunkMap.chunkDistanceManager.getChunkPriority(location);
+        int basePriority = ticketLevel - priorityBoost;
+
+        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;
+            }
+        }
+
+
+        return Math.max(1, Math.min(PlayerChunkMap.GOLDEN_TICKET, priority));
+    }
+    public void onNeighborRequest(PlayerChunk neighbor, ChunkStatus status) {
+        int currentPriority = getCurrentPriority();
+        if (!neighborPriorities.containsKey(neighbor.location.pair()) && (neighbor.neighborPriority == -1 || neighbor.neighborPriority > currentPriority)) {
+            this.neighbors.put(neighbor, currentPriority);
+            neighbor.setNeighborPriority(this, Math.max(1, currentPriority));
+        }
+    }
+
+    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();
+            }
+        }
+    }
+
+    private void recalcNeighborPriority() {
+        neighborPriority = -1;
+        if (!neighborPriorities.isEmpty()) {
+            synchronized (neighborPriorities) {
+                for (Integer neighbor : neighborPriorities.values()) {
+                    if (neighbor < neighborPriority || neighborPriority == -1) {
+                        neighborPriority = neighbor;
+                    }
+                }
+            }
+        }
+    }
+
+    public final double getDistanceFromPointInFront(EntityPlayer player, int dist) {
+        int inFront = dist * 16;
+        final float yaw = MCUtil.normalizeYaw(player.yaw);
+        double rads = Math.toRadians(yaw);
+        final double x = player.locX() + inFront * Math.cos(rads);
+        final double z = player.locZ() + inFront * Math.sin(rads);
+        return getDistance(x, z);
+    }
+
+    public final double getDistance(EntityPlayer player) {
+        return getDistance(player.locX(), player.locZ());
+    }
+    public final double getDistance(double blockX, double blockZ) {
+        int cx = MCUtil.fastFloor(blockX) >> 4;
+        int cz = MCUtil.fastFloor(blockZ) >> 4;
+        final double x = location.x - cx;
+        final double z = location.z - cz;
+        return (x * x) + (z * z);
+    }
+    // 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());
@@ -166,6 +252,12 @@ public class PlayerChunk {
         }
         return null;
     }
+    public static ChunkStatus getNextStatus(ChunkStatus status) {
+        if (status == ChunkStatus.FULL) {
+            return status;
+        }
+        return CHUNK_STATUSES.get(status.getStatusIndex() + 1);
+    }
     // Paper end
 
     public CompletableFuture<Either<IChunkAccess, PlayerChunk.Failure>> getStatusFutureUnchecked(ChunkStatus chunkstatus) {
@@ -419,8 +511,18 @@ public class PlayerChunk {
         return this.n;
     }
 
+    private void setPriority(int i) { d(i); } // Paper - OBFHELPER
     private void d(int i) {
+        if (i == n) return; // Paper
         this.n = i;
+        // Paper start
+        this.neighbors.keySet().forEach(neighbor -> {
+            if (neighbor.getCurrentPriority() > i) {
+                neighbor.setNeighborPriority(this, i);
+                this.w.changePriority(neighbor.location, neighbor::getCurrentPriority, neighbor.getCurrentPriority(), neighbor::setPriority);
+            }
+        });
+        // Paper end
     }
 
     public void a(int i) {
@@ -508,6 +610,7 @@ public class PlayerChunk {
                     Chunk fullChunk = either.left().get();
                     PlayerChunk.this.isFullChunkReady = true;
                     fullChunk.playerChunk = PlayerChunk.this;
+                    this.chunkMap.chunkDistanceManager.clearPriorityTickets(location);
 
 
                 }
@@ -583,7 +686,7 @@ public class PlayerChunk {
             this.entityTickingFuture = PlayerChunk.UNLOADED_CHUNK_FUTURE;
         }
 
-        this.w.a(this.location, this::k, this.ticketLevel, this::d);
+        this.w.a(this.location, this::k, getPreferredPriority(), this::d); // Paper - preferred priority
         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.
@@ -670,6 +773,7 @@ public class PlayerChunk {
 
     public interface c {
 
+        default void changePriority(ChunkCoordIntPair chunkcoordintpair, IntSupplier intsupplier, int i, IntConsumer intconsumer) { a(chunkcoordintpair, intsupplier, i, intconsumer); } // Paper - OBFHELPER
         void a(ChunkCoordIntPair chunkcoordintpair, IntSupplier intsupplier, int i, IntConsumer intconsumer);
     }
 
diff --git a/src/main/java/net/minecraft/server/PlayerChunkMap.java b/src/main/java/net/minecraft/server/PlayerChunkMap.java
index e772095e1c44842f743661a326c2a9a8a677ab02..5621416660d2722f26582fcecd5b61a164cd1530 100644
--- a/src/main/java/net/minecraft/server/PlayerChunkMap.java
+++ b/src/main/java/net/minecraft/server/PlayerChunkMap.java
@@ -352,6 +352,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
         this.playerViewDistanceTickMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets,
             (EntityPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ,
              com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> newState) -> {
+                checkHighPriorityChunks(player);
                 if (newState.size() != 1) {
                     return;
                 }
@@ -370,7 +371,8 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
                 }
                 ChunkCoordIntPair chunkPos = new ChunkCoordIntPair(rangeX, rangeZ);
                 PlayerChunkMap.this.world.getChunkProvider().removeTicketAtLevel(TicketType.PLAYER, chunkPos, 31, chunkPos); // entity ticking level, TODO check on update
-            });
+                PlayerChunkMap.this.world.getChunkProvider().clearPriorityTickets(chunkPos);
+            }, (player, prevPos, newPos) -> checkHighPriorityChunks(player));
         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,
@@ -387,6 +389,62 @@ 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 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)
+            ) {
+                return;
+            }
+
+            double dist = chunk.getDistance(player);
+            // Prioritize immediate
+            if (dist <= 5) {
+                chunkDistanceManager.markHighPriority(coord, (int) (29 - dist));
+                return;
+            }
+            boolean hasNeighbor = false;
+            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()) {
+                    hasNeighbor = true;
+                    break;
+                }
+            }
+            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))));
+            }
+        });
+    }
+    // Paper end
 
     public void updatePlayerMobTypeMap(Entity entity) {
         if (!this.world.paperConfig.perPlayerMobSpawns) {
@@ -516,6 +574,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
         List<CompletableFuture<Either<IChunkAccess, PlayerChunk.Failure>>> list = Lists.newArrayList();
         int j = chunkcoordintpair.x;
         int k = chunkcoordintpair.z;
+        PlayerChunk requestingNeighbor = getUpdatingChunk(chunkcoordintpair.pair()); // Paper
 
         for (int l = -i; l <= i; ++l) {
             for (int i1 = -i; i1 <= i; ++i1) {
@@ -533,6 +592,7 @@ 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);
 
                 list.add(completablefuture);
@@ -997,14 +1057,22 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
         };
 
         CompletableFuture<NBTTagCompound> chunkSaveFuture = this.world.asyncChunkTaskManager.getChunkSaveFuture(chunkcoordintpair.x, chunkcoordintpair.z);
+        PlayerChunk playerChunk = getUpdatingChunk(chunkcoordintpair.pair());
+        int chunkPriority = playerChunk != null ? playerChunk.getCurrentPriority() : 33;
+        int priority = com.destroystokyo.paper.io.PrioritizedTaskQueue.NORMAL_PRIORITY;
+
+        if (chunkPriority <= 10) {
+            priority = com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGHEST_PRIORITY;
+        } else if (chunkPriority <= 20) {
+            priority = com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGH_PRIORITY;
+        }
+        boolean isHighestPriority = priority == com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGHEST_PRIORITY;
         if (chunkSaveFuture != null) {
-            this.world.asyncChunkTaskManager.scheduleChunkLoad(chunkcoordintpair.x, chunkcoordintpair.z,
-                com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGH_PRIORITY, chunkHolderConsumer, false, chunkSaveFuture);
-            this.world.asyncChunkTaskManager.raisePriority(chunkcoordintpair.x, chunkcoordintpair.z, com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGH_PRIORITY);
+            this.world.asyncChunkTaskManager.scheduleChunkLoad(chunkcoordintpair.x, chunkcoordintpair.z, priority, chunkHolderConsumer, isHighestPriority, chunkSaveFuture);
         } else {
-            this.world.asyncChunkTaskManager.scheduleChunkLoad(chunkcoordintpair.x, chunkcoordintpair.z,
-                com.destroystokyo.paper.io.PrioritizedTaskQueue.NORMAL_PRIORITY, chunkHolderConsumer, false);
+            this.world.asyncChunkTaskManager.scheduleChunkLoad(chunkcoordintpair.x, chunkcoordintpair.z, priority, chunkHolderConsumer, isHighestPriority);
         }
+        this.world.asyncChunkTaskManager.raisePriority(chunkcoordintpair.x, chunkcoordintpair.z, priority);
         return ret;
         // Paper end
     }
@@ -1041,6 +1109,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
         });
     }
@@ -1133,7 +1202,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....
         });
     }
 
diff --git a/src/main/java/net/minecraft/server/TicketType.java b/src/main/java/net/minecraft/server/TicketType.java
index 8055f5998213ab1c6c10d03d88d2b14d220a5e40..4913205c15a2b6d5ea058890b02090b494e9c177 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<T> {
     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 <T> TicketType<T> a(String s, Comparator<T> 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
--- 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 {
             }
         }
 
-        return this.world.getChunkProvider().getChunkAtAsynchronously(x, z, gen, urgent).thenComposeAsync((either) -> {
+        CompletableFuture<Chunk> future = 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
 
