diff --git a/Spigot-Server-Patches/Asynchronous-chunk-IO-and-loading.patch b/Spigot-Server-Patches/Asynchronous-chunk-IO-and-loading.patch index 7a8708ac6e..aeb1c204a8 100644 --- a/Spigot-Server-Patches/Asynchronous-chunk-IO-and-loading.patch +++ b/Spigot-Server-Patches/Asynchronous-chunk-IO-and-loading.patch @@ -202,8 +202,12 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + + asyncChunks = getBoolean("settings.async-chunks.enable", true); + int threads = getInt("settings.async-chunks.threads", -1); ++ int cpus = Runtime.getRuntime().availableProcessors(); + if (threads <= 0) { -+ threads = (int) Math.min(Integer.getInteger("paper.maxChunkThreads", 8), Math.max(1, Runtime.getRuntime().availableProcessors() - 1)); ++ threads = (int) Math.min(Integer.getInteger("paper.maxChunkThreads", 8), Math.max(1, cpus - 1)); ++ } ++ if (cpus == 1 && !Boolean.getBoolean("Paper.allowAsyncChunksSingleCore")) { ++ asyncChunks = false; + } + + // Let Shared Host set some limits @@ -3845,6 +3849,19 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 } + // Paper end } +diff --git a/src/main/java/net/minecraft/server/SystemUtils.java b/src/main/java/net/minecraft/server/SystemUtils.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/server/SystemUtils.java ++++ b/src/main/java/net/minecraft/server/SystemUtils.java +@@ -0,0 +0,0 @@ public class SystemUtils { + i = Integer.getInteger("Paper.WorkerThreadCount", i); // Paper - allow overriding + Object object; + +- if (i <= 0) { ++ if (i <= 0 || (Runtime.getRuntime().availableProcessors() == 1 && !Boolean.getBoolean("Paper.allowAsyncChunksSingleCore"))) { // Paper - disable server worker queue if single core system + object = MoreExecutors.newDirectExecutorService(); + } else { + object = new ForkJoinPool(i, (forkjoinpool) -> { diff --git a/src/main/java/net/minecraft/server/TicketType.java b/src/main/java/net/minecraft/server/TicketType.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/server/TicketType.java diff --git a/Spigot-Server-Patches/Implement-Chunk-Priority-Urgency-System-for-Chunks.patch b/Spigot-Server-Patches/Implement-Chunk-Priority-Urgency-System-for-Chunks.patch new file mode 100644 index 0000000000..660c3f8ab2 --- /dev/null +++ b/Spigot-Server-Patches/Implement-Chunk-Priority-Urgency-System-for-Chunks.patch @@ -0,0 +1,524 @@ +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/net/minecraft/server/ChunkMapDistance.java b/src/main/java/net/minecraft/server/ChunkMapDistance.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/server/ChunkMapDistance.java ++++ b/src/main/java/net/minecraft/server/ChunkMapDistance.java +@@ -0,0 +0,0 @@ public abstract class ChunkMapDistance { + Ticket ticket1 = (Ticket) arraysetsorted.a(ticket); // CraftBukkit - decompile error + + ticket1.a(this.currentTick); +- if (ticket.b() < j) { ++ if (ticket.b() < j || (ticket.getTicketType() == TicketType.PRIORITY && ((Ticket) ticket).getObjectReason() < j)) { // Paper - check priority tickets too + this.e.b(i, ticket.b(), true); + } + +@@ -0,0 +0,0 @@ public abstract class ChunkMapDistance { + this.addTicketAtLevel(tickettype, chunkcoordintpair, i, t0); + } + ++ // Paper start ++ public boolean markUrgent(ChunkCoordIntPair coords) { ++ return this.markHighPriority(coords, 30); ++ } ++ public boolean markHighPriority(ChunkCoordIntPair coords, int priority) { ++ priority = Math.min(30, Math.max(1, priority)); ++ Ticket ticket = new Ticket(TicketType.PRIORITY, 31, priority); ++ return this.addTicket(coords.pair(), ticket); ++ } ++ public int getChunkPriority(ChunkCoordIntPair coords) { ++ int priority = 0; ++ ArraySetSorted> tickets = this.tickets.get(coords.pair()); ++ if (tickets == null) { ++ return priority; ++ } ++ for (Ticket ticket : tickets) { ++ if (ticket.getTicketType() != TicketType.PRIORITY) { ++ continue; ++ } ++ //noinspection unchecked ++ Ticket prioTicket = (Ticket) ticket; ++ if (prioTicket.getObjectReason() > priority) { ++ priority = prioTicket.getObjectReason(); ++ } ++ } ++ return priority; ++ } ++ public void clearPriorityTickets(ChunkCoordIntPair coords) { ++ ArraySetSorted> tickets = this.tickets.get(coords.pair()); ++ java.util.List> toRemove = new java.util.ArrayList<>(); ++ if (tickets == null) return; ++ for (Ticket ticket : tickets) { ++ if (ticket.getTicketType() == TicketType.PRIORITY) { ++ toRemove.add(ticket); ++ } ++ } ++ for (Ticket ticket : toRemove) { ++ this.removeTicket(coords.pair(), ticket); ++ } ++ ++ } ++ // Paper end + public boolean addTicketAtLevel(TicketType ticketType, ChunkCoordIntPair chunkcoordintpair, int level, T identifier) { + return this.addTicket(chunkcoordintpair.pair(), new Ticket<>(ticketType, level, identifier)); + // CraftBukkit end +@@ -0,0 +0,0 @@ public abstract class ChunkMapDistance { + + }); + }, i, () -> { +- return j; ++ PlayerChunk chunk = chunkMap.getUpdatingChunk(i); // Paper ++ return chunk != null && chunk.getCurrentPriority() < j ? chunk.getCurrentPriority() : j; // Paper + })); + } else { + ChunkMapDistance.this.k.a(ChunkTaskQueueSorter.a(() -> { // CraftBukkit - decompile error +diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/server/ChunkProviderServer.java ++++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java +@@ -0,0 +0,0 @@ public class ChunkProviderServer extends IChunkProvider { + public void removeTicketAtLevel(TicketType ticketType, ChunkCoordIntPair chunkPos, int ticketLevel, T identifier) { + this.chunkMapDistance.removeTicketAtLevel(ticketType, chunkPos, ticketLevel, identifier); + } ++ ++ public boolean markUrgent(ChunkCoordIntPair coords) { ++ return chunkMapDistance.markUrgent(coords); ++ } ++ public boolean markHighPriority(ChunkCoordIntPair coords, int priority) { ++ return chunkMapDistance.markHighPriority(coords, priority); ++ } ++ public void clearPriorityTickets(ChunkCoordIntPair coords) { ++ this.chunkMapDistance.clearPriorityTickets(coords); ++ } + // Paper end + + @Nullable +@@ -0,0 +0,0 @@ public class ChunkProviderServer extends IChunkProvider { + + if (!completablefuture.isDone()) { // Paper + // Paper start - async chunk io/loading ++ ChunkCoordIntPair pair = new ChunkCoordIntPair(x, z); ++ this.markUrgent(pair); + this.world.asyncChunkTaskManager.raisePriority(x, z, com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGHEST_PRIORITY); + com.destroystokyo.paper.io.chunk.ChunkTaskManager.pushChunkWait(this.world, x, z); + // Paper end +@@ -0,0 +0,0 @@ public class ChunkProviderServer extends IChunkProvider { + this.serverThreadQueue.awaitTasks(completablefuture::isDone); + com.destroystokyo.paper.io.chunk.ChunkTaskManager.popChunkWait(); // Paper - async chunk debug + this.world.timings.syncChunkLoad.stopTiming(); // Paper ++ this.clearPriorityTickets(pair); // Paper + } // Paper + ichunkaccess = (IChunkAccess) ((Either) completablefuture.join()).map((ichunkaccess1) -> { + return ichunkaccess1; +@@ -0,0 +0,0 @@ public class ChunkProviderServer extends IChunkProvider { + if (flag && !currentlyUnloading) { + // CraftBukkit end + this.chunkMapDistance.a(TicketType.UNKNOWN, chunkcoordintpair, l, chunkcoordintpair); ++ if (isUrgent) this.markUrgent(chunkcoordintpair); // Paper + if (this.a(playerchunk, l)) { + GameProfilerFiller gameprofilerfiller = this.world.getMethodProfiler(); + +@@ -0,0 +0,0 @@ public class ChunkProviderServer extends IChunkProvider { + } + } + } +- +- return this.a(playerchunk, l) ? PlayerChunk.UNLOADED_CHUNK_ACCESS_FUTURE : playerchunk.a(chunkstatus, this.playerChunkMap); ++ // Paper start ++ CompletableFuture> future = this.a(playerchunk, l) ? PlayerChunk.UNLOADED_CHUNK_ACCESS_FUTURE : playerchunk.a(chunkstatus, this.playerChunkMap); ++ if (isUrgent) { ++ future.thenAccept(either -> this.clearPriorityTickets(chunkcoordintpair)); ++ } ++ return future; ++ // Paper end + } + + private boolean a(@Nullable PlayerChunk playerchunk, int i) { +diff --git a/src/main/java/net/minecraft/server/EntityPlayer.java b/src/main/java/net/minecraft/server/EntityPlayer.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/server/EntityPlayer.java ++++ b/src/main/java/net/minecraft/server/EntityPlayer.java +@@ -0,0 +0,0 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + if (valid && (!this.isSpectator() || this.world.isLoaded(new BlockPosition(this)))) { // Paper - don't tick dead players that are not in the world currently (pending respawn) + super.tick(); + } ++ if (valid && isAlive() && this.ticksLived % 20 == 0) ((WorldServer)world).getChunkProvider().playerChunkMap.checkHighPriorityChunks(this); // Paper + + for (int i = 0; i < this.inventory.getSize(); ++i) { + ItemStack itemstack = this.inventory.getItem(i); +diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java +index 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.location.x); + chunkData.addProperty("z", playerChunk.location.z); + chunkData.addProperty("ticket-level", playerChunk.getTicketLevel()); ++ chunkData.addProperty("priority", playerChunk.getCurrentPriority()); + chunkData.addProperty("state", PlayerChunk.getChunkState(playerChunk.getTicketLevel()).toString()); + chunkData.addProperty("queued-for-unload", chunkMap.unloadQueue.contains(playerChunk.location.pair())); + chunkData.addProperty("status", status == null ? "unloaded" : status.toString()); +diff --git a/src/main/java/net/minecraft/server/PlayerChunk.java b/src/main/java/net/minecraft/server/PlayerChunk.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/server/PlayerChunk.java ++++ b/src/main/java/net/minecraft/server/PlayerChunk.java +@@ -0,0 +0,0 @@ public class PlayerChunk { + private CompletableFuture chunkSave; + public int oldTicketLevel; + private int ticketLevel; +- private int n; ++ private int n; public final int getCurrentPriority() { return n; } // Paper - OBFHELPER + final ChunkCoordIntPair location; // Paper - private -> package + private final short[] dirtyBlocks; + private int dirtyCount; +@@ -0,0 +0,0 @@ public class PlayerChunk { + return null; + } + // Paper end - no-tick view distance ++ // Paper start - Chunk gen/load priority system ++ volatile int neighborPriority = -1; ++ final java.util.concurrent.ConcurrentHashMap neighbors = new java.util.concurrent.ConcurrentHashMap<>(); ++ final it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap neighborPriorities = new it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap<>(); ++ ++ public int getPreferredPriority() { ++ int priority = neighborPriority; // if we have a neighbor priority, use it ++ int priorityBoost = chunkMap.chunkDistanceManager.getChunkPriority(location); ++ int basePriority = ticketLevel - priorityBoost; ++ ++ if (priority == -1 || priority > basePriority) { ++ if (priorityBoost > 0) { ++ //System.out.println(location + " boost " + (basePriority) + " = " + ticketLevel + " - " + priorityBoost); ++ } ++ priority = basePriority; ++ if (ticketLevel >= 34 && priorityBoost == 0) { ++ priority += 5; ++ } ++ } ++ ++ ++ return Math.max(1, Math.min(PlayerChunkMap.GOLDEN_TICKET, priority)); ++ } ++ public void onNeighborRequest(PlayerChunk neighbor, ChunkStatus status) { ++ int currentPriority = getCurrentPriority(); ++ if (!neighborPriorities.containsKey(neighbor.location.pair()) && (neighbor.neighborPriority == -1 || neighbor.neighborPriority > currentPriority)) { ++ this.neighbors.put(neighbor, currentPriority); ++ neighbor.setNeighborPriority(this, Math.max(1, currentPriority)); ++ } ++ } ++ ++ private void setNeighborPriority(PlayerChunk requester, int priority) { ++ if (priority < neighborPriority || neighborPriority == -1) { ++ synchronized (neighborPriorities) { ++ if (priority < neighborPriority || neighborPriority == -1) { ++ neighborPriority = priority; ++ neighborPriorities.put(requester.location.pair(), Integer.valueOf(priority)); ++ } ++ } ++ } ++ } ++ ++ public void onNeighborsDone() { ++ java.util.List neighbors = new java.util.ArrayList<>(this.neighbors.keySet()); ++ this.neighbors.clear(); ++ for (PlayerChunk neighbor : neighbors) { ++ synchronized (neighbor.neighborPriorities) { ++ neighbor.neighborPriorities.remove(location.pair()); ++ neighbor.recalcNeighborPriority(); ++ } ++ } ++ } ++ ++ private void recalcNeighborPriority() { ++ neighborPriority = -1; ++ if (!neighborPriorities.isEmpty()) { ++ synchronized (neighborPriorities) { ++ for (Integer neighbor : neighborPriorities.values()) { ++ if (neighbor < neighborPriority || neighborPriority == -1) { ++ neighborPriority = neighbor; ++ } ++ } ++ } ++ } ++ } ++ ++ public final double getDistanceFromPointInFront(EntityPlayer player, int dist) { ++ int inFront = dist * 16; ++ final float yaw = MCUtil.normalizeYaw(player.yaw); ++ double rads = Math.toRadians(yaw); ++ final double x = player.locX() + inFront * Math.cos(rads); ++ final double z = player.locZ() + inFront * Math.sin(rads); ++ return getDistance(x, z); ++ } ++ ++ public final double getDistance(EntityPlayer player) { ++ return getDistance(player.locX(), player.locZ()); ++ } ++ public final double getDistance(double blockX, double blockZ) { ++ int cx = MCUtil.fastFloor(blockX) >> 4; ++ int cz = MCUtil.fastFloor(blockZ) >> 4; ++ final double x = location.x - cx; ++ final double z = location.z - cz; ++ return (x * x) + (z * z); ++ } ++ // Paper end + + public PlayerChunk(ChunkCoordIntPair chunkcoordintpair, int i, LightEngine lightengine, PlayerChunk.c playerchunk_c, PlayerChunk.d playerchunk_d) { + this.statusFutures = new AtomicReferenceArray(PlayerChunk.CHUNK_STATUSES.size()); +@@ -0,0 +0,0 @@ public class PlayerChunk { + } + return null; + } ++ public static ChunkStatus getNextStatus(ChunkStatus status) { ++ if (status == ChunkStatus.FULL) { ++ return status; ++ } ++ return CHUNK_STATUSES.get(status.getStatusIndex() + 1); ++ } + // Paper end + + public CompletableFuture> getStatusFutureUnchecked(ChunkStatus chunkstatus) { +@@ -0,0 +0,0 @@ public class PlayerChunk { + return this.n; + } + ++ private void setPriority(int i) { d(i); } // Paper - OBFHELPER + private void d(int i) { ++ if (i == n) return; // Paper + this.n = i; ++ // Paper start ++ this.neighbors.keySet().forEach(neighbor -> { ++ if (neighbor.getCurrentPriority() > i) { ++ neighbor.setNeighborPriority(this, i); ++ this.w.changePriority(neighbor.location, neighbor::getCurrentPriority, neighbor.getCurrentPriority(), neighbor::setPriority); ++ } ++ }); ++ // Paper end + } + + public void a(int i) { +@@ -0,0 +0,0 @@ public class PlayerChunk { + Chunk fullChunk = either.left().get(); + PlayerChunk.this.isFullChunkReady = true; + fullChunk.playerChunk = PlayerChunk.this; ++ this.chunkMap.chunkDistanceManager.clearPriorityTickets(location); + + + } +@@ -0,0 +0,0 @@ public class PlayerChunk { + this.entityTickingFuture = PlayerChunk.UNLOADED_CHUNK_FUTURE; + } + +- this.w.a(this.location, this::k, this.ticketLevel, this::d); ++ this.w.a(this.location, this::k, getPreferredPriority(), this::d); // Paper - preferred priority + this.oldTicketLevel = this.ticketLevel; + // CraftBukkit start + // ChunkLoadEvent: Called after the chunk is loaded: isChunkLoaded returns true and chunk is ready to be modified by plugins. +@@ -0,0 +0,0 @@ public class PlayerChunk { + + public interface c { + ++ default void changePriority(ChunkCoordIntPair chunkcoordintpair, IntSupplier intsupplier, int i, IntConsumer intconsumer) { a(chunkcoordintpair, intsupplier, i, intconsumer); } // Paper - OBFHELPER + void a(ChunkCoordIntPair chunkcoordintpair, IntSupplier intsupplier, int i, IntConsumer intconsumer); + } + +diff --git a/src/main/java/net/minecraft/server/PlayerChunkMap.java b/src/main/java/net/minecraft/server/PlayerChunkMap.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/server/PlayerChunkMap.java ++++ b/src/main/java/net/minecraft/server/PlayerChunkMap.java +@@ -0,0 +0,0 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + this.playerViewDistanceTickMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets, + (EntityPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, + com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState) -> { ++ checkHighPriorityChunks(player); + if (newState.size() != 1) { + return; + } +@@ -0,0 +0,0 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + } + ChunkCoordIntPair chunkPos = new ChunkCoordIntPair(rangeX, rangeZ); + PlayerChunkMap.this.world.getChunkProvider().removeTicketAtLevel(TicketType.PLAYER, chunkPos, 31, chunkPos); // entity ticking level, TODO check on update +- }); ++ PlayerChunkMap.this.world.getChunkProvider().clearPriorityTickets(chunkPos); ++ }, (player, prevPos, newPos) -> checkHighPriorityChunks(player)); + this.playerViewDistanceNoTickMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets); + this.playerViewDistanceBroadcastMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets, + (EntityPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, +@@ -0,0 +0,0 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + }); + // Paper end - no-tick view distance + } ++ // Paper start - Chunk Prioritization ++ private static final int[][] neighborMatrix = {{-1, 0}, {0, -1}, {0, 1}, {1, 0}}; ++ public void checkHighPriorityChunks(EntityPlayer player) { ++ MCUtil.getSpiralOutChunks(new BlockPosition(player), Math.min(7, getLoadViewDistance())).forEach(coord -> { ++ PlayerChunk chunk = getUpdatingChunk(coord.pair()); ++ if (chunk == null || chunk.isFullChunkReady() || chunk.getTicketLevel() >= 34 || ++ !world.getWorldBorder().isInBounds(coord) ++ ) { ++ return; ++ } ++ ++ double dist = chunk.getDistance(player); ++ // Prioritize immediate ++ if (dist <= 5) { ++ chunkDistanceManager.markHighPriority(coord, (int) (29 - dist)); ++ return; ++ } ++ boolean hasNeighbor = false; ++ for (int[] matrix : neighborMatrix) { ++ long neighborKey = MCUtil.getCoordinateKey(coord.x + matrix[0], coord.x + matrix[1]); ++ PlayerChunk neighbor = getUpdatingChunk(neighborKey); ++ if (neighbor != null && neighbor.isFullChunkReady()) { ++ hasNeighbor = true; ++ break; ++ } ++ } ++ if (!hasNeighbor) { ++ return; ++ } ++ // Prioritize Frustum near ++ double distFront1 = chunk.getDistanceFromPointInFront(player, 2); ++ if (distFront1 <= (4*4)) { ++ if (distFront1 <= (2 * 2)) { ++ chunkDistanceManager.markHighPriority(coord, 24); ++ } else { ++ chunkDistanceManager.markHighPriority(coord, 22); ++ } ++ return; ++ } ++ // Prioritize Frustum far ++ double distFront2 = chunk.getDistanceFromPointInFront(player, 4); ++ if (distFront2 <= (3*3)) { ++ if (distFront2 <= (2 * 2)) { ++ chunkDistanceManager.markHighPriority(coord, 23); ++ } else { ++ chunkDistanceManager.markHighPriority(coord, 20); ++ } ++ return; ++ } ++ // Prioritize nearby chunks ++ if (dist <= (5*5)) { ++ chunkDistanceManager.markHighPriority(coord, (int) (16 - Math.sqrt(dist*(4D/5D)))); ++ } ++ }); ++ } ++ // Paper end + + public void updatePlayerMobTypeMap(Entity entity) { + if (!this.world.paperConfig.perPlayerMobSpawns) { +@@ -0,0 +0,0 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + List>> list = Lists.newArrayList(); + int j = chunkcoordintpair.x; + int k = chunkcoordintpair.z; ++ PlayerChunk requestingNeighbor = getUpdatingChunk(chunkcoordintpair.pair()); // Paper + + for (int l = -i; l <= i; ++l) { + for (int i1 = -i; i1 <= i; ++i1) { +@@ -0,0 +0,0 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + } + + ChunkStatus chunkstatus = (ChunkStatus) intfunction.apply(j1); ++ if (requestingNeighbor != null && requestingNeighbor != playerchunk) requestingNeighbor.onNeighborRequest(playerchunk, chunkstatus); // Paper + CompletableFuture> completablefuture = playerchunk.a(chunkstatus, this); + + list.add(completablefuture); +@@ -0,0 +0,0 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + }; + + CompletableFuture chunkSaveFuture = this.world.asyncChunkTaskManager.getChunkSaveFuture(chunkcoordintpair.x, chunkcoordintpair.z); ++ PlayerChunk playerChunk = getUpdatingChunk(chunkcoordintpair.pair()); ++ int chunkPriority = playerChunk != null ? playerChunk.getCurrentPriority() : 33; ++ int priority = com.destroystokyo.paper.io.PrioritizedTaskQueue.NORMAL_PRIORITY; ++ ++ if (chunkPriority <= 10) { ++ priority = com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGHEST_PRIORITY; ++ } else if (chunkPriority <= 20) { ++ priority = com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGH_PRIORITY; ++ } ++ boolean isHighestPriority = priority == com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGHEST_PRIORITY; + if (chunkSaveFuture != null) { +- this.world.asyncChunkTaskManager.scheduleChunkLoad(chunkcoordintpair.x, chunkcoordintpair.z, +- com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGH_PRIORITY, chunkHolderConsumer, false, chunkSaveFuture); +- this.world.asyncChunkTaskManager.raisePriority(chunkcoordintpair.x, chunkcoordintpair.z, com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGH_PRIORITY); ++ this.world.asyncChunkTaskManager.scheduleChunkLoad(chunkcoordintpair.x, chunkcoordintpair.z, priority, chunkHolderConsumer, isHighestPriority, chunkSaveFuture); + } else { +- this.world.asyncChunkTaskManager.scheduleChunkLoad(chunkcoordintpair.x, chunkcoordintpair.z, +- com.destroystokyo.paper.io.PrioritizedTaskQueue.NORMAL_PRIORITY, chunkHolderConsumer, false); ++ this.world.asyncChunkTaskManager.scheduleChunkLoad(chunkcoordintpair.x, chunkcoordintpair.z, priority, chunkHolderConsumer, isHighestPriority); + } ++ this.world.asyncChunkTaskManager.raisePriority(chunkcoordintpair.x, chunkcoordintpair.z, priority); + return ret; + // Paper end + } +@@ -0,0 +0,0 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + return CompletableFuture.completedFuture(Either.right(playerchunk_failure)); + }); + }, (runnable) -> { ++ playerchunk.onNeighborsDone(); // Paper + this.mailboxWorldGen.a(ChunkTaskQueueSorter.a(playerchunk, runnable)); // CraftBukkit - decompile error + }); + } +@@ -0,0 +0,0 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + long i = playerchunk.i().pair(); + + playerchunk.getClass(); +- mailbox.a(ChunkTaskQueueSorter.a(runnable, i, playerchunk::getTicketLevel)); // CraftBukkit - decompile error ++ mailbox.a(ChunkTaskQueueSorter.a(runnable, i, playerchunk::getCurrentPriority)); // CraftBukkit - decompile error // Paper - use priority not ticket level.... + }); + } + +diff --git a/src/main/java/net/minecraft/server/TicketType.java b/src/main/java/net/minecraft/server/TicketType.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/server/TicketType.java ++++ b/src/main/java/net/minecraft/server/TicketType.java +@@ -0,0 +0,0 @@ public class TicketType { + public static final TicketType PLUGIN_TICKET = a("plugin_ticket", (plugin1, plugin2) -> plugin1.getClass().getName().compareTo(plugin2.getClass().getName())); // CraftBukkit + public static final TicketType FUTURE_AWAIT = a("future_await", Long::compareTo); // Paper + public static final TicketType ASYNC_LOAD = a("async_load", Long::compareTo); // Paper ++ public static final TicketType PRIORITY = a("priority", Integer::compareTo, 300); // Paper + + public static TicketType a(String s, Comparator comparator) { + return new TicketType<>(s, comparator, 0L); +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 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 this.world.getChunkProvider().getChunkAtAsynchronously(x, z, gen, urgent).thenComposeAsync((either) -> { ++ CompletableFuture future = this.world.getChunkProvider().getChunkAtAsynchronously(x, z, gen, urgent).thenComposeAsync((either) -> { + net.minecraft.server.Chunk chunk = (net.minecraft.server.Chunk) either.left().orElse(null); + return CompletableFuture.completedFuture(chunk == null ? null : chunk.getBukkitChunk()); + }, MinecraftServer.getServer()); ++ if (urgent) { ++ world.asyncChunkTaskManager.raisePriority(x, z, com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGHEST_PRIORITY); ++ } ++ return future; ++ + } + // Paper end + diff --git a/Spigot-Server-Patches/Implement-Chunk-Priority-Urgency-System-for-World-Ge.patch b/Spigot-Server-Patches/Implement-Chunk-Priority-Urgency-System-for-World-Ge.patch deleted file mode 100644 index d40a6ecc55..0000000000 --- a/Spigot-Server-Patches/Implement-Chunk-Priority-Urgency-System-for-World-Ge.patch +++ /dev/null @@ -1,312 +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 World Gen - -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. - -diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/server/ChunkProviderServer.java -+++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java -@@ -0,0 +0,0 @@ public class ChunkProviderServer extends IChunkProvider { - - if (!completablefuture.isDone()) { // Paper - // Paper start - async chunk io/loading -+ PlayerChunk playerChunk = this.getChunk(ChunkCoordIntPair.pair(x, z)); -+ if (playerChunk != null) { -+ playerChunk.markChunkUrgent(chunkstatus); -+ } - this.world.asyncChunkTaskManager.raisePriority(x, z, com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGHEST_PRIORITY); - com.destroystokyo.paper.io.chunk.ChunkTaskManager.pushChunkWait(this.world, x, z); - // Paper end -@@ -0,0 +0,0 @@ public class ChunkProviderServer extends IChunkProvider { - com.destroystokyo.paper.io.chunk.ChunkTaskManager.popChunkWait(); // Paper - async chunk debug - this.world.timings.syncChunkLoad.stopTiming(); // Paper - } // Paper -+ PlayerChunk playerChunk = this.getChunk(ChunkCoordIntPair.pair(x, z)); -+ if (playerChunk != null) { -+ playerChunk.clearChunkUrgent(); -+ } - ichunkaccess = (IChunkAccess) ((Either) completablefuture.join()).map((ichunkaccess1) -> { - return ichunkaccess1; - }, (playerchunk_failure) -> { -@@ -0,0 +0,0 @@ public class ChunkProviderServer extends IChunkProvider { - } - } - } -+ // Paper start -+ if (playerchunk != null && isUrgent) { -+ playerchunk.markChunkUrgent(chunkstatus); -+ } -+ // Paper end - - return this.a(playerchunk, l) ? PlayerChunk.UNLOADED_CHUNK_ACCESS_FUTURE : playerchunk.a(chunkstatus, this.playerChunkMap); - } -diff --git a/src/main/java/net/minecraft/server/PlayerChunk.java b/src/main/java/net/minecraft/server/PlayerChunk.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/server/PlayerChunk.java -+++ b/src/main/java/net/minecraft/server/PlayerChunk.java -@@ -0,0 +0,0 @@ public class PlayerChunk { - long lastAutoSaveTime; // Paper - incremental autosave - long inactiveTimeStart; // Paper - incremental autosave - -+ // Paper start - Chunk gen/load priority system -+ volatile int chunkPriority = 0; -+ volatile boolean isUrgent = false; -+ final java.util.List urgentNeighbors = new java.util.ArrayList<>(); -+ volatile PlayerChunk rootUrgentOriginator; -+ volatile PlayerChunk urgentOriginator; -+ public void onNeighborRequest(PlayerChunk neighbor, ChunkStatus status) { -+ if (isUrgent && !neighbor.isUrgent && !java.util.Objects.equals(neighbor, rootUrgentOriginator) && !java.util.Objects.equals(neighbor, urgentOriginator)) { -+ synchronized (this.urgentNeighbors) { -+ if (!neighbor.isUrgent) { -+ neighbor.markChunkUrgent(status, this.rootUrgentOriginator, this); -+ this.urgentNeighbors.add(neighbor); -+ } -+ } -+ } -+ } -+ -+ public void onNeighborsDone() { -+ List urgentNeighbors; -+ synchronized (this.urgentNeighbors) { -+ urgentNeighbors = new java.util.ArrayList<>(this.urgentNeighbors); -+ this.urgentNeighbors.clear(); -+ } -+ for (PlayerChunk urgentNeighbor : urgentNeighbors) { -+ if (urgentNeighbor != null) { -+ urgentNeighbor.clearChunkUrgent(this); -+ } -+ } -+ } -+ -+ public void clearChunkUrgent() { -+ clearChunkUrgent(this); -+ } -+ public void clearChunkUrgent(PlayerChunk requester) { -+ if (this.isUrgent && java.util.Objects.equals(requester, this.urgentOriginator)) { -+ this.isUrgent = false; -+ this.urgentOriginator = null; -+ this.rootUrgentOriginator = null; -+ this.onNeighborsDone(); -+ } -+ } -+ -+ public void markChunkUrgent(ChunkStatus targetStatus) { -+ this.markChunkUrgent(targetStatus, this , this); -+ } -+ public void markChunkUrgent(ChunkStatus targetStatus, PlayerChunk rootUrgentOriginator, PlayerChunk urgentOriginator) { -+ if (!this.isUrgent) { -+ this.rootUrgentOriginator = rootUrgentOriginator; -+ this.urgentOriginator = urgentOriginator; -+ this.isUrgent = true; -+ int x = location.x; -+ int z = location.z; -+ IChunkAccess chunk = getAvailableChunkNow(); -+ final ChunkStatus chunkCurrentStatus = chunk == null ? null : chunk.getChunkStatus(); -+ final ChunkStatus completedStatus = this.getChunkHolderStatus(); -+ final ChunkStatus nextStatus = getNextStatus(completedStatus != null ? completedStatus : ChunkStatus.EMPTY); -+ -+ if (chunkCurrentStatus == null || completedStatus == null) { -+ this.chunkMap.world.asyncChunkTaskManager.raisePriority(x, z, com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGHEST_PRIORITY); -+ // next status is empty, empty has no neighbours needing loading -+ return; -+ } -+ -+ if (!targetStatus.isAtLeastStatus(nextStatus)) { -+ // we don't want a status greater-than the one we already have, don't prioritise these loads - they will get in the way -+ return; -+ } -+ -+ // at this point we want a chunk that has a status higher than the one we have already completed -+ -+ // does the next status need neighbours at all? -+ final int requiredNeighbours = nextStatus.getNeighborRadius(); -+ if (requiredNeighbours <= 0) { -+ // no it doesn't, we're done here. we've already prioritised this chunk, no neighbours need prioritising -+ return; -+ } -+ -+ // even though we might want a higher status than targetFinalStatus, we cannot queue neighbours for it - we -+ // instead use the current chunk status in progress (nextCompletedStatus) to ensure we aren't waiting on -+ // unprioritised logic for the next status to complete -+ -+ for (int cx = -requiredNeighbours; cx <= requiredNeighbours; ++cx) { -+ for (int cz = -requiredNeighbours; cz <= requiredNeighbours; ++cz) { -+ if (cx == 0 && cz == 0) { -+ continue; -+ } -+ PlayerChunk neighbor = this.chunkMap.getUpdatingChunk(ChunkCoordIntPair.asLong(x + cz, z + cx)); -+ if (neighbor == null) { -+ continue; -+ } -+ -+ IChunkAccess neighborChunk = neighbor.getAvailableChunkNow(); -+ ChunkStatus neededStatus = this.chunkMap.getNeededStatusByRadius(nextStatus, Math.max(Math.abs(cx), Math.abs(cz))); -+ ChunkStatus neighborCurrentStatus = neighborChunk != null ? neighborChunk.getChunkStatus() : ChunkStatus.EMPTY; -+ if (nextStatus == ChunkStatus.LIGHT || !neighborCurrentStatus.isAtLeastStatus(neededStatus)) { -+ // we don't need to gen neighbours if our current chunk's status has already gone through the gen -+ // light is always an exception, no matter what if we go through light we need its neighbours - the light engine requires them -+ this.onNeighborRequest(neighbor, neededStatus); -+ } -+ } -+ } -+ } -+ } -+ // Paper end -+ - public PlayerChunk(ChunkCoordIntPair chunkcoordintpair, int i, LightEngine lightengine, PlayerChunk.c playerchunk_c, PlayerChunk.d playerchunk_d) { - this.statusFutures = new AtomicReferenceArray(PlayerChunk.CHUNK_STATUSES.size()); - this.fullChunkFuture = PlayerChunk.UNLOADED_CHUNK_FUTURE; -@@ -0,0 +0,0 @@ public class PlayerChunk { - } - return null; - } -+ public static ChunkStatus getNextStatus(ChunkStatus status) { -+ if (status == ChunkStatus.FULL) { -+ return status; -+ } -+ return CHUNK_STATUSES.get(status.getStatusIndex() + 1); -+ } - // Paper end - - public CompletableFuture> getStatusFutureUnchecked(ChunkStatus chunkstatus) { -@@ -0,0 +0,0 @@ public class PlayerChunk { - } - - public int k() { -- return this.n; -+ return Math.max(1, this.n - this.chunkPriority - (isUrgent ? 20 : 0)); // Paper - allow modifying priority, subtracts 20 if urgent - } - - private void d(int i) { -@@ -0,0 +0,0 @@ public class PlayerChunk { - Chunk fullChunk = either.left().get(); - PlayerChunk.this.isFullChunkReady = true; - fullChunk.playerChunk = PlayerChunk.this; -+ this.clearChunkUrgent(); - - - } -diff --git a/src/main/java/net/minecraft/server/PlayerChunkMap.java b/src/main/java/net/minecraft/server/PlayerChunkMap.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/server/PlayerChunkMap.java -+++ b/src/main/java/net/minecraft/server/PlayerChunkMap.java -@@ -0,0 +0,0 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { - List>> list = Lists.newArrayList(); - int j = chunkcoordintpair.x; - int k = chunkcoordintpair.z; -+ PlayerChunk requestingNeighbor = this.requestingNeighbor; // Paper - - for (int l = -i; l <= i; ++l) { - for (int i1 = -i; i1 <= i; ++i1) { -@@ -0,0 +0,0 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { - } - - ChunkStatus chunkstatus = (ChunkStatus) intfunction.apply(j1); -+ if (requestingNeighbor != null) requestingNeighbor.onNeighborRequest(playerchunk, chunkstatus); // Paper - CompletableFuture> completablefuture = playerchunk.a(chunkstatus, this); - - list.add(completablefuture); -@@ -0,0 +0,0 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { - }; - - CompletableFuture chunkSaveFuture = this.world.asyncChunkTaskManager.getChunkSaveFuture(chunkcoordintpair.x, chunkcoordintpair.z); -+ PlayerChunk playerChunk = getUpdatingChunk(chunkcoordintpair.pair()); -+ boolean isBlockingMain = playerChunk != null && playerChunk.isUrgent; -+ int priority = isBlockingMain ? com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGHEST_PRIORITY : com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGH_PRIORITY; - if (chunkSaveFuture != null) { -- this.world.asyncChunkTaskManager.scheduleChunkLoad(chunkcoordintpair.x, chunkcoordintpair.z, -- com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGH_PRIORITY, chunkHolderConsumer, false, chunkSaveFuture); -- this.world.asyncChunkTaskManager.raisePriority(chunkcoordintpair.x, chunkcoordintpair.z, com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGH_PRIORITY); -+ this.world.asyncChunkTaskManager.scheduleChunkLoad(chunkcoordintpair.x, chunkcoordintpair.z, priority, chunkHolderConsumer, isBlockingMain, chunkSaveFuture); - } else { -- this.world.asyncChunkTaskManager.scheduleChunkLoad(chunkcoordintpair.x, chunkcoordintpair.z, -- com.destroystokyo.paper.io.PrioritizedTaskQueue.NORMAL_PRIORITY, chunkHolderConsumer, false); -+ this.world.asyncChunkTaskManager.scheduleChunkLoad(chunkcoordintpair.x, chunkcoordintpair.z, priority, chunkHolderConsumer, isBlockingMain); - } -+ this.world.asyncChunkTaskManager.raisePriority(chunkcoordintpair.x, chunkcoordintpair.z, priority); - return ret; - // Paper end - } - -+ private PlayerChunk requestingNeighbor; // Paper - private CompletableFuture> b(PlayerChunk playerchunk, ChunkStatus chunkstatus) { - ChunkCoordIntPair chunkcoordintpair = playerchunk.i(); -+ PlayerChunk prevNeighbor = requestingNeighbor; // Paper -+ this.requestingNeighbor = playerchunk; // Paper - CompletableFuture, PlayerChunk.Failure>> completablefuture = this.a(chunkcoordintpair, chunkstatus.f(), (i) -> { - return this.a(chunkstatus, i); - }); -+ this.requestingNeighbor = prevNeighbor; // Paper - - this.world.getMethodProfiler().c(() -> { - return "chunkGenerate " + chunkstatus.d(); -@@ -0,0 +0,0 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { - return CompletableFuture.completedFuture(Either.right(playerchunk_failure)); - }); - }, (runnable) -> { -+ playerchunk.onNeighborsDone(); // Paper - this.mailboxWorldGen.a(ChunkTaskQueueSorter.a(playerchunk, runnable)); // CraftBukkit - decompile error - }); - } -@@ -0,0 +0,0 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { - })); - } - -+ public ChunkStatus getNeededStatusByRadius(ChunkStatus chunkstatus, int i) { return a(chunkstatus, i); } // Paper - OBFHELPER - private ChunkStatus a(ChunkStatus chunkstatus, int i) { - ChunkStatus chunkstatus1; - -@@ -0,0 +0,0 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { - - public CompletableFuture> a(PlayerChunk playerchunk) { - ChunkCoordIntPair chunkcoordintpair = playerchunk.i(); -+ PlayerChunk prevNeighbor = this.requestingNeighbor; // Paper -+ this.requestingNeighbor = playerchunk; // Paper - CompletableFuture, PlayerChunk.Failure>> completablefuture = this.a(chunkcoordintpair, 1, (i) -> { - return ChunkStatus.FULL; - }); -+ this.requestingNeighbor = prevNeighbor; // Paper - CompletableFuture> completablefuture1 = completablefuture.thenApplyAsync((either) -> { - return either.flatMap((list) -> { - Chunk chunk = (Chunk) list.get(list.size() / 2); -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 @@ - package org.bukkit.craftbukkit; - -+import com.destroystokyo.paper.io.PrioritizedTaskQueue; - import com.google.common.base.Preconditions; - import com.google.common.collect.ImmutableList; - import com.google.common.collect.ImmutableMap; -@@ -0,0 +0,0 @@ public class CraftWorld implements World { - } - } - -- return this.world.getChunkProvider().getChunkAtAsynchronously(x, z, gen, urgent).thenComposeAsync((either) -> { -+ CompletableFuture future = this.world.getChunkProvider().getChunkAtAsynchronously(x, z, gen, urgent).thenComposeAsync((either) -> { - net.minecraft.server.Chunk chunk = (net.minecraft.server.Chunk) either.left().orElse(null); - return CompletableFuture.completedFuture(chunk == null ? null : chunk.getBukkitChunk()); - }, MinecraftServer.getServer()); -+ if (urgent) { -+ world.asyncChunkTaskManager.raisePriority(x, z, PrioritizedTaskQueue.HIGHEST_PRIORITY); -+ } -+ return future; -+ - } - // Paper end - diff --git a/Spigot-Server-Patches/MC-Utils.patch b/Spigot-Server-Patches/MC-Utils.patch index be8d0831f0..641da437e7 100644 --- a/Spigot-Server-Patches/MC-Utils.patch +++ b/Spigot-Server-Patches/MC-Utils.patch @@ -1132,6 +1132,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + + protected final ChangeCallback addCallback; + protected final ChangeCallback removeCallback; ++ protected final ChangeSourceCallback changeSourceCallback; + + public AreaMap() { + this(new PooledLinkedHashSets<>()); @@ -1143,9 +1144,13 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + + public AreaMap(final PooledLinkedHashSets pooledHashSets, final ChangeCallback addCallback, final ChangeCallback removeCallback) { ++ this(pooledHashSets, addCallback, removeCallback, null); ++ } ++ public AreaMap(final PooledLinkedHashSets pooledHashSets, final ChangeCallback addCallback, final ChangeCallback removeCallback, final ChangeSourceCallback changeSourceCallback) { + this.pooledHashSets = pooledHashSets; + this.addCallback = addCallback; + this.removeCallback = removeCallback; ++ this.changeSourceCallback = changeSourceCallback; + } + + @Nullable @@ -1208,7 +1213,11 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + + // called after the distance map updates -+ protected void updateObjectCallback(final E Object, final long oldPosition, final long newPosition, final int oldViewDistance, final int newViewDistance) {} ++ protected void updateObjectCallback(final E Object, final long oldPosition, final long newPosition, final int oldViewDistance, final int newViewDistance) { ++ if (newPosition != oldPosition && this.changeSourceCallback != null) { ++ this.changeSourceCallback.accept(Object, oldPosition, newPosition); ++ } ++ } + + public final boolean add(final E object, final int chunkX, final int chunkZ, final int viewDistance) { + final int oldViewDistance = this.objectToViewDistance.putIfAbsent(object, viewDistance); @@ -1537,6 +1546,11 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + final PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState); + + } ++ ++ @FunctionalInterface ++ public static interface ChangeSourceCallback { ++ void accept(final E object, final long prevPos, final long newPos); ++ } +} diff --git a/src/main/java/com/destroystokyo/paper/util/misc/DistanceTrackingAreaMap.java b/src/main/java/com/destroystokyo/paper/util/misc/DistanceTrackingAreaMap.java new file mode 100644 @@ -1744,7 +1758,12 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + + public PlayerAreaMap(final PooledLinkedHashSets pooledHashSets, final ChangeCallback addCallback, + final ChangeCallback removeCallback) { -+ super(pooledHashSets, addCallback, removeCallback); ++ this(pooledHashSets, addCallback, removeCallback, null); ++ } ++ ++ public PlayerAreaMap(final PooledLinkedHashSets pooledHashSets, final ChangeCallback addCallback, ++ final ChangeCallback removeCallback, final ChangeSourceCallback changeSourceCallback) { ++ super(pooledHashSets, addCallback, removeCallback, changeSourceCallback); + } + + @Override @@ -3467,6 +3486,20 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + return x < (double)truncated ? truncated - 1 : truncated; + } + ++ public static float normalizeYaw(float f) { ++ float f1 = f % 360.0F; ++ ++ if (f1 >= 180.0F) { ++ f1 -= 360.0F; ++ } ++ ++ if (f1 < -180.0F) { ++ f1 += 360.0F; ++ } ++ ++ return f1; ++ } ++ + public static int fastFloor(float x) { + int truncated = (int)x; + return x < (double)truncated ? truncated - 1 : truncated; diff --git a/Spigot-Server-Patches/Mid-Tick-Chunk-Tasks-Speed-up-processing-of-chunk-lo.patch b/Spigot-Server-Patches/Mid-Tick-Chunk-Tasks-Speed-up-processing-of-chunk-lo.patch index 28fe227204..c7a58d8ba5 100644 --- a/Spigot-Server-Patches/Mid-Tick-Chunk-Tasks-Speed-up-processing-of-chunk-lo.patch +++ b/Spigot-Server-Patches/Mid-Tick-Chunk-Tasks-Speed-up-processing-of-chunk-lo.patch @@ -116,7 +116,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + //noinspection StatementWithEmptyBody + while (pollChunkLoadTasks()) {} + -+ if (System.nanoTime() - lastMidTickChunkTask < 1000000) { ++ if (System.nanoTime() - lastMidTickChunkTask < 200000) { + return; + } + @@ -163,7 +163,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + public int midTickChunksTasksRan = 0; + private long midTickLastRan = 0; + public void midTickLoadChunks() { -+ if (!isMainThread() || System.nanoTime() - midTickLastRan < 250000) { ++ if (!isMainThread() || System.nanoTime() - midTickLastRan < 200000) { + // only check once per 0.25ms incase this code is called in a hot method + return; + } diff --git a/Spigot-Server-Patches/No-Tick-view-distance-implementation.patch b/Spigot-Server-Patches/No-Tick-view-distance-implementation.patch index febb5603be..262358aa24 100644 --- a/Spigot-Server-Patches/No-Tick-view-distance-implementation.patch +++ b/Spigot-Server-Patches/No-Tick-view-distance-implementation.patch @@ -118,6 +118,16 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 --- a/src/main/java/net/minecraft/server/PlayerChunk.java +++ b/src/main/java/net/minecraft/server/PlayerChunk.java @@ -0,0 +0,0 @@ public class PlayerChunk { + // cached here to avoid a map lookup + com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet playersInMobSpawnRange; + com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet playersInChunkTickRange; ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet playersInTickingRange; + + void updateRanges() { + long key = net.minecraft.server.MCUtil.getCoordinateKey(this.location); + this.playersInMobSpawnRange = this.chunkMap.playerMobSpawnMap.getObjectsInRange(key); + this.playersInChunkTickRange = this.chunkMap.playerChunkTickRangeMap.getObjectsInRange(key); ++ this.playersInTickingRange = this.chunkMap.playerViewDistanceTickMap.getObjectsInRange(key); } // Paper end - optimise isOutsideOfRange diff --git a/Spigot-Server-Patches/Optimize-isOutsideRange-to-use-distance-maps.patch b/Spigot-Server-Patches/Optimize-isOutsideRange-to-use-distance-maps.patch index 6162bcdf24..7ffc3b05d7 100644 --- a/Spigot-Server-Patches/Optimize-isOutsideRange-to-use-distance-maps.patch +++ b/Spigot-Server-Patches/Optimize-isOutsideRange-to-use-distance-maps.patch @@ -165,8 +165,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 --- a/src/main/java/net/minecraft/server/PlayerChunk.java +++ b/src/main/java/net/minecraft/server/PlayerChunk.java @@ -0,0 +0,0 @@ public class PlayerChunk { - } - // Paper end + long lastAutoSaveTime; // Paper - incremental autosave + long inactiveTimeStart; // Paper - incremental autosave + // Paper start - optimise isOutsideOfRange + // cached here to avoid a map lookup diff --git a/Spigot-Server-Patches/Use-more-reasonable-thread-count-default-for-bootstr.patch b/Spigot-Server-Patches/Use-more-reasonable-thread-count-default-for-bootstr.patch index b0cee26f93..dd46628827 100644 --- a/Spigot-Server-Patches/Use-more-reasonable-thread-count-default-for-bootstr.patch +++ b/Spigot-Server-Patches/Use-more-reasonable-thread-count-default-for-bootstr.patch @@ -13,7 +13,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 private static ExecutorService k() { - int i = MathHelper.clamp(Runtime.getRuntime().availableProcessors() - 1, 1, 7); -+ int i = Math.min(6, Math.max(Runtime.getRuntime().availableProcessors() - 2, 2)); // Paper - use more reasonable default - 2 is hard minimum to avoid using unlimited threads ++ int i = Math.min(8, Math.max(Runtime.getRuntime().availableProcessors() - 2, 3)); // Paper - use more reasonable default - 2 is hard minimum to avoid using unlimited threads ++ i = Integer.getInteger("Paper.WorkerThreadCount", i); // Paper - allow overriding Object object; if (i <= 0) {