aboutsummaryrefslogtreecommitdiffhomepage
path: root/Spigot-Server-Patches/0478-Optimize-isOutsideRange-to-use-distance-maps.patch
diff options
context:
space:
mode:
Diffstat (limited to 'Spigot-Server-Patches/0478-Optimize-isOutsideRange-to-use-distance-maps.patch')
-rw-r--r--Spigot-Server-Patches/0478-Optimize-isOutsideRange-to-use-distance-maps.patch384
1 files changed, 384 insertions, 0 deletions
diff --git a/Spigot-Server-Patches/0478-Optimize-isOutsideRange-to-use-distance-maps.patch b/Spigot-Server-Patches/0478-Optimize-isOutsideRange-to-use-distance-maps.patch
new file mode 100644
index 0000000000..5dd3b946ea
--- /dev/null
+++ b/Spigot-Server-Patches/0478-Optimize-isOutsideRange-to-use-distance-maps.patch
@@ -0,0 +1,384 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Spottedleaf <[email protected]>
+Date: Tue, 5 May 2020 20:40:53 -0700
+Subject: [PATCH] Optimize isOutsideRange to use distance maps
+
+Use a distance map to find the players in range quickly
+
+diff --git a/src/main/java/net/minecraft/server/ChunkMapDistance.java b/src/main/java/net/minecraft/server/ChunkMapDistance.java
+index 32d3887e2542c4ebba4a7498167fbe4b497a71ce..7e57a53ec614a2f7d2672edff9d7c0e0dca42377 100644
+--- a/src/main/java/net/minecraft/server/ChunkMapDistance.java
++++ b/src/main/java/net/minecraft/server/ChunkMapDistance.java
+@@ -31,7 +31,7 @@ public abstract class ChunkMapDistance {
+ private final Long2ObjectMap<ObjectSet<EntityPlayer>> c = new Long2ObjectOpenHashMap();
+ public final Long2ObjectOpenHashMap<ArraySetSorted<Ticket<?>>> tickets = new Long2ObjectOpenHashMap();
+ private final ChunkMapDistance.a ticketLevelTracker = new ChunkMapDistance.a();
+- private final ChunkMapDistance.b f = new ChunkMapDistance.b(8);
++ public static final int MOB_SPAWN_RANGE = 8; // private final ChunkMapDistance.b f = new ChunkMapDistance.b(8); // Paper - no longer used
+ private final ChunkMapDistance.c g = new ChunkMapDistance.c(33);
+ // Paper start use a queue, but still keep unique requirement
+ public final java.util.Queue<PlayerChunk> pendingChunkUpdates = new java.util.ArrayDeque<PlayerChunk>() {
+@@ -50,6 +50,8 @@ public abstract class ChunkMapDistance {
+ private final Executor m;
+ private long currentTick;
+
++ PlayerChunkMap chunkMap; // Paper
++
+ protected ChunkMapDistance(Executor executor, Executor executor1) {
+ executor1.getClass();
+ Mailbox<Runnable> mailbox = Mailbox.a("player ticket throttler", executor1::execute);
+@@ -94,7 +96,7 @@ public abstract class ChunkMapDistance {
+ protected abstract PlayerChunk a(long i, int j, @Nullable PlayerChunk playerchunk, int k);
+
+ public boolean a(PlayerChunkMap playerchunkmap) {
+- this.f.a();
++ //this.f.a(); // Paper - no longer used
+ this.g.a();
+ int i = Integer.MAX_VALUE - this.ticketLevelTracker.a(Integer.MAX_VALUE);
+ boolean flag = i != 0;
+@@ -230,7 +232,7 @@ public abstract class ChunkMapDistance {
+ ((ObjectSet) this.c.computeIfAbsent(i, (j) -> {
+ return new ObjectOpenHashSet();
+ })).add(entityplayer);
+- this.f.update(i, 0, true);
++ //this.f.update(i, 0, true); // Paper - no longer used
+ this.g.update(i, 0, true);
+ }
+
+@@ -241,7 +243,7 @@ public abstract class ChunkMapDistance {
+ if (objectset != null) objectset.remove(entityplayer); // Paper - some state corruption happens here, don't crash, clean up gracefully.
+ if (objectset == null || objectset.isEmpty()) { // Paper
+ this.c.remove(i);
+- this.f.update(i, Integer.MAX_VALUE, false);
++ //this.f.update(i, Integer.MAX_VALUE, false); // Paper - no longer used
+ this.g.update(i, Integer.MAX_VALUE, false);
+ }
+
+@@ -265,13 +267,17 @@ public abstract class ChunkMapDistance {
+ }
+
+ public int b() {
+- this.f.a();
+- return this.f.a.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 d(long i) {
+- this.f.a();
+- return this.f.a.containsKey(i);
++ // Paper start - use distance map to implement
++ // note: this is the is spawn chunk method
++ return this.chunkMap.playerChunkTickRangeMap.getObjectsInRange(i) != null;
++ // Paper end - use distance map to implement
+ }
+
+ public String c() {
+diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java
+index eebd4c50a7324250d3ebe7060739a71af4243f72..319059ba31f3614cc59cd4c4e4fa9242f6e4fe99 100644
+--- a/src/main/java/net/minecraft/server/ChunkProviderServer.java
++++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java
+@@ -728,6 +728,37 @@ public class ChunkProviderServer extends IChunkProvider {
+ boolean flag1 = this.world.getGameRules().getBoolean(GameRules.DO_MOB_SPAWNING) && !world.getPlayers().isEmpty(); // CraftBukkit
+
+ if (!flag) {
++ // Paper start - optimize isOutisdeRange
++ PlayerChunkMap playerChunkMap = this.playerChunkMap;
++ for (EntityPlayer player : this.world.players) {
++ if (!player.affectsSpawning || player.isSpectator()) {
++ playerChunkMap.playerMobSpawnMap.remove(player);
++ continue;
++ }
++
++ int viewDistance = this.playerChunkMap.getEffectiveViewDistance();
++
++ // copied and modified from isOutisdeRange
++ int chunkRange = world.spigotConfig.mobSpawnRange;
++ chunkRange = (chunkRange > viewDistance) ? (byte)viewDistance : chunkRange;
++ chunkRange = (chunkRange > ChunkMapDistance.MOB_SPAWN_RANGE) ? ChunkMapDistance.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.locX());
++ int chunkZ = net.minecraft.server.MCUtil.getChunkCoordinate(player.locZ());
++
++ playerChunkMap.playerMobSpawnMap.addOrUpdate(player, chunkX, chunkZ, range);
++ player.lastEntitySpawnRadiusSquared = (double)((range << 4) * (range << 4)); // used in isOutsideRange
++ player.playerNaturallySpawnedEvent = event;
++ }
++ // Paper end - optimize isOutisdeRange
+ this.world.getMethodProfiler().enter("pollingChunks");
+ int k = this.world.getGameRules().getInt(GameRules.RANDOM_TICK_SPEED);
+ boolean flag2 = world.ticksPerAnimalSpawns != 0L && worlddata.getTime() % world.ticksPerAnimalSpawns == 0L; // CraftBukkit
+@@ -757,15 +788,7 @@ public class ChunkProviderServer extends IChunkProvider {
+ this.world.getMethodProfiler().exit();
+ //List<PlayerChunk> list = Lists.newArrayList(this.playerChunkMap.f()); // Paper
+ //Collections.shuffle(list); // Paper
+- //Paper start - call player naturally spawn event
+- int chunkRange = world.spigotConfig.mobSpawnRange;
+- chunkRange = (chunkRange > world.spigotConfig.viewDistance) ? (byte) world.spigotConfig.viewDistance : chunkRange;
+- chunkRange = Math.min(chunkRange, 8);
+- for (EntityPlayer entityPlayer : this.world.getPlayers()) {
+- entityPlayer.playerNaturallySpawnedEvent = new com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent(entityPlayer.getBukkitEntity(), (byte) chunkRange);
+- entityPlayer.playerNaturallySpawnedEvent.callEvent();
+- };
+- // Paper end
++ // Paper - moved up
+ final int[] chunksTicked = {0}; this.playerChunkMap.forEachVisibleChunk((playerchunk) -> { // Paper - safe iterator incase chunk loads, also no wrapping
+ Optional<Chunk> optional = ((Either) playerchunk.a().getNow(PlayerChunk.UNLOADED_CHUNK)).left();
+
+@@ -781,9 +804,9 @@ public class ChunkProviderServer extends IChunkProvider {
+ Chunk chunk = (Chunk) optional1.get();
+ ChunkCoordIntPair chunkcoordintpair = playerchunk.i();
+
+- if (!this.playerChunkMap.isOutsideOfRange(chunkcoordintpair)) {
++ if (!this.playerChunkMap.isOutsideOfRange(playerchunk, chunkcoordintpair, false)) { // Paper - optimise isOutsideOfRange
+ chunk.setInhabitedTime(chunk.getInhabitedTime() + j);
+- if (flag1 && (this.allowMonsters || this.allowAnimals) && this.world.getWorldBorder().isInBounds(chunk.getPos()) && !this.playerChunkMap.isOutsideOfRange(chunkcoordintpair, true)) { // Spigot
++ if (flag1 && (this.allowMonsters || this.allowAnimals) && this.world.getWorldBorder().isInBounds(chunk.getPos()) && !this.playerChunkMap.isOutsideOfRange(playerchunk, chunkcoordintpair, true)) { // Spigot // Paper - optimise isOutsideOfRange
+ SpawnerCreature.a(this.world, chunk, spawnercreature_d, this.allowAnimals, this.allowMonsters, flag2);
+ }
+
+diff --git a/src/main/java/net/minecraft/server/EntityPlayer.java b/src/main/java/net/minecraft/server/EntityPlayer.java
+index b615199b2f44b6e5169113cca7cd48d37ead997a..ca93c9f3ab6ae50eb7dbc825a70620325033ccad 100644
+--- a/src/main/java/net/minecraft/server/EntityPlayer.java
++++ b/src/main/java/net/minecraft/server/EntityPlayer.java
+@@ -113,6 +113,8 @@ public class EntityPlayer extends EntityHuman implements ICrafting {
+
+ public final com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> cachedSingleHashSet; // Paper
+
++ double lastEntitySpawnRadiusSquared; // Paper - optimise isOutsideRange, this field is in blocks
++
+ public EntityPlayer(MinecraftServer minecraftserver, WorldServer worldserver, GameProfile gameprofile, PlayerInteractManager playerinteractmanager) {
+ super(worldserver, worldserver.getSpawn(), gameprofile);
+ this.spawnDimension = World.OVERWORLD;
+diff --git a/src/main/java/net/minecraft/server/PlayerChunk.java b/src/main/java/net/minecraft/server/PlayerChunk.java
+index 9cb2ff09da0b8832e58eed4d70741853a25c9011..7f660d3c528f5fb4150e4ee8b29913436f125b06 100644
+--- a/src/main/java/net/minecraft/server/PlayerChunk.java
++++ b/src/main/java/net/minecraft/server/PlayerChunk.java
+@@ -43,6 +43,18 @@ public class PlayerChunk {
+ long lastAutoSaveTime; // Paper - incremental autosave
+ long inactiveTimeStart; // Paper - incremental autosave
+
++ // Paper start - optimise isOutsideOfRange
++ // cached here to avoid a map lookup
++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> playersInMobSpawnRange;
++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> playersInChunkTickRange;
++
++ void updateRanges() {
++ long key = net.minecraft.server.MCUtil.getCoordinateKey(this.location);
++ this.playersInMobSpawnRange = this.chunkMap.playerMobSpawnMap.getObjectsInRange(key);
++ this.playersInChunkTickRange = this.chunkMap.playerChunkTickRangeMap.getObjectsInRange(key);
++ }
++ // Paper end - optimise isOutsideOfRange
++
+ public PlayerChunk(ChunkCoordIntPair chunkcoordintpair, int i, LightEngine lightengine, PlayerChunk.c playerchunk_c, PlayerChunk.d playerchunk_d) {
+ this.statusFutures = new AtomicReferenceArray(PlayerChunk.CHUNK_STATUSES.size());
+ this.fullChunkFuture = PlayerChunk.UNLOADED_CHUNK_FUTURE;
+@@ -59,6 +71,7 @@ public class PlayerChunk {
+ this.n = this.oldTicketLevel;
+ this.a(i);
+ this.chunkMap = (PlayerChunkMap)playerchunk_d; // Paper
++ this.updateRanges(); // Paper - optimise isOutsideOfRange
+ }
+
+ // Paper start
+diff --git a/src/main/java/net/minecraft/server/PlayerChunkMap.java b/src/main/java/net/minecraft/server/PlayerChunkMap.java
+index d751c3ec9d799a0c2bfe835cae1645287af79ec2..8b36a14b2896d32f99e788a5db928a4100de3912 100644
+--- a/src/main/java/net/minecraft/server/PlayerChunkMap.java
++++ b/src/main/java/net/minecraft/server/PlayerChunkMap.java
+@@ -159,6 +159,17 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
+ return MinecraftServer.getServer().applyTrackingRangeScale(vanilla);
+ }
+ // Paper end - use distance map to optimise tracker
++ // Paper start - optimise PlayerChunkMap#isOutsideRange
++ // 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 PlayerChunkMap#isOutsideRange
+
+ void addPlayerToDistanceMaps(EntityPlayer player) {
+ int chunkX = MCUtil.getChunkCoordinate(player.locX());
+@@ -172,6 +183,9 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
+ trackMap.add(player, chunkX, chunkZ, Math.min(trackRange, this.getEffectiveViewDistance()));
+ }
+ // Paper end - use distance map to optimise entity tracker
++ // Paper start - optimise PlayerChunkMap#isOutsideRange
++ this.playerChunkTickRangeMap.add(player, chunkX, chunkZ, ChunkMapDistance.MOB_SPAWN_RANGE);
++ // Paper end - optimise PlayerChunkMap#isOutsideRange
+ }
+
+ void removePlayerFromDistanceMaps(EntityPlayer player) {
+@@ -180,6 +194,10 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
+ this.playerEntityTrackerTrackMaps[i].remove(player);
+ }
+ // Paper end - use distance map to optimise tracker
++ // Paper start - optimise PlayerChunkMap#isOutsideRange
++ this.playerMobSpawnMap.remove(player);
++ this.playerChunkTickRangeMap.remove(player);
++ // Paper end - optimise PlayerChunkMap#isOutsideRange
+ }
+
+ void updateMaps(EntityPlayer player) {
+@@ -194,6 +212,9 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
+ trackMap.update(player, chunkX, chunkZ, Math.min(trackRange, this.getEffectiveViewDistance()));
+ }
+ // Paper end - use distance map to optimise entity tracker
++ // Paper start - optimise PlayerChunkMap#isOutsideRange
++ this.playerChunkTickRangeMap.update(player, chunkX, chunkZ, ChunkMapDistance.MOB_SPAWN_RANGE);
++ // Paper end - optimise PlayerChunkMap#isOutsideRange
+ }
+ // Paper end
+
+@@ -225,7 +246,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
+ this.mailboxWorldGen = this.p.a(threadedmailbox, false);
+ this.mailboxMain = this.p.a(mailbox, false);
+ this.lightEngine = new LightEngineThreaded(ilightaccess, this, this.world.getDimensionManager().hasSkyLight(), threadedmailbox1, this.p.a(threadedmailbox1, false));
+- this.chunkDistanceManager = new PlayerChunkMap.a(executor, iasynctaskhandler);
++ this.chunkDistanceManager = new PlayerChunkMap.a(executor, iasynctaskhandler); this.chunkDistanceManager.chunkMap = this; // Paper
+ this.l = supplier;
+ this.m = new VillagePlace(new File(this.w, "poi"), datafixer, flag, this.world); // Paper
+ this.setViewDistance(i);
+@@ -269,6 +290,38 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
+ this.playerEntityTrackerTrackMaps[ordinal] = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets);
+ }
+ // Paper end - use distance map to optimise entity tracker
++ // Paper start - optimise PlayerChunkMap#isOutsideRange
++ this.playerChunkTickRangeMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets,
++ (EntityPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ,
++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> newState) -> {
++ PlayerChunk playerChunk = PlayerChunkMap.this.getUpdatingChunk(MCUtil.getCoordinateKey(rangeX, rangeZ));
++ if (playerChunk != null) {
++ playerChunk.playersInChunkTickRange = newState;
++ }
++ },
++ (EntityPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ,
++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> newState) -> {
++ PlayerChunk playerChunk = PlayerChunkMap.this.getUpdatingChunk(MCUtil.getCoordinateKey(rangeX, rangeZ));
++ if (playerChunk != null) {
++ playerChunk.playersInChunkTickRange = newState;
++ }
++ });
++ this.playerMobSpawnMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets,
++ (EntityPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ,
++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> newState) -> {
++ PlayerChunk playerChunk = PlayerChunkMap.this.getUpdatingChunk(MCUtil.getCoordinateKey(rangeX, rangeZ));
++ if (playerChunk != null) {
++ playerChunk.playersInMobSpawnRange = newState;
++ }
++ },
++ (EntityPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ,
++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> newState) -> {
++ PlayerChunk playerChunk = PlayerChunkMap.this.getUpdatingChunk(MCUtil.getCoordinateKey(rangeX, rangeZ));
++ if (playerChunk != null) {
++ playerChunk.playersInMobSpawnRange = newState;
++ }
++ });
++ // Paper end - optimise PlayerChunkMap#isOutsideRange
+ }
+
+ public void updatePlayerMobTypeMap(Entity entity) {
+@@ -288,6 +341,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
+ return entityPlayer.mobCounts[enumCreatureType.ordinal()];
+ }
+
++ private static double getDistanceSquaredFromChunk(ChunkCoordIntPair chunkPos, Entity entity) { return a(chunkPos, entity); } // Paper - OBFHELPER
+ private static double a(ChunkCoordIntPair chunkcoordintpair, Entity entity) {
+ double d0 = (double) (chunkcoordintpair.x * 16 + 8);
+ double d1 = (double) (chunkcoordintpair.z * 16 + 8);
+@@ -466,6 +520,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
+ } else {
+ if (playerchunk != null) {
+ playerchunk.a(j);
++ playerchunk.updateRanges(); // Paper - optimise isOutsideOfRange
+ }
+
+ if (playerchunk != null) {
+@@ -1436,30 +1491,53 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
+ return isOutsideOfRange(chunkcoordintpair, false);
+ }
+
+- boolean isOutsideOfRange(ChunkCoordIntPair chunkcoordintpair, boolean reducedRange) {
+- int chunkRange = world.spigotConfig.mobSpawnRange;
+- chunkRange = (chunkRange > world.spigotConfig.viewDistance) ? (byte) world.spigotConfig.viewDistance : chunkRange;
+- chunkRange = (chunkRange > 8) ? 8 : chunkRange;
++ // Paper start - optimise isOutsideOfRange
++ final boolean isOutsideOfRange(ChunkCoordIntPair chunkcoordintpair, boolean reducedRange) {
++ return this.isOutsideOfRange(this.getUpdatingChunk(chunkcoordintpair.pair()), chunkcoordintpair, reducedRange);
++ }
+
+- final int finalChunkRange = chunkRange; // Paper for lambda below
+- //double blockRange = (reducedRange) ? Math.pow(chunkRange << 4, 2) : 16384.0D; // Paper - use from event
+- // Spigot end
+- long i = chunkcoordintpair.pair();
++ final boolean isOutsideOfRange(PlayerChunk playerchunk, ChunkCoordIntPair 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<EntityPlayer> playersInRange = reducedRange ? playerchunk.playersInMobSpawnRange : playerchunk.playersInChunkTickRange;
+
+- return !this.chunkDistanceManager.d(i) ? true : this.playerMap.a(i).noneMatch((entityplayer) -> {
+- // Paper start -
+- com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent event;
+- double blockRange = 16384.0D;
+- if (reducedRange) {
+- event = entityplayer.playerNaturallySpawnedEvent;
+- if (event == null || event.isCancelled()) return false;
+- blockRange = (double) ((event.getSpawnRadius() << 4) * (event.getSpawnRadius() << 4));
+- }
++ if (playersInRange == null) {
++ return true;
++ }
+
+- return (!entityplayer.isSpectator() && a(chunkcoordintpair, (Entity) entityplayer) < blockRange); // Spigot
+- // Paper end
+- });
++ Object[] backingSet = playersInRange.getBackingSet();
++
++ if (reducedRange) {
++ for (int i = 0, len = backingSet.length; i < len; ++i) {
++ Object raw = backingSet[i];
++ if (!(raw instanceof EntityPlayer)) {
++ continue;
++ }
++ EntityPlayer player = (EntityPlayer) raw;
++ // don't check spectator and whatnot, already handled by mob spawn map update
++ if (player.lastEntitySpawnRadiusSquared > getDistanceSquaredFromChunk(chunkcoordintpair, player)) {
++ return false; // in range
++ }
++ }
++ } else {
++ final double range = (ChunkMapDistance.MOB_SPAWN_RANGE * 16) * (ChunkMapDistance.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 EntityPlayer)) {
++ continue;
++ }
++ EntityPlayer player = (EntityPlayer) raw;
++ // don't check spectator and whatnot, already handled by mob spawn map update
++ if (range > getDistanceSquaredFromChunk(chunkcoordintpair, player)) {
++ return false; // in range
++ }
++ }
++ }
++ // no players in range
++ return true;
+ }
++ // Paper end - optimise isOutsideOfRange
+
+ private boolean b(EntityPlayer entityplayer) {
+ return entityplayer.isSpectator() && !this.world.getGameRules().getBoolean(GameRules.SPECTATORS_GENERATE_CHUNKS);