aboutsummaryrefslogtreecommitdiffhomepage
path: root/patches/removed
diff options
context:
space:
mode:
authorNassim Jahnke <[email protected]>2023-09-23 09:42:59 +1000
committerNassim Jahnke <[email protected]>2023-09-23 09:42:59 +1000
commitd23c4a50e6007a4828a1880cd3c37eb97c9099c9 (patch)
tree9ec9cc8a2cf8f5ff8ae8551c456393f566b544e0 /patches/removed
parentb3ec8bd8ae19e40022ad4dd5db4a1414cb4330bb (diff)
downloadPaper-d23c4a50e6007a4828a1880cd3c37eb97c9099c9.tar.gz
Paper-d23c4a50e6007a4828a1880cd3c37eb97c9099c9.zip
Fix local attribute setting
Diffstat (limited to 'patches/removed')
-rw-r--r--patches/removed/1.20.2/0403-Reduce-allocation-of-Vec3D-by-entity-tracker.patch58
-rw-r--r--patches/removed/1.20.2/0414-Use-distance-map-to-optimise-entity-tracker.patch387
-rw-r--r--patches/removed/1.20.2/0682-Allow-controlled-flushing-for-network-manager.patch150
-rw-r--r--patches/removed/1.20.2/0684-Optimize-anyPlayerCloseEnoughForSpawning-to-use-dist.patch346
-rw-r--r--patches/removed/1.20.2/0685-Optimise-chunk-tick-iteration.patch215
-rw-r--r--patches/removed/1.20.2/0691-Remove-streams-for-villager-AI.patch187
-rw-r--r--patches/removed/1.20.2/0696-Consolidate-flush-calls-for-entity-tracker-packets.patch52
-rw-r--r--patches/removed/1.20.2/0704-Optimise-non-flush-packet-sending.patch46
-rw-r--r--patches/removed/1.20.2/0705-Optimise-nearby-player-lookups.patch426
9 files changed, 1867 insertions, 0 deletions
diff --git a/patches/removed/1.20.2/0403-Reduce-allocation-of-Vec3D-by-entity-tracker.patch b/patches/removed/1.20.2/0403-Reduce-allocation-of-Vec3D-by-entity-tracker.patch
new file mode 100644
index 0000000000..1d5553e5a4
--- /dev/null
+++ b/patches/removed/1.20.2/0403-Reduce-allocation-of-Vec3D-by-entity-tracker.patch
@@ -0,0 +1,58 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Spottedleaf <[email protected]>
+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 05ac41e136da43284fb24a6b698ebd36318278fb..5ca3ad7b3d7606accd0a58b3c708fadb349608f7 100644
+--- a/src/main/java/net/minecraft/network/protocol/game/VecDeltaCodec.java
++++ b/src/main/java/net/minecraft/network/protocol/game/VecDeltaCodec.java
+@@ -5,7 +5,7 @@ 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 48586780da5d260894fe59efaa97cb1facfe73fe..dadf403ac91887f0fae87889170deb6d5732cbc1 100644
+--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
+@@ -1348,9 +1348,14 @@ 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 ae3d1077b42fb288938a8a65471f00b3f52cb18f..d1bcc1f9816826391b7ba7c79e3b1c2c013933de 100644
+--- a/src/main/java/net/minecraft/server/level/ServerEntity.java
++++ b/src/main/java/net/minecraft/server/level/ServerEntity.java
+@@ -161,7 +161,13 @@ 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/removed/1.20.2/0414-Use-distance-map-to-optimise-entity-tracker.patch b/patches/removed/1.20.2/0414-Use-distance-map-to-optimise-entity-tracker.patch
new file mode 100644
index 0000000000..ee2cad7d7d
--- /dev/null
+++ b/patches/removed/1.20.2/0414-Use-distance-map-to-optimise-entity-tracker.patch
@@ -0,0 +1,387 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Spottedleaf <[email protected]>
+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 dadf403ac91887f0fae87889170deb6d5732cbc1..52d9d0f2366f292c56eee9fe241cb43bc2900b23 100644
+--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
+@@ -157,6 +157,23 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+
+ // Paper start - distance maps
+ private final com.destroystokyo.paper.util.misc.PooledLinkedHashSets<ServerPlayer> pooledLinkedPlayerHashSets = new com.destroystokyo.paper.util.misc.PooledLinkedHashSets<>();
++ // 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
+@@ -168,6 +185,14 @@ 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) {
+@@ -178,6 +203,11 @@ 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) {
+@@ -190,6 +220,14 @@ 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
+@@ -276,6 +314,48 @@ 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() {
+@@ -960,17 +1040,7 @@ 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());
+@@ -1054,7 +1124,7 @@ 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;
+
+@@ -1098,7 +1168,37 @@ 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<ServerPlayer> list = Lists.newArrayList();
+ List<ServerPlayer> list1 = this.level.players();
+ ObjectIterator objectiterator = this.entityMap.values().iterator();
+@@ -1205,46 +1305,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+ }));
+ // Paper end
+ DebugPackets.sendPoiPacketsForChunk(this.level, chunk.getPos());
+- List<Entity> list = Lists.newArrayList();
+- List<Entity> 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
+
+ }
+
+@@ -1299,6 +1360,42 @@ 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<ServerPlayer> lastTrackerCandidates;
++
++ final void updatePlayers(com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> newTrackerCandidates) {
++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> 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 ce4b2ec2ad6138b754ced976521d1c73eb4193a8..c99d24008792b07d5e2984261215de944482006b 100644
+--- a/src/main/java/net/minecraft/world/entity/Entity.java
++++ b/src/main/java/net/minecraft/world/entity/Entity.java
+@@ -57,6 +57,7 @@ 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;
+@@ -481,6 +482,38 @@ 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<ServerPlayer> 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<Entity> 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 172d231adecf043f9f06b7f5e0365ae82327998d..ed8378ad022c375b0d18172aeccf65cb026d9d68 100644
+--- a/src/main/java/org/spigotmc/TrackingRange.java
++++ b/src/main/java/org/spigotmc/TrackingRange.java
+@@ -55,4 +55,48 @@ 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
+ }
diff --git a/patches/removed/1.20.2/0682-Allow-controlled-flushing-for-network-manager.patch b/patches/removed/1.20.2/0682-Allow-controlled-flushing-for-network-manager.patch
new file mode 100644
index 0000000000..fd7bf47eb3
--- /dev/null
+++ b/patches/removed/1.20.2/0682-Allow-controlled-flushing-for-network-manager.patch
@@ -0,0 +1,150 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Spottedleaf <[email protected]>
+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 0968827df66057ec73185ce688a62e8b27abba0c..95f5ec348ab24b28c19b46cea7b023a1d49998b5 100644
+--- a/src/main/java/net/minecraft/network/Connection.java
++++ b/src/main/java/net/minecraft/network/Connection.java
+@@ -126,6 +126,39 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
+ 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;
+ }
+@@ -298,7 +331,7 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
+ 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
+@@ -322,6 +355,14 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
+ }
+
+ 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();
+
+@@ -336,16 +377,21 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
+ }
+
+ 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);
+ }
+@@ -359,7 +405,7 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
+
+ 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) -> {
+@@ -415,6 +461,10 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
+ 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<PacketHolder> iterator = this.queue.iterator();
+@@ -422,7 +472,7 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
+ 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;
+ }
+
+@@ -434,11 +484,17 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
+
+ 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/removed/1.20.2/0684-Optimize-anyPlayerCloseEnoughForSpawning-to-use-dist.patch b/patches/removed/1.20.2/0684-Optimize-anyPlayerCloseEnoughForSpawning-to-use-dist.patch
new file mode 100644
index 0000000000..5554cf8ae0
--- /dev/null
+++ b/patches/removed/1.20.2/0684-Optimize-anyPlayerCloseEnoughForSpawning-to-use-dist.patch
@@ -0,0 +1,346 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Spottedleaf <[email protected]>
+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 c5389e7f3665c06e487dfde3200b7e229694fbd2..4164204ba80f68a768de0ed1721c6447b972a631 100644
+--- a/src/main/java/net/minecraft/server/level/ChunkHolder.java
++++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java
+@@ -79,16 +79,29 @@ public class ChunkHolder {
+
+ // Paper start
+ public void onChunkAdd() {
+-
++ // Paper start - optimise anyPlayerCloseEnoughForSpawning
++ long key = io.papermc.paper.util.MCUtil.getCoordinateKey(this.pos);
++ this.playersInMobSpawnRange = this.chunkMap.playerMobSpawnMap.getObjectsInRange(key);
++ this.playersInChunkTickRange = this.chunkMap.playerChunkTickRangeMap.getObjectsInRange(key);
++ // Paper end - optimise anyPlayerCloseEnoughForSpawning
+ }
+
+ public void onChunkRemove() {
+-
++ // Paper start - optimise anyPlayerCloseEnoughForSpawning
++ this.playersInMobSpawnRange = null;
++ this.playersInChunkTickRange = null;
++ // Paper end - optimise anyPlayerCloseEnoughForSpawning
+ }
+ // Paper end
+
+ public final io.papermc.paper.chunk.system.scheduling.NewChunkHolder newChunkHolder; // Paper - rewrite chunk system
+
++ // Paper start - optimise anyPlayerCloseEnoughForSpawning
++ // cached here to avoid a map lookup
++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> playersInMobSpawnRange;
++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> playersInChunkTickRange;
++ // Paper end - optimise anyPlayerCloseEnoughForSpawning
++
+ // Paper start - replace player chunk loader
+ private final com.destroystokyo.paper.util.maplist.ReferenceList<ServerPlayer> 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 52d9d0f2366f292c56eee9fe241cb43bc2900b23..80b2ff12e48e1aabebd9ebcf5958c1f5d073d55b 100644
+--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
+@@ -174,12 +174,24 @@ 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));
+@@ -198,6 +210,10 @@ 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);
+@@ -215,6 +231,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+ int chunkZ = MCUtil.getChunkCoordinate(player.getZ());
+ // Note: players need to be explicitly added to distance maps before they can be updated
+ this.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));
+@@ -356,6 +373,38 @@ 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<ServerPlayer> newState) -> {
++ ChunkHolder playerChunk = ChunkMap.this.getUpdatingChunkIfPresent(MCUtil.getCoordinateKey(rangeX, rangeZ));
++ if (playerChunk != null) {
++ playerChunk.playersInChunkTickRange = newState;
++ }
++ },
++ (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ,
++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> newState) -> {
++ ChunkHolder playerChunk = ChunkMap.this.getUpdatingChunkIfPresent(MCUtil.getCoordinateKey(rangeX, rangeZ));
++ if (playerChunk != null) {
++ playerChunk.playersInChunkTickRange = newState;
++ }
++ });
++ this.playerMobSpawnMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets,
++ (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ,
++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> newState) -> {
++ ChunkHolder playerChunk = ChunkMap.this.getUpdatingChunkIfPresent(MCUtil.getCoordinateKey(rangeX, rangeZ));
++ if (playerChunk != null) {
++ playerChunk.playersInMobSpawnRange = newState;
++ }
++ },
++ (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ,
++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> newState) -> {
++ ChunkHolder playerChunk = ChunkMap.this.getUpdatingChunkIfPresent(MCUtil.getCoordinateKey(rangeX, rangeZ));
++ if (playerChunk != null) {
++ playerChunk.playersInMobSpawnRange = newState;
++ }
++ });
++ // Paper end - optimise ChunkMap#anyPlayerCloseEnoughForSpawning
+ }
+
+ protected ChunkGenerator generator() {
+@@ -930,43 +979,48 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+ return this.anyPlayerCloseEnoughForSpawning(pos, false);
+ }
+
+- boolean anyPlayerCloseEnoughForSpawning(ChunkPos chunkcoordintpair, boolean reducedRange) {
+- int chunkRange = level.spigotConfig.mobSpawnRange;
+- chunkRange = (chunkRange > level.spigotConfig.viewDistance) ? (byte) level.spigotConfig.viewDistance : chunkRange;
+- chunkRange = (chunkRange > 8) ? 8 : chunkRange;
+-
+- final int finalChunkRange = chunkRange; // Paper for lambda below
+- //double blockRange = (reducedRange) ? Math.pow(chunkRange << 4, 2) : 16384.0D; // Paper - use from event
+- double blockRange = 16384.0D; // Paper
+- // Spigot end
+- long i = chunkcoordintpair.toLong();
++ // Paper start - optimise anyPlayerCloseEnoughForSpawning
++ final boolean anyPlayerCloseEnoughForSpawning(ChunkPos chunkcoordintpair, boolean reducedRange) {
++ return this.anyPlayerCloseEnoughForSpawning(this.getUpdatingChunkIfPresent(chunkcoordintpair.toLong()), chunkcoordintpair, reducedRange);
++ }
+
+- if (!this.distanceManager.hasPlayersNearby(i)) {
++ final boolean anyPlayerCloseEnoughForSpawning(ChunkHolder playerchunk, ChunkPos chunkcoordintpair, boolean reducedRange) {
++ // this function is so hot that removing the map lookup call can have an order of magnitude impact on its performance
++ // tested and confirmed via System.nanoTime()
++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> playersInRange = reducedRange ? playerchunk.playersInMobSpawnRange : playerchunk.playersInChunkTickRange;
++ if (playersInRange == null) {
+ return false;
+- } else {
+- Iterator iterator = this.playerMap.getPlayers(i).iterator();
+-
+- ServerPlayer entityplayer;
++ }
++ Object[] backingSet = playersInRange.getBackingSet();
+
+- do {
+- if (!iterator.hasNext()) {
+- return false;
++ if (reducedRange) {
++ for (int i = 0, len = backingSet.length; i < len; ++i) {
++ Object raw = backingSet[i];
++ if (!(raw instanceof ServerPlayer player)) {
++ continue;
+ }
+-
+- entityplayer = (ServerPlayer) iterator.next();
+- // Paper start - add PlayerNaturallySpawnCreaturesEvent
+- com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent event;
+- blockRange = 16384.0D;
+- if (reducedRange) {
+- event = entityplayer.playerNaturallySpawnedEvent;
+- if (event == null || event.isCancelled()) return false;
+- blockRange = (double) ((event.getSpawnRadius() << 4) * (event.getSpawnRadius() << 4));
++ // don't check spectator and whatnot, already handled by mob spawn map update
++ if (euclideanDistanceSquared(chunkcoordintpair, player) < player.lastEntitySpawnRadiusSquared) {
++ return true; // in range
+ }
+- // Paper end
+- } while (!this.playerIsCloseEnoughForSpawning(entityplayer, chunkcoordintpair, blockRange)); // Spigot
+-
+- return true;
++ }
++ } else {
++ final double range = (DistanceManager.MOB_SPAWN_RANGE * 16) * (DistanceManager.MOB_SPAWN_RANGE * 16);
++ // before spigot, mob spawn range was actually mob spawn range + tick range, but it was split
++ for (int i = 0, len = backingSet.length; i < len; ++i) {
++ Object raw = backingSet[i];
++ if (!(raw instanceof ServerPlayer player)) {
++ continue;
++ }
++ // don't check spectator and whatnot, already handled by mob spawn map update
++ if (euclideanDistanceSquared(chunkcoordintpair, player) < range) {
++ return true; // in range
++ }
++ }
+ }
++ // no players in range
++ return false;
++ // Paper end - optimise anyPlayerCloseEnoughForSpawning
+ }
+
+ public List<ServerPlayer> getPlayersCloseForSpawning(ChunkPos pos) {
+diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java
+index 0a926afa06a5e37cf2650afa1b5099a2a9ffa659..ae4a4710ba07614be42cdcbf52cee04cfa08466b 100644
+--- a/src/main/java/net/minecraft/server/level/DistanceManager.java
++++ b/src/main/java/net/minecraft/server/level/DistanceManager.java
+@@ -50,7 +50,7 @@ public abstract class DistanceManager {
+ private static final int INITIAL_TICKET_LIST_CAPACITY = 4;
+ final Long2ObjectMap<ObjectSet<ServerPlayer>> playersPerChunk = new Long2ObjectOpenHashMap();
+ // Paper - rewrite chunk system
+- private final DistanceManager.FixedPlayerDistanceChunkTracker naturalSpawnChunkCounter = new DistanceManager.FixedPlayerDistanceChunkTracker(8);
++ public static final int MOB_SPAWN_RANGE = 8; // private final DistanceManager.FixedPlayerDistanceChunkTracker naturalSpawnChunkCounter = new DistanceManager.FixedPlayerDistanceChunkTracker(8); // Paper - no longer used
+ // Paper - rewrite chunk system
+ private final ChunkMap chunkMap; // Paper
+
+@@ -136,7 +136,7 @@ public abstract class DistanceManager {
+ long i = chunkcoordintpair.toLong();
+
+ // Paper - no longer used
+- this.naturalSpawnChunkCounter.update(i, 0, true);
++ //this.naturalSpawnChunkCounter.update(i, 0, true); // Paper - no longer used
+ //this.playerTicketManager.update(i, 0, true); // Paper - no longer used
+ //this.tickingTicketsTracker.addTicket(TicketType.PLAYER, chunkcoordintpair, this.getPlayerTicketLevel(), chunkcoordintpair); // Paper - no longer used
+ }
+@@ -150,7 +150,7 @@ public abstract class DistanceManager {
+ if (objectset != null) objectset.remove(player); // Paper - some state corruption happens here, don't crash, clean up gracefully.
+ if (objectset == null || objectset.isEmpty()) { // Paper
+ this.playersPerChunk.remove(i);
+- this.naturalSpawnChunkCounter.update(i, Integer.MAX_VALUE, false);
++ // this.naturalSpawnChunkCounter.update(i, Integer.MAX_VALUE, false); // Paper - no longer used
+ //this.playerTicketManager.update(i, Integer.MAX_VALUE, false); // Paper - no longer used
+ //this.tickingTicketsTracker.removeTicket(TicketType.PLAYER, chunkcoordintpair, this.getPlayerTicketLevel(), chunkcoordintpair); // Paper - no longer used
+ }
+@@ -192,13 +192,17 @@ public abstract class DistanceManager {
+ }
+
+ public int getNaturalSpawnChunkCount() {
+- this.naturalSpawnChunkCounter.runAllUpdates();
+- return this.naturalSpawnChunkCounter.chunks.size();
++ // Paper start - use distance map to implement
++ // note: this is the spawn chunk count
++ return this.chunkMap.playerChunkTickRangeMap.size();
++ // Paper end - use distance map to implement
+ }
+
+ public boolean hasPlayersNearby(long chunkPos) {
+- this.naturalSpawnChunkCounter.runAllUpdates();
+- return this.naturalSpawnChunkCounter.chunks.containsKey(chunkPos);
++ // Paper start - use distance map to implement
++ // note: this is the is spawn chunk method
++ return this.chunkMap.playerChunkTickRangeMap.getObjectsInRange(chunkPos) != null;
++ // Paper end - use distance map to implement
+ }
+
+ public String getDebugStatus() {
+diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
+index 3f5d572994bc8b3f1e106105dc0bb202ad005b8c..5c5fe2087a7617324ab8e18389e3ffa9ac413026 100644
+--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
++++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
+@@ -517,6 +517,37 @@ public class ServerChunkCache extends ChunkSource {
+ if (flag) {
+ this.chunkMap.tick();
+ } else {
++ // Paper start - optimize isOutisdeRange
++ ChunkMap playerChunkMap = this.chunkMap;
++ for (ServerPlayer player : this.level.players) {
++ if (!player.affectsSpawning || player.isSpectator()) {
++ playerChunkMap.playerMobSpawnMap.remove(player);
++ continue;
++ }
++
++ int viewDistance = this.chunkMap.getEffectiveViewDistance();
++
++ // copied and modified from isOutisdeRange
++ int chunkRange = level.spigotConfig.mobSpawnRange;
++ chunkRange = (chunkRange > viewDistance) ? (byte)viewDistance : chunkRange;
++ chunkRange = (chunkRange > DistanceManager.MOB_SPAWN_RANGE) ? DistanceManager.MOB_SPAWN_RANGE : chunkRange;
++
++ com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent event = new com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent(player.getBukkitEntity(), (byte)chunkRange);
++ event.callEvent();
++ if (event.isCancelled() || event.getSpawnRadius() < 0 || playerChunkMap.playerChunkTickRangeMap.getLastViewDistance(player) == -1) {
++ playerChunkMap.playerMobSpawnMap.remove(player);
++ continue;
++ }
++
++ int range = Math.min(event.getSpawnRadius(), 32); // limit to max view distance
++ int chunkX = io.papermc.paper.util.MCUtil.getChunkCoordinate(player.getX());
++ int chunkZ = io.papermc.paper.util.MCUtil.getChunkCoordinate(player.getZ());
++
++ playerChunkMap.playerMobSpawnMap.addOrUpdate(player, chunkX, chunkZ, range);
++ player.lastEntitySpawnRadiusSquared = (double)((range << 4) * (range << 4)); // used in anyPlayerCloseEnoughForSpawning
++ player.playerNaturallySpawnedEvent = event;
++ }
++ // Paper end - optimize isOutisdeRange
+ LevelData worlddata = this.level.getLevelData();
+ ProfilerFiller gameprofilerfiller = this.level.getProfiler();
+
+@@ -560,15 +591,7 @@ public class ServerChunkCache extends ChunkSource {
+ boolean flag2 = this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && !this.level.players().isEmpty(); // CraftBukkit
+
+ Collections.shuffle(list);
+- // Paper start - call player naturally spawn event
+- int chunkRange = level.spigotConfig.mobSpawnRange;
+- chunkRange = (chunkRange > level.spigotConfig.viewDistance) ? (byte) level.spigotConfig.viewDistance : chunkRange;
+- chunkRange = Math.min(chunkRange, 8);
+- for (ServerPlayer entityPlayer : this.level.players()) {
+- entityPlayer.playerNaturallySpawnedEvent = new com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent(entityPlayer.getBukkitEntity(), (byte) chunkRange);
+- entityPlayer.playerNaturallySpawnedEvent.callEvent();
+- };
+- // Paper end
++ // Paper - moved natural spawn event up
+ Iterator iterator1 = list.iterator();
+
+ while (iterator1.hasNext()) {
+@@ -576,9 +599,9 @@ public class ServerChunkCache extends ChunkSource {
+ LevelChunk chunk1 = chunkproviderserver_a.chunk;
+ ChunkPos chunkcoordintpair = chunk1.getPos();
+
+- if (this.level.isNaturalSpawningAllowed(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(chunkcoordintpair)) {
++ if (this.level.isNaturalSpawningAllowed(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(chunkproviderserver_a.holder, chunkcoordintpair, false)) { // Paper - optimise anyPlayerCloseEnoughForSpawning
+ chunk1.incrementInhabitedTime(j);
+- if (flag2 && (this.spawnEnemies || this.spawnFriendlies) && this.level.getWorldBorder().isWithinBounds(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(chunkcoordintpair, true)) { // Spigot
++ if (flag2 && (this.spawnEnemies || this.spawnFriendlies) && this.level.getWorldBorder().isWithinBounds(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(chunkproviderserver_a.holder, chunkcoordintpair, true)) { // Spigot // Paper - optimise anyPlayerCloseEnoughForSpawning
+ NaturalSpawner.spawnForChunk(this.level, chunk1, spawnercreature_d, this.spawnFriendlies, this.spawnEnemies, flag1);
+ }
+
+diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java
+index ce2972c62f91724f6a9e56c8103fba219f7a60bb..6c00162847b59ffa3d1403b956545c57ba539139 100644
+--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java
++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java
+@@ -273,6 +273,7 @@ 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<ServerPlayer> 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/removed/1.20.2/0685-Optimise-chunk-tick-iteration.patch b/patches/removed/1.20.2/0685-Optimise-chunk-tick-iteration.patch
new file mode 100644
index 0000000000..760883c7b9
--- /dev/null
+++ b/patches/removed/1.20.2/0685-Optimise-chunk-tick-iteration.patch
@@ -0,0 +1,215 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Spottedleaf <[email protected]>
+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 4164204ba80f68a768de0ed1721c6447b972a631..4ae1ba645d9fdc1eb6d5a3e4f8ceed9b4841e003 100644
+--- a/src/main/java/net/minecraft/server/level/ChunkHolder.java
++++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java
+@@ -84,6 +84,11 @@ 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() {
+@@ -91,6 +96,11 @@ 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
+
+@@ -230,7 +240,7 @@ 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();
+ }
+
+@@ -254,6 +264,7 @@ 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) {
+@@ -268,8 +279,19 @@ 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 80b2ff12e48e1aabebd9ebcf5958c1f5d073d55b..bcf0dfe50add8e260a280e45673727f964bac6fd 100644
+--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
+@@ -115,6 +115,8 @@ 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;
+@@ -152,6 +154,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+ private final Queue<Runnable> unloadQueue;
+ int viewDistance;
+ public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerMobDistanceMap; // Paper
++ public final ReferenceOpenHashSet<ChunkHolder> 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 5c5fe2087a7617324ab8e18389e3ffa9ac413026..828de28f2777e2477a9c6545c8af96c4ca4e352b 100644
+--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
++++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
+@@ -48,6 +48,7 @@ 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 {
+
+@@ -574,42 +575,59 @@ public class ServerChunkCache extends ChunkSource {
+
+ this.lastSpawnState = spawnercreature_d;
+ gameprofilerfiller.popPush("filteringLoadedChunks");
+- List<ServerChunkCache.ChunkAndHolder> 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<LevelChunk> iterator1;
++ if (this.level.paperConfig().entities.spawning.perPlayerMobSpawns) {
++ iterator1 = this.entityTickingChunks.iterator();
++ } else {
++ iterator1 = this.entityTickingChunks.unsafeIterator();
++ List<LevelChunk> 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) {
+@@ -617,15 +635,24 @@ 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<ChunkHolder> 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/removed/1.20.2/0691-Remove-streams-for-villager-AI.patch b/patches/removed/1.20.2/0691-Remove-streams-for-villager-AI.patch
new file mode 100644
index 0000000000..225b1f7a12
--- /dev/null
+++ b/patches/removed/1.20.2/0691-Remove-streams-for-villager-AI.patch
@@ -0,0 +1,187 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Spottedleaf <[email protected]>
+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 b45c4f50705f80163d44d9e588f86a5770f5be38..10cbb80c7cd9ba30150d8d935c0d115719c35509 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
+@@ -52,7 +52,7 @@ public class GateBehavior<E extends LivingEntity> implements BehaviorControl<E>
+ 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;
+@@ -61,11 +61,13 @@ public class GateBehavior<E extends LivingEntity> implements BehaviorControl<E>
+
+ @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<? super E> 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;
+ })) {
+@@ -77,11 +79,11 @@ public class GateBehavior<E extends LivingEntity> implements BehaviorControl<E>
+ @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<? super E> behavior : this.behaviors) {
++ if (behavior.getStatus() == Behavior.Status.RUNNING) {
++ behavior.doStop(world, entity, time);
++ }
++ }
+ this.exitErasedMemories.forEach(entity.getBrain()::eraseMemory);
+ }
+
+@@ -117,25 +119,31 @@ public class GateBehavior<E extends LivingEntity> implements BehaviorControl<E>
+ public static enum RunningPolicy {
+ RUN_ONE {
+ @Override
+- public <E extends LivingEntity> void apply(Stream<BehaviorControl<? super E>> 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 <E extends LivingEntity> void apply(List<ShufflingList.WeightedEntry<BehaviorControl<? super E>>> tasks, ServerLevel world, E entity, long time) {
++ for (ShufflingList.WeightedEntry<BehaviorControl<? super E>> task : tasks) {
++ final BehaviorControl<? super E> behavior = task.getData();
++ if (behavior.getStatus() == Behavior.Status.STOPPED && behavior.tryStart(world, entity, time)) {
++ break;
++ }
++ }
++ // Paper end - remove streams
+ }
+ },
+ TRY_ALL {
+ @Override
+- public <E extends LivingEntity> void apply(Stream<BehaviorControl<? super E>> 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 <E extends LivingEntity> void apply(List<ShufflingList.WeightedEntry<BehaviorControl<? super E>>> tasks, ServerLevel world, E entity, long time) {
++ for (ShufflingList.WeightedEntry<BehaviorControl<? super E>> task : tasks) {
++ final BehaviorControl<? super E> behavior = task.getData();
++ if (behavior.getStatus() == Behavior.Status.STOPPED) {
++ behavior.tryStart(world, entity, time);
++ }
++ }
++ // Paper end - remove streams
+ }
+ };
+
+- public abstract <E extends LivingEntity> void apply(Stream<BehaviorControl<? super E>> tasks, ServerLevel world, E entity, long time);
++ public abstract <E extends LivingEntity> void apply(List<ShufflingList.WeightedEntry<BehaviorControl<? super E>>> 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 731ef21dbbd25d6924717de42f4569a9b5935643..fe3ab3d388f0481fb0db06b7f730f868dbf8e8a5 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
+@@ -14,7 +14,7 @@ import java.util.stream.Stream;
+ import net.minecraft.util.RandomSource;
+
+ public class ShufflingList<U> implements Iterable<U> {
+- protected final List<ShufflingList.WeightedEntry<U>> entries;
++ public final List<ShufflingList.WeightedEntry<U>> 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 1dfcc5cba6ffb463acf161a23fff1ca452184290..61a164c5bfc86faa3f4d04a66e0257016cfd937d 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
+@@ -25,13 +25,16 @@ public class NearestItemSensor extends Sensor<Mob> {
+ protected void doTick(ServerLevel world, Mob entity) {
+ Brain<?> brain = entity.getBrain();
+ List<ItemEntity> 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 312775d0430f793720211dc29bb293503e799d11..75d9c4f011b5a97def215784c92bb57bbb35d06b 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
+@@ -21,25 +21,30 @@ public class PlayerSensor extends Sensor<LivingEntity> {
+
+ @Override
+ protected void doTick(ServerLevel world, LivingEntity entity) {
+- List<Player> 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<Player> 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/removed/1.20.2/0696-Consolidate-flush-calls-for-entity-tracker-packets.patch b/patches/removed/1.20.2/0696-Consolidate-flush-calls-for-entity-tracker-packets.patch
new file mode 100644
index 0000000000..6846952891
--- /dev/null
+++ b/patches/removed/1.20.2/0696-Consolidate-flush-calls-for-entity-tracker-packets.patch
@@ -0,0 +1,52 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Spottedleaf <[email protected]>
+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 2a31265ac49b7a6e32105530d00952ee0c0d4331..488a253e218409b5f0b4a872cee0928578fa7582 100644
+--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
++++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
+@@ -656,7 +656,24 @@ 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<net.minecraft.network.Connection> 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/removed/1.20.2/0704-Optimise-non-flush-packet-sending.patch b/patches/removed/1.20.2/0704-Optimise-non-flush-packet-sending.patch
new file mode 100644
index 0000000000..702421e3eb
--- /dev/null
+++ b/patches/removed/1.20.2/0704-Optimise-non-flush-packet-sending.patch
@@ -0,0 +1,46 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Spottedleaf <[email protected]>
+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 9a9924a645c71e0cec30e29a9defcd1e22e2e8ef..15798ed13488b8b8b16ebee557dce18e3dc51708 100644
+--- a/src/main/java/net/minecraft/network/Connection.java
++++ b/src/main/java/net/minecraft/network/Connection.java
+@@ -437,9 +437,19 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
+ 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/removed/1.20.2/0705-Optimise-nearby-player-lookups.patch b/patches/removed/1.20.2/0705-Optimise-nearby-player-lookups.patch
new file mode 100644
index 0000000000..a62fd88bd6
--- /dev/null
+++ b/patches/removed/1.20.2/0705-Optimise-nearby-player-lookups.patch
@@ -0,0 +1,426 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Spottedleaf <[email protected]>
+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 4ae1ba645d9fdc1eb6d5a3e4f8ceed9b4841e003..e2202389a2c4133a183cca59c4e909fc419379ab 100644
+--- a/src/main/java/net/minecraft/server/level/ChunkHolder.java
++++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java
+@@ -89,6 +89,12 @@ 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() {
+@@ -101,6 +107,12 @@ 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 db40680bd9e026d9e98135355e4844c32e82fd51..153b82e4a3134ff0e40f267f1a005bc9184785ef 100644
+--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
+@@ -157,6 +157,12 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+ public final ReferenceOpenHashSet<ChunkHolder> 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<ServerPlayer> pooledLinkedPlayerHashSets = new com.destroystokyo.paper.util.misc.PooledLinkedHashSets<>();
+@@ -208,6 +214,7 @@ 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) {
+@@ -217,6 +224,7 @@ 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);
+@@ -248,6 +256,7 @@ 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
+@@ -408,6 +417,23 @@ 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<ServerPlayer> 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<ServerPlayer> 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 fb9cf86250939fbc9cf1bfb90f6a1a7f4a489460..84ef33f700d3205d934802dceb4a2279df00adb0 100644
+--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
+@@ -562,6 +562,84 @@ public class ServerLevel extends Level implements WorldGenLevel {
+ }
+ // Paper end
+
++ // Paper start - optimise checkDespawn
++ public final List<ServerPlayer> 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<ServerPlayer> 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<Player> getNearbyPlayers(net.minecraft.world.entity.ai.targeting.TargetingConditions condition, LivingEntity source, AABB axisalignedbb) {
++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> 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<Player> 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<Level> resourcekey, LevelStem worlddimension, ChunkProgressListener worldloadlistener, boolean flag, long i, List<CustomSpawner> 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
+@@ -687,6 +765,14 @@ 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 90d95dc95fe8868a4c0298acb0e1c6ce7bd883cb..3d5c967dbccb2a288092a3bf950ab4bfda7a3bb4 100644
+--- a/src/main/java/net/minecraft/world/entity/Mob.java
++++ b/src/main/java/net/minecraft/world/entity/Mob.java
+@@ -858,7 +858,12 @@ 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 270ce3995229aa79074e981bb45e5480a5e924d4..fb2e1d6954d244a82c70730241046efad927fc14 100644
+--- a/src/main/java/net/minecraft/world/level/Level.java
++++ b/src/main/java/net/minecraft/world/level/Level.java
+@@ -207,6 +207,69 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
+ return this.getChunkIfLoaded(chunkX, chunkZ) != null;
+ }
+ // Paper end
++ // Paper start - optimise checkDespawn
++ public final List<net.minecraft.server.level.ServerPlayer> getNearbyPlayers(@Nullable Entity source, double sourceX, double sourceY,
++ double sourceZ, double maxRange, @Nullable Predicate<Entity> 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<net.minecraft.server.level.ServerPlayer> ret = new java.util.ArrayList<>();
++ chunk.getNearestPlayers(sourceX, sourceY, sourceZ, predicate, maxRange, ret);
++ return ret;
++ }
++
++ private List<net.minecraft.server.level.ServerPlayer> getNearbyPlayersSlow(@Nullable Entity source, double sourceX, double sourceY,
++ double sourceZ, double maxRange, @Nullable Predicate<Entity> predicate) {
++ List<net.minecraft.server.level.ServerPlayer> ret = new java.util.ArrayList<>();
++ double maxRangeSquared = maxRange * maxRange;
++
++ for (net.minecraft.server.level.ServerPlayer player : (List<net.minecraft.server.level.ServerPlayer>)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<Entity> 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<net.minecraft.server.level.ServerPlayer>)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<Entity> 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<Entity> predicate) {
++ return this.getNearestPlayer(null, d0, d1, d2, d3, predicate);
++ }
++ // Paper end - optimise checkDespawn
+
+ public abstract ResourceKey<LevelStem> getTypeKey();
+
+diff --git a/src/main/java/net/minecraft/world/level/NaturalSpawner.java b/src/main/java/net/minecraft/world/level/NaturalSpawner.java
+index 7bfc95b2a4fd94bcb0347fd7aff9fe0e9b54daf1..9ae2bd64514a83dbd8c22cc35a9ca4c39add5142 100644
+--- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java
++++ b/src/main/java/net/minecraft/world/level/NaturalSpawner.java
+@@ -260,7 +260,7 @@ 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);
+@@ -334,7 +334,7 @@ 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 4d052f4cde791100f04b822899f0f436feaa23ea..4ff0d2fc9fd76e92e64abd69f2c9e299aa08ac32 100644
+--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
++++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
+@@ -221,6 +221,98 @@ public class LevelChunk extends ChunkAccess {
+ }
+ }
+ // Paper end
++ // Paper start - optimise checkDespawn
++ private boolean playerGeneralAreaCacheSet;
++ private com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<net.minecraft.server.level.ServerPlayer> playerGeneralAreaCache;
++
++ public com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<net.minecraft.server.level.ServerPlayer> 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<net.minecraft.server.level.ServerPlayer> 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<Entity> predicate) {
++ if (!this.playerGeneralAreaCacheSet) {
++ this.updateGeneralAreaCache();
++ }
++
++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<net.minecraft.server.level.ServerPlayer> 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<Entity> predicate,
++ double range, java.util.List<net.minecraft.server.level.ServerPlayer> ret) {
++ if (!this.playerGeneralAreaCacheSet) {
++ this.updateGeneralAreaCache();
++ }
++
++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<net.minecraft.server.level.ServerPlayer> 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());