diff --git a/Spigot-Server-Patches/0532-Unload-leaked-Cached-Chunks.patch b/Spigot-Server-Patches/0532-Unload-leaked-Cached-Chunks.patch index 0d6332fe86..72df162a6a 100644 --- a/Spigot-Server-Patches/0532-Unload-leaked-Cached-Chunks.patch +++ b/Spigot-Server-Patches/0532-Unload-leaked-Cached-Chunks.patch @@ -17,8 +17,43 @@ Ticking Chunks timings/profiler results. We will now detect these chunks in that iteration, and automatically add it to the unload queue when the chunk is found without any tickets. +diff --git a/src/main/java/net/minecraft/server/ChunkMapDistance.java b/src/main/java/net/minecraft/server/ChunkMapDistance.java +index 9805361e2d49fa1cfecf0c5811187fc503d0ad8e..91b9946f987a397960a08c7a606fcf4e2607e0ae 100644 +--- a/src/main/java/net/minecraft/server/ChunkMapDistance.java ++++ b/src/main/java/net/minecraft/server/ChunkMapDistance.java +@@ -33,7 +33,7 @@ public abstract class ChunkMapDistance { + public final Long2ObjectOpenHashMap>> tickets = new Long2ObjectOpenHashMap(); + private final ChunkMapDistance.a e = new ChunkMapDistance.a(); + public static final int MOB_SPAWN_RANGE = 8; //private final ChunkMapDistance.b f = new ChunkMapDistance.b(8); // Paper - no longer used +- private final ChunkMapDistance.c g = new ChunkMapDistance.c(33); ++ private final ChunkMapDistance.c g = new ChunkMapDistance.c(33); public final ChunkMapDistance.c getLevelTracker() { return g; } // Paper + // Paper start use a queue, but still keep unique requirement + public final java.util.Queue pendingChunkUpdates = new java.util.ArrayDeque() { + @Override +@@ -471,6 +471,12 @@ public abstract class ChunkMapDistance { + this.e = i; + } + ++ // Paper start - check diff below ++ public boolean isChunkLoaded(long i) { ++ return this.c(this.c(i)); ++ } ++ // Paper end ++ + private void a(long i, int j, boolean flag, boolean flag1) { + if (flag != flag1) { + Ticket ticket = new Ticket<>(TicketType.PLAYER, 33, new ChunkCoordIntPair(i)); // Paper - no-tick view distance +@@ -478,7 +484,7 @@ public abstract class ChunkMapDistance { + if (flag1) { + ChunkMapDistance.this.j.a(ChunkTaskQueueSorter.a(() -> { // CraftBukkit - decompile error + ChunkMapDistance.this.m.execute(() -> { +- if (this.c(this.c(i))) { ++ if (this.c(this.c(i))) { // Paper - diff above isChunkLoaded + ChunkMapDistance.this.addTicket(i, ticket); + ChunkMapDistance.this.l.add(i); + } else { diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java -index 54e89c9cc6c47ff2c4f4dd5d4c22a391f8a3d6e0..1209fbf1c906511954bc61ffb1a8882498855ba4 100644 +index 54e89c9cc6c47ff2c4f4dd5d4c22a391f8a3d6e0..32b6d28cd28d60937eca14187287645ea2797da7 100644 --- a/src/main/java/net/minecraft/server/ChunkProviderServer.java +++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java @@ -560,6 +560,7 @@ public class ChunkProviderServer extends IChunkProvider { @@ -46,7 +81,7 @@ index 54e89c9cc6c47ff2c4f4dd5d4c22a391f8a3d6e0..1209fbf1c906511954bc61ffb1a88824 }); this.world.getMethodProfiler().enter("customSpawners"); if (flag1) { -@@ -913,6 +915,37 @@ public class ChunkProviderServer extends IChunkProvider { +@@ -913,6 +915,40 @@ public class ChunkProviderServer extends IChunkProvider { this.playerChunkMap.g(); } @@ -54,9 +89,8 @@ index 54e89c9cc6c47ff2c4f4dd5d4c22a391f8a3d6e0..1209fbf1c906511954bc61ffb1a88824 + private void checkInactiveChunk(PlayerChunk playerchunk, long time) { + int ticketLevel = playerchunk.getTicketLevel(); + if (ticketLevel > 33 && ticketLevel == playerchunk.oldTicketLevel && -+ (playerchunk.lastActivity == 0 || time - playerchunk.lastActivity > 20*180) && -+ playerchunk.location.pair() % 20 == 0 && playerChunkMap.unloadQueue.size() < 100 && -+ PlayerChunk.getChunkState(ticketLevel) == PlayerChunk.State.INACCESSIBLE ++ (playerchunk.lastActivity == 0 || time - playerchunk.lastActivity > 20*5) && ++ playerchunk.location.pair() % 20 == 0 && playerChunkMap.unloadQueue.size() < 100 + ) { + ChunkStatus chunkHolderStatus = playerchunk.getChunkHolderStatus(); + ChunkStatus desiredStatus = PlayerChunk.getChunkStatus(ticketLevel); @@ -69,12 +103,16 @@ index 54e89c9cc6c47ff2c4f4dd5d4c22a391f8a3d6e0..1209fbf1c906511954bc61ffb1a88824 + } + playerchunk.lastActivity = time; + Chunk chunk = playerchunk.getChunk(); -+ if ((chunk != null && chunk.isAnyNeighborsLoaded()) || !playerchunk.neighborPriorities.isEmpty()) { ++ if ((chunk != null && chunk.isAnyNeighborsLoaded()) || !playerchunk.neighborPriorities.isEmpty() || !playerchunk.dependendedOnBy.isEmpty()) { + return; + } + long key = playerchunk.location.pair(); -+ ArraySetSorted> tickets = playerChunkMap.chunkDistanceManager.tickets.get(key); -+ if (tickets == null || tickets.isEmpty()) { ++ if (playerChunkMap.playerViewDistanceNoTickMap.getObjectsInRange(key) != null) { ++ return; ++ } ++ PlayerChunkMap.a distanceManager = playerChunkMap.chunkDistanceManager; ++ ArraySetSorted> tickets = distanceManager.tickets.get(key); ++ if ((tickets == null || tickets.isEmpty()) && !distanceManager.getLevelTracker().isChunkLoaded(key)) { + playerChunkMap.unloadQueue.add(key); + } + } @@ -85,18 +123,19 @@ index 54e89c9cc6c47ff2c4f4dd5d4c22a391f8a3d6e0..1209fbf1c906511954bc61ffb1a88824 public String getName() { return "ServerChunkCache: " + this.h(); diff --git a/src/main/java/net/minecraft/server/PlayerChunk.java b/src/main/java/net/minecraft/server/PlayerChunk.java -index b8fe42e8123e972b1ec97b048c35d90118076e66..ecac7b72759a3884020b9c19c58d3db3338e0fc3 100644 +index b8fe42e8123e972b1ec97b048c35d90118076e66..f213a08e42db20c511f7d70434b03ee4040676bc 100644 --- a/src/main/java/net/minecraft/server/PlayerChunk.java +++ b/src/main/java/net/minecraft/server/PlayerChunk.java -@@ -44,6 +44,7 @@ public class PlayerChunk { +@@ -44,6 +44,8 @@ public class PlayerChunk { long lastAutoSaveTime; // Paper - incremental autosave long inactiveTimeStart; // Paper - incremental autosave + long lastActivity; // Paper - fix chunk leak ++ java.util.concurrent.ConcurrentHashMap dependendedOnBy = new java.util.concurrent.ConcurrentHashMap<>(); // Paper // Paper start - optimise isOutsideOfRange // cached here to avoid a map lookup -@@ -562,6 +563,7 @@ public class PlayerChunk { +@@ -562,6 +564,7 @@ public class PlayerChunk { protected void a(PlayerChunkMap playerchunkmap) { ChunkStatus chunkstatus = getChunkStatus(this.oldTicketLevel); ChunkStatus chunkstatus1 = getChunkStatus(this.ticketLevel); @@ -105,7 +144,7 @@ index b8fe42e8123e972b1ec97b048c35d90118076e66..ecac7b72759a3884020b9c19c58d3db3 boolean flag1 = this.ticketLevel <= PlayerChunkMap.GOLDEN_TICKET; // Paper - diff on change: (flag1 = new ticket level is in loadable range) PlayerChunk.State playerchunk_state = getChunkState(this.oldTicketLevel); diff --git a/src/main/java/net/minecraft/server/PlayerChunkMap.java b/src/main/java/net/minecraft/server/PlayerChunkMap.java -index 2b09437642ec846d025b226692f2290f9bb5b556..b7d8a7487e303c2c44160ec1093987b3e0d175bc 100644 +index f42507f5a17f9388db738218f58ca76f863274ff..d4bcbf8c1030eee55064b246b25391c0a5201d83 100644 --- a/src/main/java/net/minecraft/server/PlayerChunkMap.java +++ b/src/main/java/net/minecraft/server/PlayerChunkMap.java @@ -639,6 +639,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { @@ -133,6 +172,42 @@ index 2b09437642ec846d025b226692f2290f9bb5b556..b7d8a7487e303c2c44160ec1093987b3 PlayerChunk playerchunk = (PlayerChunk) this.updatingChunks.remove(j); if (playerchunk != null) { +@@ -1147,9 +1151,27 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + return completablefuture.thenComposeAsync((either) -> { + return either.map((list) -> { // Paper - Shut up. + try { ++ // Paper start ++ list.forEach(chunk -> { ++ PlayerChunk updatingChunk = getUpdatingChunk(chunk.getPos().pair()); ++ if (updatingChunk != null) { ++ updatingChunk.dependendedOnBy.put(playerchunk.location, true); ++ updatingChunk.lastActivity = world.getTime(); ++ } ++ }); ++ // Paper end + CompletableFuture> completablefuture1 = chunkstatus.a(this.world, this.chunkGenerator, this.definedStructureManager, this.lightEngine, (ichunkaccess) -> { + return this.c(playerchunk); + }, list); ++ // Paper start ++ completablefuture1.whenComplete((unused, unused2) -> list.forEach(chunk -> { ++ PlayerChunk updatingChunk = getUpdatingChunk(chunk.getPos().pair()); ++ if (updatingChunk != null) { ++ updatingChunk.dependendedOnBy.remove(playerchunk.location); ++ updatingChunk.lastActivity = world.getTime(); ++ } ++ })); ++ // Paper end + + this.worldLoadListener.a(chunkcoordintpair, chunkstatus); + return completablefuture1; +@@ -1167,6 +1189,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + return CompletableFuture.completedFuture(Either.right(playerchunk_failure)); + }); + }, (runnable) -> { ++ playerchunk.lastActivity = world.getTime(); // Paper + this.mailboxWorldGen.a(ChunkTaskQueueSorter.a(playerchunk, runnable)); // CraftBukkit - decompile error + }); + } diff --git a/src/main/java/net/minecraft/server/StructureGenerator.java b/src/main/java/net/minecraft/server/StructureGenerator.java index acfe732af5b9f63fc2f6b78499defabe2e73ee45..25b19346fc1c702cc37275d0ec16abbbfacfb418 100644 --- a/src/main/java/net/minecraft/server/StructureGenerator.java