Paper/patches/server/0757-Optimise-nearby-player-lookups.patch
Nassim Jahnke ef0e5a642d
Updated Upstream (Bukkit/CraftBukkit/Spigot)
Upstream has released updates that appear to apply and compile correctly.
This update has not been tested by PaperMC and as with ANY update, please do your own testing

Bukkit Changes:
9ae3f10f SPIGOT-3842: Add Player#fireworkBoost() and expand Firework API
48c0c547 PR-786: Add methods to get sounds from entities

CraftBukkit Changes:
5cc9c022a SPIGOT-7152: Handle hand item changing during air interact event
4ffa1acf6 SPIGOT-7154: Players get kicked when interacting with a conversation
4daa21123 SPIGOT-3842: Add Player#fireworkBoost() and expand Firework API
e5d6a9bbf PR-1100: Add methods to get sounds from entities
b7e9f1c8b SPIGOT-7146: Reduce use of Material switch in ItemMeta

Spigot Changes:
4c157bb4 Rebuild patches
2022-09-12 13:31:45 +02:00

428 lines
24 KiB
Diff

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <spottedleaf@spottedleaf.dev>
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 538f21e6bc29c0307441fe4899dc7f600d2d1a04..09f262de1b12b09013f8277b25d13ffcf53b96d8 100644
--- a/src/main/java/net/minecraft/server/level/ChunkHolder.java
+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java
@@ -89,6 +89,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() {
@@ -101,6 +107,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 f47e2d4a7343f3e3ee68f36688720cedc58861dc..aa1b6b692f9107ef1b5091714f1ecc8b29da9897 100644
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
@@ -165,6 +165,13 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerMobDistanceMap; // Paper
public final ReferenceOpenHashSet<ChunkHolder> needsChangeBroadcasting = new ReferenceOpenHashSet<>();
+ // 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
+
// CraftBukkit start - recursion-safe executor for Chunk loadCallback() and unloadCallback()
public final CallbackExecutor callbackExecutor = new CallbackExecutor();
public static final class CallbackExecutor implements java.util.concurrent.Executor, Runnable {
@@ -242,6 +249,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
// Paper end - use distance map to optimise entity tracker
// 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
+ this.playerGeneralAreaMap.add(player, chunkX, chunkZ, GENERAL_AREA_MAP_SQUARE_RADIUS); // Paper - optimise checkDespawn
// Paper start - per player mob spawning
if (this.playerMobDistanceMap != null) {
this.playerMobDistanceMap.add(player, chunkX, chunkZ, this.distanceManager.getSimulationDistance());
@@ -260,6 +268,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);
@@ -280,6 +289,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
}
// Paper end - use distance map to optimise entity tracker
this.playerChunkTickRangeMap.update(player, chunkX, chunkZ, DistanceManager.MOB_SPAWN_RANGE); // Paper - optimise ChunkMap#anyPlayerCloseEnoughForSpawning
+ this.playerGeneralAreaMap.update(player, chunkX, chunkZ, GENERAL_AREA_MAP_SQUARE_RADIUS); // Paper - optimise checkDespawn
// Paper start - per player mob spawning
if (this.playerMobDistanceMap != null) {
this.playerMobDistanceMap.update(player, chunkX, chunkZ, this.distanceManager.getSimulationDistance());
@@ -454,6 +464,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 bcd1b2534af33e7a9d184e0ea4c9c0a4b58dacc8..25149dde919738859f6fb6b2d0405e90d1732f2b 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -411,6 +411,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
@@ -513,6 +591,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 76a2d00324a85419548005e0aa3cbd8b891b8257..931de769a3b7c993d151f3ee8e1038d95d3899a3 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 2981ba61e347b8660082ff946521fc7f219d2c0d..c85380c3bf3bf4448a28a91af78f41c235a583e4 100644
--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
@@ -235,6 +235,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());