PaperMC/Spigot-Server-Patches/0478-Optimize-isOutsideRange-to-use-distance-maps.patch

384 lines
23 KiB
Diff
Raw Normal View History

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 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..a6363b73522f9d27534b6e80f4b3789e84316c49 100644
--- a/src/main/java/net/minecraft/server/ChunkProviderServer.java
+++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java
@@ -728,6 +728,36 @@ 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
+ }
+ // 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 +787,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 +803,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 0a5f784e851414f84e5a94b53f5616531f533109..fb2b64628b974758cace770ce6debe5e88318ae8 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 {
Implement Chunk Priority / Urgency System for Chunks Mark chunks that are blocking main thread for world generation as urgent Implements a general priority system so that chunks that are sorted in the generator queues can prioritize certain chunks over another. Urgent chunks will jump to the front of the line, ensuring that a sync chunk load on an ungenerated chunk does not lag the server for a long period of time if the servers generator queues are filled with lots of chunks already. This massively reduces the lag spikes from sync chunk gens. Then we further prioritize loading order so nearby chunks have higher priority than distant chunks, reducing the pressure a high no tick view distance holds on you. Chunks in front of the player have higher priority, to help with fast traveling players keep up with their movement. This commit also improves single core cpu scenarios in that we will now automatically disable Async Chunks as well as Minecrafts thread pool. It is never recommended to use async chunks on a single CPU as context switching will be slower than just running it all on main. This also bumps the number of server worker threads by default too. Mojang does not utilize the workers in an effecient manner, resulting in them using barely any sustained CPU. So give it more workers so more chunks can be processed concurrently This change also improves urgent chunk loading, so players flying into unloaded chunks will hurt a little bit less (but still hurt) Ping #3395 #3363 (Not marking as closed, we need to make prevent moving work)
2020-05-19 10:01:53 +02:00
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 38d7cfdbf96679fb2a56c3b36f374831451f34ee..b55e8f94ef45b526c55bc5ba2a991d933bbc4170 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) {
@@ -1433,30 +1488,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);