From 746d5cb8fb1a0c2062585ff85523b4014bfc9428 Mon Sep 17 00:00:00 2001 From: Shane Freeder Date: Sat, 14 Aug 2021 15:24:53 +0100 Subject: [PATCH] Revert "Revert "Remove "Implement-Chunk-Priority-Urgency-System-for-Chunks" (Fixes #5980)"" This reverts commit d2f7432e3202a281eff22cee96dea33ebe442be6. --- .../ChunkMap.mainInvokingExecutor.patch | 36 + ...ortation-and-cancel-velocity-if-tele.patch | 2 +- ...k-Priority-Urgency-System-for-Chunks.patch | 1196 ----------------- ...mprove-Chunk-Status-Transition-Speed.patch | 7 + patches/server/Optimize-Light-Engine.patch | 14 +- 5 files changed, 51 insertions(+), 1204 deletions(-) create mode 100644 patches/server/ChunkMap.mainInvokingExecutor.patch delete mode 100644 patches/server/Implement-Chunk-Priority-Urgency-System-for-Chunks.patch diff --git a/patches/server/ChunkMap.mainInvokingExecutor.patch b/patches/server/ChunkMap.mainInvokingExecutor.patch new file mode 100644 index 0000000000..ee34df28f5 --- /dev/null +++ b/patches/server/ChunkMap.mainInvokingExecutor.patch @@ -0,0 +1,36 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Sat, 14 Aug 2021 14:49:45 +0100 +Subject: [PATCH] ChunkMap.mainInvokingExecutor + +This is a temp patch, this should maybe be moved to a more generic map or potentially wrapped into mcutil for the sake of this being such a generic concept +anyways + +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + public final ServerLevel level; + private final ThreadedLevelLightEngine lightEngine; + private final BlockableEventLoop mainThreadExecutor; ++ final java.util.concurrent.Executor mainInvokingExecutor; // Paper // Paper - Move to MCUtil? + public final ChunkGenerator generator; + public final Supplier overworldDataStorage; + private final PoiManager poiManager; +@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + this.level = world; + this.generator = chunkGenerator; + this.mainThreadExecutor = mainThreadExecutor; ++ // Paper start ++ this.mainInvokingExecutor = (run) -> { ++ if (MCUtil.isMainThread()) { ++ run.run(); ++ } else { ++ mainThreadExecutor.execute(run); ++ } ++ }; ++ // Paper end + ProcessorMailbox threadedmailbox = ProcessorMailbox.create(executor, "worldgen"); + + Objects.requireNonNull(mainThreadExecutor); diff --git a/patches/server/Fix-Entity-Teleportation-and-cancel-velocity-if-tele.patch b/patches/server/Fix-Entity-Teleportation-and-cancel-velocity-if-tele.patch index 0c7be35d3b..c4ee2d8add 100644 --- a/patches/server/Fix-Entity-Teleportation-and-cancel-velocity-if-tele.patch +++ b/patches/server/Fix-Entity-Teleportation-and-cancel-velocity-if-tele.patch @@ -27,9 +27,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 this.awaitingTeleportTime = this.tickCount; - this.player.absMoveTo(d0, d1, d2, f, f1); + this.player.moveTo(d0, d1, d2, f, f1); // Paper - use proper setPositionRotation for teleportation - this.player.forceCheckHighPriority(); // Paper this.player.connection.send(new ClientboundPlayerPositionPacket(d0 - d3, d1 - d4, d2 - d5, f - f2, f1 - f3, set, this.awaitingTeleport, flag)); } + diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java diff --git a/patches/server/Implement-Chunk-Priority-Urgency-System-for-Chunks.patch b/patches/server/Implement-Chunk-Priority-Urgency-System-for-Chunks.patch deleted file mode 100644 index 361a411e76..0000000000 --- a/patches/server/Implement-Chunk-Priority-Urgency-System-for-Chunks.patch +++ /dev/null @@ -1,1196 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -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/com/destroystokyo/paper/io/chunk/ChunkTaskManager.java b/src/main/java/com/destroystokyo/paper/io/chunk/ChunkTaskManager.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/com/destroystokyo/paper/io/chunk/ChunkTaskManager.java -+++ b/src/main/java/com/destroystokyo/paper/io/chunk/ChunkTaskManager.java -@@ -0,0 +0,0 @@ public final class ChunkTaskManager { - } - - static void dumpChunkInfo(Set seenChunks, ChunkHolder chunkHolder, int x, int z) { -- dumpChunkInfo(seenChunks, chunkHolder, x, z, 0, 1); -+ dumpChunkInfo(seenChunks, chunkHolder, x, z, 0, 4); // Paper - 1->4 - } - - static void dumpChunkInfo(Set seenChunks, ChunkHolder chunkHolder, int x, int z, int indent, int maxDepth) { -@@ -0,0 +0,0 @@ public final class ChunkTaskManager { - PaperFileIOThread.LOGGER.log(Level.ERROR, indentStr + "Chunk Status - " + ((chunk == null) ? "null chunk" : chunk.getStatus().toString())); - PaperFileIOThread.LOGGER.log(Level.ERROR, indentStr + "Chunk Ticket Status - " + ChunkHolder.getStatus(chunkHolder.getTicketLevel())); - PaperFileIOThread.LOGGER.log(Level.ERROR, indentStr + "Chunk Holder Status - " + ((holderStatus == null) ? "null" : holderStatus.toString())); -+ // Paper start -+ PaperFileIOThread.LOGGER.log(Level.ERROR, indentStr + "Chunk Holder Priority - " + chunkHolder.queueLevel); -+ -+ if (!chunkHolder.neighbors.isEmpty()) { -+ if (indent >= maxDepth) { -+ PaperFileIOThread.LOGGER.log(Level.ERROR, indentStr + "Chunk Neighbors: (Can't show, too deeply nested)"); -+ return; -+ } -+ PaperFileIOThread.LOGGER.log(Level.ERROR, indentStr + "Chunk Neighbors: "); -+ for (ChunkHolder neighbor : chunkHolder.neighbors.keySet()) { -+ ChunkStatus status = neighbor.getChunkHolderStatus(); -+ if (status != null && status.isOrAfter(ChunkHolder.getStatus(neighbor.getTicketLevel()))) { -+ continue; -+ } -+ int nx = neighbor.pos.x; -+ int nz = neighbor.pos.z; -+ if (seenChunks.contains(neighbor)) { -+ PaperFileIOThread.LOGGER.log(Level.ERROR, indentStr + " " + nx + "," + nz + " in " + chunkHolder.getWorld().getWorld().getName() + " (CIRCULAR)"); -+ continue; -+ } -+ PaperFileIOThread.LOGGER.log(Level.ERROR, indentStr + " " + nx + "," + nz + " in " + chunkHolder.getWorld().getWorld().getName() + ":"); -+ dumpChunkInfo(seenChunks, neighbor, nx, nz, indent + 1, maxDepth); -+ } -+ } -+ // Paper end - } - } - -diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/server/MCUtil.java -+++ b/src/main/java/net/minecraft/server/MCUtil.java -@@ -0,0 +0,0 @@ public final class MCUtil { - chunkData.addProperty("x", playerChunk.pos.x); - chunkData.addProperty("z", playerChunk.pos.z); - chunkData.addProperty("ticket-level", playerChunk.getTicketLevel()); -+ chunkData.addProperty("priority", playerChunk.queueLevel); // Paper - priority - chunkData.addProperty("state", ChunkHolder.getFullChunkStatus(playerChunk.getTicketLevel()).toString()); - chunkData.addProperty("queued-for-unload", chunkMap.toDrop.contains(playerChunk.pos.longKey)); - chunkData.addProperty("status", status == null ? "unloaded" : status.toString()); -diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkHolder.java -+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java -@@ -0,0 +0,0 @@ public class ChunkHolder { - private final DebugBuffer chunkToSaveHistory; - public int oldTicketLevel; - private int ticketLevel; -- private int queueLevel; -+ public volatile int queueLevel; // Paper - private->public, make volatile since this is concurrently accessed - public final ChunkPos pos; - private boolean hasChangedSections; - private final ShortSet[] changedBlocksPerSection; -@@ -0,0 +0,0 @@ public class ChunkHolder { - - boolean isUpdateQueued = false; // Paper - private final ChunkMap chunkMap; // Paper -+ public ServerLevel getWorld() { return chunkMap.level; } // Paper - // Paper start - no-tick view distance - public final LevelChunk getSendingChunk() { - // it's important that we use getChunkAtIfLoadedImmediately to mirror the chunk sending logic used -@@ -0,0 +0,0 @@ public class ChunkHolder { - } - // Paper end - -+ // 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<>(); -+ -+ private int getDemandedPriority() { -+ int priority = neighborPriority; // if we have a neighbor priority, use it -+ int myPriority = getMyPriority(); -+ -+ if (priority == -1 || (ticketLevel <= 33 && priority > myPriority)) { -+ priority = myPriority; -+ } -+ -+ return Math.max(1, Math.min(Math.max(ticketLevel, ChunkMap.MAX_CHUNK_DISTANCE), priority)); -+ } -+ -+ private int getMyPriority() { -+ if (priorityBoost == DistanceManager.URGENT_PRIORITY) { -+ return 2; // Urgent - ticket level isn't always 31 so 33-30 = 3, but allow 1 more tasks to go below this for dependents -+ } -+ return ticketLevel - priorityBoost; -+ } -+ -+ private int getNeighborsPriority() { -+ return (neighborPriorities.isEmpty() ? getMyPriority() : getDemandedPriority()) + 1; -+ } -+ -+ public void onNeighborRequest(ChunkHolder neighbor, ChunkStatus status) { -+ neighbor.setNeighborPriority(this, getNeighborsPriority()); -+ this.neighbors.compute(neighbor, (playerChunk, currentWantedStatus) -> { -+ if (currentWantedStatus == null || !currentWantedStatus.isOrAfter(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(ChunkHolder neighbor, ChunkStatus chunkstatus, ChunkAccess chunk) { -+ this.neighbors.compute(neighbor, (playerChunk, wantedStatus) -> { -+ if (wantedStatus != null && chunkstatus.isOrAfter(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(ChunkHolder requester) { -+ synchronized (neighborPriorities) { -+ neighborPriorities.remove(requester.pos.toLong()); -+ recalcNeighborPriority(); -+ } -+ checkPriority(); -+ } -+ -+ -+ private void setNeighborPriority(ChunkHolder requester, int priority) { -+ synchronized (neighborPriorities) { -+ neighborPriorities.put(requester.pos.toLong(), Integer.valueOf(priority)); -+ recalcNeighborPriority(); -+ } -+ checkPriority(); -+ } -+ -+ private void recalcNeighborPriority() { -+ neighborPriority = -1; -+ if (!neighborPriorities.isEmpty()) { -+ synchronized (neighborPriorities) { -+ for (Integer neighbor : neighborPriorities.values()) { -+ if (neighbor < neighborPriority || neighborPriority == -1) { -+ neighborPriority = neighbor; -+ } -+ } -+ } -+ } -+ } -+ private void checkPriority() { -+ if (queueLevel != getDemandedPriority()) this.chunkMap.queueHolderUpdate(this); -+ } -+ -+ public final double getDistance(ServerPlayer player) { -+ return getDistance(player.getX(), player.getZ()); -+ } -+ public final double getDistance(double blockX, double blockZ) { -+ int cx = net.minecraft.server.MCUtil.fastFloor(blockX) >> 4; -+ int cz = net.minecraft.server.MCUtil.fastFloor(blockZ) >> 4; -+ final double x = pos.x - cx; -+ final double z = pos.z - cz; -+ return (x * x) + (z * z); -+ } -+ -+ public final double getDistanceFrom(BlockPos pos) { -+ return getDistance(pos.getX(), pos.getZ()); -+ } -+ -+ public static ChunkStatus getNextStatus(ChunkStatus status) { -+ if (status == ChunkStatus.FULL) { -+ return status; -+ } -+ return CHUNK_STATUSES.get(status.getIndex() + 1); -+ } -+ public CompletableFuture> getStatusFutureUncheckedMain(ChunkStatus chunkstatus) { -+ return ensureMain(getFutureIfPresentUnchecked(chunkstatus)); -+ } -+ public CompletableFuture ensureMain(CompletableFuture future) { -+ return future.thenApplyAsync(r -> r, chunkMap.mainInvokingExecutor); -+ } -+ -+ @Override -+ public String toString() { -+ return "PlayerChunk{" + -+ "location=" + pos + -+ ", ticketLevel=" + ticketLevel + "/" + getStatus(this.ticketLevel) + -+ ", chunkHolderStatus=" + getChunkHolderStatus() + -+ ", neighborPriority=" + getNeighborsPriority() + -+ ", priority=(" + ticketLevel + " - " + priorityBoost +" vs N " + neighborPriority + ") = " + getDemandedPriority() + " A " + queueLevel + -+ '}'; -+ } -+ // Paper end -+ - // Paper start - optimise isOutsideOfRange - // cached here to avoid a map lookup - com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet playersInMobSpawnRange; -@@ -0,0 +0,0 @@ public class ChunkHolder { - // CraftBukkit start - // ChunkUnloadEvent: Called before the chunk is unloaded: isChunkLoaded is still true and chunk can still be modified by plugins. - if (playerchunk_state.isOrAfter(ChunkHolder.FullChunkStatus.BORDER) && !playerchunk_state1.isOrAfter(ChunkHolder.FullChunkStatus.BORDER)) { -- this.getFutureIfPresentUnchecked(ChunkStatus.FULL).thenAccept((either) -> { -+ this.getStatusFutureUncheckedMain(ChunkStatus.FULL).thenAccept((either) -> { // Paper - ensure main - LevelChunk chunk = (LevelChunk)either.left().orElse(null); - if (chunk != null) { - chunkStorage.callbackExecutor.execute(() -> { -@@ -0,0 +0,0 @@ public class ChunkHolder { - this.fullChunkFuture = chunkStorage.prepareAccessibleChunk(this); - this.scheduleFullChunkPromotion(chunkStorage, this.fullChunkFuture, executor, ChunkHolder.FullChunkStatus.BORDER); - // Paper start - cache ticking ready status -- this.fullChunkFuture.thenAccept(either -> { -+ ensureMain(this.fullChunkFuture).thenAccept(either -> { // Paper - ensure main - final Optional left = either.left(); - if (left.isPresent() && ChunkHolder.this.fullChunkCreateCount == expectCreateCount) { - // note: Here is a very good place to add callbacks to logic waiting on this. - LevelChunk fullChunk = either.left().get(); - ChunkHolder.this.isFullChunkReady = true; - fullChunk.playerChunk = ChunkHolder.this; -+ this.chunkMap.distanceManager.clearPriorityTickets(pos); - } - }); - this.updateChunkToSave(this.fullChunkFuture, "full"); -@@ -0,0 +0,0 @@ public class ChunkHolder { - this.tickingChunkFuture = chunkStorage.prepareTickingChunk(this); - this.scheduleFullChunkPromotion(chunkStorage, this.tickingChunkFuture, executor, ChunkHolder.FullChunkStatus.TICKING); - // Paper start - cache ticking ready status -- this.tickingChunkFuture.thenAccept(either -> { -+ ensureMain(this.tickingChunkFuture).thenAccept(either -> { // Paper - ensure main - either.ifLeft(chunk -> { - // note: Here is a very good place to add callbacks to logic waiting on this. - ChunkHolder.this.isTickingReady = true; -@@ -0,0 +0,0 @@ public class ChunkHolder { - this.entityTickingChunkFuture = chunkStorage.prepareEntityTickingChunk(this.pos); - this.scheduleFullChunkPromotion(chunkStorage, this.entityTickingChunkFuture, executor, ChunkHolder.FullChunkStatus.ENTITY_TICKING); - // Paper start - cache ticking ready status -- this.entityTickingChunkFuture.thenAccept(either -> { -+ ensureMain(this.entityTickingChunkFuture).thenAccept(either -> { // Paper ensureMain - either.ifLeft(chunk -> { - ChunkHolder.this.isEntityTickingReady = true; - }); -@@ -0,0 +0,0 @@ public class ChunkHolder { - this.demoteFullChunk(chunkStorage, playerchunk_state1); - } - -- this.onLevelChange.onLevelChange(this.pos, this::getQueueLevel, this.ticketLevel, this::setQueueLevel); -+ //this.onLevelChange.onLevelChange(this.pos, this::getQueueLevel, this.ticketLevel, this::setQueueLevel); -+ // Paper start - raise IO/load priority if priority changes, use our preferred priority -+ priorityBoost = chunkMap.distanceManager.getChunkPriority(pos); -+ int priority = getDemandedPriority(); -+ if (this.queueLevel > priority) { -+ int ioPriority = com.destroystokyo.paper.io.PrioritizedTaskQueue.NORMAL_PRIORITY; -+ if (priority <= 10) { -+ ioPriority = com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGHEST_PRIORITY; -+ } else if (priority <= 20) { -+ ioPriority = com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGH_PRIORITY; -+ } -+ chunkMap.level.asyncChunkTaskManager.raisePriority(pos.x, pos.z, ioPriority); -+ } -+ if (this.queueLevel != priority) { -+ this.onLevelChange.onLevelChange(this.pos, () -> this.queueLevel, priority, p -> this.queueLevel = p); // use preferred priority -+ int neighborsPriority = getNeighborsPriority(); -+ this.neighbors.forEach((neighbor, neighborDesired) -> neighbor.setNeighborPriority(this, neighborsPriority)); -+ } -+ // Paper end - this.oldTicketLevel = this.ticketLevel; - // CraftBukkit start - // ChunkLoadEvent: Called after the chunk is loaded: isChunkLoaded returns true and chunk is ready to be modified by plugins. - if (!playerchunk_state.isOrAfter(ChunkHolder.FullChunkStatus.BORDER) && playerchunk_state1.isOrAfter(ChunkHolder.FullChunkStatus.BORDER)) { -- this.getFutureIfPresentUnchecked(ChunkStatus.FULL).thenAccept((either) -> { -+ this.getStatusFutureUncheckedMain(ChunkStatus.FULL).thenAccept((either) -> { // Paper - ensure main - LevelChunk chunk = (LevelChunk)either.left().orElse(null); - if (chunk != null) { - chunkStorage.callbackExecutor.execute(() -> { -diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkMap.java -+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java -@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - public final ServerLevel level; - private final ThreadedLevelLightEngine lightEngine; - private final BlockableEventLoop mainThreadExecutor; -+ final java.util.concurrent.Executor mainInvokingExecutor; // Paper - public final ChunkGenerator generator; - public final Supplier overworldDataStorage; - private final PoiManager poiManager; -@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - - @Override - public void execute(Runnable runnable) { -+ org.spigotmc.AsyncCatcher.catchOp("Callback Executor execute"); - if (this.queue == null) { - this.queue = new java.util.ArrayDeque<>(); - } -@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - - @Override - public void run() { -+ org.spigotmc.AsyncCatcher.catchOp("Callback Executor run"); - if (this.queue == null) { - return; - } -@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - this.level = world; - this.generator = chunkGenerator; - this.mainThreadExecutor = mainThreadExecutor; -+ // Paper start -+ this.mainInvokingExecutor = (run) -> { -+ if (MCUtil.isMainThread()) { -+ run.run(); -+ } else { -+ mainThreadExecutor.execute(run); -+ } -+ }; -+ // Paper end - ProcessorMailbox threadedmailbox = ProcessorMailbox.create(executor, "worldgen"); - - Objects.requireNonNull(mainThreadExecutor); -@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - this.playerViewDistanceTickMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets, - (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, - com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState) -> { -+ checkHighPriorityChunks(player); - if (newState.size() != 1) { - return; - } -@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - } - ChunkPos chunkPos = new ChunkPos(rangeX, rangeZ); - ChunkMap.this.level.getChunkSource().removeTicketAtLevel(TicketType.PLAYER, chunkPos, 31, chunkPos); // entity ticking level, TODO check on update -- }); -+ // Paper start -+ ChunkMap.this.level.getChunkSource().clearPriorityTickets(chunkPos); -+ }, -+ (player, prevPos, newPos) -> { -+ player.lastHighPriorityChecked = -1; // reset and recheck -+ checkHighPriorityChunks(player); -+ }); -+ // Paper end - this.playerViewDistanceNoTickMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets); - this.playerViewDistanceBroadcastMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets, - (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, -@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - // Paper end - no-tick view distance - } - -+ // Paper start - Chunk Prioritization -+ public void queueHolderUpdate(ChunkHolder playerchunk) { -+ Runnable runnable = () -> { -+ if (isUnloading(playerchunk)) { -+ return; // unloaded -+ } -+ distanceManager.pendingChunkUpdates.add(playerchunk); -+ if (!distanceManager.pollingPendingChunkUpdates) { -+ level.getChunkSource().runDistanceManagerUpdates(); -+ } -+ }; -+ if (MCUtil.isMainThread()) { -+ // We can't use executor here because it will not execute tasks if its currently in the middle of executing tasks... -+ runnable.run(); -+ } else { -+ mainThreadExecutor.execute(runnable); -+ } -+ } -+ -+ private boolean isUnloading(ChunkHolder playerchunk) { -+ return playerchunk == null || toDrop.contains(playerchunk.pos.toLong()); -+ } -+ -+ private void updateChunkPriorityMap(it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap map, long chunk, int level) { -+ int prev = map.getOrDefault(chunk, -1); -+ if (level > prev) { -+ map.put(chunk, level); -+ } -+ } -+ -+ public void checkHighPriorityChunks(ServerPlayer player) { -+ int currentTick = MinecraftServer.currentTick; -+ if (currentTick - player.lastHighPriorityChecked < 20 || !player.isRealPlayer) { // weed out fake players -+ return; -+ } -+ player.lastHighPriorityChecked = currentTick; -+ it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap priorities = new it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap(); -+ -+ int viewDistance = getEffectiveNoTickViewDistance(); -+ net.minecraft.core.BlockPos.MutableBlockPos pos = new net.minecraft.core.BlockPos.MutableBlockPos(); -+ -+ // Prioritize circular near -+ double playerChunkX = Mth.floor(player.getX()) >> 4; -+ double playerChunkZ = Mth.floor(player.getZ()) >> 4; -+ pos.set(player.getX(), 0, player.getZ()); -+ double twoThirdModifier = 2D / 3D; -+ MCUtil.getSpiralOutChunks(pos, Math.min(6, viewDistance)).forEach(coord -> { -+ if (shouldSkipPrioritization(coord)) return; -+ -+ double dist = MCUtil.distance(playerChunkX, 0, playerChunkZ, coord.x, 0, coord.z); -+ // Prioritize immediate -+ if (dist <= 4) { -+ updateChunkPriorityMap(priorities, coord.toLong(), (int) (27 - dist)); -+ return; -+ } -+ -+ // Prioritize nearby chunks -+ updateChunkPriorityMap(priorities, coord.toLong(), (int) (20 - dist * twoThirdModifier)); -+ }); -+ -+ // Prioritize Frustum near 3 -+ ChunkPos front3 = player.getChunkInFront(3); -+ pos.set(front3.x << 4, 0, front3.z << 4); -+ MCUtil.getSpiralOutChunks(pos, Math.min(5, viewDistance)).forEach(coord -> { -+ if (shouldSkipPrioritization(coord)) return; -+ -+ double dist = MCUtil.distance(playerChunkX, 0, playerChunkZ, coord.x, 0, coord.z); -+ updateChunkPriorityMap(priorities, coord.toLong(), (int) (25 - dist * twoThirdModifier)); -+ }); -+ -+ // Prioritize Frustum near 5 -+ if (viewDistance > 4) { -+ ChunkPos front5 = player.getChunkInFront(5); -+ pos.set(front5.x << 4, 0, front5.z << 4); -+ MCUtil.getSpiralOutChunks(pos, 4).forEach(coord -> { -+ if (shouldSkipPrioritization(coord)) return; -+ -+ double dist = MCUtil.distance(playerChunkX, 0, playerChunkZ, coord.x, 0, coord.z); -+ updateChunkPriorityMap(priorities, coord.toLong(), (int) (25 - dist * twoThirdModifier)); -+ }); -+ } -+ -+ // Prioritize Frustum far 7 -+ if (viewDistance > 6) { -+ ChunkPos front7 = player.getChunkInFront(7); -+ pos.set(front7.x << 4, 0, front7.z << 4); -+ MCUtil.getSpiralOutChunks(pos, 3).forEach(coord -> { -+ if (shouldSkipPrioritization(coord)) { -+ return; -+ } -+ double dist = MCUtil.distance(playerChunkX, 0, playerChunkZ, coord.x, 0, coord.z); -+ updateChunkPriorityMap(priorities, coord.toLong(), (int) (25 - dist * twoThirdModifier)); -+ }); -+ } -+ -+ if (priorities.isEmpty()) return; -+ distanceManager.delayDistanceManagerTick = true; -+ priorities.long2IntEntrySet().fastForEach(entry -> distanceManager.markHighPriority(new ChunkPos(entry.getLongKey()), entry.getIntValue())); -+ distanceManager.delayDistanceManagerTick = false; -+ level.getChunkSource().runDistanceManagerUpdates(); -+ -+ } -+ -+ private boolean shouldSkipPrioritization(ChunkPos coord) { -+ if (playerViewDistanceNoTickMap.getObjectsInRange(coord.toLong()) == null) return true; -+ ChunkHolder chunk = getUpdatingChunkIfPresent(coord.toLong()); -+ return chunk != null && (chunk.isFullChunkReady()); -+ } -+ // Paper end -+ - // Paper start - public void updatePlayerMobTypeMap(Entity entity) { - if (!this.level.paperConfig.perPlayerMobSpawns) { -@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - List>> list = Lists.newArrayList(); - int j = centerChunk.x; - int k = centerChunk.z; -+ ChunkHolder requestingNeighbor = getUpdatingChunkIfPresent(centerChunk.toLong()); // Paper - - for (int l = -margin; l <= margin; ++l) { - for (int i1 = -margin; i1 <= margin; ++i1) { -@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - - ChunkStatus chunkstatus = (ChunkStatus) distanceToStatus.apply(j1); - CompletableFuture> completablefuture = playerchunk.getOrScheduleFuture(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 ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - }; - - CompletableFuture chunkSaveFuture = this.level.asyncChunkTaskManager.getChunkSaveFuture(pos.x, pos.z); -+ // Paper start -+ ChunkHolder playerChunk = getUpdatingChunkIfPresent(pos.toLong()); -+ int chunkPriority = playerChunk != null ? playerChunk.queueLevel : 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; -+ // Paper end - if (chunkSaveFuture != null) { -- this.level.asyncChunkTaskManager.scheduleChunkLoad(pos.x, pos.z, -- com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGH_PRIORITY, chunkHolderConsumer, false, chunkSaveFuture); -- this.level.asyncChunkTaskManager.raisePriority(pos.x, pos.z, com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGH_PRIORITY); -+ this.level.asyncChunkTaskManager.scheduleChunkLoad(pos.x, pos.z, priority, chunkHolderConsumer, isHighestPriority, chunkSaveFuture); // Paper - } else { -- this.level.asyncChunkTaskManager.scheduleChunkLoad(pos.x, pos.z, -- com.destroystokyo.paper.io.PrioritizedTaskQueue.NORMAL_PRIORITY, chunkHolderConsumer, false); -+ this.level.asyncChunkTaskManager.scheduleChunkLoad(pos.x, pos.z, priority, chunkHolderConsumer, isHighestPriority); // Paper - } -+ this.level.asyncChunkTaskManager.raisePriority(pos.x, pos.z, priority); // Paper - return ret; - // Paper end - } -@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - long i = playerchunk.getPos().toLong(); - - Objects.requireNonNull(playerchunk); -- mailbox.tell(ChunkTaskPriorityQueueSorter.message(runnable, i, playerchunk::getTicketLevel)); -+ mailbox.tell(ChunkTaskPriorityQueueSorter.message(runnable, i, () -> 1)); // Paper - final loads are always urgent! - }); - } - -diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/server/level/DistanceManager.java -+++ b/src/main/java/net/minecraft/server/level/DistanceManager.java -@@ -0,0 +0,0 @@ public abstract class DistanceManager { - } - - private static int getTicketLevelAt(SortedArraySet> arraysetsorted) { -+ org.spigotmc.AsyncCatcher.catchOp("ChunkMapDistance::getLowestTicketLevel"); // Paper - return !arraysetsorted.isEmpty() ? ((Ticket) arraysetsorted.first()).getTicketLevel() : ChunkMap.MAX_CHUNK_DISTANCE + 1; - } - -@@ -0,0 +0,0 @@ public abstract class DistanceManager { - - public boolean runAllUpdates(ChunkMap playerchunkmap) { - //this.f.a(); // Paper - no longer used -+ org.spigotmc.AsyncCatcher.catchOp("DistanceManagerTick"); // Paper - this.playerTicketManager.runAllUpdates(); - int i = Integer.MAX_VALUE - this.ticketTracker.runDistanceUpdates(Integer.MAX_VALUE); - boolean flag = i != 0; -@@ -0,0 +0,0 @@ public abstract class DistanceManager { - - // Paper start - if (!this.pendingChunkUpdates.isEmpty()) { -+ this.pollingPendingChunkUpdates = true; try { // Paper - Chunk priority - while(!this.pendingChunkUpdates.isEmpty()) { - ChunkHolder remove = this.pendingChunkUpdates.remove(); - remove.isUpdateQueued = false; - remove.updateFutures(playerchunkmap, this.mainThreadExecutor); - } -+ } finally { this.pollingPendingChunkUpdates = false; } // Paper - Chunk priority - // Paper end - return true; - } else { -@@ -0,0 +0,0 @@ public abstract class DistanceManager { - return flag; - } - } -+ boolean pollingPendingChunkUpdates = false; // Paper - Chunk priority - - boolean addTicket(long i, Ticket ticket) { // CraftBukkit - void -> boolean -+ org.spigotmc.AsyncCatcher.catchOp("ChunkMapDistance::addTicket"); // Paper - SortedArraySet> arraysetsorted = this.getTickets(i); - int j = DistanceManager.getTicketLevelAt(arraysetsorted); - Ticket ticket1 = (Ticket) arraysetsorted.addOrGet(ticket); // CraftBukkit - decompile error -@@ -0,0 +0,0 @@ public abstract class DistanceManager { - } - - boolean removeTicket(long i, Ticket ticket) { // CraftBukkit - void -> boolean -+ org.spigotmc.AsyncCatcher.catchOp("ChunkMapDistance::removeTicket"); // Paper - SortedArraySet> arraysetsorted = this.getTickets(i); -+ int oldLevel = getTicketLevelAt(arraysetsorted); // Paper - - boolean removed = false; // CraftBukkit - if (arraysetsorted.remove(ticket)) { -@@ -0,0 +0,0 @@ public abstract class DistanceManager { - this.tickets.remove(i); - } - -- this.ticketTracker.update(i, DistanceManager.getTicketLevelAt(arraysetsorted), false); -+ // Paper start - Chunk priority -+ int newLevel = getTicketLevelAt(arraysetsorted); -+ if (newLevel > oldLevel) { -+ this.ticketTracker.update(i, newLevel, false); -+ } -+ // Paper end - return removed; // CraftBukkit - } - -@@ -0,0 +0,0 @@ public abstract class DistanceManager { - }); - } - -+ // Paper start - Chunk priority -+ public static final int PRIORITY_TICKET_LEVEL = ChunkMap.MAX_CHUNK_DISTANCE; -+ public static final int URGENT_PRIORITY = 29; -+ public boolean delayDistanceManagerTick = false; -+ public boolean markUrgent(ChunkPos coords) { -+ return addPriorityTicket(coords, TicketType.URGENT, URGENT_PRIORITY); -+ } -+ public boolean markHighPriority(ChunkPos coords, int priority) { -+ priority = Math.min(URGENT_PRIORITY - 1, Math.max(1, priority)); -+ return addPriorityTicket(coords, TicketType.PRIORITY, priority); -+ } -+ -+ public void markAreaHighPriority(ChunkPos center, int priority, int radius) { -+ delayDistanceManagerTick = true; -+ priority = Math.min(URGENT_PRIORITY - 1, Math.max(1, priority)); -+ int finalPriority = priority; -+ net.minecraft.server.MCUtil.getSpiralOutChunks(center.getWorldPosition(), radius).forEach(coords -> { -+ addPriorityTicket(coords, TicketType.PRIORITY, finalPriority); -+ }); -+ delayDistanceManagerTick = false; -+ chunkMap.level.getChunkSource().runDistanceManagerUpdates(); -+ } -+ -+ public void clearAreaPriorityTickets(ChunkPos center, int radius) { -+ delayDistanceManagerTick = true; -+ net.minecraft.server.MCUtil.getSpiralOutChunks(center.getWorldPosition(), radius).forEach(coords -> { -+ this.removeTicket(coords.toLong(), new Ticket(TicketType.PRIORITY, PRIORITY_TICKET_LEVEL, coords)); -+ }); -+ delayDistanceManagerTick = false; -+ chunkMap.level.getChunkSource().runDistanceManagerUpdates(); -+ } -+ -+ private boolean hasPlayerTicket(ChunkPos coords, int level) { -+ SortedArraySet> tickets = this.tickets.get(coords.toLong()); -+ if (tickets == null || tickets.isEmpty()) { -+ return false; -+ } -+ for (Ticket ticket : tickets) { -+ if (ticket.getType() == TicketType.PLAYER && ticket.getTicketLevel() == level) { -+ return true; -+ } -+ } -+ -+ return false; -+ } -+ -+ private boolean addPriorityTicket(ChunkPos coords, TicketType ticketType, int priority) { -+ org.spigotmc.AsyncCatcher.catchOp("ChunkMapDistance::addPriorityTicket"); -+ long pair = coords.toLong(); -+ ChunkHolder chunk = chunkMap.getUpdatingChunkIfPresent(pair); -+ boolean needsTicket = chunkMap.playerViewDistanceNoTickMap.getObjectsInRange(pair) != null && !hasPlayerTicket(coords, 33); -+ -+ if (needsTicket) { -+ Ticket ticket = new Ticket<>(TicketType.PLAYER, 33, coords); -+ this.ticketsToRelease.add(pair); -+ addTicket(pair, ticket); -+ } -+ if ((chunk != null && chunk.isFullChunkReady())) { -+ if (needsTicket) { -+ chunkMap.level.getChunkSource().runDistanceManagerUpdates(); -+ } -+ return needsTicket; -+ } -+ -+ boolean success; -+ if (!(success = updatePriorityTicket(coords, ticketType, priority))) { -+ Ticket ticket = new Ticket(ticketType, PRIORITY_TICKET_LEVEL, coords); -+ ticket.priority = priority; -+ success = this.addTicket(pair, ticket); -+ } else { -+ if (chunk == null) { -+ chunk = chunkMap.getUpdatingChunkIfPresent(pair); -+ } -+ chunkMap.queueHolderUpdate(chunk); -+ } -+ -+ //chunkMap.world.getWorld().spawnParticle(priority <= 15 ? org.bukkit.Particle.EXPLOSION_HUGE : org.bukkit.Particle.EXPLOSION_NORMAL, chunkMap.world.getWorld().getPlayers(), null, coords.x << 4, 70, coords.z << 4, 2, 0, 0, 0, 1, null, true); -+ -+ chunkMap.level.getChunkSource().runDistanceManagerUpdates(); -+ -+ return success; -+ } -+ -+ private boolean updatePriorityTicket(ChunkPos coords, TicketType type, int priority) { -+ SortedArraySet> tickets = this.tickets.get(coords.toLong()); -+ if (tickets == null) { -+ return false; -+ } -+ for (Ticket ticket : tickets) { -+ if (ticket.getType() == type) { -+ // We only support increasing, not decreasing, too complicated -+ ticket.setCreatedTick(this.ticketTickCounter); -+ ticket.priority = Math.max(ticket.priority, priority); -+ return true; -+ } -+ } -+ -+ return false; -+ } -+ -+ public int getChunkPriority(ChunkPos coords) { -+ org.spigotmc.AsyncCatcher.catchOp("ChunkMapDistance::getChunkPriority"); -+ SortedArraySet> tickets = this.tickets.get(coords.toLong()); -+ if (tickets == null) { -+ return 0; -+ } -+ for (Ticket ticket : tickets) { -+ if (ticket.getType() == TicketType.URGENT) { -+ return URGENT_PRIORITY; -+ } -+ } -+ for (Ticket ticket : tickets) { -+ if (ticket.getType() == TicketType.PRIORITY && ticket.priority > 0) { -+ return ticket.priority; -+ } -+ } -+ return 0; -+ } -+ -+ public void clearPriorityTickets(ChunkPos coords) { -+ org.spigotmc.AsyncCatcher.catchOp("ChunkMapDistance::clearPriority"); -+ this.removeTicket(coords.toLong(), new Ticket(TicketType.PRIORITY, PRIORITY_TICKET_LEVEL, coords)); -+ } -+ -+ public void clearUrgent(ChunkPos coords) { -+ org.spigotmc.AsyncCatcher.catchOp("ChunkMapDistance::clearUrgent"); -+ this.removeTicket(coords.toLong(), new Ticket(TicketType.URGENT, PRIORITY_TICKET_LEVEL, coords)); -+ } -+ // Paper end -+ - protected void updateChunkForced(ChunkPos pos, boolean forced) { - Ticket ticket = new Ticket<>(TicketType.FORCED, 31, pos); - -@@ -0,0 +0,0 @@ public abstract class DistanceManager { - - public void updateViewDistance(int watchDistance) { - ObjectIterator objectiterator = this.chunks.long2ByteEntrySet().iterator(); -+ // Paper start - set the view distance before scheduling chunk loads/unloads -+ int lastViewDistance = this.viewDistance; -+ this.viewDistance = watchDistance; -+ // Paper end - - while (objectiterator.hasNext()) { - it.unimi.dsi.fastutil.longs.Long2ByteMap.Entry it_unimi_dsi_fastutil_longs_long2bytemap_entry = (it.unimi.dsi.fastutil.longs.Long2ByteMap.Entry) objectiterator.next(); - byte b0 = it_unimi_dsi_fastutil_longs_long2bytemap_entry.getByteValue(); - long j = it_unimi_dsi_fastutil_longs_long2bytemap_entry.getLongKey(); - -- this.onLevelChange(j, b0, this.haveTicketFor(b0), b0 <= watchDistance - 2); -+ this.onLevelChange(j, b0, b0 <= lastViewDistance - 2, this.haveTicketFor(b0)); // Paper - } - -- this.viewDistance = watchDistance; -+ // this.viewDistance = watchDistance; // Paper - view distance is now set further up - } - - private void onLevelChange(long pos, int distance, boolean oldWithinViewDistance, boolean withinViewDistance) { - if (oldWithinViewDistance != withinViewDistance) { -- Ticket ticket = new Ticket<>(TicketType.PLAYER, 33, new ChunkPos(pos)); // Paper - no-tick view distance -+ ChunkPos coords = new ChunkPos(pos); // Paper - reuse variable -+ Ticket ticket = new Ticket<>(TicketType.PLAYER, 33, coords); // Paper - no-tick view distance - - if (withinViewDistance) { -+ scheduleChunkLoad(pos, net.minecraft.server.MinecraftServer.currentTick, distance, (priority) -> { // Paper - smarter ticket delay based on frustum and distance -+ // Paper start - recheck its still valid if not cancel -+ if (!isChunkInRange(pos)) { -+ DistanceManager.this.ticketThrottlerReleaser.tell(ChunkTaskPriorityQueueSorter.release(() -> { -+ DistanceManager.this.mainThreadExecutor.execute(() -> { -+ DistanceManager.this.removeTicket(pos, ticket); -+ DistanceManager.this.clearPriorityTickets(coords); -+ }); -+ }, pos, false)); -+ return; -+ } -+ // abort early if we got a ticket already -+ if (hasPlayerTicket(coords, 33)) return; -+ // skip player ticket throttle for near chunks -+ if (priority <= 3) { -+ DistanceManager.this.addTicket(pos, ticket); -+ DistanceManager.this.ticketsToRelease.add(pos); -+ return; -+ } -+ // Paper end - DistanceManager.this.ticketThrottlerInput.tell(ChunkTaskPriorityQueueSorter.message(() -> { - DistanceManager.this.mainThreadExecutor.execute(() -> { -- if (this.haveTicketFor(this.getLevel(pos))) { -+ if (isChunkInRange(pos)) { if (!hasPlayerTicket(coords, 33)) { // Paper - high priority might of already added it - DistanceManager.this.addTicket(pos, ticket); - DistanceManager.this.ticketsToRelease.add(pos); -- } else { -+ }} else { // Paper - DistanceManager.this.ticketThrottlerReleaser.tell(ChunkTaskPriorityQueueSorter.release(() -> { - }, pos, false)); - } - - }); - }, pos, () -> { -- return distance; -+ return Math.min(ChunkMap.MAX_CHUNK_DISTANCE, priority); // Paper - Chunk priority - })); -+ }); // Paper - } else { - DistanceManager.this.ticketThrottlerReleaser.tell(ChunkTaskPriorityQueueSorter.release(() -> { - DistanceManager.this.mainThreadExecutor.execute(() -> { - DistanceManager.this.removeTicket(pos, ticket); -+ DistanceManager.this.clearPriorityTickets(coords); // Paper - Chunk priority - }); - }, pos, true)); - } -@@ -0,0 +0,0 @@ public abstract class DistanceManager { - private boolean haveTicketFor(int distance) { - return distance <= this.viewDistance - 2; - } -+ -+ // Paper start - smart scheduling of player tickets -+ private boolean isChunkInRange(long i) { -+ return this.haveTicketFor(this.getLevel(i)); -+ } -+ public void scheduleChunkLoad(long i, long startTick, int initialDistance, java.util.function.Consumer task) { -+ long elapsed = net.minecraft.server.MinecraftServer.currentTick - startTick; -+ ChunkPos chunkPos = new ChunkPos(i); -+ ChunkHolder updatingChunk = chunkMap.getUpdatingChunkIfPresent(i); -+ if ((updatingChunk != null && updatingChunk.isFullChunkReady()) || !isChunkInRange(i) || getChunkPriority(chunkPos) > 0) { // Copied from above -+ // no longer needed -+ task.accept(1); -+ return; -+ } -+ -+ int desireDelay = 0; -+ double minDist = Double.MAX_VALUE; -+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet players = chunkMap.playerViewDistanceNoTickMap.getObjectsInRange(i); -+ if (elapsed == 0 && initialDistance <= 4) { -+ // Aim for no delay on initial 6 chunk radius tickets save on performance of the below code to only > 6 -+ minDist = initialDistance; -+ } else if (players != null) { -+ Object[] backingSet = players.getBackingSet(); -+ -+ net.minecraft.core.BlockPos blockPos = chunkPos.getWorldPosition(); -+ -+ boolean isFront = false; -+ net.minecraft.core.BlockPos.MutableBlockPos pos = new net.minecraft.core.BlockPos.MutableBlockPos(); -+ for (int index = 0, len = backingSet.length; index < len; ++index) { -+ if (!(backingSet[index] instanceof ServerPlayer)) { -+ continue; -+ } -+ ServerPlayer player = (ServerPlayer) backingSet[index]; -+ -+ ChunkPos pointInFront = player.getChunkInFront(5); -+ pos.set(pointInFront.x << 4, 0, pointInFront.z << 4); -+ double frontDist = net.minecraft.server.MCUtil.distanceSq(pos, blockPos); -+ -+ pos.set(player.getX(), 0, player.getZ()); -+ double center = net.minecraft.server.MCUtil.distanceSq(pos, blockPos); -+ -+ double dist = Math.min(frontDist, center); -+ if (!isFront) { -+ ChunkPos pointInBack = player.getChunkInFront(-7); -+ pos.set(pointInBack.x << 4, 0, pointInBack.z << 4); -+ double backDist = net.minecraft.server.MCUtil.distanceSq(pos, blockPos); -+ if (frontDist < backDist) { -+ isFront = true; -+ } -+ } -+ if (dist < minDist) { -+ minDist = dist; -+ } -+ } -+ if (minDist == Double.MAX_VALUE) { -+ minDist = 15; -+ } else { -+ minDist = Math.sqrt(minDist) / 16; -+ } -+ if (minDist > 4) { -+ int desiredTimeDelayMax = isFront ? -+ (minDist < 10 ? 7 : 15) : // Front -+ (minDist < 10 ? 15 : 45); // Back -+ desireDelay += (desiredTimeDelayMax * 20) * (minDist / 32); -+ } -+ } else { -+ minDist = initialDistance; -+ desireDelay = 1; -+ } -+ long delay = desireDelay - elapsed; -+ if (delay <= 0 && minDist > 4 && minDist < Double.MAX_VALUE) { -+ boolean hasAnyNeighbor = false; -+ for (int x = -1; x <= 1; x++) { -+ for (int z = -1; z <= 1; z++) { -+ if (x == 0 && z == 0) continue; -+ long pair = ChunkPos.asLong(chunkPos.x + x, chunkPos.z + z); -+ ChunkHolder neighbor = chunkMap.getUpdatingChunkIfPresent(pair); -+ ChunkStatus current = neighbor != null ? neighbor.getChunkHolderStatus() : null; -+ if (current != null && current.isOrAfter(ChunkStatus.LIGHT)) { -+ hasAnyNeighbor = true; -+ } -+ } -+ } -+ if (!hasAnyNeighbor) { -+ delay += 20; -+ } -+ } -+ if (delay <= 0) { -+ task.accept((int) minDist); -+ } else { -+ int taskDelay = (int) Math.min(delay, minDist >= 10 ? 40 : (minDist < 6 ? 5 : 20)); -+ net.minecraft.server.MCUtil.scheduleTask(taskDelay, () -> scheduleChunkLoad(i, startTick, initialDistance, task), "Player Ticket Delayer"); -+ } -+ } -+ // Paper end - } - } -diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java -+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -@@ -0,0 +0,0 @@ public class ServerChunkCache extends ChunkSource { - public void removeTicketAtLevel(TicketType ticketType, ChunkPos chunkPos, int ticketLevel, T identifier) { - this.distanceManager.removeTicketAtLevel(ticketType, chunkPos, ticketLevel, identifier); - } -+ -+ public boolean markUrgent(ChunkPos coords) { -+ return this.distanceManager.markUrgent(coords); -+ } -+ -+ public boolean markHighPriority(ChunkPos coords, int priority) { -+ return this.distanceManager.markHighPriority(coords, priority); -+ } -+ -+ public void markAreaHighPriority(ChunkPos center, int priority, int radius) { -+ this.distanceManager.markAreaHighPriority(center, priority, radius); -+ } -+ -+ public void clearAreaPriorityTickets(ChunkPos center, int radius) { -+ this.distanceManager.clearAreaPriorityTickets(center, radius); -+ } -+ -+ public void clearPriorityTickets(ChunkPos coords) { -+ this.distanceManager.clearPriorityTickets(coords); -+ } - // Paper end - async chunk io - - @Nullable -@@ -0,0 +0,0 @@ public class ServerChunkCache extends ChunkSource { - Objects.requireNonNull(completablefuture); - if (!completablefuture.isDone()) { // Paper - // Paper start - async chunk io/loading -+ ChunkPos pair = new ChunkPos(x1, z1); // Paper - Chunk priority -+ this.distanceManager.markUrgent(pair); // Paper - Chunk priority - this.level.asyncChunkTaskManager.raisePriority(x1, z1, com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGHEST_PRIORITY); - com.destroystokyo.paper.io.chunk.ChunkTaskManager.pushChunkWait(this.level, x1, z1); - // Paper end -@@ -0,0 +0,0 @@ public class ServerChunkCache extends ChunkSource { - chunkproviderserver_a.managedBlock(completablefuture::isDone); - com.destroystokyo.paper.io.chunk.ChunkTaskManager.popChunkWait(); // Paper - async chunk debug - this.level.timings.syncChunkLoad.stopTiming(); // Paper -+ this.distanceManager.clearPriorityTickets(pair); // Paper - Chunk priority -+ this.distanceManager.clearUrgent(pair); // Paper - Chunk priority - } // Paper - ichunkaccess = (ChunkAccess) ((Either) completablefuture.join()).map((ichunkaccess1) -> { - return ichunkaccess1; -@@ -0,0 +0,0 @@ public class ServerChunkCache extends ChunkSource { - if (flag && !currentlyUnloading) { - // CraftBukkit end - this.distanceManager.addTicket(TicketType.UNKNOWN, chunkcoordintpair, l, chunkcoordintpair); -+ if (isUrgent) this.distanceManager.markUrgent(chunkcoordintpair); // Paper - Chunk priority - if (this.chunkAbsent(playerchunk, l)) { - ProfilerFiller gameprofilerfiller = this.level.getProfiler(); - - gameprofilerfiller.push("chunkLoad"); -+ distanceManager.delayDistanceManagerTick = false; // Paper - Chunk priority - ensure this is never false - this.runDistanceManagerUpdates(); - playerchunk = this.getVisibleChunkIfPresent(k); - gameprofilerfiller.pop(); -@@ -0,0 +0,0 @@ public class ServerChunkCache extends ChunkSource { - } - } - } -- -- return this.chunkAbsent(playerchunk, l) ? ChunkHolder.UNLOADED_CHUNK_FUTURE : playerchunk.getOrScheduleFuture(chunkstatus, this.chunkMap); -+ // Paper start - Chunk priority -+ CompletableFuture> future = this.chunkAbsent(playerchunk, l) ? ChunkHolder.UNLOADED_CHUNK_FUTURE : playerchunk.getOrScheduleFuture(chunkstatus, this.chunkMap); -+ if (isUrgent) { -+ future.thenAccept(either -> this.distanceManager.clearUrgent(chunkcoordintpair)); -+ } -+ return future; -+ // Paper end - } - - private boolean chunkAbsent(@Nullable ChunkHolder holder, int maxLevel) { -@@ -0,0 +0,0 @@ public class ServerChunkCache extends ChunkSource { - } - - public boolean runDistanceManagerUpdates() { -+ if (distanceManager.delayDistanceManagerTick) return false; // Paper - Chunk priority - boolean flag = this.distanceManager.runAllUpdates(this.chunkMap); - boolean flag1 = this.chunkMap.promoteChunkMap(); - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -0,0 +0,0 @@ public class ServerPlayer extends Player { - private int lastRecordedArmor = Integer.MIN_VALUE; - private int lastRecordedLevel = Integer.MIN_VALUE; - private int lastRecordedExperience = Integer.MIN_VALUE; -+ // Paper start - Chunk priority -+ public long lastHighPriorityChecked; -+ public void forceCheckHighPriority() { -+ lastHighPriorityChecked = -1; -+ getLevel().getChunkSource().chunkMap.checkHighPriorityChunks(this); -+ } -+ public boolean isRealPlayer; -+ // Paper end - private float lastSentHealth = -1.0E8F; - private int lastSentFood = -99999999; - private boolean lastFoodSaturationZero = true; -@@ -0,0 +0,0 @@ public class ServerPlayer extends Player { - this.maxHealthCache = this.getMaxHealth(); - this.cachedSingleMobDistanceMap = new com.destroystokyo.paper.util.PooledHashSets.PooledObjectLinkedOpenHashSet<>(this); // Paper - } -+ // Paper start - Chunk priority -+ public BlockPos getPointInFront(double inFront) { -+ double rads = Math.toRadians(net.minecraft.server.MCUtil.normalizeYaw(this.yRot + 90)); // MC rotates yaw 90 for some odd reason -+ final double x = getX() + inFront * Math.cos(rads); -+ final double z = getZ() + inFront * Math.sin(rads); -+ return new BlockPos(x, getY(), z); -+ } -+ -+ public ChunkPos getChunkInFront(double inFront) { -+ double rads = Math.toRadians(net.minecraft.server.MCUtil.normalizeYaw(this.yRot + 90)); // MC rotates yaw 90 for some odd reason -+ final double x = getX() + (inFront * 16) * Math.cos(rads); -+ final double z = getZ() + (inFront * 16) * Math.sin(rads); -+ return new ChunkPos(Mth.floor(x) >> 4, Mth.floor(z) >> 4); -+ } -+ // Paper end - - // Yes, this doesn't match Vanilla, but it's the best we can do for now. - // If this is an issue, PRs are welcome -@@ -0,0 +0,0 @@ public class ServerPlayer extends Player { - if (valid && !this.isSpectator() || !this.touchingUnloadedChunk()) { // Paper - don't tick dead players that are not in the world currently (pending respawn) - super.tick(); - } -+ if (valid && isAlive() && connection != null) ((ServerLevel)level).getChunkSource().chunkMap.checkHighPriorityChunks(this); // Paper - Chunk priority - - for (int i = 0; i < this.getInventory().getContainerSize(); ++i) { - ItemStack itemstack = this.getInventory().getItem(i); -diff --git a/src/main/java/net/minecraft/server/level/Ticket.java b/src/main/java/net/minecraft/server/level/Ticket.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/server/level/Ticket.java -+++ b/src/main/java/net/minecraft/server/level/Ticket.java -@@ -0,0 +0,0 @@ public final class Ticket implements Comparable> { - public final T key; - public long createdTick; - public long delayUnloadBy; // Paper -+ public int priority; // Paper - Chunk priority - - protected Ticket(TicketType type, int level, T argument) { - this.type = type; -diff --git a/src/main/java/net/minecraft/server/level/TicketType.java b/src/main/java/net/minecraft/server/level/TicketType.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/server/level/TicketType.java -+++ b/src/main/java/net/minecraft/server/level/TicketType.java -@@ -0,0 +0,0 @@ import net.minecraft.world.level.ChunkPos; - public class TicketType { - public static final TicketType FUTURE_AWAIT = create("future_await", Long::compareTo); // Paper - public static final TicketType ASYNC_LOAD = create("async_load", Long::compareTo); // Paper -+ public static final TicketType PRIORITY = create("priority", Comparator.comparingLong(ChunkPos::toLong), 300); // Paper -+ public static final TicketType URGENT = create("urgent", Comparator.comparingLong(ChunkPos::toLong), 300); // Paper - - private final String name; - private final Comparator comparator; -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -0,0 +0,0 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser - - this.awaitingTeleportTime = this.tickCount; - this.player.absMoveTo(d0, d1, d2, f, f1); -+ this.player.forceCheckHighPriority(); // Paper - this.player.connection.send(new ClientboundPlayerPositionPacket(d0 - d3, d1 - d4, d2 - d5, f - f2, f1 - f3, set, this.awaitingTeleport, flag)); - } - -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -0,0 +0,0 @@ public abstract class PlayerList { - net.minecraft.server.level.ChunkMap playerChunkMap = worldserver1.getChunkSource().chunkMap; - net.minecraft.server.level.DistanceManager distanceManager = playerChunkMap.distanceManager; - distanceManager.addTicketAtLevel(net.minecraft.server.level.TicketType.LOGIN, pos, 31, pos.toLong()); -- worldserver1.getChunkSource().runDistanceManagerUpdates(); -- worldserver1.getChunkSource().getChunkAtAsynchronously(chunkX, chunkZ, true, true).thenApply(chunk -> { -+ worldserver1.getChunkSource().markAreaHighPriority(pos, 28, 3); // Paper - Chunk priority -+ worldserver1.getChunkSource().getChunkAtAsynchronously(chunkX, chunkZ, true, false).thenApply(chunk -> { // Paper - Chunk priority - net.minecraft.server.level.ChunkHolder updatingChunk = playerChunkMap.getUpdatingChunkIfPresent(pos.toLong()); - if (updatingChunk != null) { - return updatingChunk.getEntityTickingChunkFuture(); -@@ -0,0 +0,0 @@ public abstract class PlayerList { - SocketAddress socketaddress = loginlistener.connection.getRemoteAddress(); - - ServerPlayer entity = new ServerPlayer(this.server, this.server.getLevel(Level.OVERWORLD), gameprofile); -+ entity.isRealPlayer = true; // Paper - Chunk priority - Player player = entity.getBukkitEntity(); - PlayerLoginEvent event = new PlayerLoginEvent(player, hostname, ((java.net.InetSocketAddress) socketaddress).getAddress(), ((java.net.InetSocketAddress) loginlistener.connection.getRawAddress()).getAddress()); - -@@ -0,0 +0,0 @@ public abstract class PlayerList { - // CraftBukkit end - - worldserver1.getChunkSource().addRegionTicket(net.minecraft.server.level.TicketType.POST_TELEPORT, new net.minecraft.world.level.ChunkPos(location.getBlockX() >> 4, location.getBlockZ() >> 4), 1, entityplayer.getId()); // Paper -+ entityplayer1.forceCheckHighPriority(); // Player - Chunk priority - while (avoidSuffocation && !worldserver1.noCollision(entityplayer1) && entityplayer1.getY() < (double) worldserver1.getMaxBuildHeight()) { - entityplayer1.setPos(entityplayer1.getX(), entityplayer1.getY() + 1.0D, entityplayer1.getZ()); - } -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -0,0 +0,0 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n - private Vec3 position; - private BlockPos blockPosition; - private Vec3 deltaMovement; -- private float yRot; -+ public float yRot; // Paper - private->public - private float xRot; - public float yRotO; - public float xRotO; -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -0,0 +0,0 @@ public class CraftWorld implements World { - return future; - } - -+ // Paper start - Chunk priority -+ if (!urgent) { -+ // If not urgent, at least use a slightly boosted priority -+ world.getChunkSource().markHighPriority(new ChunkPos(x, z), 1); -+ } -+ // Paper end - return this.world.getChunkSource().getChunkAtAsynchronously(x, z, gen, urgent).thenComposeAsync((either) -> { - net.minecraft.world.level.chunk.LevelChunk chunk = (net.minecraft.world.level.chunk.LevelChunk) either.left().orElse(null); - if (chunk != null) addTicket(x, z); // Paper -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -0,0 +0,0 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - throw new UnsupportedOperationException("Cannot set rotation of players. Consider teleporting instead."); - } - -+ // Paper start - Chunk priority -+ @Override -+ public java.util.concurrent.CompletableFuture teleportAsync(Location loc, @javax.annotation.Nonnull PlayerTeleportEvent.TeleportCause cause) { -+ ((CraftWorld)loc.getWorld()).getHandle().getChunkSource().markAreaHighPriority( -+ new net.minecraft.world.level.ChunkPos(net.minecraft.util.Mth.floor(loc.getX()) >> 4, -+ net.minecraft.util.Mth.floor(loc.getZ()) >> 4), 28, 3); // Load area high priority -+ return super.teleportAsync(loc, cause); -+ } -+ // Paper end -+ - @Override - public boolean teleport(Location location, PlayerTeleportEvent.TeleportCause cause) { - Preconditions.checkArgument(location != null, "location"); diff --git a/patches/server/Improve-Chunk-Status-Transition-Speed.patch b/patches/server/Improve-Chunk-Status-Transition-Speed.patch index 8606e368f0..d6aab78c2f 100644 --- a/patches/server/Improve-Chunk-Status-Transition-Speed.patch +++ b/patches/server/Improve-Chunk-Status-Transition-Speed.patch @@ -49,6 +49,13 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + ChunkAccess chunk = getAvailableChunkNow(); + return chunk != null && (status == null || chunk.getStatus().isOrAfter(getNextStatus(status))); + } ++ // Yanked from chunk priotisation patch - remove? ++ public static ChunkStatus getNextStatus(ChunkStatus status) { ++ if (status == ChunkStatus.FULL) { ++ return status; ++ } ++ return CHUNK_STATUSES.get(status.getIndex() + 1); ++ } + // Paper end public ChunkHolder(ChunkPos pos, int level, LevelHeightAccessor world, LevelLightEngine lightingProvider, ChunkHolder.LevelChangeListener levelUpdateListener, ChunkHolder.PlayerProvider playersWatchingChunkProvider) { diff --git a/patches/server/Optimize-Light-Engine.patch b/patches/server/Optimize-Light-Engine.patch index e4a07f018e..7bd65d800e 100644 --- a/patches/server/Optimize-Light-Engine.patch +++ b/patches/server/Optimize-Light-Engine.patch @@ -29,13 +29,13 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 --- a/src/main/java/net/minecraft/server/level/ChunkHolder.java +++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java @@ -0,0 +0,0 @@ public class ChunkHolder { - ioPriority = com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGH_PRIORITY; - } - chunkMap.level.asyncChunkTaskManager.raisePriority(pos.x, pos.z, ioPriority); -+ chunkMap.level.getChunkSource().getLightEngine().queue.changePriority(pos.toLong(), this.queueLevel, priority); // Paper - } - if (this.queueLevel != priority) { - this.onLevelChange.onLevelChange(this.pos, () -> this.queueLevel, priority, p -> this.queueLevel = p); // use preferred priority + + this.onLevelChange.onLevelChange(this.pos, this::getQueueLevel, this.ticketLevel, this::setQueueLevel); + this.oldTicketLevel = this.ticketLevel; ++ //chunkMap.level.getChunkSource().getLightEngine().queue.changePriority(pos.toLong(), this.queueLevel, priority); // Paper // Restore this in chunk priority later? + // CraftBukkit start + // ChunkLoadEvent: Called after the chunk is loaded: isChunkLoaded returns true and chunk is ready to be modified by plugins. + if (!playerchunk_state.isOrAfter(ChunkHolder.FullChunkStatus.BORDER) && playerchunk_state1.isOrAfter(ChunkHolder.FullChunkStatus.BORDER)) { diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/server/level/ChunkMap.java