From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <spottedleaf@spottedleaf.dev>
Date: Thu, 9 Jul 2020 13:34:59 -0700
Subject: [PATCH] Optimise WorldServer#notify

Iterating over all of the navigators in the world is pretty expensive.
Instead, only iterate over navigators in the current region that are
eligible for repathing.

diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
index 97ec72dc383a2637c60cfc988bca2a8a86954ffb..236ba4c1950a3cced590f520b5349eede75fd59b 100644
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
@@ -302,15 +302,81 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
     public final io.papermc.paper.chunk.SingleThreadChunkRegionManager dataRegionManager;
 
     public static final class DataRegionData implements io.papermc.paper.chunk.SingleThreadChunkRegionManager.RegionData {
+        // Paper start - optimise notify()
+        private io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet<Mob> navigators;
+
+        public io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet<Mob> getNavigators() {
+            return this.navigators;
+        }
+
+        public boolean addToNavigators(final Mob navigator) {
+            if (this.navigators == null) {
+                this.navigators = new io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet<>();
+            }
+            return this.navigators.add(navigator);
+        }
+
+        public boolean removeFromNavigators(final Mob navigator) {
+            if (this.navigators == null) {
+                return false;
+            }
+            return this.navigators.remove(navigator);
+        }
+        // Paper end - optimise notify()
     }
 
     public static final class DataRegionSectionData implements io.papermc.paper.chunk.SingleThreadChunkRegionManager.RegionSectionData {
 
+        // Paper start - optimise notify()
+        private io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet<Mob> navigators;
+
+        public io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet<Mob> getNavigators() {
+            return this.navigators;
+        }
+
+        public boolean addToNavigators(final io.papermc.paper.chunk.SingleThreadChunkRegionManager.RegionSection section, final Mob navigator) {
+            if (this.navigators == null) {
+                this.navigators = new io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet<>();
+            }
+            final boolean ret = this.navigators.add(navigator);
+            if (ret) {
+                final DataRegionData data = (DataRegionData)section.getRegion().regionData;
+                if (!data.addToNavigators(navigator)) {
+                    throw new IllegalStateException();
+                }
+            }
+            return ret;
+        }
+
+        public boolean removeFromNavigators(final io.papermc.paper.chunk.SingleThreadChunkRegionManager.RegionSection section, final Mob navigator) {
+            if (this.navigators == null) {
+                return false;
+            }
+            final boolean ret = this.navigators.remove(navigator);
+            if (ret) {
+                final DataRegionData data = (DataRegionData)section.getRegion().regionData;
+                if (!data.removeFromNavigators(navigator)) {
+                    throw new IllegalStateException();
+                }
+            }
+            return ret;
+        }
+        // Paper end - optimise notify()
+
         @Override
         public void removeFromRegion(final io.papermc.paper.chunk.SingleThreadChunkRegionManager.RegionSection section,
                                      final io.papermc.paper.chunk.SingleThreadChunkRegionManager.Region from) {
             final DataRegionSectionData sectionData = (DataRegionSectionData)section.sectionData;
             final DataRegionData fromData = (DataRegionData)from.regionData;
+            // Paper start - optimise notify()
+            if (sectionData.navigators != null) {
+                for (final Iterator<Mob> iterator = sectionData.navigators.unsafeIterator(io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet.ITERATOR_FLAG_SEE_ADDITIONS); iterator.hasNext();) {
+                    if (!fromData.removeFromNavigators(iterator.next())) {
+                        throw new IllegalStateException();
+                    }
+                }
+            }
+            // Paper end - optimise notify()
         }
 
         @Override
@@ -320,6 +386,15 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
             final DataRegionSectionData sectionData = (DataRegionSectionData)section.sectionData;
             final DataRegionData oldRegionData = oldRegion == null ? null : (DataRegionData)oldRegion.regionData;
             final DataRegionData newRegionData = (DataRegionData)newRegion.regionData;
+            // Paper start - optimise notify()
+            if (sectionData.navigators != null) {
+                for (final Iterator<Mob> iterator = sectionData.navigators.unsafeIterator(io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet.ITERATOR_FLAG_SEE_ADDITIONS); iterator.hasNext();) {
+                    if (!newRegionData.addToNavigators(iterator.next())) {
+                        throw new IllegalStateException();
+                    }
+                }
+            }
+            // Paper end - optimise notify()
         }
     }
 
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
index 091e678a736f291b546ce1e684d4e03055734e02..1d0273a9ec5afc9287b9153a3746299a2ec9adfa 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -1117,6 +1117,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
     public void tickNonPassenger(Entity entity) {
         // Paper start - log detailed entity tick information
         io.papermc.paper.util.TickThread.ensureTickThread("Cannot tick an entity off-main");
+        if (!entity.isRemoved()) this.entityManager.updateNavigatorsInRegion(entity); // Paper - optimise notify
         try {
             if (currentlyTickingEntity.get() == null) {
                 currentlyTickingEntity.lazySet(entity);
@@ -1634,9 +1635,18 @@ public class ServerLevel extends Level implements WorldGenLevel {
 
         if (Shapes.joinIsNotEmpty(voxelshape, voxelshape1, BooleanOp.NOT_SAME)) {
             List<PathNavigation> list = new ObjectArrayList();
-            Iterator iterator = this.navigatingMobs.iterator();
+            // Paper start - optimise notify()
+            io.papermc.paper.chunk.SingleThreadChunkRegionManager.Region region = this.getChunkSource().chunkMap.dataRegionManager.getRegion(pos.getX() >> 4, pos.getZ() >> 4);
+            if (region == null) {
+                return;
+            }
+            io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet<Mob> navigatorsFromRegion = ((ChunkMap.DataRegionData)region.regionData).getNavigators();
+            if (navigatorsFromRegion == null) {
+                return;
+            }
+            io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet.Iterator<Mob> iterator = navigatorsFromRegion.iterator();
 
-            while (iterator.hasNext()) {
+            try { while (iterator.hasNext()) { // Paper end - optimise notify()
                 // CraftBukkit start - fix SPIGOT-6362
                 Mob entityinsentient;
                 try {
@@ -1658,16 +1668,23 @@ public class ServerLevel extends Level implements WorldGenLevel {
 
             try {
                 this.isUpdatingNavigations = true;
-                iterator = list.iterator();
+                // Paper start - optimise notify()
+                Iterator<PathNavigation> navigationIterator = list.iterator();
 
-                while (iterator.hasNext()) {
-                    PathNavigation navigationabstract1 = (PathNavigation) iterator.next();
+                while (navigationIterator.hasNext()) {
+                    PathNavigation navigationabstract1 = navigationIterator.next();
+                    // Paper end - optimise notify()
 
                     navigationabstract1.recomputePath();
                 }
             } finally {
                 this.isUpdatingNavigations = false;
             }
+            // Paper start - optimise notify()
+            } finally {
+                iterator.finishedIterating();
+            }
+            // Paper end - optimise notify()
 
         }
         } // Paper
@@ -2465,10 +2482,12 @@ public class ServerLevel extends Level implements WorldGenLevel {
 
         public void onTickingStart(Entity entity) {
             ServerLevel.this.entityTickList.add(entity);
+            ServerLevel.this.entityManager.addNavigatorsIfPathingToRegion(entity); // Paper - optimise notify
         }
 
         public void onTickingEnd(Entity entity) {
             ServerLevel.this.entityTickList.remove(entity);
+            ServerLevel.this.entityManager.removeNavigatorsFromData(entity); // Paper - optimise notify
             // Paper start - Reset pearls when they stop being ticked
             if (paperConfig().fixes.disableUnloadedChunkEnderpearlExploit && entity instanceof net.minecraft.world.entity.projectile.ThrownEnderpearl pearl) {
                 pearl.cachedOwner = null;
diff --git a/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java b/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java
index af53372391d05dd6aa3757556418e8723b8b6d80..3f672d7c2377fca16a6d8d31cf7aaae4f009fdce 100644
--- a/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java
+++ b/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java
@@ -29,7 +29,7 @@ import net.minecraft.world.phys.Vec3;
 
 public abstract class PathNavigation {
     private static final int MAX_TIME_RECOMPUTE = 20;
-    protected final Mob mob;
+    protected final Mob mob; public final Mob getEntity() { return this.mob; } // Paper - public accessor
     protected final Level level;
     @Nullable
     protected Path path;
@@ -42,7 +42,7 @@ public abstract class PathNavigation {
     protected long lastTimeoutCheck;
     protected double timeoutLimit;
     protected float maxDistanceToWaypoint = 0.5F;
-    protected boolean hasDelayedRecomputation;
+    protected boolean hasDelayedRecomputation; protected final boolean needsPathRecalculation() { return this.hasDelayedRecomputation; } // Paper - public accessor
     protected long timeLastRecompute;
     protected NodeEvaluator nodeEvaluator;
     @Nullable
@@ -420,7 +420,7 @@ public abstract class PathNavigation {
     public boolean shouldRecomputePath(BlockPos pos) {
         if (this.hasDelayedRecomputation) {
             return false;
-        } else if (this.path != null && !this.path.isDone() && this.path.getNodeCount() != 0) {
+        } else if (this.path != null && !this.path.isDone() && this.path.getNodeCount() != 0) { // Paper - diff on change - needed for isViableForPathRecalculationChecking()
             Node node = this.path.getEndNode();
             Vec3 vec3 = new Vec3(((double)node.x + this.mob.getX()) / 2.0D, ((double)node.y + this.mob.getY()) / 2.0D, ((double)node.z + this.mob.getZ()) / 2.0D);
             return pos.closerToCenterThan(vec3, (double)(this.path.getNodeCount() - this.path.getNextNodeIndex()));
@@ -436,4 +436,11 @@ public abstract class PathNavigation {
     public boolean isStuck() {
         return this.isStuck;
     }
+
+    // Paper start
+    public boolean isViableForPathRecalculationChecking() {
+        return !this.needsPathRecalculation() &&
+            (this.path != null && !this.path.isDone() && this.path.getNodeCount() != 0);
+    }
+    // Paper end
 }
diff --git a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java
index 8ad1c6f8147cfbd4677252a0d76f147786babe59..af37b1fcf8459af41482713a9e977599ae6da556 100644
--- a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java
+++ b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java
@@ -71,6 +71,65 @@ public class PersistentEntitySectionManager<T extends EntityAccess> implements A
     }
     // CraftBukkit end
 
+    // Paper start - optimise notify()
+    public final void removeNavigatorsFromData(Entity entity, final int chunkX, final int chunkZ) {
+        if (!(entity instanceof net.minecraft.world.entity.Mob)) {
+            return;
+        }
+        io.papermc.paper.chunk.SingleThreadChunkRegionManager.RegionSection section =
+            this.entitySliceManager.world.getChunkSource().chunkMap.dataRegionManager.getRegionSection(chunkX, chunkZ);
+        if (section != null) {
+            net.minecraft.server.level.ChunkMap.DataRegionSectionData sectionData = (net.minecraft.server.level.ChunkMap.DataRegionSectionData)section.sectionData;
+            sectionData.removeFromNavigators(section, ((net.minecraft.world.entity.Mob)entity));
+        }
+    }
+
+    public final void removeNavigatorsFromData(Entity entity) {
+        if (!(entity instanceof net.minecraft.world.entity.Mob)) {
+            return;
+        }
+        BlockPos entityPos = entity.blockPosition();
+        io.papermc.paper.chunk.SingleThreadChunkRegionManager.RegionSection section =
+            this.entitySliceManager.world.getChunkSource().chunkMap.dataRegionManager.getRegionSection(entityPos.getX() >> 4, entityPos.getZ() >> 4);
+        if (section != null) {
+            net.minecraft.server.level.ChunkMap.DataRegionSectionData sectionData = (net.minecraft.server.level.ChunkMap.DataRegionSectionData)section.sectionData;
+            sectionData.removeFromNavigators(section, ((net.minecraft.world.entity.Mob)entity));
+        }
+    }
+
+    public final void addNavigatorsIfPathingToRegion(Entity entity) {
+        if (!(entity instanceof net.minecraft.world.entity.Mob)) {
+            return;
+        }
+        BlockPos entityPos = entity.blockPosition();
+        io.papermc.paper.chunk.SingleThreadChunkRegionManager.RegionSection section =
+            this.entitySliceManager.world.getChunkSource().chunkMap.dataRegionManager.getRegionSection(entityPos.getX() >> 4, entityPos.getZ() >> 4);
+        if (section != null) {
+            net.minecraft.server.level.ChunkMap.DataRegionSectionData sectionData = (net.minecraft.server.level.ChunkMap.DataRegionSectionData)section.sectionData;
+            if (((net.minecraft.world.entity.Mob)entity).getNavigation().isViableForPathRecalculationChecking()) {
+                sectionData.addToNavigators(section, ((net.minecraft.world.entity.Mob)entity));
+            }
+        }
+    }
+
+    public final void updateNavigatorsInRegion(Entity entity) {
+        if (!(entity instanceof net.minecraft.world.entity.Mob)) {
+            return;
+        }
+        BlockPos entityPos = entity.blockPosition();
+        io.papermc.paper.chunk.SingleThreadChunkRegionManager.RegionSection section =
+            this.entitySliceManager.world.getChunkSource().chunkMap.dataRegionManager.getRegionSection(entityPos.getX() >> 4, entityPos.getZ() >> 4);
+        if (section != null) {
+            net.minecraft.server.level.ChunkMap.DataRegionSectionData sectionData = (net.minecraft.server.level.ChunkMap.DataRegionSectionData)section.sectionData;
+            if (((net.minecraft.world.entity.Mob)entity).getNavigation().isViableForPathRecalculationChecking()) {
+                sectionData.addToNavigators(section, ((net.minecraft.world.entity.Mob)entity));
+            } else {
+                sectionData.removeFromNavigators(section, ((net.minecraft.world.entity.Mob)entity));
+            }
+        }
+    }
+    // Paper end - optimise notify()
+
     void removeSectionIfEmpty(long sectionPos, EntitySection<T> section) {
         if (section.isEmpty()) {
             this.sectionStorage.remove(sectionPos);
@@ -456,11 +515,25 @@ public class PersistentEntitySectionManager<T extends EntityAccess> implements A
         @Override
         public void onMove() {
             BlockPos blockposition = this.entity.blockPosition();
-            long i = SectionPos.asLong(blockposition);
+            long i = SectionPos.asLong(blockposition); final long newSectionPos = i; // Paper - diff on change, new position section
 
             if (i != this.currentSectionKey) {
                 PersistentEntitySectionManager.this.entitySliceManager.moveEntity((Entity)this.entity); // Paper
-                Visibility visibility = this.currentSection.getStatus();
+                Visibility visibility = this.currentSection.getStatus(); final Visibility oldVisibility = visibility; // Paper - diff on change - this should be OLD section visibility
+                // Paper start
+                int shift = PersistentEntitySectionManager.this.entitySliceManager.world.getChunkSource().chunkMap.dataRegionManager.regionChunkShift;
+                int oldChunkX = io.papermc.paper.util.CoordinateUtils.getChunkSectionX(this.currentSectionKey);
+                int oldChunkZ = io.papermc.paper.util.CoordinateUtils.getChunkSectionZ(this.currentSectionKey);
+                int oldRegionX = oldChunkX >> shift;
+                int oldRegionZ = oldChunkZ >> shift;
+
+                int newRegionX = io.papermc.paper.util.CoordinateUtils.getChunkSectionX(newSectionPos) >> shift;
+                int newRegionZ = io.papermc.paper.util.CoordinateUtils.getChunkSectionZ(newSectionPos) >> shift;
+
+                if (oldRegionX != newRegionX || oldRegionZ != newRegionZ) {
+                    PersistentEntitySectionManager.this.removeNavigatorsFromData((Entity)this.entity, oldChunkX, oldChunkZ);
+                }
+                // Paper end
 
                 if (!this.currentSection.remove(this.entity)) {
                     PersistentEntitySectionManager.LOGGER.warn("Entity {} wasn't found in section {} (moving to {})", new Object[]{this.entity, SectionPos.of(this.currentSectionKey), i});
@@ -472,6 +545,11 @@ public class PersistentEntitySectionManager<T extends EntityAccess> implements A
                 entitysection.add(this.entity);
                 this.currentSection = entitysection;
                 this.currentSectionKey = i;
+                // Paper start
+                if ((oldRegionX != newRegionX || oldRegionZ != newRegionZ) && oldVisibility.isTicking() && entitysection.getStatus().isTicking()) {
+                    PersistentEntitySectionManager.this.addNavigatorsIfPathingToRegion((Entity)this.entity);
+                }
+                // Paper end
                 this.updateStatus(visibility, entitysection.getStatus());
             }