diff options
Diffstat (limited to 'patches/server/1018-Optimise-chunk-tick-iteration.patch')
-rw-r--r-- | patches/server/1018-Optimise-chunk-tick-iteration.patch | 380 |
1 files changed, 380 insertions, 0 deletions
diff --git a/patches/server/1018-Optimise-chunk-tick-iteration.patch b/patches/server/1018-Optimise-chunk-tick-iteration.patch new file mode 100644 index 0000000000..6b17ae655d --- /dev/null +++ b/patches/server/1018-Optimise-chunk-tick-iteration.patch @@ -0,0 +1,380 @@ +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 7fbeebe63f755624b967374072aa2e0565ce8c35..36caf354634d6675a3f1ec6829f4778e1d0623bc 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); |