From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Spottedleaf 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 214e0657035f82e5266de06e31975d6b45405019..51c112656f26f142bd6c126253520e812a338783 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 playersInMobSpawnRange; + com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet 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 1c638f65d33e5890a7a32bf7e89d484ae2afc093..4b7c39595302a738e6d4a1749473ef9c81f84fea 100644 --- a/src/main/java/net/minecraft/server/level/ChunkMap.java +++ b/src/main/java/net/minecraft/server/level/ChunkMap.java @@ -159,12 +159,24 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider // Paper start - distance maps private final com.destroystokyo.paper.util.misc.PooledLinkedHashSets 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)); @@ -175,6 +187,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); @@ -187,6 +203,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)); @@ -278,6 +295,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 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 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 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 newState) -> { + ChunkHolder playerChunk = ChunkMap.this.getUpdatingChunkIfPresent(MCUtil.getCoordinateKey(rangeX, rangeZ)); + if (playerChunk != null) { + playerChunk.playersInMobSpawnRange = newState; + } + }); + // Paper end - optimise ChunkMap#anyPlayerCloseEnoughForSpawning } protected ChunkGenerator generator() { @@ -837,43 +886,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 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 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> 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 e465e3e45eeeecc31dafc1f04505c04ea382615a..5be828ad63184ee01519ef937e98f621a4e83dee 100644 --- a/src/main/java/net/minecraft/server/level/ServerPlayer.java +++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java @@ -270,6 +270,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 cachedSingleHashSet; // Paper public PlayerNaturallySpawnCreaturesEvent playerNaturallySpawnedEvent; // Paper