From 97a9c9724ab6e26dd40c17ac8b9fbdee0afe7e1b Mon Sep 17 00:00:00 2001 From: Spottedleaf Date: Wed, 6 May 2020 03:48:18 -0400 Subject: [PATCH] Optimize isOutsideRange to use distance maps Use a distance map to find the players in range quickly --- ...-isOutsideRange-to-use-distance-maps.patch | 387 ++++++++++++++++++ 1 file changed, 387 insertions(+) create mode 100644 Spigot-Server-Patches/0504-Optimize-isOutsideRange-to-use-distance-maps.patch diff --git a/Spigot-Server-Patches/0504-Optimize-isOutsideRange-to-use-distance-maps.patch b/Spigot-Server-Patches/0504-Optimize-isOutsideRange-to-use-distance-maps.patch new file mode 100644 index 0000000000..cb3b070c9b --- /dev/null +++ b/Spigot-Server-Patches/0504-Optimize-isOutsideRange-to-use-distance-maps.patch @@ -0,0 +1,387 @@ +From daf950caa0ecae1e657c926b8bf694df778ae219 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Tue, 5 May 2020 20:40:53 -0700 +Subject: [PATCH] Optimize isOutsideRange to use distance maps + +Use a distance map to find the players in range quickly + +diff --git a/src/main/java/net/minecraft/server/ChunkMapDistance.java b/src/main/java/net/minecraft/server/ChunkMapDistance.java +index 279c7a85fb..7cd4e29123 100644 +--- a/src/main/java/net/minecraft/server/ChunkMapDistance.java ++++ b/src/main/java/net/minecraft/server/ChunkMapDistance.java +@@ -31,7 +31,7 @@ public abstract class ChunkMapDistance { + private final Long2ObjectMap> c = new Long2ObjectOpenHashMap(); + public final Long2ObjectOpenHashMap>> tickets = new Long2ObjectOpenHashMap(); + private final ChunkMapDistance.a e = new ChunkMapDistance.a(); +- private final ChunkMapDistance.b f = new ChunkMapDistance.b(8); ++ public static final int MOB_SPAWN_RANGE = 8; //private final ChunkMapDistance.b f = new ChunkMapDistance.b(8); // Paper - no longer used + private final ChunkMapDistance.c g = new ChunkMapDistance.c(33); + private final java.util.Queue pendingChunkUpdates = new java.util.LinkedList<>(); // PAIL pendingChunkUpdates // Paper - use a queue + private final ChunkTaskQueueSorter i; +@@ -41,6 +41,8 @@ public abstract class ChunkMapDistance { + private final Executor m; + private long currentTick; + ++ PlayerChunkMap chunkMap; // Paper ++ + protected ChunkMapDistance(Executor executor, Executor executor1) { + executor1.getClass(); + Mailbox mailbox = Mailbox.a("player ticket throttler", executor1::execute); +@@ -85,7 +87,7 @@ public abstract class ChunkMapDistance { + protected abstract PlayerChunk a(long i, int j, @Nullable PlayerChunk playerchunk, int k); + + public boolean a(PlayerChunkMap playerchunkmap) { +- this.f.a(); ++ //this.f.a(); // Paper - no longer used + this.g.a(); + int i = Integer.MAX_VALUE - this.e.a(Integer.MAX_VALUE); + boolean flag = i != 0; +@@ -219,7 +221,7 @@ public abstract class ChunkMapDistance { + ((ObjectSet) this.c.computeIfAbsent(i, (j) -> { + return new ObjectOpenHashSet(); + })).add(entityplayer); +- this.f.b(i, 0, true); ++ //this.f.b(i, 0, true); // Paper - no longer used + this.g.b(i, 0, true); + } + +@@ -231,7 +233,7 @@ public abstract class ChunkMapDistance { + objectset.remove(entityplayer); + if (objectset.isEmpty()) { + this.c.remove(i); +- this.f.b(i, Integer.MAX_VALUE, false); ++ //this.f.b(i, Integer.MAX_VALUE, false); // Paper - no longer used + this.g.b(i, Integer.MAX_VALUE, false); + } + +@@ -255,13 +257,17 @@ public abstract class ChunkMapDistance { + } + + public int b() { +- this.f.a(); +- return this.f.a.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 d(long i) { +- this.f.a(); +- return this.f.a.containsKey(i); ++ // Paper start - use distance map to implement ++ // note: this is the is spawn chunk method ++ return this.chunkMap.playerChunkTickRangeMap.getObjectsInRange(i) != null; ++ // Paper end - use distance map to implement + } + + public String c() { +diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java +index 78a8a3cc68..e2abda1bc3 100644 +--- a/src/main/java/net/minecraft/server/ChunkProviderServer.java ++++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java +@@ -733,6 +733,36 @@ public class ChunkProviderServer extends IChunkProvider { + boolean flag1 = this.world.getGameRules().getBoolean(GameRules.DO_MOB_SPAWNING) && !world.getPlayers().isEmpty(); // CraftBukkit + + if (!flag) { ++ // Paper start - optimize isOutisdeRange ++ PlayerChunkMap playerChunkMap = this.playerChunkMap; ++ for (EntityPlayer player : this.world.players) { ++ if (!player.affectsSpawning || player.isSpectator()) { ++ playerChunkMap.playerMobSpawnMap.remove(player); ++ continue; ++ } ++ ++ int viewDistance = this.playerChunkMap.getEffectiveViewDistance(); ++ ++ // copied and modified from isOutisdeRange ++ int chunkRange = world.spigotConfig.mobSpawnRange; ++ chunkRange = (chunkRange > viewDistance) ? (byte)viewDistance : chunkRange; ++ chunkRange = (chunkRange > ChunkMapDistance.MOB_SPAWN_RANGE) ? ChunkMapDistance.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.locX()); ++ int chunkZ = net.minecraft.server.MCUtil.getChunkCoordinate(player.locZ()); ++ ++ playerChunkMap.playerMobSpawnMap.addOrUpdate(player, chunkX, chunkZ, range); ++ player.lastEntitySpawnRadiusSquared = (double)((range << 4) * (range << 4)); // used in isOutsideRange ++ } ++ // Paper end - optimize isOutisdeRange + this.world.getMethodProfiler().enter("pollingChunks"); + int k = this.world.getGameRules().getInt(GameRules.RANDOM_TICK_SPEED); + BlockPosition blockposition = this.world.getSpawn(); +@@ -767,15 +797,7 @@ public class ChunkProviderServer extends IChunkProvider { + + this.world.timings.countNaturalMobs.stopTiming(); // Paper - timings + this.world.getMethodProfiler().exit(); +- //Paper start - call player naturally spawn event +- int chunkRange = world.spigotConfig.mobSpawnRange; +- chunkRange = (chunkRange > world.spigotConfig.viewDistance) ? (byte) world.spigotConfig.viewDistance : chunkRange; +- chunkRange = Math.min(chunkRange, 8); +- for (EntityPlayer entityPlayer : this.world.players) { +- entityPlayer.playerNaturallySpawnedEvent = new com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent(entityPlayer.getBukkitEntity(), (byte) chunkRange); +- entityPlayer.playerNaturallySpawnedEvent.callEvent(); +- }; +- // Paper end ++ // Paper - replaced by above + final int[] chunksTicked = {0}; this.playerChunkMap.forEachVisibleChunk((playerchunk) -> { // Paper - safe iterator incase chunk loads, also no wrapping + Optional optional = ((Either) playerchunk.b().getNow(PlayerChunk.UNLOADED_CHUNK)).left(); + +@@ -789,10 +811,10 @@ public class ChunkProviderServer extends IChunkProvider { + this.world.getMethodProfiler().exit(); + ChunkCoordIntPair chunkcoordintpair = playerchunk.i(); + +- if (!this.playerChunkMap.isOutsideOfRange(chunkcoordintpair)) { ++ if (!this.playerChunkMap.isOutsideOfRange(playerchunk, chunkcoordintpair, false)) { // Paper - optimise isOutsideOfRange + // Paper end + chunk.setInhabitedTime(chunk.getInhabitedTime() + j); +- if (flag1 && (this.allowMonsters || this.allowAnimals) && this.world.getWorldBorder().isInBounds(chunk.getPos()) && !this.playerChunkMap.isOutsideOfRange(chunkcoordintpair, true)) { // Spigot ++ if (flag1 && (this.allowMonsters || this.allowAnimals) && this.world.getWorldBorder().isInBounds(chunk.getPos()) && !this.playerChunkMap.isOutsideOfRange(playerchunk, chunkcoordintpair, true)) { // Spigot // Paper - optimise isOutsideOfRange + this.world.getMethodProfiler().enter("spawner"); + this.world.timings.mobSpawn.startTiming(); // Spigot + EnumCreatureType[] aenumcreaturetype1 = aenumcreaturetype; +diff --git a/src/main/java/net/minecraft/server/EntityPlayer.java b/src/main/java/net/minecraft/server/EntityPlayer.java +index 48bbaec4b6..6e8179b465 100644 +--- a/src/main/java/net/minecraft/server/EntityPlayer.java ++++ b/src/main/java/net/minecraft/server/EntityPlayer.java +@@ -109,6 +109,8 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + + public final com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet cachedSingleHashSet; // Paper + ++ double lastEntitySpawnRadiusSquared; // Paper - optimise isOutsideRange, this field is in blocks ++ + public EntityPlayer(MinecraftServer minecraftserver, WorldServer worldserver, GameProfile gameprofile, PlayerInteractManager playerinteractmanager) { + super((World) worldserver, gameprofile); + playerinteractmanager.player = this; +diff --git a/src/main/java/net/minecraft/server/PlayerChunk.java b/src/main/java/net/minecraft/server/PlayerChunk.java +index 568fbbd5f2..8742d13499 100644 +--- a/src/main/java/net/minecraft/server/PlayerChunk.java ++++ b/src/main/java/net/minecraft/server/PlayerChunk.java +@@ -148,6 +148,18 @@ public class PlayerChunk { + } + // Paper end + ++ // Paper start - optimise isOutsideOfRange ++ // 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.location); ++ this.playersInMobSpawnRange = this.chunkMap.playerMobSpawnMap.getObjectsInRange(key); ++ this.playersInChunkTickRange = this.chunkMap.playerChunkTickRangeMap.getObjectsInRange(key); ++ } ++ // Paper end - optimise isOutsideOfRange ++ + public PlayerChunk(ChunkCoordIntPair chunkcoordintpair, int i, LightEngine lightengine, PlayerChunk.c playerchunk_c, PlayerChunk.d playerchunk_d) { + this.statusFutures = new AtomicReferenceArray(PlayerChunk.CHUNK_STATUSES.size()); + this.fullChunkFuture = PlayerChunk.UNLOADED_CHUNK_FUTURE; +@@ -164,6 +176,7 @@ public class PlayerChunk { + this.n = this.oldTicketLevel; + this.a(i); + this.chunkMap = (PlayerChunkMap)playerchunk_d; // Paper ++ this.updateRanges(); // Paper - optimise isOutsideOfRange + } + + // Paper start +diff --git a/src/main/java/net/minecraft/server/PlayerChunkMap.java b/src/main/java/net/minecraft/server/PlayerChunkMap.java +index a780cf5b45..099e612171 100644 +--- a/src/main/java/net/minecraft/server/PlayerChunkMap.java ++++ b/src/main/java/net/minecraft/server/PlayerChunkMap.java +@@ -130,6 +130,17 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + final com.destroystokyo.paper.util.misc.PlayerAreaMap[] playerEntityTrackerTrackMaps; + final int[] entityTrackerTrackRanges; + // Paper end - use distance map to optimise tracker ++ // Paper start - optimise PlayerChunkMap#isOutsideRange ++ // 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 PlayerChunkMap#isOutsideRange + + void addPlayerToDistanceMaps(EntityPlayer player) { + int chunkX = MCUtil.getChunkCoordinate(player.locX()); +@@ -143,6 +154,9 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + trackMap.add(player, chunkX, chunkZ, Math.min(trackRange, this.getEffectiveViewDistance())); + } + // Paper end - use distance map to optimise entity tracker ++ // Paper start - optimise PlayerChunkMap#isOutsideRange ++ this.playerChunkTickRangeMap.add(player, chunkX, chunkZ, ChunkMapDistance.MOB_SPAWN_RANGE); ++ // Paper end - optimise PlayerChunkMap#isOutsideRange + } + + void removePlayerFromDistanceMaps(EntityPlayer player) { +@@ -151,6 +165,10 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + this.playerEntityTrackerTrackMaps[i].remove(player); + } + // Paper end - use distance map to optimise tracker ++ // Paper start - optimise PlayerChunkMap#isOutsideRange ++ this.playerMobSpawnMap.remove(player); ++ this.playerChunkTickRangeMap.remove(player); ++ // Paper end - optimise PlayerChunkMap#isOutsideRange + } + + void updateMaps(EntityPlayer player) { +@@ -165,6 +183,9 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + trackMap.update(player, chunkX, chunkZ, Math.min(trackRange, this.getEffectiveViewDistance())); + } + // Paper end - use distance map to optimise entity tracker ++ // Paper start - optimise PlayerChunkMap#isOutsideRange ++ this.playerChunkTickRangeMap.update(player, chunkX, chunkZ, ChunkMapDistance.MOB_SPAWN_RANGE); ++ // Paper end - optimise PlayerChunkMap#isOutsideRange + } + + +@@ -197,7 +218,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + this.mailboxWorldGen = this.p.a(threadedmailbox, false); + this.mailboxMain = this.p.a(mailbox, false); + this.lightEngine = new LightEngineThreaded(ilightaccess, this, this.world.getWorldProvider().f(), threadedmailbox1, this.p.a(threadedmailbox1, false)); +- this.chunkDistanceManager = new PlayerChunkMap.a(executor, iasynctaskhandler); ++ this.chunkDistanceManager = new PlayerChunkMap.a(executor, iasynctaskhandler); this.chunkDistanceManager.chunkMap = this; // Paper + this.l = supplier; + this.m = new VillagePlace(new File(this.w, "poi"), datafixer, this.world); // Paper + this.setViewDistance(i); +@@ -240,6 +261,38 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + this.playerEntityTrackerTrackMaps[ordinal] = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets); + } + // Paper end - use distance map to optimise entity tracker ++ // Paper start - optimise PlayerChunkMap#isOutsideRange ++ this.playerChunkTickRangeMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets, ++ (EntityPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState) -> { ++ PlayerChunk playerChunk = PlayerChunkMap.this.getUpdatingChunk(MCUtil.getCoordinateKey(rangeX, rangeZ)); ++ if (playerChunk != null) { ++ playerChunk.playersInChunkTickRange = newState; ++ } ++ }, ++ (EntityPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState) -> { ++ PlayerChunk playerChunk = PlayerChunkMap.this.getUpdatingChunk(MCUtil.getCoordinateKey(rangeX, rangeZ)); ++ if (playerChunk != null) { ++ playerChunk.playersInChunkTickRange = newState; ++ } ++ }); ++ this.playerMobSpawnMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets, ++ (EntityPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState) -> { ++ PlayerChunk playerChunk = PlayerChunkMap.this.getUpdatingChunk(MCUtil.getCoordinateKey(rangeX, rangeZ)); ++ if (playerChunk != null) { ++ playerChunk.playersInMobSpawnRange = newState; ++ } ++ }, ++ (EntityPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState) -> { ++ PlayerChunk playerChunk = PlayerChunkMap.this.getUpdatingChunk(MCUtil.getCoordinateKey(rangeX, rangeZ)); ++ if (playerChunk != null) { ++ playerChunk.playersInMobSpawnRange = newState; ++ } ++ }); ++ // Paper end - optimise PlayerChunkMap#isOutsideRange + } + + public void updatePlayerMobTypeMap(Entity entity) { +@@ -259,6 +312,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + return entityPlayer.mobCounts[enumCreatureType.ordinal()]; + } + ++ private static double getDistanceSquaredFromChunk(ChunkCoordIntPair chunkPos, Entity entity) { return a(chunkPos, entity); } // Paper - OBFHELPER + private static double a(ChunkCoordIntPair chunkcoordintpair, Entity entity) { + double d0 = (double) (chunkcoordintpair.x * 16 + 8); + double d1 = (double) (chunkcoordintpair.z * 16 + 8); +@@ -439,6 +493,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + } else { + if (playerchunk != null) { + playerchunk.a(j); ++ playerchunk.updateRanges(); // Paper - optimise isOutsideOfRange + } + + if (playerchunk != null) { +@@ -1420,30 +1475,53 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + return isOutsideOfRange(chunkcoordintpair, false); + } + +- boolean isOutsideOfRange(ChunkCoordIntPair chunkcoordintpair, boolean reducedRange) { +- int chunkRange = world.spigotConfig.mobSpawnRange; +- chunkRange = (chunkRange > world.spigotConfig.viewDistance) ? (byte) world.spigotConfig.viewDistance : chunkRange; +- chunkRange = (chunkRange > 8) ? 8 : chunkRange; ++ // Paper start - optimise isOutsideOfRange ++ final boolean isOutsideOfRange(ChunkCoordIntPair chunkcoordintpair, boolean reducedRange) { ++ return this.isOutsideOfRange(this.getUpdatingChunk(chunkcoordintpair.pair()), chunkcoordintpair, reducedRange); ++ } + +- final int finalChunkRange = chunkRange; // Paper for lambda below +- //double blockRange = (reducedRange) ? Math.pow(chunkRange << 4, 2) : 16384.0D; // Paper - use from event +- // Spigot end +- long i = chunkcoordintpair.pair(); ++ final boolean isOutsideOfRange(PlayerChunk playerchunk, ChunkCoordIntPair 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; + +- return !this.chunkDistanceManager.d(i) ? true : this.playerMap.a(i).noneMatch((entityplayer) -> { +- // Paper start - +- com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent event; +- double blockRange = 16384.0D; +- if (reducedRange) { +- event = entityplayer.playerNaturallySpawnedEvent; +- if (event == null || event.isCancelled()) return false; +- blockRange = (double) ((event.getSpawnRadius() << 4) * (event.getSpawnRadius() << 4)); +- } ++ if (playersInRange == null) { ++ return true; ++ } + +- return (!entityplayer.isSpectator() && a(chunkcoordintpair, (Entity) entityplayer) < blockRange); // Spigot +- // Paper end +- }); ++ Object[] backingSet = playersInRange.getBackingSet(); ++ ++ if (reducedRange) { ++ for (int i = 0, len = backingSet.length; i < len; ++i) { ++ Object raw = backingSet[i]; ++ if (!(raw instanceof EntityPlayer)) { ++ continue; ++ } ++ EntityPlayer player = (EntityPlayer) raw; ++ // don't check spectator and whatnot, already handled by mob spawn map update ++ if (player.lastEntitySpawnRadiusSquared > getDistanceSquaredFromChunk(chunkcoordintpair, player)) { ++ return false; // in range ++ } ++ } ++ } else { ++ final double range = (ChunkMapDistance.MOB_SPAWN_RANGE * 16) * (ChunkMapDistance.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 EntityPlayer)) { ++ continue; ++ } ++ EntityPlayer player = (EntityPlayer) raw; ++ // don't check spectator and whatnot, already handled by mob spawn map update ++ if (range > getDistanceSquaredFromChunk(chunkcoordintpair, player)) { ++ return false; // in range ++ } ++ } ++ } ++ // no players in range ++ return true; + } ++ // Paper end - optimise isOutsideOfRange + + private boolean b(EntityPlayer entityplayer) { + return entityplayer.isSpectator() && !this.world.getGameRules().getBoolean(GameRules.SPECTATORS_GENERATE_CHUNKS); +-- +2.26.0 +