aboutsummaryrefslogtreecommitdiffhomepage
path: root/patches/server/0734-Optimise-nearby-player-lookups.patch
diff options
context:
space:
mode:
Diffstat (limited to 'patches/server/0734-Optimise-nearby-player-lookups.patch')
-rw-r--r--patches/server/0734-Optimise-nearby-player-lookups.patch426
1 files changed, 426 insertions, 0 deletions
diff --git a/patches/server/0734-Optimise-nearby-player-lookups.patch b/patches/server/0734-Optimise-nearby-player-lookups.patch
new file mode 100644
index 0000000000..c283aa0ebf
--- /dev/null
+++ b/patches/server/0734-Optimise-nearby-player-lookups.patch
@@ -0,0 +1,426 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Spottedleaf <[email protected]>
+Date: Thu, 27 Aug 2020 16:22:52 -0700
+Subject: [PATCH] Optimise nearby player lookups
+
+Use a distance map to map out close players.
+Note that it's important that we cache the distance map value per chunk
+since the penalty of a map lookup could outweigh the benefits of
+searching less players (as it basically did in the outside range patch).
+
+diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java
+index 1fb298ff60b59a7074fb9d7a79709f05887ce32c..94d52a21d40a31cd6e8251f79ffc885de16e48f4 100644
+--- a/src/main/java/net/minecraft/server/level/ChunkHolder.java
++++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java
+@@ -93,6 +93,12 @@ public class ChunkHolder {
+ this.chunkMap.needsChangeBroadcasting.add(this);
+ }
+ // Paper end - optimise chunk tick iteration
++ // Paper start - optimise checkDespawn
++ LevelChunk chunk = this.getFullChunkNowUnchecked();
++ if (chunk != null) {
++ chunk.updateGeneralAreaCache();
++ }
++ // Paper end - optimise checkDespawn
+ }
+
+ public void onChunkRemove() {
+@@ -105,6 +111,12 @@ public class ChunkHolder {
+ this.chunkMap.needsChangeBroadcasting.remove(this);
+ }
+ // Paper end - optimise chunk tick iteration
++ // Paper start - optimise checkDespawn
++ LevelChunk chunk = this.getFullChunkNowUnchecked();
++ if (chunk != null) {
++ chunk.removeGeneralAreaCache();
++ }
++ // Paper end - optimise checkDespawn
+ }
+ // Paper end
+
+diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
+index 57b45ce5feaa5015b5468a0b44f354e96d7d95fc..841df3f621081f6b67711cbd047e8bde498eedbf 100644
+--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
+@@ -152,6 +152,12 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+ public final ReferenceOpenHashSet<ChunkHolder> needsChangeBroadcasting = new ReferenceOpenHashSet<>();
+
+ // Paper - rewrite chunk system
++ // Paper start - optimise checkDespawn
++ public static final int GENERAL_AREA_MAP_SQUARE_RADIUS = 40;
++ public static final double GENERAL_AREA_MAP_ACCEPTABLE_SEARCH_RANGE = 16.0 * (GENERAL_AREA_MAP_SQUARE_RADIUS - 1);
++ public static final double GENERAL_AREA_MAP_ACCEPTABLE_SEARCH_RANGE_SQUARED = GENERAL_AREA_MAP_ACCEPTABLE_SEARCH_RANGE * GENERAL_AREA_MAP_ACCEPTABLE_SEARCH_RANGE;
++ public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerGeneralAreaMap;
++ // Paper end - optimise checkDespawn
+
+ // Paper start - distance maps
+ private final com.destroystokyo.paper.util.misc.PooledLinkedHashSets<ServerPlayer> pooledLinkedPlayerHashSets = new com.destroystokyo.paper.util.misc.PooledLinkedHashSets<>();
+@@ -204,6 +210,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+ trackMap.add(player, chunkX, chunkZ, Math.min(trackRange, net.minecraft.server.ChunkSystem.getSendViewDistance(player)));
+ }
+ // Paper end - use distance map to optimise entity tracker
++ this.playerGeneralAreaMap.add(player, chunkX, chunkZ, GENERAL_AREA_MAP_SQUARE_RADIUS); // Paper - optimise checkDespawn
+ }
+
+ void removePlayerFromDistanceMaps(ServerPlayer player) {
+@@ -213,6 +220,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+ this.playerMobSpawnMap.remove(player);
+ this.playerChunkTickRangeMap.remove(player);
+ // Paper end - optimise ChunkMap#anyPlayerCloseEnoughForSpawning
++ this.playerGeneralAreaMap.remove(player); // Paper - optimise checkDespawns
+ // Paper start - per player mob spawning
+ if (this.playerMobDistanceMap != null) {
+ this.playerMobDistanceMap.remove(player);
+@@ -244,6 +252,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+ trackMap.update(player, chunkX, chunkZ, Math.min(trackRange, net.minecraft.server.ChunkSystem.getSendViewDistance(player)));
+ }
+ // Paper end - use distance map to optimise entity tracker
++ this.playerGeneralAreaMap.update(player, chunkX, chunkZ, GENERAL_AREA_MAP_SQUARE_RADIUS); // Paper - optimise checkDespawn
+ }
+ // Paper end
+ // Paper start
+@@ -397,6 +406,23 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+ }
+ });
+ // Paper end - optimise ChunkMap#anyPlayerCloseEnoughForSpawning
++ // Paper start - optimise checkDespawn
++ this.playerGeneralAreaMap = 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) -> {
++ LevelChunk chunk = ChunkMap.this.level.getChunkSource().getChunkAtIfCachedImmediately(rangeX, rangeZ);
++ if (chunk != null) {
++ chunk.updateGeneralAreaCache(newState);
++ }
++ },
++ (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ,
++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> newState) -> {
++ LevelChunk chunk = ChunkMap.this.level.getChunkSource().getChunkAtIfCachedImmediately(rangeX, rangeZ);
++ if (chunk != null) {
++ chunk.updateGeneralAreaCache(newState);
++ }
++ });
++ // Paper end - optimise checkDespawn
+ }
+
+ protected ChunkGenerator generator() {
+diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
+index 4f32dff7ec2fc55b085b13464667707454413dac..3fda64e9d530b85ddcfe4277f64286fc3356512f 100644
+--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
+@@ -440,6 +440,84 @@ public class ServerLevel extends Level implements WorldGenLevel {
+ // Paper end
+ public final ReferenceOpenHashSet<ServerPlayer> pendingLogin = new ReferenceOpenHashSet<>(); // Paper
+
++ // Paper start - optimise checkDespawn
++ public final List<ServerPlayer> playersAffectingSpawning = new java.util.ArrayList<>();
++ // Paper end - optimise checkDespawn
++ // Paper start - optimise get nearest players for entity AI
++ @Override
++ public final ServerPlayer getNearestPlayer(net.minecraft.world.entity.ai.targeting.TargetingConditions condition, @Nullable LivingEntity source,
++ double centerX, double centerY, double centerZ) {
++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> nearby;
++ nearby = this.getChunkSource().chunkMap.playerGeneralAreaMap.getObjectsInRange(Mth.floor(centerX) >> 4, Mth.floor(centerZ) >> 4);
++
++ if (nearby == null) {
++ return null;
++ }
++
++ Object[] backingSet = nearby.getBackingSet();
++
++ double closestDistanceSquared = Double.MAX_VALUE;
++ ServerPlayer closest = null;
++
++ for (int i = 0, len = backingSet.length; i < len; ++i) {
++ Object _player = backingSet[i];
++ if (!(_player instanceof ServerPlayer)) {
++ continue;
++ }
++ ServerPlayer player = (ServerPlayer)_player;
++
++ double distanceSquared = player.distanceToSqr(centerX, centerY, centerZ);
++ if (distanceSquared < closestDistanceSquared && condition.test(source, player)) {
++ closest = player;
++ closestDistanceSquared = distanceSquared;
++ }
++ }
++
++ return closest;
++ }
++
++ @Override
++ public Player getNearestPlayer(net.minecraft.world.entity.ai.targeting.TargetingConditions pathfindertargetcondition, LivingEntity entityliving) {
++ return this.getNearestPlayer(pathfindertargetcondition, entityliving, entityliving.getX(), entityliving.getY(), entityliving.getZ());
++ }
++
++ @Override
++ public Player getNearestPlayer(net.minecraft.world.entity.ai.targeting.TargetingConditions pathfindertargetcondition,
++ double d0, double d1, double d2) {
++ return this.getNearestPlayer(pathfindertargetcondition, null, d0, d1, d2);
++ }
++
++ @Override
++ public List<Player> getNearbyPlayers(net.minecraft.world.entity.ai.targeting.TargetingConditions condition, LivingEntity source, AABB axisalignedbb) {
++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> nearby;
++ double centerX = (axisalignedbb.maxX + axisalignedbb.minX) * 0.5;
++ double centerZ = (axisalignedbb.maxZ + axisalignedbb.minZ) * 0.5;
++ nearby = this.getChunkSource().chunkMap.playerGeneralAreaMap.getObjectsInRange(Mth.floor(centerX) >> 4, Mth.floor(centerZ) >> 4);
++
++ List<Player> ret = new java.util.ArrayList<>();
++
++ if (nearby == null) {
++ return ret;
++ }
++
++ Object[] backingSet = nearby.getBackingSet();
++
++ for (int i = 0, len = backingSet.length; i < len; ++i) {
++ Object _player = backingSet[i];
++ if (!(_player instanceof ServerPlayer)) {
++ continue;
++ }
++ ServerPlayer player = (ServerPlayer)_player;
++
++ if (axisalignedbb.contains(player.getX(), player.getY(), player.getZ()) && condition.test(source, player)) {
++ ret.add(player);
++ }
++ }
++
++ return ret;
++ }
++ // Paper end - optimise get nearest players for entity AI
++
+ // Add env and gen to constructor, IWorldDataServer -> WorldDataServer
+ public ServerLevel(MinecraftServer minecraftserver, Executor executor, LevelStorageSource.LevelStorageAccess convertable_conversionsession, PrimaryLevelData iworlddataserver, ResourceKey<Level> resourcekey, LevelStem worlddimension, ChunkProgressListener worldloadlistener, boolean flag, long i, List<CustomSpawner> list, boolean flag1, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider) {
+ // Holder holder = worlddimension.typeHolder(); // CraftBukkit - decompile error
+@@ -543,6 +621,14 @@ public class ServerLevel extends Level implements WorldGenLevel {
+ }
+
+ public void tick(BooleanSupplier shouldKeepTicking) {
++ // Paper start - optimise checkDespawn
++ this.playersAffectingSpawning.clear();
++ for (ServerPlayer player : this.players) {
++ if (net.minecraft.world.entity.EntitySelector.affectsSpawning.test(player)) {
++ this.playersAffectingSpawning.add(player);
++ }
++ }
++ // Paper end - optimise checkDespawn
+ ProfilerFiller gameprofilerfiller = this.getProfiler();
+
+ this.handlingTick = true;
+diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java
+index 5fb88a3b7242a2712a568aaccebe601f89bfee3a..557e90e54439ce0430075403392b5052d5181feb 100644
+--- a/src/main/java/net/minecraft/world/entity/Mob.java
++++ b/src/main/java/net/minecraft/world/entity/Mob.java
+@@ -808,7 +808,12 @@ public abstract class Mob extends LivingEntity {
+ if (this.level.getDifficulty() == Difficulty.PEACEFUL && this.shouldDespawnInPeaceful()) {
+ this.discard();
+ } else if (!this.isPersistenceRequired() && !this.requiresCustomPersistence()) {
+- Player entityhuman = this.level.findNearbyPlayer(this, -1.0D, EntitySelector.affectsSpawning); // Paper
++ // Paper start - optimise checkDespawn
++ Player entityhuman = this.level.findNearbyPlayer(this, level.paperConfig().entities.spawning.despawnRanges.get(this.getType().getCategory()).hard() + 1, EntitySelector.affectsSpawning); // Paper
++ if (entityhuman == null) {
++ entityhuman = ((ServerLevel)this.level).playersAffectingSpawning.isEmpty() ? null : ((ServerLevel)this.level).playersAffectingSpawning.get(0);
++ }
++ // Paper end - optimise checkDespawn
+
+ if (entityhuman != null) {
+ double d0 = entityhuman.distanceToSqr((Entity) this);
+diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
+index aa90454e70e5c25eb331ceb212df3128d64b1567..c1a3bcc8d9df2bf25a9c73faeac84652756d430a 100644
+--- a/src/main/java/net/minecraft/world/level/Level.java
++++ b/src/main/java/net/minecraft/world/level/Level.java
+@@ -205,6 +205,69 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
+ return this.getChunkIfLoaded(chunkX, chunkZ) != null;
+ }
+ // Paper end
++ // Paper start - optimise checkDespawn
++ public final List<net.minecraft.server.level.ServerPlayer> getNearbyPlayers(@Nullable Entity source, double sourceX, double sourceY,
++ double sourceZ, double maxRange, @Nullable Predicate<Entity> predicate) {
++ LevelChunk chunk;
++ if (maxRange < 0.0 || maxRange >= net.minecraft.server.level.ChunkMap.GENERAL_AREA_MAP_ACCEPTABLE_SEARCH_RANGE ||
++ (chunk = (LevelChunk)this.getChunkIfLoadedImmediately(Mth.floor(sourceX) >> 4, Mth.floor(sourceZ) >> 4)) == null) {
++ return this.getNearbyPlayersSlow(source, sourceX, sourceY, sourceZ, maxRange, predicate);
++ }
++
++ List<net.minecraft.server.level.ServerPlayer> ret = new java.util.ArrayList<>();
++ chunk.getNearestPlayers(sourceX, sourceY, sourceZ, predicate, maxRange, ret);
++ return ret;
++ }
++
++ private List<net.minecraft.server.level.ServerPlayer> getNearbyPlayersSlow(@Nullable Entity source, double sourceX, double sourceY,
++ double sourceZ, double maxRange, @Nullable Predicate<Entity> predicate) {
++ List<net.minecraft.server.level.ServerPlayer> ret = new java.util.ArrayList<>();
++ double maxRangeSquared = maxRange * maxRange;
++
++ for (net.minecraft.server.level.ServerPlayer player : (List<net.minecraft.server.level.ServerPlayer>)this.players()) {
++ if ((maxRange < 0.0 || player.distanceToSqr(sourceX, sourceY, sourceZ) < maxRangeSquared)) {
++ if (predicate == null || predicate.test(player)) {
++ ret.add(player);
++ }
++ }
++ }
++
++ return ret;
++ }
++
++ private net.minecraft.server.level.ServerPlayer getNearestPlayerSlow(@Nullable Entity source, double sourceX, double sourceY,
++ double sourceZ, double maxRange, @Nullable Predicate<Entity> predicate) {
++ net.minecraft.server.level.ServerPlayer closest = null;
++ double closestRangeSquared = maxRange < 0.0 ? Double.MAX_VALUE : maxRange * maxRange;
++
++ for (net.minecraft.server.level.ServerPlayer player : (List<net.minecraft.server.level.ServerPlayer>)this.players()) {
++ double distanceSquared = player.distanceToSqr(sourceX, sourceY, sourceZ);
++ if (distanceSquared < closestRangeSquared && (predicate == null || predicate.test(player))) {
++ closest = player;
++ closestRangeSquared = distanceSquared;
++ }
++ }
++
++ return closest;
++ }
++
++
++ public final net.minecraft.server.level.ServerPlayer getNearestPlayer(@Nullable Entity source, double sourceX, double sourceY,
++ double sourceZ, double maxRange, @Nullable Predicate<Entity> predicate) {
++ LevelChunk chunk;
++ if (maxRange < 0.0 || maxRange >= net.minecraft.server.level.ChunkMap.GENERAL_AREA_MAP_ACCEPTABLE_SEARCH_RANGE ||
++ (chunk = (LevelChunk)this.getChunkIfLoadedImmediately(Mth.floor(sourceX) >> 4, Mth.floor(sourceZ) >> 4)) == null) {
++ return this.getNearestPlayerSlow(source, sourceX, sourceY, sourceZ, maxRange, predicate);
++ }
++
++ return chunk.findNearestPlayer(sourceX, sourceY, sourceZ, maxRange, predicate);
++ }
++
++ @Override
++ public @Nullable Player getNearestPlayer(double d0, double d1, double d2, double d3, @Nullable Predicate<Entity> predicate) {
++ return this.getNearestPlayer(null, d0, d1, d2, d3, predicate);
++ }
++ // Paper end - optimise checkDespawn
+
+ public abstract ResourceKey<LevelStem> getTypeKey();
+
+diff --git a/src/main/java/net/minecraft/world/level/NaturalSpawner.java b/src/main/java/net/minecraft/world/level/NaturalSpawner.java
+index 4150e8cd7197eac53042d56f0a53a4951f8824ce..e31a2eea9a62ab2c0bed1a97dab6bae231b8cd8b 100644
+--- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java
++++ b/src/main/java/net/minecraft/world/level/NaturalSpawner.java
+@@ -259,7 +259,7 @@ public final class NaturalSpawner {
+ blockposition_mutableblockposition.set(l, i, i1);
+ double d0 = (double) l + 0.5D;
+ double d1 = (double) i1 + 0.5D;
+- Player entityhuman = world.getNearestPlayer(d0, (double) i, d1, -1.0D, false);
++ Player entityhuman = (chunk instanceof LevelChunk) ? ((LevelChunk)chunk).findNearestPlayer(d0, i, d1, 576.0D, net.minecraft.world.entity.EntitySelector.NO_SPECTATORS) : world.getNearestPlayer(d0, (double) i, d1, -1.0D, false); // Paper - use chunk's player cache to optimize search in range
+
+ if (entityhuman != null) {
+ double d2 = entityhuman.distanceToSqr(d0, (double) i, d1);
+@@ -332,7 +332,7 @@ public final class NaturalSpawner {
+ }
+
+ private static boolean isRightDistanceToPlayerAndSpawnPoint(ServerLevel world, ChunkAccess chunk, BlockPos.MutableBlockPos pos, double squaredDistance) {
+- return squaredDistance <= 576.0D ? false : (world.getSharedSpawnPos().closerToCenterThan(new Vec3((double) pos.getX() + 0.5D, (double) pos.getY(), (double) pos.getZ() + 0.5D), 24.0D) ? false : Objects.equals(new ChunkPos(pos), chunk.getPos()) || world.isNaturalSpawningAllowed((BlockPos) pos));
++ return squaredDistance <= 576.0D ? false : (world.getSharedSpawnPos().closerToCenterThan(new Vec3((double) pos.getX() + 0.5D, (double) pos.getY(), (double) pos.getZ() + 0.5D), 24.0D) ? false : Objects.equals(new ChunkPos(pos), chunk.getPos()) || world.isNaturalSpawningAllowed((BlockPos) pos)); // Paper - diff on change, copy into caller
+ }
+
+ private static Boolean isValidSpawnPostitionForType(ServerLevel world, MobCategory group, StructureManager structureAccessor, ChunkGenerator chunkGenerator, MobSpawnSettings.SpawnerData spawnEntry, BlockPos.MutableBlockPos pos, double squaredDistance) { // Paper
+diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
+index b0f53c99a89b900ffe49bdd277329829b44775d4..05499ae9fc331471db6e763a2adb46b5da8522d3 100644
+--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
++++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
+@@ -269,6 +269,98 @@ public class LevelChunk extends ChunkAccess {
+ }
+ }
+ // Paper end
++ // Paper start - optimise checkDespawn
++ private boolean playerGeneralAreaCacheSet;
++ private com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<net.minecraft.server.level.ServerPlayer> playerGeneralAreaCache;
++
++ public com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<net.minecraft.server.level.ServerPlayer> getPlayerGeneralAreaCache() {
++ if (!this.playerGeneralAreaCacheSet) {
++ this.updateGeneralAreaCache();
++ }
++ return this.playerGeneralAreaCache;
++ }
++
++ public void updateGeneralAreaCache() {
++ this.updateGeneralAreaCache(((ServerLevel)this.level).getChunkSource().chunkMap.playerGeneralAreaMap.getObjectsInRange(this.coordinateKey));
++ }
++
++ public void removeGeneralAreaCache() {
++ this.playerGeneralAreaCacheSet = false;
++ this.playerGeneralAreaCache = null;
++ }
++
++ public void updateGeneralAreaCache(com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<net.minecraft.server.level.ServerPlayer> value) {
++ this.playerGeneralAreaCacheSet = true;
++ this.playerGeneralAreaCache = value;
++ }
++
++ public net.minecraft.server.level.ServerPlayer findNearestPlayer(double sourceX, double sourceY, double sourceZ,
++ double maxRange, java.util.function.Predicate<Entity> predicate) {
++ if (!this.playerGeneralAreaCacheSet) {
++ this.updateGeneralAreaCache();
++ }
++
++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<net.minecraft.server.level.ServerPlayer> nearby = this.playerGeneralAreaCache;
++
++ if (nearby == null) {
++ return null;
++ }
++
++ Object[] backingSet = nearby.getBackingSet();
++ double closestDistance = maxRange < 0.0 ? Double.MAX_VALUE : maxRange * maxRange;
++ net.minecraft.server.level.ServerPlayer closest = null;
++ for (int i = 0, len = backingSet.length; i < len; ++i) {
++ Object _player = backingSet[i];
++ if (!(_player instanceof net.minecraft.server.level.ServerPlayer)) {
++ continue;
++ }
++ net.minecraft.server.level.ServerPlayer player = (net.minecraft.server.level.ServerPlayer)_player;
++
++ double distance = player.distanceToSqr(sourceX, sourceY, sourceZ);
++ if (distance < closestDistance && predicate.test(player)) {
++ closest = player;
++ closestDistance = distance;
++ }
++ }
++
++ return closest;
++ }
++
++ public void getNearestPlayers(double sourceX, double sourceY, double sourceZ, java.util.function.Predicate<Entity> predicate,
++ double range, java.util.List<net.minecraft.server.level.ServerPlayer> ret) {
++ if (!this.playerGeneralAreaCacheSet) {
++ this.updateGeneralAreaCache();
++ }
++
++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<net.minecraft.server.level.ServerPlayer> nearby = this.playerGeneralAreaCache;
++
++ if (nearby == null) {
++ return;
++ }
++
++ double rangeSquared = range * range;
++
++ Object[] backingSet = nearby.getBackingSet();
++ for (int i = 0, len = backingSet.length; i < len; ++i) {
++ Object _player = backingSet[i];
++ if (!(_player instanceof net.minecraft.server.level.ServerPlayer)) {
++ continue;
++ }
++ net.minecraft.server.level.ServerPlayer player = (net.minecraft.server.level.ServerPlayer)_player;
++
++ if (range >= 0.0) {
++ double distanceSquared = player.distanceToSqr(sourceX, sourceY, sourceZ);
++ if (distanceSquared > rangeSquared) {
++ continue;
++ }
++ }
++
++ if (predicate == null || predicate.test(player)) {
++ ret.add(player);
++ }
++ }
++ }
++ // Paper end - optimise checkDespawn
+
+ public LevelChunk(ServerLevel world, ProtoChunk protoChunk, @Nullable LevelChunk.PostLoadProcessor entityLoader) {
+ this(world, protoChunk.getPos(), protoChunk.getUpgradeData(), protoChunk.unpackBlockTicks(), protoChunk.unpackFluidTicks(), protoChunk.getInhabitedTime(), protoChunk.getSections(), entityLoader, protoChunk.getBlendingData());