From 7e02ed1a607e64853ac6b40663ab3390bc16f207 Mon Sep 17 00:00:00 2001 From: Nassim Jahnke Date: Sat, 23 Sep 2023 09:42:59 +1000 Subject: [PATCH] Fix local attribute setting --- patches/server/Adventure.patch | 25 +- ...o-not-accept-invalid-client-settings.patch | 2 +- ...trolled-flushing-for-network-manager.patch | 150 ------ ...ush-calls-for-entity-tracker-packets.patch | 52 --- .../Optimise-chunk-tick-iteration.patch | 215 --------- .../Optimise-nearby-player-lookups.patch | 426 ------------------ .../Optimise-non-flush-packet-sending.patch | 46 -- ...erCloseEnoughForSpawning-to-use-dist.patch | 346 -------------- ...llocation-of-Vec3D-by-entity-tracker.patch | 58 --- .../Remove-streams-for-villager-AI.patch | 187 -------- ...tance-map-to-optimise-entity-tracker.patch | 387 ---------------- 11 files changed, 22 insertions(+), 1872 deletions(-) delete mode 100644 patches/unapplied/server/Allow-controlled-flushing-for-network-manager.patch delete mode 100644 patches/unapplied/server/Consolidate-flush-calls-for-entity-tracker-packets.patch delete mode 100644 patches/unapplied/server/Optimise-chunk-tick-iteration.patch delete mode 100644 patches/unapplied/server/Optimise-nearby-player-lookups.patch delete mode 100644 patches/unapplied/server/Optimise-non-flush-packet-sending.patch delete mode 100644 patches/unapplied/server/Optimize-anyPlayerCloseEnoughForSpawning-to-use-dist.patch delete mode 100644 patches/unapplied/server/Reduce-allocation-of-Vec3D-by-entity-tracker.patch delete mode 100644 patches/unapplied/server/Remove-streams-for-villager-AI.patch delete mode 100644 patches/unapplied/server/Use-distance-map-to-optimise-entity-tracker.patch diff --git a/patches/server/Adventure.patch b/patches/server/Adventure.patch index b788c64d00..9a40167530 100644 --- a/patches/server/Adventure.patch +++ b/patches/server/Adventure.patch @@ -2346,10 +2346,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 } // CraftBukkit end this.language = clientOptions.language(); -+ // Paper start -+ this.adventure$locale = net.kyori.adventure.translation.Translator.parseLocale(this.language); -+ this.connection.connection.channel.attr(PaperAdventure.LOCALE_ATTRIBUTE).set(this.adventure$locale); -+ // Paper end ++ this.adventure$locale = net.kyori.adventure.translation.Translator.parseLocale(this.language); // Paper this.requestedViewDistance = clientOptions.viewDistance(); this.chatVisibility = clientOptions.chatVisibility(); this.canChatColor = clientOptions.chatColors(); @@ -2418,6 +2415,18 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 // CraftBukkit end this.connection.send(new ClientboundDisconnectPacket(ichatbasecomponent), PacketSendListener.thenRun(() -> { +diff --git a/src/main/java/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java +@@ -0,0 +0,0 @@ public class ServerConfigurationPacketListenerImpl extends ServerCommonPacketLis + @Override + public void handleClientInformation(ServerboundClientInformationPacket packet) { + this.clientInformation = packet.information(); ++ this.connection.channel.attr(io.papermc.paper.adventure.PaperAdventure.LOCALE_ATTRIBUTE).set(net.kyori.adventure.translation.Translator.parseLocale(packet.information().language())); // Paper + } + + @Override diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java @@ -2502,6 +2511,14 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 } else { this.chat(s, message, true); } +@@ -0,0 +0,0 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + public void handleClientInformation(ServerboundClientInformationPacket packet) { + PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); + this.player.updateOptions(packet.information()); ++ this.connection.channel.attr(io.papermc.paper.adventure.PaperAdventure.LOCALE_ATTRIBUTE).set(net.kyori.adventure.translation.Translator.parseLocale(packet.information().language())); // Paper + } + + @Override diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java diff --git a/patches/server/Do-not-accept-invalid-client-settings.patch b/patches/server/Do-not-accept-invalid-client-settings.patch index 5143abdf3a..e7f4c580cd 100644 --- a/patches/server/Do-not-accept-invalid-client-settings.patch +++ b/patches/server/Do-not-accept-invalid-client-settings.patch @@ -20,5 +20,5 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + // Paper end - do not accept invalid information this.player.updateOptions(packet.information()); + this.connection.channel.attr(io.papermc.paper.adventure.PaperAdventure.LOCALE_ATTRIBUTE).set(net.kyori.adventure.translation.Translator.parseLocale(packet.information().language())); // Paper } - diff --git a/patches/unapplied/server/Allow-controlled-flushing-for-network-manager.patch b/patches/unapplied/server/Allow-controlled-flushing-for-network-manager.patch deleted file mode 100644 index 8a340b3963..0000000000 --- a/patches/unapplied/server/Allow-controlled-flushing-for-network-manager.patch +++ /dev/null @@ -1,150 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Sat, 4 Apr 2020 15:27:44 -0700 -Subject: [PATCH] Allow controlled flushing for network manager - -Only make one flush call when emptying the packet queue too - -This patch will be used to optimise out flush calls in later -patches. - -diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/network/Connection.java -+++ b/src/main/java/net/minecraft/network/Connection.java -@@ -0,0 +0,0 @@ public class Connection extends SimpleChannelInboundHandler> { - public ConnectionProtocol protocol; - // Paper end - -+ // Paper start - allow controlled flushing -+ volatile boolean canFlush = true; -+ private final java.util.concurrent.atomic.AtomicInteger packetWrites = new java.util.concurrent.atomic.AtomicInteger(); -+ private int flushPacketsStart; -+ private final Object flushLock = new Object(); -+ -+ public void disableAutomaticFlush() { -+ synchronized (this.flushLock) { -+ this.flushPacketsStart = this.packetWrites.get(); // must be volatile and before canFlush = false -+ this.canFlush = false; -+ } -+ } -+ -+ public void enableAutomaticFlush() { -+ synchronized (this.flushLock) { -+ this.canFlush = true; -+ if (this.packetWrites.get() != this.flushPacketsStart) { // must be after canFlush = true -+ this.flush(); // only make the flush call if we need to -+ } -+ } -+ } -+ -+ private final void flush() { -+ if (this.channel.eventLoop().inEventLoop()) { -+ this.channel.flush(); -+ } else { -+ this.channel.eventLoop().execute(() -> { -+ this.channel.flush(); -+ }); -+ } -+ } -+ // Paper end - allow controlled flushing -+ - public Connection(PacketFlow side) { - this.receiving = side; - } -@@ -0,0 +0,0 @@ public class Connection extends SimpleChannelInboundHandler> { - io.papermc.paper.util.MCUtil.isMainThread() && packet.isReady() && this.queue.isEmpty() && - (packet.getExtraPackets() == null || packet.getExtraPackets().isEmpty()) - ))) { -- this.sendPacket(packet, callbacks); -+ this.sendPacket(packet, callbacks, null); // Paper - return; - } - // write the packets to the queue, then flush - antixray hooks there already -@@ -0,0 +0,0 @@ public class Connection extends SimpleChannelInboundHandler> { - } - - private void sendPacket(Packet packet, @Nullable PacketSendListener callbacks) { -+ // Paper start - add flush parameter -+ this.sendPacket(packet, callbacks, Boolean.TRUE); -+ } -+ private void sendPacket(Packet packet, @Nullable PacketSendListener callbacks, Boolean flushConditional) { -+ this.packetWrites.getAndIncrement(); // must be befeore using canFlush -+ boolean effectiveFlush = flushConditional == null ? this.canFlush : flushConditional.booleanValue(); -+ final boolean flush = effectiveFlush || packet instanceof net.minecraft.network.protocol.game.ClientboundKeepAlivePacket || packet instanceof ClientboundDisconnectPacket; // no delay for certain packets -+ // Paper end - add flush parameter - ConnectionProtocol enumprotocol = ConnectionProtocol.getProtocolForPacket(packet); - ConnectionProtocol enumprotocol1 = this.getCurrentProtocol(); - -@@ -0,0 +0,0 @@ public class Connection extends SimpleChannelInboundHandler> { - } - - if (this.channel.eventLoop().inEventLoop()) { -- this.doSendPacket(packet, callbacks, enumprotocol, enumprotocol1); -+ this.doSendPacket(packet, callbacks, enumprotocol, enumprotocol1, flush); // Paper - } else { - this.channel.eventLoop().execute(() -> { -- this.doSendPacket(packet, callbacks, enumprotocol, enumprotocol1); -+ this.doSendPacket(packet, callbacks, enumprotocol, enumprotocol1, flush); // Paper - }); - } - - } - - private void doSendPacket(Packet packet, @Nullable PacketSendListener callbacks, ConnectionProtocol packetState, ConnectionProtocol currentState) { -+ // Paper start - add flush parameter -+ this.doSendPacket(packet, callbacks, packetState, currentState, true); -+ } -+ private void doSendPacket(Packet packet, @Nullable PacketSendListener callbacks, ConnectionProtocol packetState, ConnectionProtocol currentState, boolean flush) { -+ // Paper end - add flush parameter - if (packetState != currentState) { - this.setProtocol(packetState); - } -@@ -0,0 +0,0 @@ public class Connection extends SimpleChannelInboundHandler> { - - try { - // Paper end -- ChannelFuture channelfuture = this.channel.writeAndFlush(packet); -+ ChannelFuture channelfuture = flush ? this.channel.writeAndFlush(packet) : this.channel.write(packet); // Paper - add flush parameter - - if (callbacks != null) { - channelfuture.addListener((future) -> { -@@ -0,0 +0,0 @@ public class Connection extends SimpleChannelInboundHandler> { - private boolean processQueue() { - try { // Paper - add pending task queue - if (this.queue.isEmpty()) return true; -+ // Paper start - make only one flush call per sendPacketQueue() call -+ final boolean needsFlush = this.canFlush; -+ boolean hasWrotePacket = false; -+ // Paper end - make only one flush call per sendPacketQueue() call - // If we are on main, we are safe here in that nothing else should be processing queue off main anymore - // But if we are not on main due to login/status, the parent is synchronized on packetQueue - java.util.Iterator iterator = this.queue.iterator(); -@@ -0,0 +0,0 @@ public class Connection extends SimpleChannelInboundHandler> { - PacketHolder queued = iterator.next(); // poll -> peek - - // Fix NPE (Spigot bug caused by handleDisconnection()) -- if (queued == null) { -+ if (false && queued == null) { // Paper - diff on change, this logic is redundant: iterator guarantees ret of an element - on change, hook the flush logic here - return true; - } - -@@ -0,0 +0,0 @@ public class Connection extends SimpleChannelInboundHandler> { - - Packet packet = queued.packet; - if (!packet.isReady()) { -+ // Paper start - make only one flush call per sendPacketQueue() call -+ if (hasWrotePacket && (needsFlush || this.canFlush)) { -+ this.flush(); -+ } -+ // Paper end - make only one flush call per sendPacketQueue() call - return false; - } else { - iterator.remove(); - if (queued.tryMarkConsumed()) { // Paper - try to mark isConsumed flag for de-duplicating packet -- this.sendPacket(packet, queued.listener); -+ this.sendPacket(packet, queued.listener, (!iterator.hasNext() && (needsFlush || this.canFlush)) ? Boolean.TRUE : Boolean.FALSE); // Paper - make only one flush call per sendPacketQueue() call -+ hasWrotePacket = true; // Paper - make only one flush call per sendPacketQueue() call - } - } - } diff --git a/patches/unapplied/server/Consolidate-flush-calls-for-entity-tracker-packets.patch b/patches/unapplied/server/Consolidate-flush-calls-for-entity-tracker-packets.patch deleted file mode 100644 index 46ff12fb1a..0000000000 --- a/patches/unapplied/server/Consolidate-flush-calls-for-entity-tracker-packets.patch +++ /dev/null @@ -1,52 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Sat, 4 Apr 2020 17:00:20 -0700 -Subject: [PATCH] Consolidate flush calls for entity tracker packets - -Most server packets seem to be sent from here, so try to avoid -expensive flush calls from them. - -This change was motivated due to local testing: - -- My server spawn has 130 cows in it (for testing a prev. patch) -- Try to let 200 players join spawn - -Without this change, I could only get 20 players on before they -all started timing out due to the load put on the Netty I/O threads. - -With this change I could get all 200 on at 0ms ping. - -(one of the primary issues is that my CPU is kinda trash, and having -4 extra threads at 100% is just too much for it). - -So in general this patch should reduce Netty I/O thread load. - -diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java -+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -@@ -0,0 +0,0 @@ public class ServerChunkCache extends ChunkSource { - this.level.timings.broadcastChunkUpdates.stopTiming(); // Paper - timing - gameprofilerfiller.pop(); - // Paper end - use set of chunks requiring updates, rather than iterating every single one loaded -+ // Paper start - controlled flush for entity tracker packets -+ List disabledFlushes = new java.util.ArrayList<>(this.level.players.size()); -+ for (ServerPlayer player : this.level.players) { -+ net.minecraft.server.network.ServerGamePacketListenerImpl connection = player.connection; -+ if (connection != null) { -+ connection.connection.disableAutomaticFlush(); -+ disabledFlushes.add(connection.connection); -+ } -+ } -+ try { // Paper end - controlled flush for entity tracker packets - this.chunkMap.tick(); -+ // Paper start - controlled flush for entity tracker packets -+ } finally { -+ for (net.minecraft.network.Connection networkManager : disabledFlushes) { -+ networkManager.enableAutomaticFlush(); -+ } -+ } -+ // Paper end - controlled flush for entity tracker packets - } - } - diff --git a/patches/unapplied/server/Optimise-chunk-tick-iteration.patch b/patches/unapplied/server/Optimise-chunk-tick-iteration.patch deleted file mode 100644 index 5e3384dc36..0000000000 --- a/patches/unapplied/server/Optimise-chunk-tick-iteration.patch +++ /dev/null @@ -1,215 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Thu, 7 May 2020 05:48:54 -0700 -Subject: [PATCH] Optimise chunk tick iteration - -Use a dedicated list of entity ticking chunks to reduce the cost - -diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkHolder.java -+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java -@@ -0,0 +0,0 @@ public class ChunkHolder { - this.playersInMobSpawnRange = this.chunkMap.playerMobSpawnMap.getObjectsInRange(key); - this.playersInChunkTickRange = this.chunkMap.playerChunkTickRangeMap.getObjectsInRange(key); - // Paper end - optimise anyPlayerCloseEnoughForSpawning -+ // Paper start - optimise chunk tick iteration -+ if (this.needsBroadcastChanges()) { -+ this.chunkMap.needsChangeBroadcasting.add(this); -+ } -+ // Paper end - optimise chunk tick iteration - } - - public void onChunkRemove() { -@@ -0,0 +0,0 @@ public class ChunkHolder { - this.playersInMobSpawnRange = null; - this.playersInChunkTickRange = null; - // Paper end - optimise anyPlayerCloseEnoughForSpawning -+ // Paper start - optimise chunk tick iteration -+ if (this.needsBroadcastChanges()) { -+ this.chunkMap.needsChangeBroadcasting.remove(this); -+ } -+ // Paper end - optimise chunk tick iteration - } - // Paper end - -@@ -0,0 +0,0 @@ public class ChunkHolder { - - if (i < 0 || i >= this.changedBlocksPerSection.length) return; // CraftBukkit - SPIGOT-6086, SPIGOT-6296 - if (this.changedBlocksPerSection[i] == null) { -- this.hasChangedSections = true; -+ this.hasChangedSections = true; this.addToBroadcastMap(); // Paper - optimise chunk tick iteration - this.changedBlocksPerSection[i] = new ShortOpenHashSet(); - } - -@@ -0,0 +0,0 @@ public class ChunkHolder { - int k = this.lightEngine.getMaxLightSection(); - - if (y >= j && y <= k) { -+ this.addToBroadcastMap(); // Paper - optimise chunk tick iteration - int l = y - j; - - if (lightType == LightLayer.SKY) { -@@ -0,0 +0,0 @@ public class ChunkHolder { - } - } - -+ // Paper start - optimise chunk tick iteration -+ public final boolean needsBroadcastChanges() { -+ return this.hasChangedSections || !this.skyChangedLightSectionFilter.isEmpty() || !this.blockChangedLightSectionFilter.isEmpty(); -+ } -+ -+ private void addToBroadcastMap() { -+ org.spigotmc.AsyncCatcher.catchOp("ChunkHolder update"); -+ this.chunkMap.needsChangeBroadcasting.add(this); -+ } -+ // Paper end - optimise chunk tick iteration -+ - public void broadcastChanges(LevelChunk chunk) { -- if (this.hasChangedSections || !this.skyChangedLightSectionFilter.isEmpty() || !this.blockChangedLightSectionFilter.isEmpty()) { -+ if (this.needsBroadcastChanges()) { // Paper - moved into above, other logic needs to call - Level world = chunk.getLevel(); - List list; - -diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkMap.java -+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java -@@ -0,0 +0,0 @@ import org.bukkit.craftbukkit.generator.CustomChunkGenerator; - import org.bukkit.entity.Player; - // CraftBukkit end - -+import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; // Paper -+ - public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider { - - private static final byte CHUNK_TYPE_REPLACEABLE = -1; -@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - private final Queue unloadQueue; - int viewDistance; - public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerMobDistanceMap; // Paper -+ public final ReferenceOpenHashSet needsChangeBroadcasting = new ReferenceOpenHashSet<>(); - - // Paper - rewrite chunk system - -diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java -+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -@@ -0,0 +0,0 @@ import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemp - import net.minecraft.world.level.storage.DimensionDataStorage; - import net.minecraft.world.level.storage.LevelData; - import net.minecraft.world.level.storage.LevelStorageSource; -+import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; // Paper - - public class ServerChunkCache extends ChunkSource { - -@@ -0,0 +0,0 @@ public class ServerChunkCache extends ChunkSource { - - this.lastSpawnState = spawnercreature_d; - gameprofilerfiller.popPush("filteringLoadedChunks"); -- List list = Lists.newArrayListWithCapacity(l); -- Iterator iterator = this.chunkMap.getChunks().iterator(); -+ // Paper - moved down - this.level.timings.chunkTicks.startTiming(); // Paper - -- while (iterator.hasNext()) { -- ChunkHolder playerchunk = (ChunkHolder) iterator.next(); -- LevelChunk chunk = playerchunk.getTickingChunk(); -- -- if (chunk != null) { -- list.add(new ServerChunkCache.ChunkAndHolder(chunk, playerchunk)); -- } -- } -+ // Paper - moved down - - gameprofilerfiller.popPush("spawnAndTick"); - boolean flag2 = this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && !this.level.players().isEmpty(); // CraftBukkit - -- Collections.shuffle(list); -+ // Paper - only shuffle if per-player mob spawning is disabled - // Paper - moved natural spawn event up -- Iterator iterator1 = list.iterator(); - -+ // Paper start - optimise chunk tick iteratio -+ Iterator iterator1; -+ if (this.level.paperConfig().entities.spawning.perPlayerMobSpawns) { -+ iterator1 = this.entityTickingChunks.iterator(); -+ } else { -+ iterator1 = this.entityTickingChunks.unsafeIterator(); -+ List shuffled = Lists.newArrayListWithCapacity(this.entityTickingChunks.size()); -+ while (iterator1.hasNext()) { -+ shuffled.add(iterator1.next()); -+ } -+ Collections.shuffle(shuffled); -+ iterator1 = shuffled.iterator(); -+ } -+ try { - while (iterator1.hasNext()) { -- ServerChunkCache.ChunkAndHolder chunkproviderserver_a = (ServerChunkCache.ChunkAndHolder) iterator1.next(); -- LevelChunk chunk1 = chunkproviderserver_a.chunk; -+ LevelChunk chunk1 = iterator1.next(); -+ ChunkHolder holder = chunk1.playerChunk; -+ if (holder != null) { -+ // Paper - move down -+ // Paper end - optimise chunk tick iteration - ChunkPos chunkcoordintpair = chunk1.getPos(); - -- if (this.level.isNaturalSpawningAllowed(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(chunkproviderserver_a.holder, chunkcoordintpair, false)) { // Paper - optimise anyPlayerCloseEnoughForSpawning -+ if ((true || this.level.isNaturalSpawningAllowed(chunkcoordintpair)) && this.chunkMap.anyPlayerCloseEnoughForSpawning(holder, chunkcoordintpair, false)) { // Paper - optimise anyPlayerCloseEnoughForSpawning // Paper - the chunk is known ticking - chunk1.incrementInhabitedTime(j); -- if (flag2 && (this.spawnEnemies || this.spawnFriendlies) && this.level.getWorldBorder().isWithinBounds(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(chunkproviderserver_a.holder, chunkcoordintpair, true)) { // Spigot // Paper - optimise anyPlayerCloseEnoughForSpawning -+ if (flag2 && (this.spawnEnemies || this.spawnFriendlies) && this.level.getWorldBorder().isWithinBounds(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(holder, chunkcoordintpair, true)) { // Spigot // Paper - optimise anyPlayerCloseEnoughForSpawning & optimise chunk tick iteration - NaturalSpawner.spawnForChunk(this.level, chunk1, spawnercreature_d, this.spawnFriendlies, this.spawnEnemies, flag1); - } - -- if (this.level.shouldTickBlocksAt(chunkcoordintpair.toLong())) { -+ if (true || this.level.shouldTickBlocksAt(chunkcoordintpair.toLong())) { // Paper - the chunk is known ticking - this.level.tickChunk(chunk1, k); - } - } -+ // Paper start - optimise chunk tick iteration -+ } -+ } -+ -+ } finally { -+ if (iterator1 instanceof io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet.Iterator safeIterator) { -+ safeIterator.finishedIterating(); -+ } - } -+ // Paper end - optimise chunk tick iteration - this.level.timings.chunkTicks.stopTiming(); // Paper - gameprofilerfiller.popPush("customSpawners"); - if (flag2) { -@@ -0,0 +0,0 @@ public class ServerChunkCache extends ChunkSource { - this.level.tickCustomSpawners(this.spawnEnemies, this.spawnFriendlies); - } // Paper - timings - } -- -- gameprofilerfiller.popPush("broadcast"); -- list.forEach((chunkproviderserver_a1) -> { -- this.level.timings.broadcastChunkUpdates.startTiming(); // Paper - timing -- chunkproviderserver_a1.holder.broadcastChanges(chunkproviderserver_a1.chunk); -- this.level.timings.broadcastChunkUpdates.stopTiming(); // Paper - timing -- }); - gameprofilerfiller.pop(); -+ // Paper start - use set of chunks requiring updates, rather than iterating every single one loaded -+ gameprofilerfiller.popPush("broadcast"); -+ this.level.timings.broadcastChunkUpdates.startTiming(); // Paper - timing -+ if (!this.chunkMap.needsChangeBroadcasting.isEmpty()) { -+ ReferenceOpenHashSet copy = this.chunkMap.needsChangeBroadcasting.clone(); -+ this.chunkMap.needsChangeBroadcasting.clear(); -+ for (ChunkHolder holder : copy) { -+ holder.broadcastChanges(holder.getFullChunkNowUnchecked()); // LevelChunks are NEVER unloaded -+ if (holder.needsBroadcastChanges()) { -+ // I DON'T want to KNOW what DUMB plugins might be doing. -+ this.chunkMap.needsChangeBroadcasting.add(holder); -+ } -+ } -+ } -+ this.level.timings.broadcastChunkUpdates.stopTiming(); // Paper - timing - gameprofilerfiller.pop(); -+ // Paper end - use set of chunks requiring updates, rather than iterating every single one loaded - this.chunkMap.tick(); - } - } diff --git a/patches/unapplied/server/Optimise-nearby-player-lookups.patch b/patches/unapplied/server/Optimise-nearby-player-lookups.patch deleted file mode 100644 index f8f1c8712f..0000000000 --- a/patches/unapplied/server/Optimise-nearby-player-lookups.patch +++ /dev/null @@ -1,426 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkHolder.java -+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java -@@ -0,0 +0,0 @@ public class ChunkHolder { - this.chunkMap.needsChangeBroadcasting.add(this); - } - // Paper end - optimise chunk tick iteration -+ // Paper start - optimise checkDespawn -+ LevelChunk chunk = this.getFullChunkNowUnchecked(); -+ if (chunk != null) { -+ chunk.updateGeneralAreaCache(); -+ } -+ // Paper end - optimise checkDespawn - } - - public void onChunkRemove() { -@@ -0,0 +0,0 @@ public class ChunkHolder { - this.chunkMap.needsChangeBroadcasting.remove(this); - } - // Paper end - optimise chunk tick iteration -+ // Paper start - optimise checkDespawn -+ LevelChunk chunk = this.getFullChunkNowUnchecked(); -+ if (chunk != null) { -+ chunk.removeGeneralAreaCache(); -+ } -+ // Paper end - optimise checkDespawn - } - // Paper end - -diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkMap.java -+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java -@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - public final ReferenceOpenHashSet needsChangeBroadcasting = new ReferenceOpenHashSet<>(); - - // Paper - rewrite chunk system -+ // 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 - - // Paper start - distance maps - private final com.destroystokyo.paper.util.misc.PooledLinkedHashSets pooledLinkedPlayerHashSets = new com.destroystokyo.paper.util.misc.PooledLinkedHashSets<>(); -@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - trackMap.add(player, chunkX, chunkZ, Math.min(trackRange, io.papermc.paper.chunk.system.ChunkSystem.getSendViewDistance(player))); - } - // Paper end - use distance map to optimise entity tracker -+ this.playerGeneralAreaMap.add(player, chunkX, chunkZ, GENERAL_AREA_MAP_SQUARE_RADIUS); // Paper - optimise checkDespawn - } - - void removePlayerFromDistanceMaps(ServerPlayer player) { -@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - this.playerMobSpawnMap.remove(player); - this.playerChunkTickRangeMap.remove(player); - // Paper end - optimise ChunkMap#anyPlayerCloseEnoughForSpawning -+ this.playerGeneralAreaMap.remove(player); // Paper - optimise checkDespawns - // Paper start - per player mob spawning - if (this.playerMobDistanceMap != null) { - this.playerMobDistanceMap.remove(player); -@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - trackMap.update(player, chunkX, chunkZ, Math.min(trackRange, io.papermc.paper.chunk.system.ChunkSystem.getSendViewDistance(player))); - } - // Paper end - use distance map to optimise entity tracker -+ this.playerGeneralAreaMap.update(player, chunkX, chunkZ, GENERAL_AREA_MAP_SQUARE_RADIUS); // Paper - optimise checkDespawn - } - // Paper end - // Paper start -@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - } - }); - // Paper end - optimise ChunkMap#anyPlayerCloseEnoughForSpawning -+ // 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 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 newState) -> { -+ LevelChunk chunk = ChunkMap.this.level.getChunkSource().getChunkAtIfCachedImmediately(rangeX, rangeZ); -+ if (chunk != null) { -+ chunk.updateGeneralAreaCache(newState); -+ } -+ }); -+ // Paper end - optimise checkDespawn - } - - protected ChunkGenerator generator() { -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -0,0 +0,0 @@ public class ServerLevel extends Level implements WorldGenLevel { - } - // Paper end - -+ // Paper start - optimise checkDespawn -+ public final List 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 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 getNearbyPlayers(net.minecraft.world.entity.ai.targeting.TargetingConditions condition, LivingEntity source, AABB axisalignedbb) { -+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet 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 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, IWorldDataServer -> WorldDataServer - public ServerLevel(MinecraftServer minecraftserver, Executor executor, LevelStorageSource.LevelStorageAccess convertable_conversionsession, PrimaryLevelData iworlddataserver, ResourceKey resourcekey, LevelStem worlddimension, ChunkProgressListener worldloadlistener, boolean flag, long i, List list, boolean flag1, @Nullable RandomSequences randomsequences, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider) { - // IRegistryCustom.Dimension iregistrycustom_dimension = minecraftserver.registryAccess(); // CraftBukkit - decompile error -@@ -0,0 +0,0 @@ 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.PLAYER_AFFECTS_SPAWNING.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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/world/entity/Mob.java -+++ b/src/main/java/net/minecraft/world/entity/Mob.java -@@ -0,0 +0,0 @@ public abstract class Mob extends LivingEntity implements Targeting { - 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.PLAYER_AFFECTS_SPAWNING); // Paper -+ // Paper start - optimise checkDespawn -+ Player entityhuman = this.level().findNearbyPlayer(this, level().paperConfig().entities.spawning.despawnRanges.get(this.getType().getCategory()).hard() + 1, EntitySelector.PLAYER_AFFECTS_SPAWNING); // 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); -diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/world/level/Level.java -+++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -0,0 +0,0 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - return this.getChunkIfLoaded(chunkX, chunkZ) != null; - } - // Paper end -+ // Paper start - optimise checkDespawn -+ public final List getNearbyPlayers(@Nullable Entity source, double sourceX, double sourceY, -+ double sourceZ, double maxRange, @Nullable Predicate 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 ret = new java.util.ArrayList<>(); -+ chunk.getNearestPlayers(sourceX, sourceY, sourceZ, predicate, maxRange, ret); -+ return ret; -+ } -+ -+ private List getNearbyPlayersSlow(@Nullable Entity source, double sourceX, double sourceY, -+ double sourceZ, double maxRange, @Nullable Predicate predicate) { -+ List ret = new java.util.ArrayList<>(); -+ double maxRangeSquared = maxRange * maxRange; -+ -+ for (net.minecraft.server.level.ServerPlayer player : (List)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 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)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 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 predicate) { -+ return this.getNearestPlayer(null, d0, d1, d2, d3, predicate); -+ } -+ // Paper end - optimise checkDespawn - - public abstract ResourceKey getTypeKey(); - -diff --git a/src/main/java/net/minecraft/world/level/NaturalSpawner.java b/src/main/java/net/minecraft/world/level/NaturalSpawner.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java -+++ b/src/main/java/net/minecraft/world/level/NaturalSpawner.java -@@ -0,0 +0,0 @@ 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); -@@ -0,0 +0,0 @@ public final class NaturalSpawner { - } - - private static boolean isRightDistanceToPlayerAndSpawnPoint(ServerLevel world, ChunkAccess chunk, BlockPos.MutableBlockPos pos, double squaredDistance) { -- return squaredDistance <= 576.0D ? false : (world.getSharedSpawnPos().closerToCenterThan(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.isNaturalSpawningAllowed((BlockPos) pos)); -+ return squaredDistance <= 576.0D ? false : (world.getSharedSpawnPos().closerToCenterThan(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.isNaturalSpawningAllowed((BlockPos) pos)); // Paper - diff on change, copy into caller - } - - // Paper start -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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -@@ -0,0 +0,0 @@ public class LevelChunk extends ChunkAccess { - } - } - // Paper end -+ // Paper start - optimise checkDespawn -+ private boolean playerGeneralAreaCacheSet; -+ private com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet playerGeneralAreaCache; -+ -+ public com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet 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 removeGeneralAreaCache() { -+ this.playerGeneralAreaCacheSet = false; -+ this.playerGeneralAreaCache = null; -+ } -+ -+ public void updateGeneralAreaCache(com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet 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 predicate) { -+ if (!this.playerGeneralAreaCacheSet) { -+ this.updateGeneralAreaCache(); -+ } -+ -+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet 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 predicate, -+ double range, java.util.List ret) { -+ if (!this.playerGeneralAreaCacheSet) { -+ this.updateGeneralAreaCache(); -+ } -+ -+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet 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 world, ProtoChunk protoChunk, @Nullable LevelChunk.PostLoadProcessor entityLoader) { - this(world, protoChunk.getPos(), protoChunk.getUpgradeData(), protoChunk.unpackBlockTicks(), protoChunk.unpackFluidTicks(), protoChunk.getInhabitedTime(), protoChunk.getSections(), entityLoader, protoChunk.getBlendingData()); diff --git a/patches/unapplied/server/Optimise-non-flush-packet-sending.patch b/patches/unapplied/server/Optimise-non-flush-packet-sending.patch deleted file mode 100644 index 02299d8b5a..0000000000 --- a/patches/unapplied/server/Optimise-non-flush-packet-sending.patch +++ /dev/null @@ -1,46 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Tue, 22 Sep 2020 01:49:19 -0700 -Subject: [PATCH] Optimise non-flush packet sending - -Places like entity tracking make heavy use of packet sending, -and internally netty will use some very expensive thread wakeup -calls when scheduling. - -Thanks to various hacks in ProtocolLib as well as other -plugins, we cannot simply use a queue of packets to group -send on execute. We have to call execute for each packet. - -Tux's suggestion here is exactly what was needed - tag -the Runnable indicating it should not make a wakeup call. - -Big thanks to Tux for making this possible as I had given -up on this optimisation before he came along. - -Locally this patch drops the entity tracker tick by a full 1.5x. - -diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/network/Connection.java -+++ b/src/main/java/net/minecraft/network/Connection.java -@@ -0,0 +0,0 @@ public class Connection extends SimpleChannelInboundHandler> { - if (this.channel.eventLoop().inEventLoop()) { - this.doSendPacket(packet, callbacks, enumprotocol, enumprotocol1, flush); // Paper - } else { -+ // Paper start - optimise packets that are not flushed -+ // note: since the type is not dynamic here, we need to actually copy the old executor code -+ // into two branches. On conflict, just re-copy - no changes were made inside the executor code. -+ if (!flush) { -+ io.netty.util.concurrent.AbstractEventExecutor.LazyRunnable run = () -> { -+ this.doSendPacket(packet, callbacks, enumprotocol, enumprotocol1, flush); // Paper - add flush parameter -+ }; -+ this.channel.eventLoop().execute(run); -+ } else { // Paper end - optimise packets that are not flushed - this.channel.eventLoop().execute(() -> { -- this.doSendPacket(packet, callbacks, enumprotocol, enumprotocol1, flush); // Paper -+ this.doSendPacket(packet, callbacks, enumprotocol, enumprotocol1, flush); // Paper - add flush parameter // Paper - diff on change - }); -+ } // Paper - } - - } diff --git a/patches/unapplied/server/Optimize-anyPlayerCloseEnoughForSpawning-to-use-dist.patch b/patches/unapplied/server/Optimize-anyPlayerCloseEnoughForSpawning-to-use-dist.patch deleted file mode 100644 index ef4e163d54..0000000000 --- a/patches/unapplied/server/Optimize-anyPlayerCloseEnoughForSpawning-to-use-dist.patch +++ /dev/null @@ -1,346 +0,0 @@ -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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkHolder.java -+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java -@@ -0,0 +0,0 @@ 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 -+ - // Paper start - replace player chunk loader - private final com.destroystokyo.paper.util.maplist.ReferenceList playersSentChunkTo = new com.destroystokyo.paper.util.maplist.ReferenceList<>(); - -diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkMap.java -+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java -@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - return net.minecraft.server.MinecraftServer.getServer().getScaledTrackingDistance(vanilla); - } - // Paper end - use distance map to optimise tracker -+ // 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.level.playerChunkLoader.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)); -@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - void removePlayerFromDistanceMaps(ServerPlayer player) { - this.level.playerChunkLoader.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); -@@ -0,0 +0,0 @@ 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.level.playerChunkLoader.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)); -@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - this.playerEntityTrackerTrackMaps[ordinal] = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets); - } - // Paper end - use distance map to optimise entity tracker -+ // 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() { -@@ -0,0 +0,0 @@ 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/server/level/DistanceManager.java -+++ b/src/main/java/net/minecraft/server/level/DistanceManager.java -@@ -0,0 +0,0 @@ public abstract class DistanceManager { - private static final int INITIAL_TICKET_LIST_CAPACITY = 4; - 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 - // Paper - rewrite chunk system - private final ChunkMap chunkMap; // Paper - -@@ -0,0 +0,0 @@ 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 - } -@@ -0,0 +0,0 @@ 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 - } -@@ -0,0 +0,0 @@ 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java -+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -@@ -0,0 +0,0 @@ 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(); - -@@ -0,0 +0,0 @@ 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()) { -@@ -0,0 +0,0 @@ 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -0,0 +0,0 @@ public class ServerPlayer extends Player { - public Integer clientViewDistance; - // 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 - public org.bukkit.event.player.PlayerQuitEvent.QuitReason quitReason = null; // Paper - there are a lot of changes to do if we change all methods leading to the event diff --git a/patches/unapplied/server/Reduce-allocation-of-Vec3D-by-entity-tracker.patch b/patches/unapplied/server/Reduce-allocation-of-Vec3D-by-entity-tracker.patch deleted file mode 100644 index 8756d0f651..0000000000 --- a/patches/unapplied/server/Reduce-allocation-of-Vec3D-by-entity-tracker.patch +++ /dev/null @@ -1,58 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Mon, 27 Apr 2020 00:04:16 -0700 -Subject: [PATCH] Reduce allocation of Vec3D by entity tracker - - -diff --git a/src/main/java/net/minecraft/network/protocol/game/VecDeltaCodec.java b/src/main/java/net/minecraft/network/protocol/game/VecDeltaCodec.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/network/protocol/game/VecDeltaCodec.java -+++ b/src/main/java/net/minecraft/network/protocol/game/VecDeltaCodec.java -@@ -0,0 +0,0 @@ import org.jetbrains.annotations.VisibleForTesting; - - public class VecDeltaCodec { - private static final double TRUNCATION_STEPS = 4096.0D; -- private Vec3 base = Vec3.ZERO; -+ public Vec3 base = Vec3.ZERO; // Paper - - @VisibleForTesting - static long encode(double value) { -diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkMap.java -+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java -@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - public void updatePlayer(ServerPlayer player) { - org.spigotmc.AsyncCatcher.catchOp("player tracker update"); // Spigot - if (player != this.entity) { -+ // Paper start - remove allocation of Vec3D here -+ // Vec3 vec3d = player.position().subtract(this.entity.position()); -+ double vec3d_dx = player.getX() - this.entity.getX(); -+ double vec3d_dz = player.getZ() - this.entity.getZ(); -+ // Paper end - remove allocation of Vec3D here - Vec3 vec3d = player.position().subtract(this.entity.position()); - double d0 = (double) Math.min(this.getEffectiveRange(), io.papermc.paper.chunk.system.ChunkSystem.getSendViewDistance(player) * 16); // Paper - per player view distance -- double d1 = vec3d.x * vec3d.x + vec3d.z * vec3d.z; -+ double d1 = vec3d_dx * vec3d_dx + vec3d_dz * vec3d_dz; // Paper - double d2 = d0 * d0; - boolean flag = d1 <= d2 && this.entity.broadcastToPlayer(player); - -diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/server/level/ServerEntity.java -+++ b/src/main/java/net/minecraft/server/level/ServerEntity.java -@@ -0,0 +0,0 @@ public class ServerEntity { - i = Mth.floor(this.entity.getYRot() * 256.0F / 360.0F); - j = Mth.floor(this.entity.getXRot() * 256.0F / 360.0F); - Vec3 vec3d = this.entity.trackingPosition(); -- boolean flag1 = this.positionCodec.delta(vec3d).lengthSqr() >= 7.62939453125E-6D; -+ // Paper start - reduce allocation of Vec3D here -+ Vec3 base = this.positionCodec.base; -+ double vec3d_dx = vec3d.x - base.x; -+ double vec3d_dy = vec3d.y - base.y; -+ double vec3d_dz = vec3d.z - base.z; -+ boolean flag1 = (vec3d_dx * vec3d_dx + vec3d_dy * vec3d_dy + vec3d_dz * vec3d_dz) >= 7.62939453125E-6D; -+ // Paper end - reduce allocation of Vec3D here - Packet packet1 = null; - boolean flag2 = flag1 || this.tickCount % 60 == 0; - boolean flag3 = Math.abs(i - this.yRotp) >= 1 || Math.abs(j - this.xRotp) >= 1; diff --git a/patches/unapplied/server/Remove-streams-for-villager-AI.patch b/patches/unapplied/server/Remove-streams-for-villager-AI.patch deleted file mode 100644 index 20855f3d22..0000000000 --- a/patches/unapplied/server/Remove-streams-for-villager-AI.patch +++ /dev/null @@ -1,187 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Thu, 27 Aug 2020 20:51:40 -0700 -Subject: [PATCH] Remove streams for villager AI - - -diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/GateBehavior.java b/src/main/java/net/minecraft/world/entity/ai/behavior/GateBehavior.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/behavior/GateBehavior.java -+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/GateBehavior.java -@@ -0,0 +0,0 @@ public class GateBehavior implements BehaviorControl - if (this.hasRequiredMemories(entity)) { - this.status = Behavior.Status.RUNNING; - this.orderPolicy.apply(this.behaviors); -- this.runningPolicy.apply(this.behaviors.stream(), world, entity, time); -+ this.runningPolicy.apply(this.behaviors.entries, world, entity, time); - return true; - } else { - return false; -@@ -0,0 +0,0 @@ public class GateBehavior implements BehaviorControl - - @Override - public final void tickOrStop(ServerLevel world, E entity, long time) { -- this.behaviors.stream().filter((task) -> { -- return task.getStatus() == Behavior.Status.RUNNING; -- }).forEach((task) -> { -- task.tickOrStop(world, entity, time); -- }); -+ // Paper start -+ for (BehaviorControl task : this.behaviors) { -+ if (task.getStatus() == Behavior.Status.RUNNING) { -+ task.tickOrStop(world, entity, time); -+ } -+ } -+ // Paper end - if (this.behaviors.stream().noneMatch((task) -> { - return task.getStatus() == Behavior.Status.RUNNING; - })) { -@@ -0,0 +0,0 @@ public class GateBehavior implements BehaviorControl - @Override - public final void doStop(ServerLevel world, E entity, long time) { - this.status = Behavior.Status.STOPPED; -- this.behaviors.stream().filter((task) -> { -- return task.getStatus() == Behavior.Status.RUNNING; -- }).forEach((task) -> { -- task.doStop(world, entity, time); -- }); -+ for (BehaviorControl behavior : this.behaviors) { -+ if (behavior.getStatus() == Behavior.Status.RUNNING) { -+ behavior.doStop(world, entity, time); -+ } -+ } - this.exitErasedMemories.forEach(entity.getBrain()::eraseMemory); - } - -@@ -0,0 +0,0 @@ public class GateBehavior implements BehaviorControl - public static enum RunningPolicy { - RUN_ONE { - @Override -- public void apply(Stream> tasks, ServerLevel world, E entity, long time) { -- tasks.filter((task) -> { -- return task.getStatus() == Behavior.Status.STOPPED; -- }).filter((task) -> { -- return task.tryStart(world, entity, time); -- }).findFirst(); -+ // Paper start - remove streams -+ public void apply(List>> tasks, ServerLevel world, E entity, long time) { -+ for (ShufflingList.WeightedEntry> task : tasks) { -+ final BehaviorControl behavior = task.getData(); -+ if (behavior.getStatus() == Behavior.Status.STOPPED && behavior.tryStart(world, entity, time)) { -+ break; -+ } -+ } -+ // Paper end - remove streams - } - }, - TRY_ALL { - @Override -- public void apply(Stream> tasks, ServerLevel world, E entity, long time) { -- tasks.filter((task) -> { -- return task.getStatus() == Behavior.Status.STOPPED; -- }).forEach((task) -> { -- task.tryStart(world, entity, time); -- }); -+ // Paper start - remove streams -+ public void apply(List>> tasks, ServerLevel world, E entity, long time) { -+ for (ShufflingList.WeightedEntry> task : tasks) { -+ final BehaviorControl behavior = task.getData(); -+ if (behavior.getStatus() == Behavior.Status.STOPPED) { -+ behavior.tryStart(world, entity, time); -+ } -+ } -+ // Paper end - remove streams - } - }; - -- public abstract void apply(Stream> tasks, ServerLevel world, E entity, long time); -+ public abstract void apply(List>> tasks, ServerLevel world, E entity, long time); // Paper - remove streams - } - } -diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/ShufflingList.java b/src/main/java/net/minecraft/world/entity/ai/behavior/ShufflingList.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/behavior/ShufflingList.java -+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/ShufflingList.java -@@ -0,0 +0,0 @@ import java.util.stream.Stream; - import net.minecraft.util.RandomSource; - - public class ShufflingList implements Iterable { -- protected final List> entries; -+ public final List> entries; // Paper - public - private final RandomSource random = RandomSource.create(); - private final boolean isUnsafe; // Paper - -diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/NearestItemSensor.java b/src/main/java/net/minecraft/world/entity/ai/sensing/NearestItemSensor.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/sensing/NearestItemSensor.java -+++ b/src/main/java/net/minecraft/world/entity/ai/sensing/NearestItemSensor.java -@@ -0,0 +0,0 @@ public class NearestItemSensor extends Sensor { - protected void doTick(ServerLevel world, Mob entity) { - Brain brain = entity.getBrain(); - List list = world.getEntitiesOfClass(ItemEntity.class, entity.getBoundingBox().inflate(32.0D, 16.0D, 32.0D), (itemEntity) -> { -- return true; -+ return itemEntity.closerThan(entity, MAX_DISTANCE_TO_WANTED_ITEM) && entity.wantsToPickUp(itemEntity.getItem()); // Paper - move predicate into getEntities - }); -- list.sort(Comparator.comparingDouble(entity::distanceToSqr)); -+ list.sort((e1, e2) -> Double.compare(entity.distanceToSqr(e1), entity.distanceToSqr(e2))); // better to take the sort perf hit than using line of sight more than we need to. -+ // Paper start - remove streams - // Paper start - remove streams in favour of lists - ItemEntity nearest = null; -- for (ItemEntity entityItem : list) { -- if (entity.wantsToPickUp(entityItem.getItem()) && entityItem.closerThan(entity, 32.0D) && entity.hasLineOfSight(entityItem)) { -+ for (int i = 0; i < list.size(); i++) { -+ ItemEntity entityItem = list.get(i); -+ if (entity.hasLineOfSight(entityItem)) { -+ // Paper end - remove streams - nearest = entityItem; - break; - } -diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/PlayerSensor.java b/src/main/java/net/minecraft/world/entity/ai/sensing/PlayerSensor.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/sensing/PlayerSensor.java -+++ b/src/main/java/net/minecraft/world/entity/ai/sensing/PlayerSensor.java -@@ -0,0 +0,0 @@ public class PlayerSensor extends Sensor { - - @Override - protected void doTick(ServerLevel world, LivingEntity entity) { -- List players = new java.util.ArrayList<>(world.players()); -- players.removeIf(player -> !EntitySelector.NO_SPECTATORS.test(player) || !entity.closerThan(player, 16.0D)); -- players.sort(Comparator.comparingDouble(entity::distanceTo)); -+ // Paper start - remove streams -+ List players = (List)world.getNearbyPlayers(entity, entity.getX(), entity.getY(), entity.getZ(), 16.0D, EntitySelector.NO_SPECTATORS); -+ players.sort((e1, e2) -> Double.compare(entity.distanceToSqr(e1), entity.distanceToSqr(e2))); - Brain brain = entity.getBrain(); - - brain.setMemory(MemoryModuleType.NEAREST_PLAYERS, players); - -- Player nearest = null, nearestTargetable = null; -- for (Player player : players) { -- if (Sensor.isEntityTargetable(entity, player)) { -- if (nearest == null) nearest = player; -- if (Sensor.isEntityAttackable(entity, player)) { -- nearestTargetable = player; -- break; // Both variables are assigned, no reason to loop further -- } -+ Player firstTargetable = null; -+ Player firstAttackable = null; -+ for (int index = 0, len = players.size(); index < len; ++index) { -+ Player player = players.get(index); -+ if (firstTargetable == null && isEntityTargetable(entity, player)) { -+ firstTargetable = player; -+ } -+ if (firstAttackable == null && isEntityAttackable(entity, player)) { -+ firstAttackable = player; -+ } -+ -+ if (firstAttackable != null && firstTargetable != null) { -+ break; - } - } -- brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_PLAYER, nearest); -- brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_ATTACKABLE_PLAYER, nearestTargetable); -- // Paper end -+ brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_PLAYER, firstTargetable); -+ brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_ATTACKABLE_PLAYER, Optional.ofNullable(firstAttackable)); -+ // Paper end - remove streams - } - } diff --git a/patches/unapplied/server/Use-distance-map-to-optimise-entity-tracker.patch b/patches/unapplied/server/Use-distance-map-to-optimise-entity-tracker.patch deleted file mode 100644 index 1aa1b10ca7..0000000000 --- a/patches/unapplied/server/Use-distance-map-to-optimise-entity-tracker.patch +++ /dev/null @@ -1,387 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Tue, 5 May 2020 20:18:05 -0700 -Subject: [PATCH] Use distance map to optimise entity tracker - -Use the distance map to find candidate players for tracking. - -diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkMap.java -+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java -@@ -0,0 +0,0 @@ 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<>(); -+ // Paper start - use distance map to optimise tracker -+ public static boolean isLegacyTrackingEntity(Entity entity) { -+ return entity.isLegacyTrackingEntity; -+ } -+ -+ // inlined EnumMap, TrackingRange.TrackingRangeType -+ static final org.spigotmc.TrackingRange.TrackingRangeType[] TRACKING_RANGE_TYPES = org.spigotmc.TrackingRange.TrackingRangeType.values(); -+ public final com.destroystokyo.paper.util.misc.PlayerAreaMap[] playerEntityTrackerTrackMaps; -+ final int[] entityTrackerTrackRanges; -+ public final int getEntityTrackerRange(final int ordinal) { -+ return this.entityTrackerTrackRanges[ordinal]; -+ } -+ -+ private int convertSpigotRangeToVanilla(final int vanilla) { -+ return net.minecraft.server.MinecraftServer.getServer().getScaledTrackingDistance(vanilla); -+ } -+ // Paper end - use distance map to optimise tracker - - void addPlayerToDistanceMaps(ServerPlayer player) { - this.level.playerChunkLoader.addPlayer(player); // Paper - replace chunk loader -@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - this.playerMobDistanceMap.add(player, chunkX, chunkZ, io.papermc.paper.chunk.system.ChunkSystem.getTickViewDistance(player)); - } - // Paper end - per player mob spawning -+ // Paper start - use distance map to optimise entity tracker -+ for (int i = 0, len = TRACKING_RANGE_TYPES.length; i < len; ++i) { -+ com.destroystokyo.paper.util.misc.PlayerAreaMap trackMap = this.playerEntityTrackerTrackMaps[i]; -+ int trackRange = this.entityTrackerTrackRanges[i]; -+ -+ trackMap.add(player, chunkX, chunkZ, Math.min(trackRange, io.papermc.paper.chunk.system.ChunkSystem.getSendViewDistance(player))); -+ } -+ // Paper end - use distance map to optimise entity tracker - } - - void removePlayerFromDistanceMaps(ServerPlayer player) { -@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - this.playerMobDistanceMap.remove(player); - } - // Paper end - per player mob spawning -+ // Paper start - use distance map to optimise tracker -+ for (int i = 0, len = TRACKING_RANGE_TYPES.length; i < len; ++i) { -+ this.playerEntityTrackerTrackMaps[i].remove(player); -+ } -+ // Paper end - use distance map to optimise tracker - } - - void updateMaps(ServerPlayer player) { -@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - this.playerMobDistanceMap.update(player, chunkX, chunkZ, io.papermc.paper.chunk.system.ChunkSystem.getTickViewDistance(player)); - } - // Paper end - per player mob spawning -+ // Paper start - use distance map to optimise entity tracker -+ for (int i = 0, len = TRACKING_RANGE_TYPES.length; i < len; ++i) { -+ com.destroystokyo.paper.util.misc.PlayerAreaMap trackMap = this.playerEntityTrackerTrackMaps[i]; -+ int trackRange = this.entityTrackerTrackRanges[i]; -+ -+ trackMap.update(player, chunkX, chunkZ, Math.min(trackRange, io.papermc.paper.chunk.system.ChunkSystem.getSendViewDistance(player))); -+ } -+ // Paper end - use distance map to optimise entity tracker - } - // Paper end - // Paper start -@@ -0,0 +0,0 @@ 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 - use distance map to optimise entity tracker -+ this.playerEntityTrackerTrackMaps = new com.destroystokyo.paper.util.misc.PlayerAreaMap[TRACKING_RANGE_TYPES.length]; -+ this.entityTrackerTrackRanges = new int[TRACKING_RANGE_TYPES.length]; -+ -+ org.spigotmc.SpigotWorldConfig spigotWorldConfig = this.level.spigotConfig; -+ -+ for (int ordinal = 0, len = TRACKING_RANGE_TYPES.length; ordinal < len; ++ordinal) { -+ org.spigotmc.TrackingRange.TrackingRangeType trackingRangeType = TRACKING_RANGE_TYPES[ordinal]; -+ int configuredSpigotValue; -+ switch (trackingRangeType) { -+ case PLAYER: -+ configuredSpigotValue = spigotWorldConfig.playerTrackingRange; -+ break; -+ case ANIMAL: -+ configuredSpigotValue = spigotWorldConfig.animalTrackingRange; -+ break; -+ case MONSTER: -+ configuredSpigotValue = spigotWorldConfig.monsterTrackingRange; -+ break; -+ case MISC: -+ configuredSpigotValue = spigotWorldConfig.miscTrackingRange; -+ break; -+ case OTHER: -+ configuredSpigotValue = spigotWorldConfig.otherTrackingRange; -+ break; -+ case ENDERDRAGON: -+ configuredSpigotValue = EntityType.ENDER_DRAGON.clientTrackingRange() * 16; -+ break; -+ case DISPLAY: -+ configuredSpigotValue = spigotWorldConfig.displayTrackingRange; -+ break; -+ default: -+ throw new IllegalStateException("Missing case for enum " + trackingRangeType); -+ } -+ configuredSpigotValue = convertSpigotRangeToVanilla(configuredSpigotValue); -+ -+ int trackRange = (configuredSpigotValue >>> 4) + ((configuredSpigotValue & 15) != 0 ? 1 : 0); -+ this.entityTrackerTrackRanges[ordinal] = trackRange; -+ -+ this.playerEntityTrackerTrackMaps[ordinal] = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets); -+ } -+ // Paper end - use distance map to optimise entity tracker - } - - protected ChunkGenerator generator() { -@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - } - - public void move(ServerPlayer player) { -- ObjectIterator objectiterator = this.entityMap.values().iterator(); -- -- while (objectiterator.hasNext()) { -- ChunkMap.TrackedEntity playerchunkmap_entitytracker = (ChunkMap.TrackedEntity) objectiterator.next(); -- -- if (playerchunkmap_entitytracker.entity == player) { -- playerchunkmap_entitytracker.updatePlayers(this.level.players()); -- } else { -- playerchunkmap_entitytracker.updatePlayer(player); -- } -- } -+ // Paper - delay this logic for the entity tracker tick, no need to duplicate it - - int i = SectionPos.blockToSectionCoord(player.getBlockX()); - int j = SectionPos.blockToSectionCoord(player.getBlockZ()); -@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - - entity.tracker = playerchunkmap_entitytracker; // Paper - Fast access to tracker - this.entityMap.put(entity.getId(), playerchunkmap_entitytracker); -- playerchunkmap_entitytracker.updatePlayers(this.level.players()); -+ playerchunkmap_entitytracker.updatePlayers(entity.getPlayersInTrackRange()); // Paper - don't search all players - if (entity instanceof ServerPlayer) { - ServerPlayer entityplayer = (ServerPlayer) entity; - -@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - entity.tracker = null; // Paper - We're no longer tracked - } - -+ // Paper start - optimised tracker -+ private final void processTrackQueue() { -+ this.level.timings.tracker1.startTiming(); -+ try { -+ for (TrackedEntity tracker : this.entityMap.values()) { -+ // update tracker entry -+ tracker.updatePlayers(tracker.entity.getPlayersInTrackRange()); -+ } -+ } finally { -+ this.level.timings.tracker1.stopTiming(); -+ } -+ -+ -+ this.level.timings.tracker2.startTiming(); -+ try { -+ for (TrackedEntity tracker : this.entityMap.values()) { -+ tracker.serverEntity.sendChanges(); -+ } -+ } finally { -+ this.level.timings.tracker2.stopTiming(); -+ } -+ } -+ // Paper end - optimised tracker -+ - protected void tick() { -+ // Paper start - optimized tracker -+ if (true) { -+ this.processTrackQueue(); -+ return; -+ } -+ // Paper end - optimized tracker - List list = Lists.newArrayList(); - List list1 = this.level.players(); - ObjectIterator objectiterator = this.entityMap.values().iterator(); -@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - })); - // Paper end - DebugPackets.sendPoiPacketsForChunk(this.level, chunk.getPos()); -- List list = Lists.newArrayList(); -- List list1 = Lists.newArrayList(); -- ObjectIterator objectiterator = this.entityMap.values().iterator(); -- -- while (objectiterator.hasNext()) { -- ChunkMap.TrackedEntity playerchunkmap_entitytracker = (ChunkMap.TrackedEntity) objectiterator.next(); -- Entity entity = playerchunkmap_entitytracker.entity; -- -- if (entity != player && entity.chunkPosition().equals(chunk.getPos())) { -- playerchunkmap_entitytracker.updatePlayer(player); -- if (entity instanceof Mob && ((Mob) entity).getLeashHolder() != null) { -- list.add(entity); -- } -- -- if (!entity.getPassengers().isEmpty()) { -- list1.add(entity); -- } -- } -- } -- -- Iterator iterator; -- Entity entity1; -- -- if (!list.isEmpty()) { -- iterator = list.iterator(); -- -- while (iterator.hasNext()) { -- entity1 = (Entity) iterator.next(); -- player.connection.send(new ClientboundSetEntityLinkPacket(entity1, ((Mob) entity1).getLeashHolder())); -- } -- } -- -- if (!list1.isEmpty()) { -- iterator = list1.iterator(); -- -- while (iterator.hasNext()) { -- entity1 = (Entity) iterator.next(); -- player.connection.send(new ClientboundSetPassengersPacket(entity1)); -- } -- } -+ // Paper - no longer needed - this was used to account for clients bugging out since they needed a chunk to store entities, but they no longer need a chunk - - } - -@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - this.lastSectionPos = SectionPos.of((EntityAccess) entity); - } - -+ // Paper start - use distance map to optimise tracker -+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet lastTrackerCandidates; -+ -+ final void updatePlayers(com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newTrackerCandidates) { -+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet oldTrackerCandidates = this.lastTrackerCandidates; -+ this.lastTrackerCandidates = newTrackerCandidates; -+ -+ if (newTrackerCandidates != null) { -+ Object[] rawData = newTrackerCandidates.getBackingSet(); -+ for (int i = 0, len = rawData.length; i < len; ++i) { -+ Object raw = rawData[i]; -+ if (!(raw instanceof ServerPlayer)) { -+ continue; -+ } -+ ServerPlayer player = (ServerPlayer)raw; -+ this.updatePlayer(player); -+ } -+ } -+ -+ if (oldTrackerCandidates == newTrackerCandidates) { -+ // this is likely the case. -+ // means there has been no range changes, so we can just use the above for tracking. -+ return; -+ } -+ -+ // stuff could have been removed, so we need to check the trackedPlayers set -+ // for players that were removed -+ -+ for (ServerPlayerConnection conn : this.seenBy.toArray(new ServerPlayerConnection[0])) { // avoid CME -+ if (newTrackerCandidates == null || !newTrackerCandidates.contains(conn.getPlayer())) { -+ this.updatePlayer(conn.getPlayer()); -+ } -+ } -+ } -+ // Paper end - use distance map to optimise tracker -+ - public boolean equals(Object object) { - return object instanceof ChunkMap.TrackedEntity ? ((ChunkMap.TrackedEntity) object).entity.getId() == this.entity.getId() : false; - } -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -0,0 +0,0 @@ import net.minecraft.network.syncher.EntityDataSerializers; - import net.minecraft.network.syncher.SynchedEntityData; - import net.minecraft.resources.ResourceKey; - import net.minecraft.resources.ResourceLocation; -+import io.papermc.paper.util.MCUtil; - import net.minecraft.server.MinecraftServer; - import net.minecraft.server.level.ServerLevel; - import net.minecraft.server.level.ServerPlayer; -@@ -0,0 +0,0 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { - - public boolean updatingSectionStatus = false; - // Paper end -+ // Paper start - optimise entity tracking -+ final org.spigotmc.TrackingRange.TrackingRangeType trackingRangeType = org.spigotmc.TrackingRange.getTrackingRangeType(this); -+ -+ public boolean isLegacyTrackingEntity = false; -+ -+ public final void setLegacyTrackingEntity(final boolean isLegacyTrackingEntity) { -+ this.isLegacyTrackingEntity = isLegacyTrackingEntity; -+ } -+ -+ public final com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet getPlayersInTrackRange() { -+ // determine highest range of passengers -+ if (this.passengers.isEmpty()) { -+ return ((ServerLevel)this.level).getChunkSource().chunkMap.playerEntityTrackerTrackMaps[this.trackingRangeType.ordinal()] -+ .getObjectsInRange(MCUtil.getCoordinateKey(this)); -+ } -+ Iterable passengers = this.getIndirectPassengers(); -+ net.minecraft.server.level.ChunkMap chunkMap = ((ServerLevel)this.level).getChunkSource().chunkMap; -+ org.spigotmc.TrackingRange.TrackingRangeType type = this.trackingRangeType; -+ int range = chunkMap.getEntityTrackerRange(type.ordinal()); -+ -+ for (Entity passenger : passengers) { -+ org.spigotmc.TrackingRange.TrackingRangeType passengerType = passenger.trackingRangeType; -+ int passengerRange = chunkMap.getEntityTrackerRange(passengerType.ordinal()); -+ if (passengerRange > range) { -+ type = passengerType; -+ range = passengerRange; -+ } -+ } -+ -+ return chunkMap.playerEntityTrackerTrackMaps[type.ordinal()].getObjectsInRange(MCUtil.getCoordinateKey(this)); -+ } -+ // Paper end - optimise entity tracking - - public Entity(EntityType type, Level world) { - this.id = Entity.ENTITY_COUNTER.incrementAndGet(); -diff --git a/src/main/java/org/spigotmc/TrackingRange.java b/src/main/java/org/spigotmc/TrackingRange.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/org/spigotmc/TrackingRange.java -+++ b/src/main/java/org/spigotmc/TrackingRange.java -@@ -0,0 +0,0 @@ public class TrackingRange - return config.otherTrackingRange; - } - } -+ -+ // Paper start - optimise entity tracking -+ // copied from above, TODO check on update -+ public static TrackingRangeType getTrackingRangeType(Entity entity) -+ { -+ if (entity instanceof net.minecraft.world.entity.boss.enderdragon.EnderDragon) return TrackingRangeType.ENDERDRAGON; // Paper - enderdragon is exempt -+ if ( entity instanceof ServerPlayer ) -+ { -+ return TrackingRangeType.PLAYER; -+ // Paper start - Simplify and set water mobs to animal tracking range -+ } -+ switch (entity.activationType) { -+ case RAIDER: -+ case MONSTER: -+ case FLYING_MONSTER: -+ return TrackingRangeType.MONSTER; -+ case WATER: -+ case VILLAGER: -+ case ANIMAL: -+ return TrackingRangeType.ANIMAL; -+ case MISC: -+ } -+ if ( entity instanceof ItemFrame || entity instanceof Painting || entity instanceof ItemEntity || entity instanceof ExperienceOrb ) -+ // Paper end -+ { -+ return TrackingRangeType.MISC; -+ } else if (entity instanceof Display) { -+ return TrackingRangeType.DISPLAY; -+ } else -+ { -+ return TrackingRangeType.OTHER; -+ } -+ } -+ -+ public static enum TrackingRangeType { -+ PLAYER, -+ ANIMAL, -+ MONSTER, -+ MISC, -+ OTHER, -+ ENDERDRAGON, -+ DISPLAY; -+ } -+ // Paper end - optimise entity tracking - }