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/PlayerChunkMap.java b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java
index e30995df572df6135e159d34cd7646fd08db4a5a..b48d419f82f0d9df1f8848d95066bad7e8e5629a 100644
--- a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java
+++ b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java
@@ -353,17 +353,82 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
 
     public static final class DataRegionData implements com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.RegionData {
 
+        // Tuinity start - optimise notify()
+        private com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet<EntityInsentient> navigators;
 
+        public com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet<EntityInsentient> getNavigators() {
+            return this.navigators;
+        }
+
+        public boolean addToNavigators(final EntityInsentient navigator) {
+            if (this.navigators == null) {
+                this.navigators = new com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet<>();
+            }
+            return this.navigators.add(navigator);
+        }
+
+        public boolean removeFromNavigators(final EntityInsentient navigator) {
+            if (this.navigators == null) {
+                return false;
+            }
+            return this.navigators.remove(navigator);
+        }
+        // Tuinity end - optimise notify()
 
     }
 
     public static final class DataRegionSectionData implements com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.RegionSectionData {
 
+        // Tuinity start - optimise notify()
+        private com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet<EntityInsentient> navigators;
+
+        public com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet<EntityInsentient> getNavigators() {
+            return this.navigators;
+        }
+
+        public boolean addToNavigators(final com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.RegionSection section, final EntityInsentient navigator) {
+            if (this.navigators == null) {
+                this.navigators = new com.tuinity.tuinity.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 com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.RegionSection section, final EntityInsentient 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;
+        }
+        // Tuinity end - optimise notify()
+
         @Override
         public void removeFromRegion(final com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.RegionSection section,
                                      final com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.Region from) {
             final DataRegionSectionData sectionData = (DataRegionSectionData)section.sectionData;
             final DataRegionData fromData = (DataRegionData)from.regionData;
+            // Tuinity start - optimise notify()
+            if (sectionData.navigators != null) {
+                for (final Iterator<EntityInsentient> iterator = sectionData.navigators.unsafeIterator(com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet.ITERATOR_FLAG_SEE_ADDITIONS); iterator.hasNext();) {
+                    if (!fromData.removeFromNavigators(iterator.next())) {
+                        throw new IllegalStateException();
+                    }
+                }
+            }
+            // Tuinity end - optimise notify()
         }
 
         @Override
@@ -373,6 +438,15 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
             final DataRegionSectionData sectionData = (DataRegionSectionData)section.sectionData;
             final DataRegionData oldRegionData = oldRegion == null ? null : (DataRegionData)oldRegion.regionData;
             final DataRegionData newRegionData = (DataRegionData)newRegion.regionData;
+            // Tuinity start - optimise notify()
+            if (sectionData.navigators != null) {
+                for (final Iterator<EntityInsentient> iterator = sectionData.navigators.unsafeIterator(com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet.ITERATOR_FLAG_SEE_ADDITIONS); iterator.hasNext();) {
+                    if (!newRegionData.addToNavigators(iterator.next())) {
+                        throw new IllegalStateException();
+                    }
+                }
+            }
+            // Tuinity end - optimise notify()
         }
     }
     // Tuiniy end
diff --git a/src/main/java/net/minecraft/server/level/WorldServer.java b/src/main/java/net/minecraft/server/level/WorldServer.java
index e67924369f778b16a2ee8d29c571756c1be1ebf9..a278a5ba17a510f784c798ad7f7f47bf7ccc301e 100644
--- a/src/main/java/net/minecraft/server/level/WorldServer.java
+++ b/src/main/java/net/minecraft/server/level/WorldServer.java
@@ -1085,6 +1085,15 @@ public class WorldServer extends World implements GeneratorAccessSeed {
                 gameprofilerfiller.enter("checkDespawn");
                 if (!entity.dead) {
                     entity.checkDespawn();
+                    // Tuinity start - optimise notify()
+                    if (entity.inChunk && entity.valid) {
+                        if (this.getChunkProvider().isInEntityTickingChunk(entity)) {
+                            this.updateNavigatorsInRegion(entity);
+                        }
+                    } else {
+                        this.removeNavigatorsFromData(entity);
+                    }
+                    // Tuinity end - optimise notify()
                 }
 
                 gameprofilerfiller.exit();
@@ -1107,7 +1116,14 @@ public class WorldServer extends World implements GeneratorAccessSeed {
                     this.removeEntityFromChunk(entity);
                     this.entitiesById.remove(entity.getId()); // Tuinity
                     this.unregisterEntity(entity);
+                } else if (entity.inChunk && entity.valid) { // Tuinity start - optimise notify()
+                    if (this.getChunkProvider().isInEntityTickingChunk(entity)) {
+                        this.updateNavigatorsInRegion(entity);
+                    }
+                } else {
+                    this.removeNavigatorsFromData(entity);
                 }
+                // Tuinity end - optimise notify()
 
                 gameprofilerfiller.exit();
             }
@@ -1517,6 +1533,12 @@ public class WorldServer extends World implements GeneratorAccessSeed {
             int i = MathHelper.floor(entity.locX() / 16.0D);
             int j =  Math.min(15, Math.max(0, MathHelper.floor(entity.locY() / 16.0D))); // Paper - stay consistent with chunk add/remove behavior
             int k = MathHelper.floor(entity.locZ() / 16.0D);
+            // Tuinity start
+            int oldRegionX = entity.chunkX >> this.getChunkProvider().playerChunkMap.dataRegionManager.regionChunkShift;
+            int oldRegionZ = entity.chunkZ >> this.getChunkProvider().playerChunkMap.dataRegionManager.regionChunkShift;
+            int newRegionX = i >> this.getChunkProvider().playerChunkMap.dataRegionManager.regionChunkShift;
+            int newRegionZ = k >> this.getChunkProvider().playerChunkMap.dataRegionManager.regionChunkShift;
+            // Tuinity end
 
             if (!entity.inChunk || entity.chunkX != i || entity.chunkY != j || entity.chunkZ != k) {
                 // Paper start - remove entity if its in a chunk more correctly.
@@ -1526,6 +1548,12 @@ public class WorldServer extends World implements GeneratorAccessSeed {
                 }
                 // Paper end
 
+                // Tuinity start
+                if (oldRegionX != newRegionX || oldRegionZ != newRegionZ) {
+                    this.removeNavigatorsFromData(entity);
+                }
+                // Tuinity end
+
                 if (entity.inChunk && this.isChunkLoaded(entity.chunkX, entity.chunkZ)) {
                     this.getChunkAt(entity.chunkX, entity.chunkZ).a(entity, entity.chunkY);
                 }
@@ -1539,6 +1567,11 @@ public class WorldServer extends World implements GeneratorAccessSeed {
                 } else {
                     this.getChunkAt(i, k).a(entity);
                 }
+                // Tuinity start
+                if (entity.inChunk && (oldRegionX != newRegionX || oldRegionZ != newRegionZ)) {
+                    this.addNavigatorsIfPathingToRegion(entity);
+                }
+                // Tuinity end
             }
 
             this.getMethodProfiler().exit();
@@ -2001,9 +2034,64 @@ public class WorldServer extends World implements GeneratorAccessSeed {
             // Tuinity end
         }
         new com.destroystokyo.paper.event.entity.EntityRemoveFromWorldEvent(entity.getBukkitEntity()).callEvent(); // Paper - fire while valid
+        this.removeNavigatorsFromData(entity); // Tuinity - optimise notify()
         entity.valid = false; // CraftBukkit
     }
 
+    // Tuinity start - optimise notify()
+    void removeNavigatorsIfNotPathingFromRegion(Entity entity) {
+        com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.RegionSection section =
+                this.getChunkProvider().playerChunkMap.dataRegionManager.getRegionSection(entity.chunkX, entity.chunkZ);
+        if (section != null) {
+            PlayerChunkMap.DataRegionSectionData sectionData = (PlayerChunkMap.DataRegionSectionData)section.sectionData;
+            if (entity instanceof EntityInsentient) {
+                if (!((EntityInsentient)entity).getNavigation().isViableForPathRecalculationChecking()) {
+                    sectionData.removeFromNavigators(section, (EntityInsentient)entity);
+                }
+            }
+        }
+    }
+
+    void removeNavigatorsFromData(Entity entity) {
+        com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.RegionSection section =
+                this.getChunkProvider().playerChunkMap.dataRegionManager.getRegionSection(entity.chunkX, entity.chunkZ);
+        if (section != null) {
+            PlayerChunkMap.DataRegionSectionData sectionData = (PlayerChunkMap.DataRegionSectionData)section.sectionData;
+            if (entity instanceof EntityInsentient) {
+                sectionData.removeFromNavigators(section, ((EntityInsentient)entity));
+            }
+        }
+    }
+
+    void addNavigatorsIfPathingToRegion(Entity entity) {
+        com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.RegionSection section =
+                this.getChunkProvider().playerChunkMap.dataRegionManager.getRegionSection(entity.chunkX, entity.chunkZ);
+        if (section != null) {
+            PlayerChunkMap.DataRegionSectionData sectionData = (PlayerChunkMap.DataRegionSectionData)section.sectionData;
+            if (entity instanceof EntityInsentient) {
+                if (((EntityInsentient)entity).getNavigation().isViableForPathRecalculationChecking()) {
+                    sectionData.addToNavigators(section, ((EntityInsentient)entity));
+                }
+            }
+        }
+    }
+
+    void updateNavigatorsInRegion(Entity entity) {
+        com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.RegionSection section =
+                this.getChunkProvider().playerChunkMap.dataRegionManager.getRegionSection(entity.chunkX, entity.chunkZ);
+        if (section != null) {
+            PlayerChunkMap.DataRegionSectionData sectionData = (PlayerChunkMap.DataRegionSectionData)section.sectionData;
+            if (entity instanceof EntityInsentient) {
+                if (((EntityInsentient)entity).getNavigation().isViableForPathRecalculationChecking()) {
+                    sectionData.addToNavigators(section, ((EntityInsentient)entity));
+                } else {
+                    sectionData.removeFromNavigators(section, ((EntityInsentient)entity));
+                }
+            }
+        }
+    }
+    // Tuinity end - optimise notify()
+
     private void registerEntity(Entity entity) {
         org.spigotmc.AsyncCatcher.catchOp("entity register"); // Spigot
         // Paper start - don't double enqueue entity registration
@@ -2184,16 +2272,26 @@ public class WorldServer extends World implements GeneratorAccessSeed {
         VoxelShape voxelshape1 = iblockdata1.getCollisionShape(this, blockposition);
 
         if (VoxelShapes.c(voxelshape, voxelshape1, OperatorBoolean.NOT_SAME)) {
+            // Tuinity start - optimise notify()
+            com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.Region region = this.getChunkProvider().playerChunkMap.dataRegionManager.getRegion(blockposition.getX() >> 4, blockposition.getZ() >> 4);
+            if (region == null) {
+                return;
+            }
+            com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet<EntityInsentient> navigatorsFromRegion = ((PlayerChunkMap.DataRegionData)region.regionData).getNavigators();
+            if (navigatorsFromRegion == null) {
+                return;
+            }
             boolean wasTicking = this.tickingEntities; this.tickingEntities = true; // Paper
             // Tuinity start
-            com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet.Iterator iterator = this.navigatorsForIteration.iterator();
+            com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet.Iterator<EntityInsentient> iterator = navigatorsFromRegion.iterator();
+            // Tuinity end - optimise notify()
             try { // Tuinity end
 
             while (iterator.hasNext()) {
                 // CraftBukkit start - fix SPIGOT-6362
                 NavigationAbstract navigationabstract;
                 try {
-                    navigationabstract = (NavigationAbstract) iterator.next();
+                    navigationabstract = (NavigationAbstract) iterator.next().getNavigation(); // Tuinity - optimise notify
                 } catch (java.util.ConcurrentModificationException ex) {
                     // This can happen because the pathfinder update below may trigger a chunk load, which in turn may cause more navigators to register
                     // In this case we just run the update again across all the iterators as the chunk will then be loaded
diff --git a/src/main/java/net/minecraft/world/entity/ai/navigation/NavigationAbstract.java b/src/main/java/net/minecraft/world/entity/ai/navigation/NavigationAbstract.java
index e6ba9b7fbf08ae0dd083a1ebee8eb7ed8a937751..148bdbc2cffb002d8b6dd05e70854ab503804949 100644
--- a/src/main/java/net/minecraft/world/entity/ai/navigation/NavigationAbstract.java
+++ b/src/main/java/net/minecraft/world/entity/ai/navigation/NavigationAbstract.java
@@ -30,7 +30,7 @@ import net.minecraft.world.phys.Vec3D;
 
 public abstract class NavigationAbstract {
 
-    protected final EntityInsentient a; public Entity getEntity() { return a; } // Paper - OBFHELPER
+    protected final EntityInsentient a; public final EntityInsentient getEntity() { return a; } // Paper - OBFHELPER // Tuinity - match types
     protected final World b;
     @Nullable
     protected PathEntity c; protected final PathEntity getCurrentPath() { return this.c; } // Paper - OBFHELPER
@@ -43,7 +43,7 @@ public abstract class NavigationAbstract {
     protected long j;
     protected double k;
     protected float l;
-    protected boolean m;
+    protected boolean m; protected final boolean needsPathRecalculation() { return this.m; } // Tuinity - OBFHELPER
     protected long n;
     protected PathfinderAbstract o;
     private BlockPosition p;
@@ -52,6 +52,13 @@ public abstract class NavigationAbstract {
     private final Pathfinder s; public Pathfinder getPathfinder() { return this.s; } // Paper - OBFHELPER
     private boolean t;
 
+    // Tuinity start
+    public boolean isViableForPathRecalculationChecking() {
+        return !this.needsPathRecalculation() &&
+                (this.c != null && !this.c.c() && this.c.e() != 0);
+    }
+    // Tuinity end
+
     public NavigationAbstract(EntityInsentient entityinsentient, World world) {
         this.g = Vec3D.ORIGIN;
         this.h = BaseBlockPosition.ZERO;
@@ -415,7 +422,7 @@ public abstract class NavigationAbstract {
     }
 
     public void b(BlockPosition blockposition) {
-        if (this.c != null && !this.c.c() && this.c.e() != 0) {
+        if (this.c != null && !this.c.c() && this.c.e() != 0) { // Tuinity - diff on change - needed for isViableForPathRecalculationChecking()
             PathPoint pathpoint = this.c.d();
             Vec3D vec3d = new Vec3D(((double) pathpoint.a + this.a.locX()) / 2.0D, ((double) pathpoint.b + this.a.locY()) / 2.0D, ((double) pathpoint.c + this.a.locZ()) / 2.0D);
 
