diff --git a/patches/server/Correctly-handle-recursion-for-chunkholder-updates.patch b/patches/server/Correctly-handle-recursion-for-chunkholder-updates.patch index 5970a60371..0b5bd3a9db 100644 --- a/patches/server/Correctly-handle-recursion-for-chunkholder-updates.patch +++ b/patches/server/Correctly-handle-recursion-for-chunkholder-updates.patch @@ -17,6 +17,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + protected long updateCount; // Paper - correctly handle recursion protected void updateFutures(ChunkMap chunkStorage, Executor executor) { + io.papermc.paper.util.TickThread.ensureTickThread("Async ticket level update"); // Paper + long updateCount = ++this.updateCount; // Paper - correctly handle recursion ChunkStatus chunkstatus = ChunkHolder.getStatus(this.oldTicketLevel); ChunkStatus chunkstatus1 = ChunkHolder.getStatus(this.ticketLevel); diff --git a/patches/server/Distance-manager-tick-timings.patch b/patches/server/Distance-manager-tick-timings.patch index f7f90e4f4c..7af0b68df4 100644 --- a/patches/server/Distance-manager-tick-timings.patch +++ b/patches/server/Distance-manager-tick-timings.patch @@ -23,8 +23,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 --- 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 boolean runDistanceManagerUpdates() { + if (distanceManager.delayDistanceManagerTick) return false; // Paper - Chunk priority if (this.chunkMap.unloadingPlayerChunk) { net.minecraft.server.MinecraftServer.LOGGER.fatal("Cannot tick distance manager while unloading playerchunks", new Throwable()); throw new IllegalStateException("Cannot tick distance manager while unloading playerchunks"); } // Paper + co.aikar.timings.MinecraftTimings.distanceManagerTick.startTiming(); try { // Paper - add timings for distance manager boolean flag = this.distanceManager.runAllUpdates(this.chunkMap); diff --git a/patches/server/Do-not-allow-ticket-level-changes-while-unloading-pl.patch b/patches/server/Do-not-allow-ticket-level-changes-while-unloading-pl.patch index ee7cf93730..e181843062 100644 --- a/patches/server/Do-not-allow-ticket-level-changes-while-unloading-pl.patch +++ b/patches/server/Do-not-allow-ticket-level-changes-while-unloading-pl.patch @@ -53,9 +53,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 --- 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 boolean runDistanceManagerUpdates() { + if (distanceManager.delayDistanceManagerTick) return false; // Paper - Chunk priority + if (this.chunkMap.unloadingPlayerChunk) { net.minecraft.server.MinecraftServer.LOGGER.fatal("Cannot tick distance manager while unloading playerchunks", new Throwable()); throw new IllegalStateException("Cannot tick distance manager while unloading playerchunks"); } // Paper boolean flag = this.distanceManager.runAllUpdates(this.chunkMap); boolean flag1 = this.chunkMap.promoteChunkMap(); 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 151bd60e5e..f054058e86 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 @@ -21,15 +21,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 this.lastGoodX = this.awaitingPositionFromClient.x; this.lastGoodY = this.awaitingPositionFromClient.y; this.lastGoodZ = this.awaitingPositionFromClient.z; -@@ -0,0 +0,0 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser - // CraftBukkit end - - 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.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 new file mode 100644 index 0000000000..ceba229bb7 --- /dev/null +++ b/patches/server/Implement-Chunk-Priority-Urgency-System-for-Chunks.patch @@ -0,0 +1,1245 @@ +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 { + private boolean resendLight; + private CompletableFuture pendingFullStateConfirmation; + ++ public ServerLevel getWorld() { return chunkMap.level; } // Paper + boolean isUpdateQueued = false; // Paper + private final ChunkMap chunkMap; // Paper + +@@ -0,0 +0,0 @@ public class ChunkHolder { + }); + } + ++ // Paper start ++ private boolean loadCallbackScheduled = false; ++ private boolean unloadCallbackScheduled = false; ++ // Paper end ++ + private void demoteFullChunk(ChunkMap playerchunkmap, ChunkHolder.FullChunkStatus playerchunk_state) { + this.pendingFullStateConfirmation.cancel(false); + playerchunkmap.onFullChunkStatusChange(this.pos, playerchunk_state); + } + + protected void updateFutures(ChunkMap chunkStorage, Executor executor) { ++ io.papermc.paper.util.TickThread.ensureTickThread("Async ticket level update"); // Paper + ChunkStatus chunkstatus = ChunkHolder.getStatus(this.oldTicketLevel); + ChunkStatus chunkstatus1 = ChunkHolder.getStatus(this.ticketLevel); + boolean flag = this.oldTicketLevel <= ChunkMap.MAX_CHUNK_DISTANCE; +@@ -0,0 +0,0 @@ public class ChunkHolder { + // 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) -> { ++ io.papermc.paper.util.TickThread.ensureTickThread("Async full status chunk future completion"); // Paper + LevelChunk chunk = (LevelChunk)either.left().orElse(null); +- if (chunk != null) { ++ if (chunk != null && chunk.wasLoadCallbackInvoked() && ChunkHolder.this.ticketLevel > 33) { // Paper - only invoke unload if load was called ++ // Paper start - only schedule once, now the future is no longer completed as RIGHT if unloaded... ++ if (ChunkHolder.this.unloadCallbackScheduled) { ++ return; ++ } ++ ChunkHolder.this.unloadCallbackScheduled = true; ++ // Paper end - only schedule once, now the future is no longer completed as RIGHT if unloaded... + chunkStorage.callbackExecutor.execute(() -> { ++ // Paper start - only schedule once, now the future is no longer completed as RIGHT if unloaded... ++ ChunkHolder.this.unloadCallbackScheduled = false; ++ if (ChunkHolder.this.ticketLevel <= 33) { ++ return; ++ } ++ // Paper end - only schedule once, now the future is no longer completed as RIGHT if unloaded... + // Minecraft will apply the chunks tick lists to the world once the chunk got loaded, and then store the tick + // lists again inside the chunk once the chunk becomes inaccessible and set the chunk's needsSaving flag. + // These actions may however happen deferred, so we manually set the needsSaving flag already here. +@@ -0,0 +0,0 @@ public class ChunkHolder { + this.scheduleFullChunkPromotion(chunkStorage, this.fullChunkFuture, executor, ChunkHolder.FullChunkStatus.BORDER); + // Paper start - cache ticking ready status + this.fullChunkFuture.thenAccept(either -> { ++ io.papermc.paper.util.TickThread.ensureTickThread("Async full chunk future completion"); // Paper + 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.scheduleFullChunkPromotion(chunkStorage, this.tickingChunkFuture, executor, ChunkHolder.FullChunkStatus.TICKING); + // Paper start - cache ticking ready status + this.tickingChunkFuture.thenAccept(either -> { ++ io.papermc.paper.util.TickThread.ensureTickThread("Async full chunk future completion"); // Paper + 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.scheduleFullChunkPromotion(chunkStorage, this.entityTickingChunkFuture, executor, ChunkHolder.FullChunkStatus.ENTITY_TICKING); + // Paper start - cache ticking ready status + this.entityTickingChunkFuture.thenAccept(either -> { ++ io.papermc.paper.util.TickThread.ensureTickThread("Async full chunk future completion"); // Paper + either.ifLeft(chunk -> { + ChunkHolder.this.isEntityTickingReady = true; + // Paper start - entity ticking chunk set +@@ -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 currRequestedPriority = this.requestedPriority; ++ int priority = getDemandedPriority(); ++ int newRequestedPriority = this.requestedPriority = priority; ++ 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); ++ chunkMap.level.getChunkSource().getLightEngine().queue.changePriority(pos.toLong(), this.queueLevel, priority); // Paper // Restore this in chunk priority later? ++ } ++ if (currRequestedPriority != newRequestedPriority) { ++ 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) -> { ++ io.papermc.paper.util.TickThread.ensureTickThread("Async full status chunk future completion"); // Paper + LevelChunk chunk = (LevelChunk)either.left().orElse(null); +- if (chunk != null) { ++ if (chunk != null && ChunkHolder.this.oldTicketLevel <= 33 && !chunk.wasLoadCallbackInvoked()) { // Paper - ensure ticket level is set to loaded before calling, as now this can complete with ticket level > 33 ++ // Paper start - only schedule once, now the future is no longer completed as RIGHT if unloaded... ++ if (ChunkHolder.this.loadCallbackScheduled) { ++ return; ++ } ++ ChunkHolder.this.loadCallbackScheduled = true; ++ // Paper end - only schedule once, now the future is no longer completed as RIGHT if unloaded... + chunkStorage.callbackExecutor.execute(() -> { +- chunk.loadCallback(); ++ ChunkHolder.this.loadCallbackScheduled = false; // Paper - only schedule once, now the future is no longer completed as RIGHT if unloaded... ++ if (ChunkHolder.this.oldTicketLevel <= 33) chunk.loadCallback(); // Paper " + }); + } + }).exceptionally((throwable) -> { +@@ -0,0 +0,0 @@ public class ChunkHolder { + }; + } + +- // Paper start ++ // 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<>(); ++ int requestedPriority = ChunkMap.MAX_CHUNK_DISTANCE + 1; // this priority is possible pending, but is used to ensure needless updates are not queued ++ ++ 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) { ++ if (!Integer.valueOf(priority).equals(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 (this.requestedPriority != 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 + ++ '}'; ++ } + public final boolean isEntityTickingReady() { + return this.isEntityTickingReady; + } +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 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); +@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + }); + } + ++ // 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); ++ } ++ } ++ // 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 + if (requiredStatus == ChunkStatus.EMPTY) { + return this.scheduleChunkLoad(chunkcoordintpair); + } else { ++ // Paper start - revert 1.17 chunk system changes ++ CompletableFuture> future = holder.getOrScheduleFuture(requiredStatus.getParent(), this); ++ return future.thenComposeAsync((either) -> { ++ Optional optional = either.left(); ++ if (!optional.isPresent()) { ++ return CompletableFuture.completedFuture(either); ++ } ++ // Paper end - revert 1.17 chunk system changes + if (requiredStatus == ChunkStatus.LIGHT) { + this.distanceManager.addTicket(TicketType.LIGHT, chunkcoordintpair, 33 + ChunkStatus.getDistance(ChunkStatus.LIGHT), chunkcoordintpair); + } + +- Optional optional = ((Either) holder.getOrScheduleFuture(requiredStatus.getParent(), this).getNow(ChunkHolder.UNLOADED_CHUNK)).left(); ++ // Paper - revert 1.17 chunk system changes + + if (optional.isPresent() && ((ChunkAccess) optional.get()).getStatus().isOrAfter(requiredStatus)) { + CompletableFuture> completablefuture = requiredStatus.load(this.level, this.structureManager, this.lightEngine, (ichunkaccess) -> { +@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + } else { + return this.scheduleChunkGeneration(holder, requiredStatus); + } ++ }, this.mainThreadExecutor).thenComposeAsync(CompletableFuture::completedFuture, this.mainThreadExecutor); // Paper - revert 1.17 chunk system changes + } + } + +@@ -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.requestedPriority : 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 + this.releaseLightTicket(chunkcoordintpair); + return CompletableFuture.completedFuture(Either.right(playerchunk_failure)); + }); +- }, executor); ++ }, executor).thenComposeAsync((either) -> { // Paper start - force competion on the main thread ++ return CompletableFuture.completedFuture(either); ++ }, this.mainThreadExecutor); // use the main executor, we want to ensure only one chunk callback can be completed per runnable execute ++ // Paper end - force competion on the main thread + } + + protected void releaseLightTicket(ChunkPos pos) { +@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + long i = chunkHolder.getPos().toLong(); + + Objects.requireNonNull(chunkHolder); +- mailbox.tell(ChunkTaskPriorityQueueSorter.message(runnable, i, chunkHolder::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> tickets) { ++ org.spigotmc.AsyncCatcher.catchOp("ChunkMapDistance::getTicketLevelAt"); // Paper + return !tickets.isEmpty() ? ((Ticket) tickets.first()).getTicketLevel() : ChunkMap.MAX_CHUNK_DISTANCE + 1; + } + +@@ -0,0 +0,0 @@ public abstract class DistanceManager { + public boolean runAllUpdates(ChunkMap chunkStorage) { + this.naturalSpawnChunkCounter.runAllUpdates(); + this.tickingTicketsTracker.runAllUpdates(); ++ 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(chunkStorage, 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); +@@ -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 addPriorityTicket(ChunkPos coords, TicketType ticketType, int priority) { ++ org.spigotmc.AsyncCatcher.catchOp("ChunkMapDistance::addPriorityTicket"); ++ long pair = coords.toLong(); ++ ChunkHolder chunk = chunkMap.getUpdatingChunkIfPresent(pair); ++ if ((chunk != null && chunk.isFullChunkReady())) { ++ return false; ++ } ++ ++ 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); + long i = pos.toLong(); +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 { + return CompletableFuture.completedFuture(either); + }, this.mainThreadProcessor); + } ++ ++ 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_b.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 (create && !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(leastStatus, this.chunkMap); ++ // Paper start - Chunk priority ++ CompletableFuture> future = this.chunkAbsent(playerchunk, l) ? ChunkHolder.UNLOADED_CHUNK_FUTURE : playerchunk.getOrScheduleFuture(leastStatus, 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; ++ public boolean isRealPlayer; // Paper - chunk priority + 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 +diff --git a/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java b/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java ++++ b/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java +@@ -0,0 +0,0 @@ + package net.minecraft.server.level; + + import com.mojang.datafixers.util.Pair; ++import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; // Paper + import it.unimi.dsi.fastutil.objects.ObjectArrayList; + import it.unimi.dsi.fastutil.objects.ObjectList; + import it.unimi.dsi.fastutil.objects.ObjectListIterator; +@@ -0,0 +0,0 @@ import net.minecraft.util.thread.ProcessorMailbox; + import net.minecraft.world.level.ChunkPos; + import net.minecraft.world.level.LightLayer; + import net.minecraft.world.level.chunk.ChunkAccess; ++import net.minecraft.world.level.chunk.ChunkStatus; + import net.minecraft.world.level.chunk.DataLayer; + import net.minecraft.world.level.chunk.LevelChunkSection; + import net.minecraft.world.level.chunk.LightChunkGetter; +@@ -0,0 +0,0 @@ import org.apache.logging.log4j.Logger; + public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCloseable { + private static final Logger LOGGER = LogManager.getLogger(); + private final ProcessorMailbox taskMailbox; +- private final ObjectList> lightTasks = new ObjectArrayList<>(); +- private final ChunkMap chunkMap; ++ // Paper start ++ private static final int MAX_PRIORITIES = ChunkMap.MAX_CHUNK_DISTANCE + 2; ++ ++ static class ChunkLightQueue { ++ public boolean shouldFastUpdate; ++ java.util.ArrayDeque pre = new java.util.ArrayDeque(); ++ java.util.ArrayDeque post = new java.util.ArrayDeque(); ++ ++ ChunkLightQueue(long chunk) {} ++ } ++ ++ static class PendingLightTask { ++ long chunkId; ++ IntSupplier priority; ++ Runnable pre; ++ Runnable post; ++ boolean fastUpdate; ++ ++ public PendingLightTask(long chunkId, IntSupplier priority, Runnable pre, Runnable post, boolean fastUpdate) { ++ this.chunkId = chunkId; ++ this.priority = priority; ++ this.pre = pre; ++ this.post = post; ++ this.fastUpdate = fastUpdate; ++ } ++ } ++ ++ ++ // Retain the chunks priority level for queued light tasks ++ class LightQueue { ++ private int size = 0; ++ private final Long2ObjectLinkedOpenHashMap[] buckets = new Long2ObjectLinkedOpenHashMap[MAX_PRIORITIES]; ++ private final java.util.concurrent.ConcurrentLinkedQueue pendingTasks = new java.util.concurrent.ConcurrentLinkedQueue<>(); ++ private final java.util.concurrent.ConcurrentLinkedQueue priorityChanges = new java.util.concurrent.ConcurrentLinkedQueue<>(); ++ ++ private LightQueue() { ++ for (int i = 0; i < buckets.length; i++) { ++ buckets[i] = new Long2ObjectLinkedOpenHashMap<>(); ++ } ++ } ++ ++ public void changePriority(long pair, int currentPriority, int priority) { ++ this.priorityChanges.add(() -> { ++ ChunkLightQueue remove = this.buckets[currentPriority].remove(pair); ++ if (remove != null) { ++ ChunkLightQueue existing = this.buckets[Math.max(1, priority)].put(pair, remove); ++ if (existing != null) { ++ remove.pre.addAll(existing.pre); ++ remove.post.addAll(existing.post); ++ } ++ } ++ }); ++ } ++ ++ public final void addChunk(long chunkId, IntSupplier priority, Runnable pre, Runnable post) { ++ pendingTasks.add(new PendingLightTask(chunkId, priority, pre, post, true)); ++ tryScheduleUpdate(); ++ } ++ ++ public final void add(long chunkId, IntSupplier priority, ThreadedLevelLightEngine.TaskType type, Runnable run) { ++ pendingTasks.add(new PendingLightTask(chunkId, priority, type == TaskType.PRE_UPDATE ? run : null, type == TaskType.POST_UPDATE ? run : null, false)); ++ } ++ public final void add(PendingLightTask update) { ++ int priority = update.priority.getAsInt(); ++ ChunkLightQueue lightQueue = this.buckets[priority].computeIfAbsent(update.chunkId, ChunkLightQueue::new); ++ ++ if (update.pre != null) { ++ this.size++; ++ lightQueue.pre.add(update.pre); ++ } ++ if (update.post != null) { ++ this.size++; ++ lightQueue.post.add(update.post); ++ } ++ if (update.fastUpdate) { ++ lightQueue.shouldFastUpdate = true; ++ } ++ } ++ ++ public final boolean isEmpty() { ++ return this.size == 0 && this.pendingTasks.isEmpty(); ++ } ++ ++ public final int size() { ++ return this.size; ++ } ++ ++ public boolean poll(java.util.List pre, java.util.List post) { ++ PendingLightTask pending; ++ while ((pending = pendingTasks.poll()) != null) { ++ add(pending); ++ } ++ Runnable run; ++ while ((run = priorityChanges.poll()) != null) { ++ run.run(); ++ } ++ boolean hasWork = false; ++ Long2ObjectLinkedOpenHashMap[] buckets = this.buckets; ++ int priority = 0; ++ while (priority < MAX_PRIORITIES && !isEmpty()) { ++ Long2ObjectLinkedOpenHashMap bucket = buckets[priority]; ++ if (bucket.isEmpty()) { ++ priority++; ++ if (hasWork) { ++ return true; ++ } else { ++ continue; ++ } ++ } ++ ChunkLightQueue queue = bucket.removeFirst(); ++ this.size -= queue.pre.size() + queue.post.size(); ++ pre.addAll(queue.pre); ++ post.addAll(queue.post); ++ queue.pre.clear(); ++ queue.post.clear(); ++ hasWork = true; ++ if (queue.shouldFastUpdate) { ++ return true; ++ } ++ } ++ return hasWork; ++ } ++ } ++ ++ final LightQueue queue = new LightQueue(); ++ // Paper end ++ private final ChunkMap chunkMap; private final ChunkMap playerChunkMap; // Paper + private final ProcessorHandle> sorterMailbox; + private volatile int taskPerBatch = 5; + private final AtomicBoolean scheduled = new AtomicBoolean(); + + public ThreadedLevelLightEngine(LightChunkGetter chunkProvider, ChunkMap chunkStorage, boolean hasBlockLight, ProcessorMailbox processor, ProcessorHandle> executor) { + super(chunkProvider, true, hasBlockLight); +- this.chunkMap = chunkStorage; ++ this.chunkMap = chunkStorage; this.playerChunkMap = chunkMap; // Paper + this.sorterMailbox = executor; + this.taskMailbox = processor; + } +@@ -0,0 +0,0 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl + } + + private void addTask(int x, int z, IntSupplier completedLevelSupplier, ThreadedLevelLightEngine.TaskType stage, Runnable task) { +- this.sorterMailbox.tell(ChunkTaskPriorityQueueSorter.message(() -> { +- this.lightTasks.add(Pair.of(stage, task)); +- if (this.lightTasks.size() >= this.taskPerBatch) { +- this.runUpdate(); +- } +- +- }, ChunkPos.asLong(x, z), completedLevelSupplier)); ++ // Paper start - replace method ++ this.queue.add(ChunkPos.asLong(x, z), completedLevelSupplier, stage, task); ++ // Paper end + } + + @Override +@@ -0,0 +0,0 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl + + public CompletableFuture lightChunk(ChunkAccess chunk, boolean excludeBlocks) { + ChunkPos chunkPos = chunk.getPos(); +- chunk.setLightCorrect(false); +- this.addTask(chunkPos.x, chunkPos.z, ThreadedLevelLightEngine.TaskType.PRE_UPDATE, Util.name(() -> { ++ // Paper start ++ //ichunkaccess.b(false); // Don't need to disable this ++ long pair = chunkPos.toLong(); ++ CompletableFuture future = new CompletableFuture<>(); ++ IntSupplier prioritySupplier = playerChunkMap.getChunkQueueLevel(pair); ++ boolean[] skippedPre = {false}; ++ this.queue.addChunk(pair, prioritySupplier, Util.name(() -> { ++ // Paper end + LevelChunkSection[] levelChunkSections = chunk.getSections(); + + for(int i = 0; i < chunk.getSectionsCount(); ++i) { +@@ -0,0 +0,0 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl + + }, () -> { + return "lightChunk " + chunkPos + " " + excludeBlocks; +- })); +- return CompletableFuture.supplyAsync(() -> { ++ // Paper start - merge the 2 together ++ }), () -> { ++ this.chunkMap.releaseLightTicket(chunkPos); // Paper - moved from below, we want to call this even when returning early ++ if (skippedPre[0]) return; // Paper - future's already complete + chunk.setLightCorrect(true); + super.retainData(chunkPos, false); +- this.chunkMap.releaseLightTicket(chunkPos); +- return chunk; +- }, (runnable) -> { +- this.addTask(chunkPos.x, chunkPos.z, ThreadedLevelLightEngine.TaskType.POST_UPDATE, runnable); ++ //this.chunkMap.releaseLightTicket(chunkPos); // Paper - moved up ++ future.complete(chunk); + }); ++ return future; ++ // Paper end + } + + public void tryScheduleUpdate() { +- if ((!this.lightTasks.isEmpty() || super.hasLightWork()) && this.scheduled.compareAndSet(false, true)) { ++ if ((!this.queue.isEmpty() || super.hasLightWork()) && this.scheduled.compareAndSet(false, true)) { // Paper + this.taskMailbox.tell(() -> { + this.runUpdate(); + this.scheduled.set(false); ++ tryScheduleUpdate(); // Paper - if we still have work to do, do it! + }); + } + + } + ++ // Paper start - replace impl ++ private final java.util.List pre = new java.util.ArrayList<>(); ++ private final java.util.List post = new java.util.ArrayList<>(); + private void runUpdate() { +- int i = Math.min(this.lightTasks.size(), this.taskPerBatch); +- ObjectListIterator> objectListIterator = this.lightTasks.iterator(); +- +- int j; +- for(j = 0; objectListIterator.hasNext() && j < i; ++j) { +- Pair pair = objectListIterator.next(); +- if (pair.getFirst() == ThreadedLevelLightEngine.TaskType.PRE_UPDATE) { +- pair.getSecond().run(); +- } ++ if (queue.poll(pre, post)) { ++ pre.forEach(Runnable::run); ++ pre.clear(); ++ super.runUpdates(Integer.MAX_VALUE, true, true); ++ post.forEach(Runnable::run); ++ post.clear(); ++ } else { ++ // might have level updates to go still ++ super.runUpdates(Integer.MAX_VALUE, true, true); + } +- +- objectListIterator.back(j); +- super.runUpdates(Integer.MAX_VALUE, true, true); +- +- for(int var5 = 0; objectListIterator.hasNext() && var5 < i; ++var5) { +- Pair pair2 = objectListIterator.next(); +- if (pair2.getFirst() == ThreadedLevelLightEngine.TaskType.POST_UPDATE) { +- pair2.getSecond().run(); +- } +- +- objectListIterator.remove(); +- } +- ++ // Paper end + } + + public void setTaskPerBatch(int taskBatchSize) { +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> { + private final int ticketLevel; + public final T key; + public long createdTick; ++ 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 + // CraftBukkit end + + 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.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 { + } + + public void placeNewPlayer(Connection connection, ServerPlayer player) { ++ player.isRealPlayer = true; // Paper - Chunk priority + ServerPlayer prev = pendingPlayers.put(player.getUUID(), player);// Paper + if (prev != null) { + disconnectPendingPlayer(prev); +@@ -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(); +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, i + private BlockPos blockPosition; + private ChunkPos chunkPosition; + 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/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +@@ -0,0 +0,0 @@ public class LevelChunk extends ChunkAccess { + return NEIGHBOUR_CACHE_RADIUS; + } + +- boolean loadedTicketLevel; ++ boolean loadedTicketLevel; public final boolean wasLoadCallbackInvoked() { return this.loadedTicketLevel; } // Paper - public accessor + private long neighbourChunksLoadedBitset; + private final LevelChunk[] loadedNeighbourChunks = new LevelChunk[(NEIGHBOUR_CACHE_RADIUS * 2 + 1) * (NEIGHBOUR_CACHE_RADIUS * 2 + 1)]; + +@@ -0,0 +0,0 @@ public class LevelChunk extends ChunkAccess { + + // CraftBukkit start + public void loadCallback() { ++ if (this.loadedTicketLevel) { LOGGER.error("Double calling chunk load!", new Throwable()); } // Paper + // Paper start - neighbour cache + int chunkX = this.chunkPos.x; + int chunkZ = this.chunkPos.z; +@@ -0,0 +0,0 @@ public class LevelChunk extends ChunkAccess { + } + + public void unloadCallback() { ++ if (!this.loadedTicketLevel) { LOGGER.error("Double calling chunk unload!", new Throwable()); } // Paper + org.bukkit.Server server = this.level.getCraftServer(); + org.bukkit.event.world.ChunkUnloadEvent unloadEvent = new org.bukkit.event.world.ChunkUnloadEvent(this.bukkitChunk, this.isUnsaved()); + server.getPluginManager().callEvent(unloadEvent); +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 extends CraftRegionAccessor 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/MC-Utils.patch b/patches/server/MC-Utils.patch index c78054cc3c..b9823521fa 100644 --- a/patches/server/MC-Utils.patch +++ b/patches/server/MC-Utils.patch @@ -5286,6 +5286,25 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 } if (!playerchunk_state1.isOrAfter(playerchunk_state)) { +@@ -0,0 +0,0 @@ public class ChunkHolder { + } + }; + } ++ ++ // Paper start ++ public final boolean isEntityTickingReady() { ++ return this.isEntityTickingReady; ++ } ++ ++ public final boolean isTickingReady() { ++ return this.isTickingReady; ++ } ++ ++ public final boolean isFullChunkReady() { ++ return this.isFullChunkReady; ++ } ++ // Paper end + } 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 @@ -5446,6 +5465,38 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 } @Override +@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + public class ChunkDistanceManager extends DistanceManager { + + protected ChunkDistanceManager(Executor workerExecutor, Executor mainThreadExecutor) { +- super(workerExecutor, mainThreadExecutor); ++ super(workerExecutor, mainThreadExecutor, ChunkMap.this); + } + + @Override +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 { + final Executor mainThreadExecutor; + private long ticketTickCounter; + private int simulationDistance = 10; ++ private final ChunkMap chunkMap; // Paper + +- protected DistanceManager(Executor workerExecutor, Executor mainThreadExecutor) { ++ protected DistanceManager(Executor workerExecutor, Executor mainThreadExecutor, ChunkMap chunkMap) { + Objects.requireNonNull(mainThreadExecutor); + ProcessorHandle mailbox = ProcessorHandle.of("player ticket throttler", mainThreadExecutor::execute); + ChunkTaskPriorityQueueSorter chunktaskqueuesorter = new ChunkTaskPriorityQueueSorter(ImmutableList.of(mailbox), workerExecutor, 4); +@@ -0,0 +0,0 @@ public abstract class DistanceManager { + this.ticketThrottlerInput = chunktaskqueuesorter.getProcessor(mailbox, true); + this.ticketThrottlerReleaser = chunktaskqueuesorter.getReleaseProcessor(mailbox); + this.mainThreadExecutor = mainThreadExecutor; ++ this.chunkMap = chunkMap; // Paper + } + + protected void purgeStaleTickets() { 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 diff --git a/patches/server/fix-converting-txt-to-json-file.patch b/patches/server/fix-converting-txt-to-json-file.patch index 16a0562672..13a97ad031 100644 --- a/patches/server/fix-converting-txt-to-json-file.patch +++ b/patches/server/fix-converting-txt-to-json-file.patch @@ -58,4 +58,4 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + abstract public void loadAndSaveFiles(); // Paper - moved from DedicatedPlayerList constructor public void placeNewPlayer(Connection connection, ServerPlayer player) { - ServerPlayer prev = pendingPlayers.put(player.getUUID(), player);// Paper + player.isRealPlayer = true; // Paper - Chunk priority