Paper/patches/server/0794-Optimise-nearby-player-lookups.patch
Spottedleaf 7d10cdea03
Merge tuinity (#6413)
This PR contains all of Tuinity's patches. Very notable ones are:

- Highly optimised collisions
- Optimised entity lookups by bounding box (Mojang made regressions in 1.17, this brings it back to 1.16)
- Starlight https://github.com/PaperMC/Starlight
- Rewritten dataconverter system https://github.com/PaperMC/DataConverter
- Random block ticking optimisation (wrongly dropped from Paper 1.17)
- Chunk ticking optimisations
- Anything else I've forgotten in the 60 or so patches

If you are a previous Tuinity user, your config will not migrate. You must do it yourself. The config options have simply been moved into paper.yml, so it will be an easy migration. However, please note that the chunk loading options in tuinity.yml are NOT compatible with the options in paper.yml. 

* Port tuinity, initial patchset

* Update gradle to 7.2

jmp said it fixes rebuildpatches not working for me. it fucking better

* Completely clean apply

* Remove tuinity config, add per player api patch

* Remove paper reobf mappings patch

* Properly update gradlew

* Force clean rebuild

* Mark fixups

Comments and ATs still need to be done

* grep -r "Tuinity"

* Fixup

* Ensure gameprofile lastaccess is written only under the state lock

* update URL for dataconverter

* Only clean rebuild tuinity patches

might fix merge conflicts

* Use UTF-8 for gradlew

* Clean rb patches again

* Convert block ids used as item ids

Neither the converters of pre 1.13 nor DFU handled these cases,
as by the time they were written the game at the time didn't
consider these ids valid - they would be air. Because of this,
some worlds have logspam since only DataConverter (not DFU or
legacy converters) will warn when an invalid id has been
seen.

While quite a few do need to now be considered as air, quite a lot
do not. So it makes sense to add conversion for these items, instead
of simply suppressing or ignoring the logs. I've now added id -> string conversion
for all block ids that could be used as items that existed in the game
before 1.7.10 (I have no interest in tracking down the
exact version block ids stopped working) that were on
https://minecraft-ids.grahamedgecombe.com/

Items that did not directly convert to new items will
be instead converted to air: stems, wheat crops, piston head,
tripwire wire block

* Fix LightPopulated parsing in V1466

The DFU code was checking if the number existed, not if it
didn't exist. I misread the original code.

* Always parse protochunk light sources unless it is marked as non-lit

Chunks not marked as lit will always go through the light engine,
so they should always have their block sources parsed.

* Update custom names to JSON for players

Missed this fix from CB, as it was inside
the DataFixers class.

I decided to double check all of the CB changes again:

DataFixers.java was the only area I missed, as I had inspected all
datafixer diffs and implemented them all into DataConverter. I also
checked Bootstrap.java again, and re-evaluated their changes. I had
previously done this, but determined that they were all bad.

The changes to make standing_sign block map to oak_sign block in
V1450 is bad, because that's not the item id V1450 accepts. Only
in 1.14 did oak_sign even exist, and as expected there is a converter
to rename all existing sign items/blocks.

The fix to register the portal block under id 1440 is useless, as
the flattenning logic will default to the lowest registered id - which
is the exact blockstate that CB registers into 1440. So it just
doesn't do anything.

The extra item ids in the id -> string converter are already added,
but I found this from EMC originally.

The change for the spawn egg id 23 -> Arrow is just wrong,
that id DOES correspond to TippedArrow, NOT Arrow. As
expected, the spawn egg already has a dedicated mapping for
Arrow, which is id 10 - which was Arrow's entity id.

I also ported a fix for the cooked_fished id update. This doesn't
really matter since there is already a dataconverter to fix this,
but the game didn't accept cooked_fished at the time. So I see
no harm.

* Review all converters and walkers

- Refactor V99 to have helper methods for defining entity/tile
  entity types
- Automatically namespace all ids that should be namespaced.
  While vanilla never saved non-namespaced data for things that
  are namespaced, plugins/users might have.
- Synchronised the identity ensure map in HelperBlockFlatteningV1450
- Code style consistency
- Add missing log warning in V102 for ITEM_NAME type conversion
- Use getBoolean instead of getByte
- Use ConverterAbstractEntityRename for V143 TippedArrow -> Arrow
  rename, as it will affect ENTITY_NAME type
- Always set isVillager to false in V502 for Zombie
- Register V808's converter under subversion 1 like DFU
- Register a breakpoint for V1.17.1. In the future, all final
  versions of major releases will have a breakpoint so that
  the work required to determine if a converter needs a breakpoint
  is minimal
- Validate that a dataconverter is only registered for a version
  that is registered
- ConverterFlattenTileEntity is actually ConverterFlattenEntity
  It even registered the converters under TILE_ENTITY, instead of
  ENTITY.
- Fix id comparison in V1492 STRUCTURE_FEATURE renamer
- Use ConverterAbstractStatsRename for V1510 stats renamer
  At the time I had written that class, the abstract renamer didn't
  exist.
- Ensure OwnerUUID is at least set to empty string in
  V1904 if the ocelot is converted to a cat (this is
  likely so that it retains a collar)
- Use generic read/write for Records in V1946
  Records is actually a list, not a map. So reading map was
  invalid.

* Always set light to zero when propagating decrease

This fixes an almost infinite loop where light values
would be spam queued on a very small subset on blocks.

This also likely fixes the memory issues people were
seeing.

* re-organize patches

* Apply and fix conflicts

* Revert some patches

getChunkAt retains chunks so that plugins don't spam loads
revert mc-4 fix will remain unless issues pop up

* Shuffle iterated chunks if per player is not enabled

Can help with some mob spawning stacking up at locations

* Make per player default, migrate all configs

* Adjust comments in fixups

* Rework config for player chunk loader

Old config is not compatible. Move all configs to be
under `settings` in paper.yml

The player chunk loader has been modified to
less aggressively load chunks, but to send
chunks at higher rates compared to tuinity. There are
new config entries to tune this behavior.

* Add back old constructor to CompressionEncoder/Decoder (fixes
 Tuinity #358)

* Raise chunk loading default limits

* Reduce worldgen thread workers for lower core count cpus

* Raise limits for chunk loading config

Also place it under `chunk-loading`

* Disable max chunk send rate by default

* Fix conflicts and rebuild patches

* Drop default send rate again

Appears to be still causing problems for no known reason

* Raise chunk send limits to 100 per player

While a low limit fixes ping issues for some people, most people
do not suffer from this issue and thus should not suffer from
an extremely slow load-in rate.

* Rebase part 1

Autosquash the fixups

* Move not implemented up

* Fixup mc-dev fixes

Missed this one

* Rebase per player viewdistance api into the original api patch

* Remove old light engine patch part 1

The prioritisation must be kept from it, so that part
has been rebased into the priority patch.
Part 2 will deal with rebasing all of the patches _after_

* Rebase remaining patches for old light patch removal

* Remove other mid tick patch

* Remove Optimize-PlayerChunkMap-memory-use-for-visibleChunks.patch

Replaced by `Do not copy visible chunks`

* Revert AT for Vec3i setX/Y/Z

The class is immutable. set should not be exposed

* Remove old IntegerUtil class

* Replace old CraftChunk#getEntities patch

* Remove import for SWMRNibbleArray in ChunkAccess

* Finished merge checklist

* Remove ensureTickThread impl in urgency patch

Co-authored-by: Spottedleaf <Spottedleaf@users.noreply.github.com>
Co-authored-by: Jason Penilla <11360596+jpenilla@users.noreply.github.com>
2021-08-31 04:02:11 -07:00

431 lines
25 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 54cb9f909df7d1041cec553e1f54ff8ecff48bbe..82b6b099e3014ab539f4155639efd33ebba88a3e 100644
--- a/src/main/java/net/minecraft/server/level/ChunkHolder.java
+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java
@@ -242,6 +242,12 @@ public class ChunkHolder {
long key = net.minecraft.server.MCUtil.getCoordinateKey(this.pos);
this.playersInMobSpawnRange = this.chunkMap.playerMobSpawnMap.getObjectsInRange(key);
this.playersInChunkTickRange = this.chunkMap.playerChunkTickRangeMap.getObjectsInRange(key);
+ // Paper start - optimise checkDespawn
+ LevelChunk chunk = this.getFullChunkUnchecked();
+ if (chunk != null) {
+ chunk.updateGeneralAreaCache();
+ }
+ // Paper end - optimise checkDespawn
}
// Paper end - optimise isOutsideOfRange
long lastAutoSaveTime; // Paper - incremental autosave
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
index b5015f0a59aeb77636e6b1dd4df7381660d0788c..bc315cc74cfded9f96e55f1d5de8a41334af8017 100644
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
@@ -218,6 +218,12 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
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
+ // 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
void addPlayerToDistanceMaps(ServerPlayer player) {
int chunkX = MCUtil.getChunkCoordinate(player.getX());
@@ -238,6 +244,9 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
this.playerChunkTickRangeMap.add(player, chunkX, chunkZ, DistanceManager.MOB_SPAWN_RANGE);
// Paper end - optimise PlayerChunkMap#isOutsideRange
this.playerChunkManager.addPlayer(player); // Paper - replace chunk loader
+ // Paper start - optimise checkDespawn
+ this.playerGeneralAreaMap.add(player, chunkX, chunkZ, GENERAL_AREA_MAP_SQUARE_RADIUS);
+ // Paper end - optimise checkDespawn
}
void removePlayerFromDistanceMaps(ServerPlayer player) {
@@ -251,6 +260,9 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
this.playerChunkTickRangeMap.remove(player);
// Paper end - optimise PlayerChunkMap#isOutsideRange
this.playerChunkManager.removePlayer(player); // Paper - replace chunk loader
+ // Paper start - optimise checkDespawn
+ this.playerGeneralAreaMap.remove(player);
+ // Paper end - optimise checkDespawn
}
void updateMaps(ServerPlayer player) {
@@ -269,6 +281,9 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
this.playerChunkTickRangeMap.update(player, chunkX, chunkZ, DistanceManager.MOB_SPAWN_RANGE);
// Paper end - optimise PlayerChunkMap#isOutsideRange
this.playerChunkManager.updatePlayer(player); // Paper - replace chunk loader
+ // Paper start - optimise checkDespawn
+ this.playerGeneralAreaMap.update(player, chunkX, chunkZ, GENERAL_AREA_MAP_SQUARE_RADIUS);
+ // Paper end - optimise checkDespawn
}
// Paper end
// Paper start
@@ -427,6 +442,23 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
}
});
// Paper end - optimise PlayerChunkMap#isOutsideRange
+ // 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
}
// Paper start - Chunk Prioritization
@@ -740,7 +772,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
} else {
if (holder != null) {
holder.setTicketLevel(level);
- holder.updateRanges(); // Paper - optimise isOutsideOfRange
+ // Paper - move to correct place
}
if (holder != null) {
@@ -755,6 +787,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
holder = (ChunkHolder) this.pendingUnloads.remove(pos);
if (holder != null) {
holder.setTicketLevel(level);
+ holder.updateRanges(); // Paper - optimise isOutsideOfRange // Paper - move to correct place
} else {
holder = new ChunkHolder(new ChunkPos(pos), level, this.level, this.lightEngine, this.queueSorter, this);
// Paper start
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
index 3b26b6a24516bb2e2331edb7dc41a28b57835aa1..6e8115c3ab1f6986fdf3b3716cb09add91424306 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -392,6 +392,83 @@ public class ServerLevel extends Level implements WorldGenLevel {
}
}
// Paper end - rewrite ticklistserver
+ // 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, WorldData -> WorldDataServer
public ServerLevel(MinecraftServer minecraftserver, Executor executor, LevelStorageSource.LevelStorageAccess convertable_conversionsession, ServerLevelData iworlddataserver, ResourceKey<Level> resourcekey, DimensionType dimensionmanager, ChunkProgressListener worldloadlistener, ChunkGenerator chunkgenerator, boolean flag, long i, List<CustomSpawner> list, boolean flag1, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider) {
@@ -492,6 +569,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 bada11542390b7575466f0e7062470665b8266c4..8a864238e154e2131834d013652746b7e7a78c97 100644
--- a/src/main/java/net/minecraft/world/entity/Mob.java
+++ b/src/main/java/net/minecraft/world/entity/Mob.java
@@ -789,7 +789,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.hardDespawnDistance + 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); // CraftBukkit - decompile error
diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
index 5efafcadda2bfc5cab8ce5d3ede83a0467fe696f..b93056b91e7ebd49e6ddb53ccb6c05c056088df9 100644
--- a/src/main/java/net/minecraft/world/level/Level.java
+++ b/src/main/java/net/minecraft/world/level/Level.java
@@ -246,6 +246,69 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
return ret;
}
// 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
protected Level(WritableLevelData worlddatamutable, ResourceKey<Level> resourcekey, final DimensionType dimensionmanager, Supplier<ProfilerFiller> supplier, boolean flag, boolean flag1, long i, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider, org.bukkit.World.Environment env, java.util.concurrent.Executor executor) { // Paper - Anti-Xray - Pass executor
this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName()); // Spigot
diff --git a/src/main/java/net/minecraft/world/level/NaturalSpawner.java b/src/main/java/net/minecraft/world/level/NaturalSpawner.java
index 88145f04989c71a686aae1b486087ecdf55e268c..403f50ef908f65c62d4aaa7e5328faa0a7654480 100644
--- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java
+++ b/src/main/java/net/minecraft/world/level/NaturalSpawner.java
@@ -262,7 +262,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);
@@ -335,7 +335,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().closerThan((Position) (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.isPositionEntityTicking((BlockPos) pos));
+ return squaredDistance <= 576.0D ? false : (world.getSharedSpawnPos().closerThan((Position) (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.isPositionEntityTicking((BlockPos) pos)); // Paper - diff on change, copy into caller
}
private static Boolean isValidSpawnPostitionForType(ServerLevel world, MobCategory group, StructureFeatureManager 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 9e75ee7f43722f05dd5df4ef00f7d3e271f4c6e5..86686c24b0b7de4b4bfadbc77419a8872a8e86ee 100644
--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
@@ -369,6 +369,93 @@ public class LevelChunk implements 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 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 worldserver, ProtoChunk protoChunk, @Nullable Consumer<LevelChunk> consumer) {
this(worldserver, protoChunk.getPos(), protoChunk.getBiomes(), protoChunk.getUpgradeData(), protoChunk.getBlockTicks(), protoChunk.getLiquidTicks(), protoChunk.getInhabitedTime(), protoChunk.getSections(), consumer);