aboutsummaryrefslogtreecommitdiffhomepage
path: root/patches/server/0433-Optimize-anyPlayerCloseEnoughForSpawning-to-use-dist.patch
blob: 6630f7f50a5be47f27f3feff0fa0e0fe59616acb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Tue, 5 May 2020 20:40:53 -0700
Subject: [PATCH] Optimize anyPlayerCloseEnoughForSpawning to use distance maps

Use a distance map to find the players in range quickly

diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java
index 674a6492e2eb10cfd29989643eedcea08b2d5218..dff475b327d6edaa4dcb9bc09029237f8f659846 100644
--- a/src/main/java/net/minecraft/server/level/ChunkHolder.java
+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java
@@ -76,6 +76,23 @@ public class ChunkHolder {
     boolean isUpdateQueued = false; // Paper
     private final ChunkMap chunkMap; // Paper
 
+    // Paper start - optimise anyPlayerCloseEnoughForSpawning
+    // cached here to avoid a map lookup
+    com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> playersInMobSpawnRange;
+    com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> playersInChunkTickRange;
+
+    void onChunkAdd() {
+        long key = net.minecraft.server.MCUtil.getCoordinateKey(this.pos);
+        this.playersInMobSpawnRange = this.chunkMap.playerMobSpawnMap.getObjectsInRange(key);
+        this.playersInChunkTickRange = this.chunkMap.playerChunkTickRangeMap.getObjectsInRange(key);
+    }
+
+    void onChunkRemove() {
+        this.playersInMobSpawnRange = null;
+        this.playersInChunkTickRange = null;
+    }
+    // Paper end - optimise anyPlayerCloseEnoughForSpawning
+
     public ChunkHolder(ChunkPos pos, int level, LevelHeightAccessor world, LevelLightEngine lightingProvider, ChunkHolder.LevelChangeListener levelUpdateListener, ChunkHolder.PlayerProvider playersWatchingChunkProvider) {
         this.futures = new AtomicReferenceArray(ChunkHolder.CHUNK_STATUSES.size());
         this.fullChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE;
@@ -97,6 +114,7 @@ public class ChunkHolder {
         this.setTicketLevel(level);
         this.changedBlocksPerSection = new ShortSet[world.getSectionsCount()];
         this.chunkMap = (ChunkMap)playersWatchingChunkProvider; // Paper
+        this.onChunkAdd(); // Paper - optimise anyPlayerCloseEnoughForSpawning
     }
 
     // Paper start
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
index d5e037385263cb094cbbf15823e38e81acafc7d7..0959adc5ec64d1939fc212151d5e0df073ebc312 100644
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
@@ -181,11 +181,23 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
     final CallbackExecutor chunkLoadConversionCallbackExecutor = new CallbackExecutor(); // Paper
     // Paper start - distance maps
     private final com.destroystokyo.paper.util.misc.PooledLinkedHashSets<ServerPlayer> pooledLinkedPlayerHashSets = new com.destroystokyo.paper.util.misc.PooledLinkedHashSets<>();
+    // Paper start - optimise ChunkMap#anyPlayerCloseEnoughForSpawning
+    // A note about the naming used here:
+    // Previously, mojang used a "spawn range" of 8 for controlling both ticking and
+    // mob spawn range. However, spigot makes the spawn range configurable by
+    // checking if the chunk is in the tick range (8) and the spawn range
+    // obviously this means a spawn range > 8 cannot be implemented
+
+    // these maps are named after spigot's uses
+    public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerMobSpawnMap; // this map is absent from updateMaps since it's controlled at the start of the chunkproviderserver tick
+    public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerChunkTickRangeMap;
+    // Paper end - optimise ChunkMap#anyPlayerCloseEnoughForSpawning
 
     void addPlayerToDistanceMaps(ServerPlayer player) {
         int chunkX = MCUtil.getChunkCoordinate(player.getX());
         int chunkZ = MCUtil.getChunkCoordinate(player.getZ());
         // Note: players need to be explicitly added to distance maps before they can be updated
+        this.playerChunkTickRangeMap.add(player, chunkX, chunkZ, DistanceManager.MOB_SPAWN_RANGE); // Paper - optimise ChunkMap#anyPlayerCloseEnoughForSpawning
         // Paper start - per player mob spawning
         if (this.playerMobDistanceMap != null) {
             this.playerMobDistanceMap.add(player, chunkX, chunkZ, this.distanceManager.getSimulationDistance());
@@ -195,6 +207,10 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
 
     void removePlayerFromDistanceMaps(ServerPlayer player) {
 
+        // Paper start - optimise ChunkMap#anyPlayerCloseEnoughForSpawning
+        this.playerMobSpawnMap.remove(player);
+        this.playerChunkTickRangeMap.remove(player);
+        // Paper end - optimise ChunkMap#anyPlayerCloseEnoughForSpawning
         // Paper start - per player mob spawning
         if (this.playerMobDistanceMap != null) {
             this.playerMobDistanceMap.remove(player);
@@ -206,6 +222,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
         int chunkX = MCUtil.getChunkCoordinate(player.getX());
         int chunkZ = MCUtil.getChunkCoordinate(player.getZ());
         // Note: players need to be explicitly added to distance maps before they can be updated
+        this.playerChunkTickRangeMap.update(player, chunkX, chunkZ, DistanceManager.MOB_SPAWN_RANGE); // Paper - optimise ChunkMap#anyPlayerCloseEnoughForSpawning
         // Paper start - per player mob spawning
         if (this.playerMobDistanceMap != null) {
             this.playerMobDistanceMap.update(player, chunkX, chunkZ, this.distanceManager.getSimulationDistance());
@@ -286,6 +303,38 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
         this.regionManagers.add(this.dataRegionManager);
         // Paper end
         this.playerMobDistanceMap = this.level.paperConfig.perPlayerMobSpawns ? new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets) : null; // Paper
+        // Paper start - optimise ChunkMap#anyPlayerCloseEnoughForSpawning
+        this.playerChunkTickRangeMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets,
+            (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ,
+             com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> newState) -> {
+                ChunkHolder playerChunk = ChunkMap.this.getUpdatingChunkIfPresent(MCUtil.getCoordinateKey(rangeX, rangeZ));
+                if (playerChunk != null) {
+                    playerChunk.playersInChunkTickRange = newState;
+                }
+            },
+            (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ,
+             com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> newState) -> {
+                ChunkHolder playerChunk = ChunkMap.this.getUpdatingChunkIfPresent(MCUtil.getCoordinateKey(rangeX, rangeZ));
+                if (playerChunk != null) {
+                    playerChunk.playersInChunkTickRange = newState;
+                }
+            });
+        this.playerMobSpawnMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets,
+            (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ,
+             com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> newState) -> {
+                ChunkHolder playerChunk = ChunkMap.this.getUpdatingChunkIfPresent(MCUtil.getCoordinateKey(rangeX, rangeZ));
+                if (playerChunk != null) {
+                    playerChunk.playersInMobSpawnRange = newState;
+                }
+            },
+            (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ,
+             com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> newState) -> {
+                ChunkHolder playerChunk = ChunkMap.this.getUpdatingChunkIfPresent(MCUtil.getCoordinateKey(rangeX, rangeZ));
+                if (playerChunk != null) {
+                    playerChunk.playersInMobSpawnRange = newState;
+                }
+            });
+        // Paper end - optimise ChunkMap#anyPlayerCloseEnoughForSpawning
     }
 
     protected ChunkGenerator generator() {
@@ -537,6 +586,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
                 holder = (ChunkHolder) this.pendingUnloads.remove(pos);
                 if (holder != null) {
                     holder.setTicketLevel(level);
+                    holder.onChunkAdd(); // Paper - optimise anyPlayerCloseEnoughForSpawning - PUT HERE AFTER RE-ADDING ONLY
                 } else {
                     holder = new ChunkHolder(new ChunkPos(pos), level, this.level, this.lightEngine, this.queueSorter, this);
                     // Paper start
@@ -640,6 +690,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
             ChunkHolder playerchunk = (ChunkHolder) this.updatingChunkMap.remove(j);
 
             if (playerchunk != null) {
+                playerchunk.onChunkRemove(); // Paper
                 this.pendingUnloads.put(j, playerchunk);
                 this.modified = true;
                 this.scheduleUnload(j, playerchunk); // Paper - Move up - don't leak chunks
@@ -1386,43 +1437,48 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
         return this.anyPlayerCloseEnoughForSpawning(pos, false);
     }
 
-    boolean anyPlayerCloseEnoughForSpawning(ChunkPos chunkcoordintpair, boolean reducedRange) {
-        int chunkRange = level.spigotConfig.mobSpawnRange;
-        chunkRange = (chunkRange > level.spigotConfig.viewDistance) ? (byte) level.spigotConfig.viewDistance : chunkRange;
-        chunkRange = (chunkRange > 8) ? 8 : chunkRange;
-
-        final int finalChunkRange = chunkRange; // Paper for lambda below
-        //double blockRange = (reducedRange) ? Math.pow(chunkRange << 4, 2) : 16384.0D; // Paper - use from event
-        double blockRange = 16384.0D; // Paper
-        // Spigot end
-        long i = chunkcoordintpair.toLong();
+    // Paper start - optimise anyPlayerCloseEnoughForSpawning
+    final boolean anyPlayerCloseEnoughForSpawning(ChunkPos chunkcoordintpair, boolean reducedRange) {
+        return this.anyPlayerCloseEnoughForSpawning(this.getUpdatingChunkIfPresent(chunkcoordintpair.toLong()), chunkcoordintpair, reducedRange);
+    }
 
-        if (!this.distanceManager.hasPlayersNearby(i)) {
+    final boolean anyPlayerCloseEnoughForSpawning(ChunkHolder playerchunk, ChunkPos chunkcoordintpair, boolean reducedRange) {
+        // this function is so hot that removing the map lookup call can have an order of magnitude impact on its performance
+        // tested and confirmed via System.nanoTime()
+        com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> playersInRange = reducedRange ? playerchunk.playersInMobSpawnRange : playerchunk.playersInChunkTickRange;
+        if (playersInRange == null) {
             return false;
-        } else {
-            Iterator iterator = this.playerMap.getPlayers(i).iterator();
-
-            ServerPlayer entityplayer;
+        }
+        Object[] backingSet = playersInRange.getBackingSet();
 
-            do {
-                if (!iterator.hasNext()) {
-                    return false;
+        if (reducedRange) {
+            for (int i = 0, len = backingSet.length; i < len; ++i) {
+                Object raw = backingSet[i];
+                if (!(raw instanceof ServerPlayer player)) {
+                    continue;
                 }
-
-                entityplayer = (ServerPlayer) iterator.next();
-                // Paper start - add PlayerNaturallySpawnCreaturesEvent
-                com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent event;
-                blockRange = 16384.0D;
-                if (reducedRange) {
-                    event = entityplayer.playerNaturallySpawnedEvent;
-                    if (event == null || event.isCancelled()) return false;
-                    blockRange = (double) ((event.getSpawnRadius() << 4) * (event.getSpawnRadius() << 4));
+                // don't check spectator and whatnot, already handled by mob spawn map update
+                if (euclideanDistanceSquared(chunkcoordintpair, player) < player.lastEntitySpawnRadiusSquared) {
+                    return true; // in range
                 }
-                // Paper end
-            } while (!this.playerIsCloseEnoughForSpawning(entityplayer, chunkcoordintpair, blockRange)); // Spigot
-
-            return true;
+            }
+        } else {
+            final double range = (DistanceManager.MOB_SPAWN_RANGE * 16) * (DistanceManager.MOB_SPAWN_RANGE * 16);
+            // before spigot, mob spawn range was actually mob spawn range + tick range, but it was split
+            for (int i = 0, len = backingSet.length; i < len; ++i) {
+                Object raw = backingSet[i];
+                if (!(raw instanceof ServerPlayer player)) {
+                    continue;
+                }
+                // don't check spectator and whatnot, already handled by mob spawn map update
+                if (euclideanDistanceSquared(chunkcoordintpair, player) < range) {
+                    return true; // in range
+                }
+            }
         }
+        // no players in range
+        return false;
+        // Paper end - optimise anyPlayerCloseEnoughForSpawning
     }
 
     public List<ServerPlayer> getPlayersCloseForSpawning(ChunkPos pos) {
diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java
index 4a9d0fca55b71f817defcb4286154c0a47bede03..62118348d6fb00f063507debb488e1ff431d139c 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 {
     final Long2ObjectMap<ObjectSet<ServerPlayer>> playersPerChunk = new Long2ObjectOpenHashMap();
     public final Long2ObjectOpenHashMap<SortedArraySet<Ticket<?>>> tickets = new Long2ObjectOpenHashMap();
     private final DistanceManager.ChunkTicketTracker ticketTracker = new DistanceManager.ChunkTicketTracker();
-    private final DistanceManager.FixedPlayerDistanceChunkTracker naturalSpawnChunkCounter = new DistanceManager.FixedPlayerDistanceChunkTracker(8);
+    public static final int MOB_SPAWN_RANGE = 8; // private final ChunkMapDistance.b f = new ChunkMapDistance.b(8); // Paper - no longer used
     private final TickingTracker tickingTicketsTracker = new TickingTracker();
     private final DistanceManager.PlayerTicketTracker playerTicketManager = new DistanceManager.PlayerTicketTracker(33);
     // Paper start use a queue, but still keep unique requirement
@@ -140,7 +140,7 @@ public abstract class DistanceManager {
     protected abstract ChunkHolder updateChunkScheduling(long pos, int level, @Nullable ChunkHolder holder, int k);
 
     public boolean runAllUpdates(ChunkMap chunkStorage) {
-        this.naturalSpawnChunkCounter.runAllUpdates();
+        //this.f.a(); // Paper - no longer used
         this.tickingTicketsTracker.runAllUpdates();
         this.playerTicketManager.runAllUpdates();
         int i = Integer.MAX_VALUE - this.ticketTracker.runDistanceUpdates(Integer.MAX_VALUE);
@@ -289,7 +289,7 @@ public abstract class DistanceManager {
         ((ObjectSet) this.playersPerChunk.computeIfAbsent(i, (j) -> {
             return new ObjectOpenHashSet();
         })).add(player);
-        this.naturalSpawnChunkCounter.update(i, 0, true);
+        //this.f.update(i, 0, true); // Paper - no longer used
         this.playerTicketManager.update(i, 0, true);
         this.tickingTicketsTracker.addTicket(TicketType.PLAYER, chunkcoordintpair, this.getPlayerTicketLevel(), chunkcoordintpair);
     }
@@ -303,7 +303,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.f.update(i, Integer.MAX_VALUE, false); // Paper - no longer used
             this.playerTicketManager.update(i, Integer.MAX_VALUE, false);
             this.tickingTicketsTracker.removeTicket(TicketType.PLAYER, chunkcoordintpair, this.getPlayerTicketLevel(), chunkcoordintpair);
         }
@@ -347,13 +347,17 @@ public abstract class DistanceManager {
     // Paper end
 
     public int getNaturalSpawnChunkCount() {
-        this.naturalSpawnChunkCounter.runAllUpdates();
-        return this.naturalSpawnChunkCounter.chunks.size();
+        // Paper start - use distance map to implement
+        // note: this is the spawn chunk count
+        return this.chunkMap.playerChunkTickRangeMap.size();
+        // Paper end - use distance map to implement
     }
 
     public boolean hasPlayersNearby(long chunkPos) {
-        this.naturalSpawnChunkCounter.runAllUpdates();
-        return this.naturalSpawnChunkCounter.chunks.containsKey(chunkPos);
+        // Paper start - use distance map to implement
+        // note: this is the is spawn chunk method
+        return this.chunkMap.playerChunkTickRangeMap.getObjectsInRange(chunkPos) != null;
+        // Paper end - use distance map to implement
     }
 
     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 1b6fb81079d3ad5d3c33be67a1c05111f9dd5f2d..1d5587bb9cf785de4b6d59234eb700c2e1a8a7dd 100644
--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
@@ -880,6 +880,37 @@ public class ServerChunkCache extends ChunkSource {
         if (flag) {
             this.chunkMap.tick();
         } else {
+            // Paper start - optimize isOutisdeRange
+            ChunkMap playerChunkMap = this.chunkMap;
+            for (ServerPlayer player : this.level.players) {
+                if (!player.affectsSpawning || player.isSpectator()) {
+                    playerChunkMap.playerMobSpawnMap.remove(player);
+                    continue;
+                }
+
+                int viewDistance = this.chunkMap.getEffectiveViewDistance();
+
+                // copied and modified from isOutisdeRange
+                int chunkRange = level.spigotConfig.mobSpawnRange;
+                chunkRange = (chunkRange > viewDistance) ? (byte)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.playerChunkTickRangeMap.getLastViewDistance(player) == -1) {
+                    playerChunkMap.playerMobSpawnMap.remove(player);
+                    continue;
+                }
+
+                int range = Math.min(event.getSpawnRadius(), 32); // limit to max view distance
+                int chunkX = net.minecraft.server.MCUtil.getChunkCoordinate(player.getX());
+                int chunkZ = net.minecraft.server.MCUtil.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 - optimize isOutisdeRange
             LevelData worlddata = this.level.getLevelData();
             ProfilerFiller gameprofilerfiller = this.level.getProfiler();
 
@@ -923,15 +954,7 @@ public class ServerChunkCache extends ChunkSource {
             boolean flag2 = this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && !this.level.players().isEmpty(); // CraftBukkit
 
             Collections.shuffle(list);
-            // Paper start - call player naturally spawn event
-            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 end
+            // Paper - moved natural spawn event up
             Iterator iterator1 = list.iterator();
 
             while (iterator1.hasNext()) {
@@ -939,9 +962,9 @@ public class ServerChunkCache extends ChunkSource {
                 LevelChunk chunk1 = chunkproviderserver_a.chunk;
                 ChunkPos chunkcoordintpair = chunk1.getPos();
 
-                if (this.level.isNaturalSpawningAllowed(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(chunkcoordintpair)) {
+                if (this.level.isNaturalSpawningAllowed(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(chunkproviderserver_a.holder, chunkcoordintpair, false)) { // Paper - optimise anyPlayerCloseEnoughForSpawning
                     chunk1.incrementInhabitedTime(j);
-                    if (flag2 && (this.spawnEnemies || this.spawnFriendlies) && this.level.getWorldBorder().isWithinBounds(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(chunkcoordintpair, true)) { // Spigot
+                    if (flag2 && (this.spawnEnemies || this.spawnFriendlies) && this.level.getWorldBorder().isWithinBounds(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(chunkproviderserver_a.holder, chunkcoordintpair, true)) { // Spigot // Paper - optimise anyPlayerCloseEnoughForSpawning
                         NaturalSpawner.spawnForChunk(this.level, chunk1, spawnercreature_d, this.spawnFriendlies, this.spawnEnemies, flag1);
                     }
 
diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java
index 89156bd30cc41dd80de47c7e6e45db034904dbf3..a7a3c17cf97486f9f1af0cdc00686c22b37449b6 100644
--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java
+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java
@@ -251,6 +251,7 @@ public class ServerPlayer extends Player {
     // CraftBukkit end
     public PlayerNaturallySpawnCreaturesEvent playerNaturallySpawnedEvent; // Paper
 
+    public double lastEntitySpawnRadiusSquared; // Paper - optimise isOutsideRange, this field is in blocks
     public final com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> cachedSingleHashSet; // Paper
 
     public ServerPlayer(MinecraftServer server, ServerLevel world, GameProfile profile) {