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 74d674b2684b0db4aa6c183edc6091d53e9ee882..626bcbc6dd013260c3f8b38a1d14e7ba35dc1e01 100644 --- a/src/main/java/net/minecraft/server/level/ChunkHolder.java +++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java @@ -73,6 +73,18 @@ public class ChunkHolder { boolean isUpdateQueued = false; // Paper private final ChunkMap chunkMap; // Paper + // 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; + + void updateRanges() { + long key = net.minecraft.server.MCUtil.getCoordinateKey(this.pos); + this.playersInMobSpawnRange = this.chunkMap.playerMobSpawnMap.getObjectsInRange(key); + this.playersInChunkTickRange = this.chunkMap.playerChunkTickRangeMap.getObjectsInRange(key); + } + // Paper end - optimise anyPlayerCloseEnoughForSpawning + public ChunkHolder(ChunkPos pos, int level, LevelHeightAccessor world, LevelLightEngine lightingProvider, ChunkHolder.LevelChangeListener levelUpdateListener, ChunkHolder.PlayerProvider playersWatchingChunkProvider) { this.futures = new AtomicReferenceArray(ChunkHolder.CHUNK_STATUSES.size()); this.fullChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE; @@ -94,6 +106,7 @@ public class ChunkHolder { this.setTicketLevel(level); this.changedBlocksPerSection = new ShortSet[world.getSectionsCount()]; this.chunkMap = (ChunkMap)playersWatchingChunkProvider; // Paper + this.updateRanges(); // Paper - optimise anyPlayerCloseEnoughForSpawning } // CraftBukkit start diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java index 62a8482b73796f2c6b76c0e039cb21e799bc9416..44dc880042dc16a4146c6d3bb35a8eb7b28fd3a4 100644 --- a/src/main/java/net/minecraft/server/level/ChunkMap.java +++ b/src/main/java/net/minecraft/server/level/ChunkMap.java @@ -177,21 +177,40 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider final CallbackExecutor chunkLoadConversionCallbackExecutor = new CallbackExecutor(); // Paper // Paper start - distance maps private final com.destroystokyo.paper.util.misc.PooledLinkedHashSets pooledLinkedPlayerHashSets = new com.destroystokyo.paper.util.misc.PooledLinkedHashSets<>(); + // 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) { 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 + // Paper start - optimise ChunkMap#anyPlayerCloseEnoughForSpawning + this.playerChunkTickRangeMap.update(player, chunkX, chunkZ, DistanceManager.MOB_SPAWN_RANGE); + this.playerChunkTickRangeMap.add(player, chunkX, chunkZ, DistanceManager.MOB_SPAWN_RANGE); + // Paper end - optimise ChunkMap#anyPlayerCloseEnoughForSpawning } void removePlayerFromDistanceMaps(ServerPlayer player) { - + // Paper start - optimise ChunkMap#anyPlayerCloseEnoughForSpawning + this.playerMobSpawnMap.remove(player); + this.playerChunkTickRangeMap.remove(player); + // Paper end - optimise ChunkMap#anyPlayerCloseEnoughForSpawning } void updateMaps(ServerPlayer player) { 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.update(player, chunkX, chunkZ, DistanceManager.MOB_SPAWN_RANGE); // Paper - optimise ChunkMap#anyPlayerCloseEnoughForSpawning } // Paper end // Paper start @@ -266,6 +285,38 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider this.regionManagers.add(this.dataRegionManager); // Paper end this.playerMobDistanceMap = this.level.paperConfig.perPlayerMobSpawns ? new com.destroystokyo.paper.util.PlayerMobDistanceMap() : 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() { @@ -466,6 +517,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider } else { if (holder != null) { holder.setTicketLevel(level); + holder.updateRanges(); // Paper - optimise anyPlayerCloseEnoughForSpawning } if (holder != null) { @@ -1310,43 +1362,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 8868ffcda194e8c2300181a2cdda9337dbde6284..95f195980e28bb59f43e5ca1d5e79ebe8c3ddaea 100644 --- a/src/main/java/net/minecraft/server/level/DistanceManager.java +++ b/src/main/java/net/minecraft/server/level/DistanceManager.java @@ -48,7 +48,7 @@ public abstract class DistanceManager { final Long2ObjectMap> playersPerChunk = new Long2ObjectOpenHashMap(); public final Long2ObjectOpenHashMap>> tickets = new Long2ObjectOpenHashMap(); private final DistanceManager.ChunkTicketTracker ticketTracker = new DistanceManager.ChunkTicketTracker(); - private final DistanceManager.FixedPlayerDistanceChunkTracker naturalSpawnChunkCounter = new DistanceManager.FixedPlayerDistanceChunkTracker(8); + public static final int MOB_SPAWN_RANGE = 8; // private final ChunkMapDistance.b f = new ChunkMapDistance.b(8); // Paper - no longer used private final TickingTracker tickingTicketsTracker = new TickingTracker(); private final DistanceManager.PlayerTicketTracker playerTicketManager = new DistanceManager.PlayerTicketTracker(33); // Paper start use a queue, but still keep unique requirement @@ -125,7 +125,7 @@ public abstract class DistanceManager { protected abstract ChunkHolder updateChunkScheduling(long pos, int level, @Nullable ChunkHolder holder, int k); public boolean runAllUpdates(ChunkMap chunkStorage) { - this.naturalSpawnChunkCounter.runAllUpdates(); + //this.f.a(); // Paper - no longer used this.tickingTicketsTracker.runAllUpdates(); this.playerTicketManager.runAllUpdates(); int i = Integer.MAX_VALUE - this.ticketTracker.runDistanceUpdates(Integer.MAX_VALUE); @@ -272,7 +272,7 @@ public abstract class DistanceManager { ((ObjectSet) this.playersPerChunk.computeIfAbsent(i, (j) -> { return new ObjectOpenHashSet(); })).add(player); - this.naturalSpawnChunkCounter.update(i, 0, true); + //this.f.update(i, 0, true); // Paper - no longer used this.playerTicketManager.update(i, 0, true); this.tickingTicketsTracker.addTicket(TicketType.PLAYER, chunkcoordintpair, this.getPlayerTicketLevel(), chunkcoordintpair); } @@ -286,7 +286,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.f.update(i, Integer.MAX_VALUE, false); // Paper - no longer used this.playerTicketManager.update(i, Integer.MAX_VALUE, false); this.tickingTicketsTracker.removeTicket(TicketType.PLAYER, chunkcoordintpair, this.getPlayerTicketLevel(), chunkcoordintpair); } @@ -324,13 +324,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 41d2027cd4cf8f5de7bd59283361f7f1075356cb..24d0b02264e4cced08a60f36b5c41bb350a1dc60 100644 --- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java +++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java @@ -881,6 +881,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 = net.minecraft.server.MCUtil.getChunkCoordinate(player.getX()); + int chunkZ = net.minecraft.server.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(); @@ -928,15 +959,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()) { @@ -944,9 +967,9 @@ public class ServerChunkCache extends ChunkSource { LevelChunk chunk1 = chunkproviderserver_a.chunk; ChunkPos chunkcoordintpair = chunk1.getPos(); - if (this.level.isPositionEntityTicking(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(chunkcoordintpair)) { + if (this.level.isPositionEntityTicking(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 beebb7a0e6b8b1fa4e7d2f9fdf1962357cc2ebc3..2f13055a39c26fe12d2c1094103186635e536166 100644 --- a/src/main/java/net/minecraft/server/level/ServerPlayer.java +++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java @@ -253,6 +253,7 @@ public class ServerPlayer extends Player { // CraftBukkit end public PlayerNaturallySpawnCreaturesEvent playerNaturallySpawnedEvent; // Paper + public double lastEntitySpawnRadiusSquared; // Paper - optimise isOutsideRange, this field is in blocks public final com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet cachedSingleHashSet; // Paper public ServerPlayer(MinecraftServer server, ServerLevel world, GameProfile profile) {