From b653276565ed37042caf47fb7ea4e7a36899b5a3 Mon Sep 17 00:00:00 2001 From: Spottedleaf Date: Wed, 17 Jul 2024 11:33:13 -0700 Subject: [PATCH] Finish chunk tick iteration optimisation port from Moonrise --- .../0988-Moonrise-optimisation-patches.patch | 101 ++++- .../0998-Optional-per-player-mob-spawns.patch | 12 +- ...ng-PreCreatureSpawnEvent-with-per-pl.patch | 4 +- .../1036-Write-SavedData-IO-async.patch | 4 +- .../1034-Optimise-chunk-tick-iteration.patch | 380 ------------------ 5 files changed, 98 insertions(+), 403 deletions(-) delete mode 100644 patches/unapplied/server/1034-Optimise-chunk-tick-iteration.patch diff --git a/patches/server/0988-Moonrise-optimisation-patches.patch b/patches/server/0988-Moonrise-optimisation-patches.patch index 93f61b991b..b3cb82cd6e 100644 --- a/patches/server/0988-Moonrise-optimisation-patches.patch +++ b/patches/server/0988-Moonrise-optimisation-patches.patch @@ -25105,7 +25105,7 @@ index 3dc1daa3c6a04d3ff1a2353773b465fc380994a2..3575782f13a7f3c52e64dc5046803305 } } diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -index 60f678c26fb5144386d8697ddfd5b6d841563b6f..9aec513f05b10e78579f79ccda6acf285f13222e 100644 +index 60f678c26fb5144386d8697ddfd5b6d841563b6f..17fafa62f21e505f9f77cf486f7f766a404f7c31 100644 --- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java +++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java @@ -46,7 +46,7 @@ import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemp @@ -25117,7 +25117,7 @@ index 60f678c26fb5144386d8697ddfd5b6d841563b6f..9aec513f05b10e78579f79ccda6acf28 public static final org.slf4j.Logger LOGGER = com.mojang.logging.LogUtils.getLogger(); // Paper private static final List CHUNK_STATUSES = ChunkStatus.getStatusList(); -@@ -71,6 +71,61 @@ public class ServerChunkCache extends ChunkSource { +@@ -71,6 +71,62 @@ public class ServerChunkCache extends ChunkSource { private final ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable fullChunks = new ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable<>(); long chunkFutureAwaitCounter; // Paper end @@ -25176,10 +25176,11 @@ index 60f678c26fb5144386d8697ddfd5b6d841563b6f..9aec513f05b10e78579f79ccda6acf28 + return load ? this.syncLoad(chunkX, chunkZ, toStatus) : null; + } + // Paper end - rewrite chunk system ++ private ServerChunkCache.ChunkAndHolder[] iterationCopy; // Paper - chunk tick iteration optimisations public ServerChunkCache(ServerLevel world, LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, StructureTemplateManager structureTemplateManager, Executor workerExecutor, ChunkGenerator chunkGenerator, int viewDistance, int simulationDistance, boolean dsync, ChunkProgressListener worldGenerationProgressListener, ChunkStatusUpdateListener chunkStatusChangeListener, Supplier persistentStateManagerFactory) { this.level = world; -@@ -97,13 +152,7 @@ public class ServerChunkCache extends ChunkSource { +@@ -97,13 +153,7 @@ public class ServerChunkCache extends ChunkSource { } // CraftBukkit end // Paper start @@ -25194,7 +25195,7 @@ index 60f678c26fb5144386d8697ddfd5b6d841563b6f..9aec513f05b10e78579f79ccda6acf28 @Nullable public ChunkAccess getChunkAtImmediately(int x, int z) { -@@ -174,63 +223,25 @@ public class ServerChunkCache extends ChunkSource { +@@ -174,63 +224,25 @@ public class ServerChunkCache extends ChunkSource { @Nullable @Override public ChunkAccess getChunk(int x, int z, ChunkStatus leastStatus, boolean create) { @@ -25210,13 +25211,13 @@ index 60f678c26fb5144386d8697ddfd5b6d841563b6f..9aec513f05b10e78579f79ccda6acf28 - } - // Paper end - Perf: Optimise getChunkAt calls for loaded chunks - ProfilerFiller gameprofilerfiller = this.level.getProfiler(); +- +- gameprofilerfiller.incrementCounter("getChunk"); +- long k = ChunkPos.asLong(x, z); + // Paper start - rewrite chunk system + if (leastStatus == ChunkStatus.FULL) { + final LevelChunk ret = this.fullChunks.get(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(x, z)); -- gameprofilerfiller.incrementCounter("getChunk"); -- long k = ChunkPos.asLong(x, z); -- - for (int l = 0; l < 4; ++l) { - if (k == this.lastChunkPos[l] && leastStatus == this.lastChunkStatus[l]) { - ChunkAccess ichunkaccess = this.lastChunk[l]; @@ -25268,7 +25269,7 @@ index 60f678c26fb5144386d8697ddfd5b6d841563b6f..9aec513f05b10e78579f79ccda6acf28 } private void clearCache() { -@@ -261,56 +272,59 @@ public class ServerChunkCache extends ChunkSource { +@@ -261,56 +273,59 @@ public class ServerChunkCache extends ChunkSource { } private CompletableFuture> getChunkFutureMainThread(int chunkX, int chunkZ, ChunkStatus leastStatus, boolean create) { @@ -25364,7 +25365,7 @@ index 60f678c26fb5144386d8697ddfd5b6d841563b6f..9aec513f05b10e78579f79ccda6acf28 } @Override -@@ -323,16 +337,7 @@ public class ServerChunkCache extends ChunkSource { +@@ -323,16 +338,7 @@ public class ServerChunkCache extends ChunkSource { } public boolean runDistanceManagerUpdates() { // Paper - public @@ -25382,7 +25383,7 @@ index 60f678c26fb5144386d8697ddfd5b6d841563b6f..9aec513f05b10e78579f79ccda6acf28 } // Paper start -@@ -342,13 +347,14 @@ public class ServerChunkCache extends ChunkSource { +@@ -342,13 +348,14 @@ public class ServerChunkCache extends ChunkSource { // Paper end public boolean isPositionTicking(long pos) { @@ -25401,7 +25402,7 @@ index 60f678c26fb5144386d8697ddfd5b6d841563b6f..9aec513f05b10e78579f79ccda6acf28 try (co.aikar.timings.Timing timed = level.timings.chunkSaveData.startTiming()) { // Paper - Timings this.chunkMap.saveAllChunks(flush); } // Paper - Timings -@@ -361,12 +367,7 @@ public class ServerChunkCache extends ChunkSource { +@@ -361,12 +368,7 @@ public class ServerChunkCache extends ChunkSource { } public void close(boolean save) throws IOException { @@ -25415,7 +25416,7 @@ index 60f678c26fb5144386d8697ddfd5b6d841563b6f..9aec513f05b10e78579f79ccda6acf28 } // CraftBukkit start - modelled on below -@@ -394,6 +395,7 @@ public class ServerChunkCache extends ChunkSource { +@@ -394,6 +396,7 @@ public class ServerChunkCache extends ChunkSource { this.level.getProfiler().popPush("chunks"); if (tickChunks) { this.level.timings.chunks.startTiming(); // Paper - timings @@ -25423,7 +25424,7 @@ index 60f678c26fb5144386d8697ddfd5b6d841563b6f..9aec513f05b10e78579f79ccda6acf28 this.tickChunks(); this.level.timings.chunks.stopTiming(); // Paper - timings this.chunkMap.tick(); -@@ -408,6 +410,7 @@ public class ServerChunkCache extends ChunkSource { +@@ -408,6 +411,7 @@ public class ServerChunkCache extends ChunkSource { } private void tickChunks() { @@ -25431,7 +25432,45 @@ index 60f678c26fb5144386d8697ddfd5b6d841563b6f..9aec513f05b10e78579f79ccda6acf28 long i = this.level.getGameTime(); long j = i - this.lastInhabitedUpdate; -@@ -460,14 +463,19 @@ public class ServerChunkCache extends ChunkSource { +@@ -417,18 +421,29 @@ public class ServerChunkCache extends ChunkSource { + + gameprofilerfiller.push("pollingChunks"); + gameprofilerfiller.push("filteringLoadedChunks"); +- List list = Lists.newArrayListWithCapacity(this.chunkMap.size()); +- Iterator iterator = this.chunkMap.getChunks().iterator(); +- if (this.level.getServer().tickRateManager().runsNormally()) this.level.timings.chunkTicks.startTiming(); // Paper ++ // Paper start - chunk tick iteration optimisations ++ List list; ++ { ++ final ca.spottedleaf.moonrise.common.list.ReferenceList tickingChunks = ++ ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel) this.level).moonrise$getTickingChunks(); + +- while (iterator.hasNext()) { +- ChunkHolder playerchunk = (ChunkHolder) iterator.next(); +- LevelChunk chunk = playerchunk.getTickingChunk(); ++ final ServerChunkCache.ChunkAndHolder[] raw = tickingChunks.getRawDataUnchecked(); ++ final int size = tickingChunks.size(); + +- if (chunk != null) { +- list.add(new ServerChunkCache.ChunkAndHolder(chunk, playerchunk)); ++ if (this.iterationCopy == null || this.iterationCopy.length < size) { ++ this.iterationCopy = new ServerChunkCache.ChunkAndHolder[raw.length]; + } ++ System.arraycopy(raw, 0, this.iterationCopy, 0, size); ++ ++ list = it.unimi.dsi.fastutil.objects.ObjectArrayList.wrap( ++ this.iterationCopy, size ++ ); + } ++ // Paper end - chunk tick iteration optimisations ++ Iterator iterator = null; // Paper - chunk tick iteration optimisations ++ if (this.level.getServer().tickRateManager().runsNormally()) this.level.timings.chunkTicks.startTiming(); // Paper ++ ++ // Paper - chunk tick iteration optimisations + + if (this.level.tickRateManager().runsNormally()) { + gameprofilerfiller.popPush("naturalSpawnCount"); +@@ -460,14 +475,19 @@ public class ServerChunkCache extends ChunkSource { LevelChunk chunk1 = chunkproviderserver_a.chunk; ChunkPos chunkcoordintpair = chunk1.getPos(); @@ -25453,7 +25492,35 @@ index 60f678c26fb5144386d8697ddfd5b6d841563b6f..9aec513f05b10e78579f79ccda6acf28 } } } -@@ -493,11 +501,12 @@ public class ServerChunkCache extends ChunkSource { +@@ -482,22 +502,35 @@ public class ServerChunkCache extends ChunkSource { + } + + gameprofilerfiller.popPush("broadcast"); +- list.forEach((chunkproviderserver_a1) -> { +- this.level.timings.broadcastChunkUpdates.startTiming(); // Paper - timing +- chunkproviderserver_a1.holder.broadcastChanges(chunkproviderserver_a1.chunk); +- this.level.timings.broadcastChunkUpdates.stopTiming(); // Paper - timing +- }); ++ // Paper start - chunk tick iteration optimisations ++ this.level.timings.broadcastChunkUpdates.startTiming(); // Paper - timing ++ { ++ final it.unimi.dsi.fastutil.objects.ObjectArrayList chunks = (it.unimi.dsi.fastutil.objects.ObjectArrayList)list; ++ final ServerChunkCache.ChunkAndHolder[] raw = chunks.elements(); ++ final int size = chunks.size(); ++ ++ Objects.checkFromToIndex(0, size, raw.length); ++ for (int idx = 0; idx < size; ++idx) { ++ final ServerChunkCache.ChunkAndHolder holder = raw[idx]; ++ raw[idx] = null; ++ ++ holder.holder().broadcastChanges(holder.chunk()); ++ } ++ } ++ this.level.timings.broadcastChunkUpdates.stopTiming(); // Paper - timing ++ // Paper end - chunk tick iteration optimisations + gameprofilerfiller.pop(); + gameprofilerfiller.pop(); + } } private void getFullChunk(long pos, Consumer chunkConsumer) { @@ -25470,7 +25537,7 @@ index 60f678c26fb5144386d8697ddfd5b6d841563b6f..9aec513f05b10e78579f79ccda6acf28 } -@@ -591,6 +600,12 @@ public class ServerChunkCache extends ChunkSource { +@@ -591,6 +624,12 @@ public class ServerChunkCache extends ChunkSource { this.chunkMap.setServerViewDistance(watchDistance); } @@ -25483,7 +25550,7 @@ index 60f678c26fb5144386d8697ddfd5b6d841563b6f..9aec513f05b10e78579f79ccda6acf28 public void setSimulationDistance(int simulationDistance) { this.distanceManager.updateSimulationDistance(simulationDistance); } -@@ -669,21 +684,19 @@ public class ServerChunkCache extends ChunkSource { +@@ -669,21 +708,19 @@ public class ServerChunkCache extends ChunkSource { @Override // CraftBukkit start - process pending Chunk loadCallback() and unloadCallback() after each run task public boolean pollTask() { diff --git a/patches/server/0998-Optional-per-player-mob-spawns.patch b/patches/server/0998-Optional-per-player-mob-spawns.patch index a5b1729654..b13e13f0b2 100644 --- a/patches/server/0998-Optional-per-player-mob-spawns.patch +++ b/patches/server/0998-Optional-per-player-mob-spawns.patch @@ -37,10 +37,10 @@ index edb36dee707433d4f9419aef6ac6cc0bec5f285e..c282566ee45d79b4fb3297989e054c80 // Paper end diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -index 9aec513f05b10e78579f79ccda6acf285f13222e..96a03770d47c47e07615815bcc3d474d6cd7711b 100644 +index 17fafa62f21e505f9f77cf486f7f766a404f7c31..2ed9a088d0fd56043d161909ce8e0df683a6413a 100644 --- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java +++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -@@ -437,7 +437,19 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon +@@ -449,14 +449,26 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon gameprofilerfiller.popPush("naturalSpawnCount"); this.level.timings.countNaturalMobs.startTiming(); // Paper - timings int k = this.distanceManager.getNaturalSpawnChunkCount(); @@ -61,6 +61,14 @@ index 9aec513f05b10e78579f79ccda6acf285f13222e..96a03770d47c47e07615815bcc3d474d this.level.timings.countNaturalMobs.stopTiming(); // Paper - timings this.lastSpawnState = spawnercreature_d; + gameprofilerfiller.popPush("spawnAndTick"); + boolean flag = this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && !this.level.players().isEmpty(); // CraftBukkit + +- Util.shuffle(list, this.level.random); ++ if (!this.level.paperConfig().entities.spawning.perPlayerMobSpawns) Util.shuffle(list, this.level.random); // Paper - per player mob spawns - do not need this when per-player is enabled + // Paper start - PlayerNaturallySpawnCreaturesEvent + int chunkRange = level.spigotConfig.mobSpawnRange; + chunkRange = (chunkRange > level.spigotConfig.viewDistance) ? (byte) level.spigotConfig.viewDistance : chunkRange; diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java index fd61e10fd3151bf8cc206a93d4ede22a7c681dbf..23ddb7735623e502eda2b7b3029c6597f4b0f9a0 100644 --- a/src/main/java/net/minecraft/server/level/ServerPlayer.java diff --git a/patches/server/1002-Improve-cancelling-PreCreatureSpawnEvent-with-per-pl.patch b/patches/server/1002-Improve-cancelling-PreCreatureSpawnEvent-with-per-pl.patch index e670998b87..f8dc0458f9 100644 --- a/patches/server/1002-Improve-cancelling-PreCreatureSpawnEvent-with-per-pl.patch +++ b/patches/server/1002-Improve-cancelling-PreCreatureSpawnEvent-with-per-pl.patch @@ -37,10 +37,10 @@ index c282566ee45d79b4fb3297989e054c803873a294..dc64227e3b0d7855ef8870935aa437b7 } // Paper end diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -index 96a03770d47c47e07615815bcc3d474d6cd7711b..5f195875564822f422127462fbbeea5df4a4e1cb 100644 +index 2ed9a088d0fd56043d161909ce8e0df683a6413a..cdb9160244cc69acd36ac9afcfe109a15ab2ab58 100644 --- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java +++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -@@ -443,7 +443,17 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon +@@ -455,7 +455,17 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon if ((this.spawnFriendlies || this.spawnEnemies) && this.level.paperConfig().entities.spawning.perPlayerMobSpawns) { // don't count mobs when animals and monsters are disabled // re-set mob counts for (ServerPlayer player : this.level.players) { diff --git a/patches/server/1036-Write-SavedData-IO-async.patch b/patches/server/1036-Write-SavedData-IO-async.patch index 8f31373c74..f92c991a18 100644 --- a/patches/server/1036-Write-SavedData-IO-async.patch +++ b/patches/server/1036-Write-SavedData-IO-async.patch @@ -6,10 +6,10 @@ Subject: [PATCH] Write SavedData IO async Co-Authored-By: Shane Freeder diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -index 5f195875564822f422127462fbbeea5df4a4e1cb..5149f7a5f0ee8620df582dcf26151e5842463cae 100644 +index cdb9160244cc69acd36ac9afcfe109a15ab2ab58..8a0b00d645e4cf2ca96ec7e8ebc6ef726a0ab8b0 100644 --- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java +++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -@@ -368,6 +368,13 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon +@@ -369,6 +369,13 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon public void close(boolean save) throws IOException { ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getChunkTaskScheduler().chunkHolderManager.close(save, true); // Paper - rewrite chunk system diff --git a/patches/unapplied/server/1034-Optimise-chunk-tick-iteration.patch b/patches/unapplied/server/1034-Optimise-chunk-tick-iteration.patch deleted file mode 100644 index bc67b755cd..0000000000 --- a/patches/unapplied/server/1034-Optimise-chunk-tick-iteration.patch +++ /dev/null @@ -1,380 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Sat, 23 Sep 2023 21:36:36 -0700 -Subject: [PATCH] Optimise chunk tick iteration - -When per-player mob spawning is enabled we do not need to randomly -shuffle the chunk list. Additionally, we can use the NearbyPlayers -class to quickly retrieve nearby players instead of possible -searching all players on the server. - -diff --git a/src/main/java/io/papermc/paper/util/player/NearbyPlayers.java b/src/main/java/io/papermc/paper/util/player/NearbyPlayers.java -index c3ce8a42dddd76b7189ad5685b23f9d9f8ccadb3..f164256d59b761264876ca0c85f812d101bfd5de 100644 ---- a/src/main/java/io/papermc/paper/util/player/NearbyPlayers.java -+++ b/src/main/java/io/papermc/paper/util/player/NearbyPlayers.java -@@ -17,7 +17,8 @@ public final class NearbyPlayers { - GENERAL_SMALL, - GENERAL_REALLY_SMALL, - TICK_VIEW_DISTANCE, -- VIEW_DISTANCE; -+ VIEW_DISTANCE, // Paper - optimise chunk iteration -+ SPAWN_RANGE, // Paper - optimise chunk iteration - } - - private static final NearbyMapType[] MOB_TYPES = NearbyMapType.values(); -@@ -26,10 +27,12 @@ public final class NearbyPlayers { - private static final int GENERAL_AREA_VIEW_DISTANCE = 33; - private static final int GENERAL_SMALL_VIEW_DISTANCE = 10; - private static final int GENERAL_REALLY_SMALL_VIEW_DISTANCE = 3; -+ private static final int SPAWN_RANGE_VIEW_DISTANCE = net.minecraft.server.level.DistanceManager.MOB_SPAWN_RANGE; // Paper - optimise chunk iteration - - public static final int GENERAL_AREA_VIEW_DISTANCE_BLOCKS = (GENERAL_AREA_VIEW_DISTANCE << 4); - public static final int GENERAL_SMALL_AREA_VIEW_DISTANCE_BLOCKS = (GENERAL_SMALL_VIEW_DISTANCE << 4); - public static final int GENERAL_REALLY_SMALL_AREA_VIEW_DISTANCE_BLOCKS = (GENERAL_REALLY_SMALL_VIEW_DISTANCE << 4); -+ public static final int SPAWN_RANGE_VIEW_DISTANCE_BLOCKS = (SPAWN_RANGE_VIEW_DISTANCE << 4); // Paper - optimise chunk iteration - - private final ServerLevel world; - private final Reference2ReferenceOpenHashMap players = new Reference2ReferenceOpenHashMap<>(); -@@ -80,6 +83,7 @@ public final class NearbyPlayers { - players[NearbyMapType.GENERAL_REALLY_SMALL.ordinal()].update(chunk.x, chunk.z, GENERAL_REALLY_SMALL_VIEW_DISTANCE); - players[NearbyMapType.TICK_VIEW_DISTANCE.ordinal()].update(chunk.x, chunk.z, ChunkSystem.getTickViewDistance(player)); - players[NearbyMapType.VIEW_DISTANCE.ordinal()].update(chunk.x, chunk.z, ChunkSystem.getLoadViewDistance(player)); -+ players[NearbyMapType.SPAWN_RANGE.ordinal()].update(chunk.x, chunk.z, SPAWN_RANGE_VIEW_DISTANCE); // Paper - optimise chunk iteration - } - - public TrackedChunk getChunk(final ChunkPos pos) { -diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java -index 13d15a135dd0373bef4a5ac9ffb56dbbf53353a0..472b9494f8a34a8ba90d6a2936b0db7530a229ad 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkHolder.java -+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java -@@ -77,11 +77,19 @@ public class ChunkHolder { - - // Paper start - public void onChunkAdd() { -- -+ // Paper start - optimise chunk tick iteration -+ if (this.needsBroadcastChanges()) { -+ this.chunkMap.needsChangeBroadcasting.add(this); -+ } -+ // Paper end - optimise chunk tick iteration - } - - public void onChunkRemove() { -- -+ // Paper start - optimise chunk tick iteration -+ if (this.needsBroadcastChanges()) { -+ this.chunkMap.needsChangeBroadcasting.remove(this); -+ } -+ // Paper end - optimise chunk tick iteration - } - // Paper end - -@@ -216,7 +224,7 @@ public class ChunkHolder { - - if (i < 0 || i >= this.changedBlocksPerSection.length) return; // CraftBukkit - SPIGOT-6086, SPIGOT-6296 - if (this.changedBlocksPerSection[i] == null) { -- this.hasChangedSections = true; -+ this.hasChangedSections = true; this.addToBroadcastMap(); // Paper - optimise chunk tick iteration - this.changedBlocksPerSection[i] = new ShortOpenHashSet(); - } - -@@ -236,6 +244,7 @@ public class ChunkHolder { - int k = this.lightEngine.getMaxLightSection(); - - if (y >= j && y <= k) { -+ this.addToBroadcastMap(); // Paper - optimise chunk tick iteration - int l = y - j; - - if (lightType == LightLayer.SKY) { -@@ -254,9 +263,19 @@ public class ChunkHolder { - this.broadcast(this.getPlayers(onChunkViewEdge), packet); // Paper - rewrite chunk system - } - // Paper end - starlight -+ // Paper start - optimise chunk tick iteration -+ public final boolean needsBroadcastChanges() { -+ return this.hasChangedSections || !this.skyChangedLightSectionFilter.isEmpty() || !this.blockChangedLightSectionFilter.isEmpty(); -+ } -+ -+ private void addToBroadcastMap() { -+ io.papermc.paper.util.TickThread.ensureTickThread(this.chunkMap.level, this.pos, "Asynchronous ChunkHolder update is not allowed"); -+ this.chunkMap.needsChangeBroadcasting.add(this); -+ } -+ // Paper end - optimise chunk tick iteration - - public void broadcastChanges(LevelChunk chunk) { -- if (this.hasChangedSections || !this.skyChangedLightSectionFilter.isEmpty() || !this.blockChangedLightSectionFilter.isEmpty()) { -+ if (this.needsBroadcastChanges()) { // Paper - optimise chunk tick iteration; moved into above, other logic needs to call - Level world = chunk.getLevel(); - List list; - -diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index 48f7997e8a20f5a5a77516cbde990d0aacc2078a..dbe9df1e1973db133f7c8516256697ef7c968137 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkMap.java -+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java -@@ -194,6 +194,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - this.playerEntityTrackerTrackMaps[i].remove(player); - } - // Paper end - use distance map to optimise tracker -+ this.playerMobSpawnMap.remove(player); // Paper - optimise chunk tick iteration - } - - void updateMaps(ServerPlayer player) { -@@ -243,6 +244,10 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - } - public final io.papermc.paper.util.player.NearbyPlayers nearbyPlayers; - // Paper end -+ // Paper start - optimise chunk tick iteration -+ public final it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet needsChangeBroadcasting = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(); -+ public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerMobSpawnMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets); -+ // Paper end - optimise chunk tick iteration - - public ChunkMap(ServerLevel world, LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, StructureTemplateManager structureTemplateManager, Executor executor, BlockableEventLoop mainThreadExecutor, LightChunkGetter chunkProvider, ChunkGenerator chunkGenerator, ChunkProgressListener worldGenerationProgressListener, ChunkStatusUpdateListener chunkStatusChangeListener, Supplier persistentStateManagerFactory, int viewDistance, boolean dsync) { - super(new RegionStorageInfo(session.getLevelId(), world.dimension(), "chunk"), session.getDimensionPath(world.dimension()).resolve("region"), dataFixer, dsync); -@@ -411,7 +416,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - } - // Paper end - Optional per player mob spawns - -- private static double euclideanDistanceSquared(ChunkPos pos, Entity entity) { -+ public static double euclideanDistanceSquared(ChunkPos pos, Entity entity) { // Paper - optimise chunk iteration; public - double d0 = (double) SectionPos.sectionToBlockCoord(pos.x, 8); - double d1 = (double) SectionPos.sectionToBlockCoord(pos.z, 8); - double d2 = d0 - entity.getX(); -diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java -index ed5154e41ca858f4d6b4d1c276c66831c038d2a6..cdb3c2cde5d9133ef60cf96d91762e6a7c8aeb4a 100644 ---- a/src/main/java/net/minecraft/server/level/DistanceManager.java -+++ b/src/main/java/net/minecraft/server/level/DistanceManager.java -@@ -49,7 +49,7 @@ public abstract class DistanceManager { - private static final int INITIAL_TICKET_LIST_CAPACITY = 4; - final Long2ObjectMap> playersPerChunk = new Long2ObjectOpenHashMap(); - // Paper - rewrite chunk system -- private final DistanceManager.FixedPlayerDistanceChunkTracker naturalSpawnChunkCounter = new DistanceManager.FixedPlayerDistanceChunkTracker(8); -+ public static final int MOB_SPAWN_RANGE = 8; //private final DistanceManager.FixedPlayerDistanceChunkTracker naturalSpawnChunkCounter = new DistanceManager.FixedPlayerDistanceChunkTracker(8); // Paper - optimise chunk tick iteration - // Paper - rewrite chunk system - private final ChunkMap chunkMap; // Paper - -@@ -135,7 +135,7 @@ public abstract class DistanceManager { - long i = chunkcoordintpair.toLong(); - - // Paper - no longer used -- this.naturalSpawnChunkCounter.update(i, 0, true); -+ //this.naturalSpawnChunkCounter.update(i, 0, true); // Paper - optimise chunk tick iteration - //this.playerTicketManager.update(i, 0, true); // Paper - no longer used - //this.tickingTicketsTracker.addTicket(TicketType.PLAYER, chunkcoordintpair, this.getPlayerTicketLevel(), chunkcoordintpair); // Paper - no longer used - } -@@ -149,7 +149,7 @@ public abstract class DistanceManager { - if (objectset != null) objectset.remove(player); // Paper - some state corruption happens here, don't crash, clean up gracefully - if (objectset == null || objectset.isEmpty()) { // Paper - this.playersPerChunk.remove(i); -- this.naturalSpawnChunkCounter.update(i, Integer.MAX_VALUE, false); -+ //this.naturalSpawnChunkCounter.update(i, Integer.MAX_VALUE, false); // Paper - optimise chunk tick iteration - //this.playerTicketManager.update(i, Integer.MAX_VALUE, false); // Paper - no longer used - //this.tickingTicketsTracker.removeTicket(TicketType.PLAYER, chunkcoordintpair, this.getPlayerTicketLevel(), chunkcoordintpair); // Paper - no longer used - } -@@ -191,13 +191,11 @@ public abstract class DistanceManager { - } - - public int getNaturalSpawnChunkCount() { -- this.naturalSpawnChunkCounter.runAllUpdates(); -- return this.naturalSpawnChunkCounter.chunks.size(); -+ return this.chunkMap.playerMobSpawnMap.size(); // Paper - optimise chunk tick iteration - } - - public boolean hasPlayersNearby(long chunkPos) { -- this.naturalSpawnChunkCounter.runAllUpdates(); -- return this.naturalSpawnChunkCounter.chunks.containsKey(chunkPos); -+ return this.chunkMap.playerMobSpawnMap.getObjectsInRange(chunkPos) != null; // Paper - optimise chunk tick iteration - } - - public String getDebugStatus() { -diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -index 01577dbb16ff6abc9edcf897f7fadaad14350184..e2c67c011503c9c37b9d637c0268717baac13e32 100644 ---- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java -+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -@@ -499,18 +499,10 @@ public class ServerChunkCache extends ChunkSource { - - gameprofilerfiller.push("pollingChunks"); - gameprofilerfiller.push("filteringLoadedChunks"); -- List list = Lists.newArrayListWithCapacity(this.chunkMap.size()); -- Iterator iterator = this.chunkMap.getChunks().iterator(); -+ // Paper - optimise chunk tick iteration - if (this.level.getServer().tickRateManager().runsNormally()) this.level.timings.chunkTicks.startTiming(); // Paper - -- while (iterator.hasNext()) { -- ChunkHolder playerchunk = (ChunkHolder) iterator.next(); -- LevelChunk chunk = playerchunk.getTickingChunk(); -- -- if (chunk != null) { -- list.add(new ServerChunkCache.ChunkAndHolder(chunk, playerchunk)); -- } -- } -+ // Paper - optimise chunk tick iteration - - if (this.level.tickRateManager().runsNormally()) { - gameprofilerfiller.popPush("naturalSpawnCount"); -@@ -545,38 +537,109 @@ public class ServerChunkCache extends ChunkSource { - gameprofilerfiller.popPush("spawnAndTick"); - boolean flag = this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && !this.level.players().isEmpty(); // CraftBukkit - -- Util.shuffle(list, this.level.random); -- // Paper start - PlayerNaturallySpawnCreaturesEvent -- int chunkRange = level.spigotConfig.mobSpawnRange; -- chunkRange = (chunkRange > level.spigotConfig.viewDistance) ? (byte) level.spigotConfig.viewDistance : chunkRange; -- chunkRange = Math.min(chunkRange, 8); -- for (ServerPlayer entityPlayer : this.level.players()) { -- entityPlayer.playerNaturallySpawnedEvent = new com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent(entityPlayer.getBukkitEntity(), (byte) chunkRange); -- entityPlayer.playerNaturallySpawnedEvent.callEvent(); -+ // Paper start - optimise chunk tick iteration -+ ChunkMap playerChunkMap = this.chunkMap; -+ for (ServerPlayer player : this.level.players) { -+ if (!player.affectsSpawning || player.isSpectator()) { -+ playerChunkMap.playerMobSpawnMap.remove(player); -+ player.playerNaturallySpawnedEvent = null; -+ player.lastEntitySpawnRadiusSquared = -1.0; -+ continue; -+ } -+ -+ int viewDistance = io.papermc.paper.chunk.system.ChunkSystem.getTickViewDistance(player); -+ -+ // copied and modified from isOutisdeRange -+ int chunkRange = (int)level.spigotConfig.mobSpawnRange; -+ chunkRange = (chunkRange > viewDistance) ? viewDistance : chunkRange; -+ chunkRange = (chunkRange > DistanceManager.MOB_SPAWN_RANGE) ? DistanceManager.MOB_SPAWN_RANGE : chunkRange; -+ -+ com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent event = new com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent(player.getBukkitEntity(), (byte)chunkRange); -+ event.callEvent(); -+ if (event.isCancelled() || event.getSpawnRadius() < 0) { -+ playerChunkMap.playerMobSpawnMap.remove(player); -+ player.playerNaturallySpawnedEvent = null; -+ player.lastEntitySpawnRadiusSquared = -1.0; -+ continue; -+ } -+ -+ int range = Math.min(event.getSpawnRadius(), DistanceManager.MOB_SPAWN_RANGE); // limit to max spawn range -+ int chunkX = io.papermc.paper.util.CoordinateUtils.getChunkCoordinate(player.getX()); -+ int chunkZ = io.papermc.paper.util.CoordinateUtils.getChunkCoordinate(player.getZ()); -+ -+ playerChunkMap.playerMobSpawnMap.addOrUpdate(player, chunkX, chunkZ, range); -+ player.lastEntitySpawnRadiusSquared = (double)((range << 4) * (range << 4)); // used in anyPlayerCloseEnoughForSpawning -+ player.playerNaturallySpawnedEvent = event; - } -- // Paper end - PlayerNaturallySpawnCreaturesEvent -+ // Paper end - optimise chunk tick iteration - int l = this.level.getGameRules().getInt(GameRules.RULE_RANDOMTICKING); - boolean flag1 = this.level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) != 0L && this.level.getLevelData().getGameTime() % this.level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) == 0L; // CraftBukkit -- Iterator iterator1 = list.iterator(); -+ // Paper - optimise chunk tick iteration - - int chunksTicked = 0; // Paper -- while (iterator1.hasNext()) { -- ServerChunkCache.ChunkAndHolder chunkproviderserver_a = (ServerChunkCache.ChunkAndHolder) iterator1.next(); -- LevelChunk chunk1 = chunkproviderserver_a.chunk; -+ // Paper start - optimise chunk tick iteration -+ io.papermc.paper.util.player.NearbyPlayers nearbyPlayers = this.chunkMap.getNearbyPlayers(); // Paper - optimise chunk tick iteration -+ Iterator chunkIterator; -+ if (this.level.paperConfig().entities.spawning.perPlayerMobSpawns) { -+ chunkIterator = this.tickingChunks.iterator(); -+ } else { -+ chunkIterator = this.tickingChunks.unsafeIterator(); -+ List shuffled = Lists.newArrayListWithCapacity(this.tickingChunks.size()); -+ while (chunkIterator.hasNext()) { -+ shuffled.add(chunkIterator.next()); -+ } -+ Util.shuffle(shuffled, this.level.random); -+ chunkIterator = shuffled.iterator(); -+ } -+ try { -+ // Paper end - optimise chunk tick iteration -+ while (chunkIterator.hasNext()) { -+ LevelChunk chunk1 = chunkIterator.next(); -+ // Paper end - optimise chunk tick iteration - ChunkPos chunkcoordintpair = chunk1.getPos(); - -- if (this.level.isNaturalSpawningAllowed(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(chunkcoordintpair)) { -+ // Paper start - optimise chunk tick iteration -+ com.destroystokyo.paper.util.maplist.ReferenceList playersNearby -+ = nearbyPlayers.getPlayers(chunkcoordintpair, io.papermc.paper.util.player.NearbyPlayers.NearbyMapType.SPAWN_RANGE); -+ if (playersNearby == null) { -+ continue; -+ } -+ Object[] rawData = playersNearby.getRawData(); -+ boolean spawn = false; -+ boolean tick = false; -+ for (int itr = 0, len = playersNearby.size(); itr < len; ++itr) { -+ ServerPlayer player = (ServerPlayer)rawData[itr]; -+ if (player.isSpectator()) { -+ continue; -+ } -+ -+ double distance = ChunkMap.euclideanDistanceSquared(chunkcoordintpair, player); -+ spawn |= player.lastEntitySpawnRadiusSquared >= distance; -+ tick |= ((double)io.papermc.paper.util.player.NearbyPlayers.SPAWN_RANGE_VIEW_DISTANCE_BLOCKS) * ((double)io.papermc.paper.util.player.NearbyPlayers.SPAWN_RANGE_VIEW_DISTANCE_BLOCKS) >= distance; -+ if (spawn & tick) { -+ break; -+ } -+ } -+ if (tick && chunk1.chunkStatus.isOrAfter(net.minecraft.server.level.FullChunkStatus.ENTITY_TICKING)) { -+ // Paper end - optimise chunk tick iteration - chunk1.incrementInhabitedTime(j); -- if (flag && (this.spawnEnemies || this.spawnFriendlies) && this.level.getWorldBorder().isWithinBounds(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(chunkcoordintpair, true)) { // Spigot -+ if (spawn && flag && (this.spawnEnemies || this.spawnFriendlies) && this.level.getWorldBorder().isWithinBounds(chunkcoordintpair)) { // Spigot // Paper - optimise chunk tick iteration - NaturalSpawner.spawnForChunk(this.level, chunk1, spawnercreature_d, this.spawnFriendlies, this.spawnEnemies, flag1); - } - -- if (this.level.shouldTickBlocksAt(chunkcoordintpair.toLong())) { -+ if (true || this.level.shouldTickBlocksAt(chunkcoordintpair.toLong())) { // Paper - optimise chunk tick iteration - this.level.tickChunk(chunk1, l); - if ((chunksTicked++ & 1) == 0) net.minecraft.server.MinecraftServer.getServer().executeMidTickTasks(); // Paper - } - } - } -+ // Paper start - optimise chunk tick iteration -+ } finally { -+ if (chunkIterator instanceof io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet.Iterator safeIterator) { -+ safeIterator.finishedIterating(); -+ } -+ } -+ // Paper end - optimise chunk tick iteration - this.level.timings.chunkTicks.stopTiming(); // Paper - - gameprofilerfiller.popPush("customSpawners"); -@@ -588,11 +651,23 @@ public class ServerChunkCache extends ChunkSource { - } - - gameprofilerfiller.popPush("broadcast"); -- list.forEach((chunkproviderserver_a1) -> { -+ // Paper - optimise chunk tick iteration - this.level.timings.broadcastChunkUpdates.startTiming(); // Paper - timing -- chunkproviderserver_a1.holder.broadcastChanges(chunkproviderserver_a1.chunk); -+ // Paper start - optimise chunk tick iteration -+ if (!this.chunkMap.needsChangeBroadcasting.isEmpty()) { -+ it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet copy = this.chunkMap.needsChangeBroadcasting.clone(); -+ this.chunkMap.needsChangeBroadcasting.clear(); -+ for (ChunkHolder holder : copy) { -+ holder.broadcastChanges(holder.getFullChunkNowUnchecked()); // LevelChunks are NEVER unloaded -+ if (holder.needsBroadcastChanges()) { -+ // I DON'T want to KNOW what DUMB plugins might be doing. -+ this.chunkMap.needsChangeBroadcasting.add(holder); -+ } -+ } -+ } -+ // Paper end - optimise chunk tick iteration - this.level.timings.broadcastChunkUpdates.stopTiming(); // Paper - timing -- }); -+ // Paper - optimise chunk tick iteration - gameprofilerfiller.pop(); - gameprofilerfiller.pop(); - } -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 09a80b6816e50ea0d733725c59164520136308d6..6a4637eef14cbd84bbe26ef16f004b8f93367a3d 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -342,6 +342,9 @@ public class ServerPlayer extends Player { - }); - } - // Paper end - replace player chunk loader -+ // Paper start - optimise chunk tick iteration -+ public double lastEntitySpawnRadiusSquared = -1.0; -+ // Paper end - optimise chunk tick iteration - - public ServerPlayer(MinecraftServer server, ServerLevel world, GameProfile profile, ClientInformation clientOptions) { - super(world, world.getSharedSpawnPos(), world.getSharedSpawnAngle(), profile);