aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorSpottedleaf <[email protected]>2024-07-17 11:33:13 -0700
committerSpottedleaf <[email protected]>2024-07-17 11:34:26 -0700
commitb653276565ed37042caf47fb7ea4e7a36899b5a3 (patch)
treebca96e4ba943a0d4a0098ebd84a725f278fd9628
parent4efd24b336eaccb4bc1283a5b8f6dac447a3fd0b (diff)
downloadPaper-b653276565ed37042caf47fb7ea4e7a36899b5a3.tar.gz
Paper-b653276565ed37042caf47fb7ea4e7a36899b5a3.zip
Finish chunk tick iteration optimisation port from Moonrise
-rw-r--r--patches/server/0988-Moonrise-optimisation-patches.patch101
-rw-r--r--patches/server/0998-Optional-per-player-mob-spawns.patch12
-rw-r--r--patches/server/1002-Improve-cancelling-PreCreatureSpawnEvent-with-per-pl.patch4
-rw-r--r--patches/server/1036-Write-SavedData-IO-async.patch4
-rw-r--r--patches/unapplied/server/1034-Optimise-chunk-tick-iteration.patch380
5 files changed, 98 insertions, 403 deletions
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<ChunkStatus> 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<net.minecraft.world.level.chunk.LevelChunk> 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<DimensionDataStorage> 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<ChunkResult<ChunkAccess>> 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<ServerChunkCache.ChunkAndHolder> 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<ServerChunkCache.ChunkAndHolder> list;
++ {
++ final ca.spottedleaf.moonrise.common.list.ReferenceList<net.minecraft.server.level.ServerChunkCache.ChunkAndHolder> 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<net.minecraft.server.level.ServerChunkCache.ChunkAndHolder> chunks = (it.unimi.dsi.fastutil.objects.ObjectArrayList<net.minecraft.server.level.ServerChunkCache.ChunkAndHolder>)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<LevelChunk> 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 <[email protected]>
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 <[email protected]>
-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<ServerPlayer, TrackedPlayer[]> 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<ChunkHolder> 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<Runnable> mainThreadExecutor, LightChunkGetter chunkProvider, ChunkGenerator chunkGenerator, ChunkProgressListener worldGenerationProgressListener, ChunkStatusUpdateListener chunkStatusChangeListener, Supplier<DimensionDataStorage> 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<ObjectSet<ServerPlayer>> 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<ServerChunkCache.ChunkAndHolder> 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<LevelChunk> chunkIterator;
-+ if (this.level.paperConfig().entities.spawning.perPlayerMobSpawns) {
-+ chunkIterator = this.tickingChunks.iterator();
-+ } else {
-+ chunkIterator = this.tickingChunks.unsafeIterator();
-+ List<LevelChunk> 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<ServerPlayer> 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<ChunkHolder> 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);