PaperMC/patches/server/0412-Optimize-anyPlayerCloseEnoughForSpawning-to-use-dist.patch
Jake Potrebic afe633df08
convert API/server tests to mockito (#8848)
* convert API tests to mockito

* convert server tests to mockito

* add co-author
2023-02-15 13:27:40 -08:00

346 lines
22 KiB
Diff

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 anyPlayerCloseEnoughForSpawning to use distance maps
Use a distance map to find the players in range quickly
diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java
index 2ba3bb4e5670ece798a8882801a856d82851c00a..a61f55ed1fbe5aac5289014cb95cb6950b4c77fa 100644
--- a/src/main/java/net/minecraft/server/level/ChunkHolder.java
+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java
@@ -83,16 +83,29 @@ public class ChunkHolder {
// Paper start
public void onChunkAdd() {
-
+ // Paper start - optimise anyPlayerCloseEnoughForSpawning
+ long key = io.papermc.paper.util.MCUtil.getCoordinateKey(this.pos);
+ this.playersInMobSpawnRange = this.chunkMap.playerMobSpawnMap.getObjectsInRange(key);
+ this.playersInChunkTickRange = this.chunkMap.playerChunkTickRangeMap.getObjectsInRange(key);
+ // Paper end - optimise anyPlayerCloseEnoughForSpawning
}
public void onChunkRemove() {
-
+ // Paper start - optimise anyPlayerCloseEnoughForSpawning
+ this.playersInMobSpawnRange = null;
+ this.playersInChunkTickRange = null;
+ // Paper end - optimise anyPlayerCloseEnoughForSpawning
}
// Paper end
public final io.papermc.paper.chunk.system.scheduling.NewChunkHolder newChunkHolder; // Paper - rewrite chunk system
+ // Paper start - optimise anyPlayerCloseEnoughForSpawning
+ // cached here to avoid a map lookup
+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> playersInMobSpawnRange;
+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> playersInChunkTickRange;
+ // Paper end - optimise anyPlayerCloseEnoughForSpawning
+
public ChunkHolder(ChunkPos pos, LevelHeightAccessor world, LevelLightEngine lightingProvider, ChunkHolder.PlayerProvider playersWatchingChunkProvider, io.papermc.paper.chunk.system.scheduling.NewChunkHolder newChunkHolder) { // Paper - rewrite chunk system
this.newChunkHolder = newChunkHolder; // Paper - rewrite chunk system
this.chunkToSaveHistory = null;
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
index 69ac6b4fbdf4cebbaa36234e17e2d51c732de8af..e87ade8697a33b9ea23dce59838a9ccd02ab09f6 100644
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
@@ -156,12 +156,24 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
// Paper start - distance maps
private final com.destroystokyo.paper.util.misc.PooledLinkedHashSets<ServerPlayer> pooledLinkedPlayerHashSets = new com.destroystokyo.paper.util.misc.PooledLinkedHashSets<>();
public final io.papermc.paper.chunk.PlayerChunkLoader playerChunkManager = new io.papermc.paper.chunk.PlayerChunkLoader(this, this.pooledLinkedPlayerHashSets); // Paper - replace chunk loader
+ // Paper start - optimise ChunkMap#anyPlayerCloseEnoughForSpawning
+ // 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 ChunkMap#anyPlayerCloseEnoughForSpawning
void addPlayerToDistanceMaps(ServerPlayer player) {
this.playerChunkManager.addPlayer(player); // Paper - replace chunk loader
int chunkX = MCUtil.getChunkCoordinate(player.getX());
int chunkZ = MCUtil.getChunkCoordinate(player.getZ());
// 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
// Paper start - per player mob spawning
if (this.playerMobDistanceMap != null) {
this.playerMobDistanceMap.add(player, chunkX, chunkZ, io.papermc.paper.chunk.system.ChunkSystem.getTickViewDistance(player));
@@ -172,6 +184,10 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
void removePlayerFromDistanceMaps(ServerPlayer player) {
this.playerChunkManager.removePlayer(player); // Paper - replace chunk loader
+ // Paper start - optimise ChunkMap#anyPlayerCloseEnoughForSpawning
+ this.playerMobSpawnMap.remove(player);
+ this.playerChunkTickRangeMap.remove(player);
+ // Paper end - optimise ChunkMap#anyPlayerCloseEnoughForSpawning
// Paper start - per player mob spawning
if (this.playerMobDistanceMap != null) {
this.playerMobDistanceMap.remove(player);
@@ -184,6 +200,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
int chunkZ = MCUtil.getChunkCoordinate(player.getZ());
// Note: players need to be explicitly added to distance maps before they can be updated
this.playerChunkManager.updatePlayer(player); // Paper - replace chunk loader
+ this.playerChunkTickRangeMap.update(player, chunkX, chunkZ, DistanceManager.MOB_SPAWN_RANGE); // Paper - optimise ChunkMap#anyPlayerCloseEnoughForSpawning
// Paper start - per player mob spawning
if (this.playerMobDistanceMap != null) {
this.playerMobDistanceMap.update(player, chunkX, chunkZ, io.papermc.paper.chunk.system.ChunkSystem.getTickViewDistance(player));
@@ -275,6 +292,38 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
this.regionManagers.add(this.dataRegionManager);
// Paper end
this.playerMobDistanceMap = this.level.paperConfig().entities.spawning.perPlayerMobSpawns ? new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets) : null; // Paper
+ // Paper start - optimise ChunkMap#anyPlayerCloseEnoughForSpawning
+ this.playerChunkTickRangeMap = 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) -> {
+ ChunkHolder playerChunk = ChunkMap.this.getUpdatingChunkIfPresent(MCUtil.getCoordinateKey(rangeX, rangeZ));
+ if (playerChunk != null) {
+ playerChunk.playersInChunkTickRange = newState;
+ }
+ },
+ (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ,
+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> newState) -> {
+ ChunkHolder playerChunk = ChunkMap.this.getUpdatingChunkIfPresent(MCUtil.getCoordinateKey(rangeX, rangeZ));
+ if (playerChunk != null) {
+ playerChunk.playersInChunkTickRange = newState;
+ }
+ });
+ this.playerMobSpawnMap = 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) -> {
+ ChunkHolder playerChunk = ChunkMap.this.getUpdatingChunkIfPresent(MCUtil.getCoordinateKey(rangeX, rangeZ));
+ if (playerChunk != null) {
+ playerChunk.playersInMobSpawnRange = newState;
+ }
+ },
+ (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ,
+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> newState) -> {
+ ChunkHolder playerChunk = ChunkMap.this.getUpdatingChunkIfPresent(MCUtil.getCoordinateKey(rangeX, rangeZ));
+ if (playerChunk != null) {
+ playerChunk.playersInMobSpawnRange = newState;
+ }
+ });
+ // Paper end - optimise ChunkMap#anyPlayerCloseEnoughForSpawning
}
protected ChunkGenerator generator() {
@@ -834,43 +883,48 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
return this.anyPlayerCloseEnoughForSpawning(pos, false);
}
- boolean anyPlayerCloseEnoughForSpawning(ChunkPos chunkcoordintpair, boolean reducedRange) {
- int chunkRange = level.spigotConfig.mobSpawnRange;
- chunkRange = (chunkRange > level.spigotConfig.viewDistance) ? (byte) level.spigotConfig.viewDistance : chunkRange;
- chunkRange = (chunkRange > 8) ? 8 : chunkRange;
-
- final int finalChunkRange = chunkRange; // Paper for lambda below
- //double blockRange = (reducedRange) ? Math.pow(chunkRange << 4, 2) : 16384.0D; // Paper - use from event
- double blockRange = 16384.0D; // Paper
- // Spigot end
- long i = chunkcoordintpair.toLong();
+ // Paper start - optimise anyPlayerCloseEnoughForSpawning
+ final boolean anyPlayerCloseEnoughForSpawning(ChunkPos chunkcoordintpair, boolean reducedRange) {
+ return this.anyPlayerCloseEnoughForSpawning(this.getUpdatingChunkIfPresent(chunkcoordintpair.toLong()), chunkcoordintpair, reducedRange);
+ }
- if (!this.distanceManager.hasPlayersNearby(i)) {
+ final boolean anyPlayerCloseEnoughForSpawning(ChunkHolder playerchunk, ChunkPos 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<ServerPlayer> playersInRange = reducedRange ? playerchunk.playersInMobSpawnRange : playerchunk.playersInChunkTickRange;
+ if (playersInRange == null) {
return false;
- } else {
- Iterator iterator = this.playerMap.getPlayers(i).iterator();
-
- ServerPlayer entityplayer;
+ }
+ Object[] backingSet = playersInRange.getBackingSet();
- do {
- if (!iterator.hasNext()) {
- return false;
+ if (reducedRange) {
+ for (int i = 0, len = backingSet.length; i < len; ++i) {
+ Object raw = backingSet[i];
+ if (!(raw instanceof ServerPlayer player)) {
+ continue;
}
-
- entityplayer = (ServerPlayer) iterator.next();
- // Paper start - add PlayerNaturallySpawnCreaturesEvent
- com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent event;
- blockRange = 16384.0D;
- if (reducedRange) {
- event = entityplayer.playerNaturallySpawnedEvent;
- if (event == null || event.isCancelled()) return false;
- blockRange = (double) ((event.getSpawnRadius() << 4) * (event.getSpawnRadius() << 4));
+ // don't check spectator and whatnot, already handled by mob spawn map update
+ if (euclideanDistanceSquared(chunkcoordintpair, player) < player.lastEntitySpawnRadiusSquared) {
+ return true; // in range
}
- // Paper end
- } while (!this.playerIsCloseEnoughForSpawning(entityplayer, chunkcoordintpair, blockRange)); // Spigot
-
- return true;
+ }
+ } else {
+ final double range = (DistanceManager.MOB_SPAWN_RANGE * 16) * (DistanceManager.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 ServerPlayer player)) {
+ continue;
+ }
+ // don't check spectator and whatnot, already handled by mob spawn map update
+ if (euclideanDistanceSquared(chunkcoordintpair, player) < range) {
+ return true; // in range
+ }
+ }
}
+ // no players in range
+ return false;
+ // Paper end - optimise anyPlayerCloseEnoughForSpawning
}
public List<ServerPlayer> getPlayersCloseForSpawning(ChunkPos pos) {
diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java
index d3c3db919e9b0507e8543313d9028394e5163673..52cba8f68d274cce106304aef1249a95474d3238 100644
--- a/src/main/java/net/minecraft/server/level/DistanceManager.java
+++ b/src/main/java/net/minecraft/server/level/DistanceManager.java
@@ -54,7 +54,7 @@ public abstract class DistanceManager {
private static final int BLOCK_TICKING_LEVEL_THRESHOLD = 33;
final Long2ObjectMap<ObjectSet<ServerPlayer>> playersPerChunk = new Long2ObjectOpenHashMap();
// Paper - rewrite chunk system
- private final DistanceManager.FixedPlayerDistanceChunkTracker naturalSpawnChunkCounter = new DistanceManager.FixedPlayerDistanceChunkTracker(8);
+ public static final int MOB_SPAWN_RANGE = 8; // private final DistanceManager.FixedPlayerDistanceChunkTracker naturalSpawnChunkCounter = new DistanceManager.FixedPlayerDistanceChunkTracker(8); // Paper - no longer used
//private final TickingTracker tickingTicketsTracker = new TickingTracker(); // Paper - no longer used
//private final DistanceManager.PlayerTicketTracker playerTicketManager = new DistanceManager.PlayerTicketTracker(33); // Paper - no longer used
// Paper - rewrite chunk system
@@ -142,7 +142,7 @@ public abstract class DistanceManager {
long i = chunkcoordintpair.toLong();
// Paper - no longer used
- this.naturalSpawnChunkCounter.update(i, 0, true);
+ //this.naturalSpawnChunkCounter.update(i, 0, true); // Paper - no longer used
//this.playerTicketManager.update(i, 0, true); // Paper - no longer used
//this.tickingTicketsTracker.addTicket(TicketType.PLAYER, chunkcoordintpair, this.getPlayerTicketLevel(), chunkcoordintpair); // Paper - no longer used
}
@@ -156,7 +156,7 @@ public abstract class DistanceManager {
if (objectset != null) objectset.remove(player); // Paper - some state corruption happens here, don't crash, clean up gracefully.
if (objectset == null || objectset.isEmpty()) { // Paper
this.playersPerChunk.remove(i);
- this.naturalSpawnChunkCounter.update(i, Integer.MAX_VALUE, false);
+ // this.naturalSpawnChunkCounter.update(i, Integer.MAX_VALUE, false); // Paper - no longer used
//this.playerTicketManager.update(i, Integer.MAX_VALUE, false); // Paper - no longer used
//this.tickingTicketsTracker.removeTicket(TicketType.PLAYER, chunkcoordintpair, this.getPlayerTicketLevel(), chunkcoordintpair); // Paper - no longer used
}
@@ -198,13 +198,17 @@ public abstract class DistanceManager {
}
public int getNaturalSpawnChunkCount() {
- this.naturalSpawnChunkCounter.runAllUpdates();
- return this.naturalSpawnChunkCounter.chunks.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 hasPlayersNearby(long chunkPos) {
- this.naturalSpawnChunkCounter.runAllUpdates();
- return this.naturalSpawnChunkCounter.chunks.containsKey(chunkPos);
+ // Paper start - use distance map to implement
+ // note: this is the is spawn chunk method
+ return this.chunkMap.playerChunkTickRangeMap.getObjectsInRange(chunkPos) != null;
+ // Paper end - use distance map to implement
}
public String getDebugStatus() {
diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
index 665e088cb0b73f6a0c62f29c56da462bab7c927e..298e4468f7b5346733257f7117f76c66e9a1d8f0 100644
--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
@@ -658,6 +658,37 @@ public class ServerChunkCache extends ChunkSource {
if (flag) {
this.chunkMap.tick();
} else {
+ // Paper start - optimize isOutisdeRange
+ ChunkMap playerChunkMap = this.chunkMap;
+ for (ServerPlayer player : this.level.players) {
+ if (!player.affectsSpawning || player.isSpectator()) {
+ playerChunkMap.playerMobSpawnMap.remove(player);
+ continue;
+ }
+
+ int viewDistance = this.chunkMap.getEffectiveViewDistance();
+
+ // copied and modified from isOutisdeRange
+ int chunkRange = level.spigotConfig.mobSpawnRange;
+ chunkRange = (chunkRange > viewDistance) ? (byte)viewDistance : chunkRange;
+ chunkRange = (chunkRange > DistanceManager.MOB_SPAWN_RANGE) ? DistanceManager.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 = io.papermc.paper.util.MCUtil.getChunkCoordinate(player.getX());
+ int chunkZ = io.papermc.paper.util.MCUtil.getChunkCoordinate(player.getZ());
+
+ playerChunkMap.playerMobSpawnMap.addOrUpdate(player, chunkX, chunkZ, range);
+ player.lastEntitySpawnRadiusSquared = (double)((range << 4) * (range << 4)); // used in anyPlayerCloseEnoughForSpawning
+ player.playerNaturallySpawnedEvent = event;
+ }
+ // Paper end - optimize isOutisdeRange
LevelData worlddata = this.level.getLevelData();
ProfilerFiller gameprofilerfiller = this.level.getProfiler();
@@ -701,15 +732,7 @@ public class ServerChunkCache extends ChunkSource {
boolean flag2 = this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && !this.level.players().isEmpty(); // CraftBukkit
Collections.shuffle(list);
- // Paper start - call player naturally spawn event
- int chunkRange = level.spigotConfig.mobSpawnRange;
- chunkRange = (chunkRange > level.spigotConfig.viewDistance) ? (byte) level.spigotConfig.viewDistance : chunkRange;
- chunkRange = Math.min(chunkRange, 8);
- for (ServerPlayer entityPlayer : this.level.players()) {
- entityPlayer.playerNaturallySpawnedEvent = new com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent(entityPlayer.getBukkitEntity(), (byte) chunkRange);
- entityPlayer.playerNaturallySpawnedEvent.callEvent();
- };
- // Paper end
+ // Paper - moved natural spawn event up
Iterator iterator1 = list.iterator();
while (iterator1.hasNext()) {
@@ -717,9 +740,9 @@ public class ServerChunkCache extends ChunkSource {
LevelChunk chunk1 = chunkproviderserver_a.chunk;
ChunkPos chunkcoordintpair = chunk1.getPos();
- if (this.level.isNaturalSpawningAllowed(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(chunkcoordintpair)) {
+ if (this.level.isNaturalSpawningAllowed(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(chunkproviderserver_a.holder, chunkcoordintpair, false)) { // Paper - optimise anyPlayerCloseEnoughForSpawning
chunk1.incrementInhabitedTime(j);
- if (flag2 && (this.spawnEnemies || this.spawnFriendlies) && this.level.getWorldBorder().isWithinBounds(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(chunkcoordintpair, true)) { // Spigot
+ if (flag2 && (this.spawnEnemies || this.spawnFriendlies) && this.level.getWorldBorder().isWithinBounds(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(chunkproviderserver_a.holder, chunkcoordintpair, true)) { // Spigot // Paper - optimise anyPlayerCloseEnoughForSpawning
NaturalSpawner.spawnForChunk(this.level, chunk1, spawnercreature_d, this.spawnFriendlies, this.spawnEnemies, flag1);
}
diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java
index 33b6f23f483535714fc020da47c79fca33271c62..35d28066295bfbdac3f35b0565ea658bd20de892 100644
--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java
+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java
@@ -264,6 +264,7 @@ public class ServerPlayer extends Player {
public String kickLeaveMessage = null; // SPIGOT-3034: Forward leave message to PlayerQuitEvent
// CraftBukkit end
public boolean isRealPlayer; // Paper
+ public double lastEntitySpawnRadiusSquared; // Paper - optimise isOutsideRange, this field is in blocks
public final com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> cachedSingleHashSet; // Paper
public PlayerNaturallySpawnCreaturesEvent playerNaturallySpawnedEvent; // Paper