From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Aikar <aikar@aikar.co>
Date: Mon, 10 Aug 2020 17:12:02 +0300
Subject: [PATCH] Optimize Villagers

This change reimplements the entire BehaviorFindPosition method to
get rid of all of the streams, and implement the logic in a more sane way.

We keep vanilla behavior 100% the same with this change, just wrote more
optimal, as we can abort iterating POI's as soon as we find a match...

One slight change is that Minecraft adds a random delay before a POI is
attempted again. I've increased the amount of that delay based on the distance
to said POI, so farther POI's will not be attempted as often.

Additionally, we spiral out, so we favor local POI's before  we ever favor farther POI's.

We also try to pathfind 1 POI at a time instead of collecting multiple POI's then tossing them
all to the pathfinder, so that once we get a match we can return before even looking at other
POI's.

This benefits us in that ideally, a villager will constantly find the near POI's and
not even try to pathfind to the farther POI. Trying to pathfind to distant POI's is
what causes significant lag.

Other improvements here is to stop spamming the POI manager with empty nullables.
Vanilla used them to represent if they needed to load POI data off disk or not.

Well, we load POI data async on chunk load, so we have it, and we surely do not ever
want to load POI data sync either for unloaded chunks!

So this massively reduces object count in the POI hashmaps, resulting in less hash collions,
and also less memory use.

Additionally, unemployed villagers were using significant time due to major inefficiency in
the code rebuilding data that is static every single invocation of every POI type...

So we cache that and only rebuild it if professions change, which should be never unless
a plugin manipulates and adds custom professions, which it will handle by rebuilding.

Ported to Yatopia and 1.16.1 by MrIvanPlays
Co-authored-by: MrIvanPlays <ivan@mrivanplays.com>

diff --git a/src/main/java/net/minecraft/server/BehaviorFindPosition.java b/src/main/java/net/minecraft/server/BehaviorFindPosition.java
index 63a761ebef80d4af09cdc2682e496d78492c4a3a..d97373816e9c44edc2de090bd22aa696404e0012 100644
--- a/src/main/java/net/minecraft/server/BehaviorFindPosition.java
+++ b/src/main/java/net/minecraft/server/BehaviorFindPosition.java
@@ -16,7 +16,7 @@ public class BehaviorFindPosition extends Behavior<EntityCreature> {
     private final VillagePlaceType b;
     private final MemoryModuleType<GlobalPos> c;
     private final boolean d;
-    private final Optional<Byte> e;
+    private final Optional<Byte> e; private Optional<Byte> getPoiAquisitionEvent() { return this.e; } // Yatopia - OBFHELPER
     private long f;
     private final Long2ObjectMap<BehaviorFindPosition.a> g;
 
@@ -48,7 +48,7 @@ public class BehaviorFindPosition extends Behavior<EntityCreature> {
         if (this.d && entitycreature.isBaby()) {
             return false;
         } else if (this.f == 0L) {
-            this.f = entitycreature.world.getTime() + (long) worldserver.random.nextInt(20);
+            this.f = entitycreature.world.getTime() + (long) java.util.concurrent.ThreadLocalRandom.current().nextInt(20); // Yatopia
             return false;
         } else {
             return worldserver.getTime() >= this.f;
@@ -56,12 +56,57 @@ public class BehaviorFindPosition extends Behavior<EntityCreature> {
     }
 
     protected void a(WorldServer worldserver, EntityCreature entitycreature, long i) {
-        this.f = i + 20L + (long) worldserver.getRandom().nextInt(20);
+        this.f = i + 20L + (long) java.util.concurrent.ThreadLocalRandom.current().nextInt(20); // Yatopia
         VillagePlace villageplace = worldserver.y();
 
+        // Yatopia start - replace implementation completely
+        BlockPosition blockposition2 = new BlockPosition(entitycreature.locX(), entitycreature.locY(), entitycreature.locZ());
+        int dist = 48;
+        int requiredDist = dist * dist;
+        int cdist = Math.floorDiv(dist, 16);
+        Predicate<VillagePlaceType> predicate = this.b.getPredicate();
+        int maxPoiAttempts = 4;
+        int poiAttempts = 0;
+        OUT:
+        for (ChunkCoordIntPair chunkcoordintpair : MCUtil.getSpiralOutChunks(blockposition2, cdist)) {
+            for (int i1 = 0; i1 < 16; i1++) {
+                java.util.Optional<VillagePlaceSection> section = villageplace.getSection(SectionPosition.a(chunkcoordintpair, i1).asLong());
+                if (section == null || !section.isPresent()) continue;
+                for (java.util.Map.Entry<VillagePlaceType, java.util.Set<VillagePlaceRecord>> e : section.get().getRecords().entrySet()) {
+                    if (!predicate.test(e.getKey())) continue;
+                    for (VillagePlaceRecord record : e.getValue()) {
+                        if (!record.hasVacancy()) continue;
+
+                        BlockPosition pos = record.getPosition();
+                        long key = pos.asLong();
+                        if (this.g.containsKey(key)) {
+                            continue;
+                        }
+                        double poiDist = pos.distanceSquared(blockposition2);
+                        if (poiDist <= (double) requiredDist) {
+                            this.g.put(key, new BehaviorFindPosition.a(java.util.concurrent.ThreadLocalRandom.current(), (long) (this.f + Math.sqrt(poiDist) * 4))); // use dist instead of 40 to blacklist longer if farther distance
+                            ++poiAttempts;
+                            PathEntity pathentity = entitycreature.getNavigation().a(com.google.common.collect.ImmutableSet.of(pos), 8, false, this.b.getValidRange());
+
+                            if (pathentity != null && pathentity.canReach()) {
+                                GlobalPos globalPos = GlobalPos.create(worldserver.getDimensionKey(), pos);
+                                entitycreature.getBehaviorController().setMemory(c, globalPos);
+                                this.getPoiAquisitionEvent().ifPresent((event) -> worldserver.broadcastEntityEffect(entitycreature, event));
+                                break OUT;
+                            }
+                            if (poiAttempts >= maxPoiAttempts) {
+                                break OUT;
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
         this.g.long2ObjectEntrySet().removeIf((entry) -> {
-            return !((BehaviorFindPosition.a) entry.getValue()).b(i);
+            return entry.getValue().b < f;
         });
+        /*
         Predicate<BlockPosition> predicate = (blockposition) -> {
             BehaviorFindPosition.a behaviorfindposition_a = (BehaviorFindPosition.a) this.g.get(blockposition.asLong());
 
@@ -102,7 +147,7 @@ public class BehaviorFindPosition extends Behavior<EntityCreature> {
                 });
             }
         }
-
+         */ // Yatopia end
     }
 
     static class a {
diff --git a/src/main/java/net/minecraft/server/PathEntity.java b/src/main/java/net/minecraft/server/PathEntity.java
index c81a5d50c480b064ab60ed6f25f9e2c0bedb6ece..a86ad4c8a28b9aacdb1e99320fddf2344b08bd7e 100644
--- a/src/main/java/net/minecraft/server/PathEntity.java
+++ b/src/main/java/net/minecraft/server/PathEntity.java
@@ -114,6 +114,7 @@ public class PathEntity {
         }
     }
 
+    public final boolean canReach() { return j(); } // Yatopia - OBFHELPER
     public boolean j() {
         return this.h;
     }
diff --git a/src/main/java/net/minecraft/server/RegionFileSection.java b/src/main/java/net/minecraft/server/RegionFileSection.java
index 04256a95108b8182e8f808e856e0d2b62165e242..69cc2d1a0642029793827f4195723c380e01c821 100644
--- a/src/main/java/net/minecraft/server/RegionFileSection.java
+++ b/src/main/java/net/minecraft/server/RegionFileSection.java
@@ -52,10 +52,13 @@ public class RegionFileSection<R> extends RegionFileCache implements AutoCloseab
 
     @Nullable
     protected Optional<R> c(long i) {
-        return (Optional) this.c.get(i);
+        return this.c.getOrDefault(i, Optional.empty()); // Yatopia
     }
 
+    protected final Optional<R> getSection(long i) { return d(i); } // Yatopia - OBFHELPER
     protected Optional<R> d(long i) {
+        // Yatopia start - replace method - never load POI data sync, we load this in chunk load already, reduce ops
+        /*
         SectionPosition sectionposition = SectionPosition.a(i);
 
         if (this.b(sectionposition)) {
@@ -75,6 +78,10 @@ public class RegionFileSection<R> extends RegionFileCache implements AutoCloseab
                 }
             }
         }
+         */
+        // If it's an unloaded chunk, well too bad.
+        return this.c(i);
+        // Yatopia end
     }
 
     protected boolean b(SectionPosition sectionposition) {
@@ -118,7 +125,7 @@ public class RegionFileSection<R> extends RegionFileCache implements AutoCloseab
     private <T> void a(ChunkCoordIntPair chunkcoordintpair, DynamicOps<T> dynamicops, @Nullable T t0) {
         if (t0 == null) {
             for (int i = 0; i < 16; ++i) {
-                this.c.put(SectionPosition.a(chunkcoordintpair, i).s(), Optional.empty());
+                //this.c.put(SectionPosition.a(chunkcoordintpair, i).s(), Optional.empty()); // Yatopia - NO!!!
             }
         } else {
             Dynamic<T> dynamic = new Dynamic(dynamicops, t0);
@@ -140,7 +147,7 @@ public class RegionFileSection<R> extends RegionFileCache implements AutoCloseab
                     return dataresult.resultOrPartial(logger::error);
                 });
 
-                this.c.put(i1, optional);
+                if (optional.isPresent()) this.c.put(i1, optional); // Yatopia
                 optional.ifPresent((object) -> {
                     this.b(i1);
                     if (flag) {
@@ -213,7 +220,7 @@ public class RegionFileSection<R> extends RegionFileCache implements AutoCloseab
         if (optional != null && optional.isPresent()) {
             this.d.add(i);
         } else {
-            RegionFileSection.LOGGER.warn("No data for position: {}", SectionPosition.a(i));
+            //RegionFileSection.LOGGER.warn("No data for position: {}", SectionPosition.a(i)); // Yatopia - hush
         }
     }
 
diff --git a/src/main/java/net/minecraft/server/SectionPosition.java b/src/main/java/net/minecraft/server/SectionPosition.java
index f95925f1c5d091f1a129d0437bb6e175c6ac080f..cf5b25d9375724ea2afcf5cd59e0ccd302cdf45e 100644
--- a/src/main/java/net/minecraft/server/SectionPosition.java
+++ b/src/main/java/net/minecraft/server/SectionPosition.java
@@ -173,6 +173,7 @@ public class SectionPosition extends BaseBlockPosition {
         return (((long) i & 4194303L) << 42) | (((long) j & 1048575L)) | (((long) k & 4194303L) << 20); // Paper - Simplify to reduce instruction count
     }
 
+    public long asLong() { return s(); } // Yatopia - OBFHELPER
     public long s() {
         return (((long) getX() & 4194303L) << 42) | (((long) getY() & 1048575L)) | (((long) getZ() & 4194303L) << 20); // Paper - Simplify to reduce instruction count
     }
diff --git a/src/main/java/net/minecraft/server/VillagePlaceRecord.java b/src/main/java/net/minecraft/server/VillagePlaceRecord.java
index 0b40c2f4dada7d8432e3f91e9cf206c2bda3b24b..1b956d973ebef2345755201dec3449545c458a60 100644
--- a/src/main/java/net/minecraft/server/VillagePlaceRecord.java
+++ b/src/main/java/net/minecraft/server/VillagePlaceRecord.java
@@ -44,6 +44,7 @@ public class VillagePlaceRecord {
         }
     }
 
+    protected final boolean increaseVacancy() { return c(); } // Yatopia - OBFHELPER
     protected boolean c() {
         if (this.c >= this.b.b()) {
             return false;
@@ -54,14 +55,17 @@ public class VillagePlaceRecord {
         }
     }
 
+    public final boolean hasVacancy() { return d(); } // Yatopia - OBFHELPER
     public boolean d() {
         return this.c > 0;
     }
 
+    public final boolean isOccupied() { return e(); } // Yatopia - OBFHELPER
     public boolean e() {
         return this.c != this.b.b();
     }
 
+    public final BlockPosition getPosition() { return f(); } // Yatopia - OBFHELPER
     public BlockPosition f() {
         return this.a;
     }
diff --git a/src/main/java/net/minecraft/server/VillagePlaceSection.java b/src/main/java/net/minecraft/server/VillagePlaceSection.java
index 77c66bc9952542d2444b402896a3d9f622ca2ff9..d4018b9b615dae2b9e30517be51322656601b171 100644
--- a/src/main/java/net/minecraft/server/VillagePlaceSection.java
+++ b/src/main/java/net/minecraft/server/VillagePlaceSection.java
@@ -23,12 +23,12 @@ public class VillagePlaceSection {
 
     private static final Logger LOGGER = LogManager.getLogger();
     private final Short2ObjectMap<VillagePlaceRecord> b;
-    private final Map<VillagePlaceType, Set<VillagePlaceRecord>> c;
+    private final Map<VillagePlaceType, Set<VillagePlaceRecord>> c; public final Map<VillagePlaceType, Set<VillagePlaceRecord>> getRecords() { return c; } // Yatopia - OBFHELPER
     private final Runnable d;
     private boolean e;
 
     public static Codec<VillagePlaceSection> a(Runnable runnable) {
-        Codec codec = RecordCodecBuilder.create((instance) -> {
+        Codec<VillagePlaceSection> codec = RecordCodecBuilder.create((instance) -> { // Yatopia - decompile fix
             return instance.group(RecordCodecBuilder.point(runnable), Codec.BOOL.optionalFieldOf("Valid", false).forGetter((villageplacesection) -> {
                 return villageplacesection.e;
             }), VillagePlaceRecord.a(runnable).listOf().fieldOf("Records").forGetter((villageplacesection) -> {
diff --git a/src/main/java/net/minecraft/server/VillagePlaceType.java b/src/main/java/net/minecraft/server/VillagePlaceType.java
index a5718af9b614ae505067131f04ebb490617d6aa4..142b47df08991a3e72c9f7927af6933a72f7b4cb 100644
--- a/src/main/java/net/minecraft/server/VillagePlaceType.java
+++ b/src/main/java/net/minecraft/server/VillagePlaceType.java
@@ -14,11 +14,20 @@ import java.util.stream.Collectors;
 
 public class VillagePlaceType {
 
+    static Set<VillagePlaceType> professionCache; // Yatopia
     private static final Supplier<Set<VillagePlaceType>> y = Suppliers.memoize(() -> {
         return (Set) IRegistry.VILLAGER_PROFESSION.g().map(VillagerProfession::b).collect(Collectors.toSet());
     });
     public static final Predicate<VillagePlaceType> a = (villageplacetype) -> {
-        return ((Set) VillagePlaceType.y.get()).contains(villageplacetype);
+        // Yatopia start
+        if (professionCache == null) {
+            professionCache = new java.util.HashSet<>();
+            for (VillagerProfession profession : IRegistry.VILLAGER_PROFESSION) {
+                professionCache.add(profession.getPlaceType());
+            }
+        }
+        return professionCache.contains(villageplacetype);
+        // Yatopia end
     };
     public static final Predicate<VillagePlaceType> b = (villageplacetype) -> {
         return true;
@@ -83,10 +92,12 @@ public class VillagePlaceType {
         return this.D;
     }
 
+    public final Predicate<VillagePlaceType> getPredicate() { return c(); } // Yatopia - OBFHELPER
     public Predicate<VillagePlaceType> c() {
         return this.E;
     }
 
+    public final int getValidRange() { return d(); } // Yatopia - OBFHELPER
     public int d() {
         return this.F;
     }
diff --git a/src/main/java/net/minecraft/server/VillagerProfession.java b/src/main/java/net/minecraft/server/VillagerProfession.java
index 3c60da7ac6faebe9d964e893974e42613c59b4c1..1b012914cb3fcbc4bb456195ade96668b6742cfd 100644
--- a/src/main/java/net/minecraft/server/VillagerProfession.java
+++ b/src/main/java/net/minecraft/server/VillagerProfession.java
@@ -35,6 +35,7 @@ public class VillagerProfession {
         this.t = soundeffect;
     }
 
+    public final VillagePlaceType getPlaceType() { return b(); } // Yatopia - OBFHELPER
     public VillagePlaceType b() {
         return this.q;
     }
@@ -61,6 +62,7 @@ public class VillagerProfession {
     }
 
     static VillagerProfession a(String s, VillagePlaceType villageplacetype, ImmutableSet<Item> immutableset, ImmutableSet<Block> immutableset1, @Nullable SoundEffect soundeffect) {
+        VillagePlaceType.professionCache = null; // Yatopia
         return (VillagerProfession) IRegistry.a((IRegistry) IRegistry.VILLAGER_PROFESSION, new MinecraftKey(s), (Object) (new VillagerProfession(s, villageplacetype, immutableset, immutableset1, soundeffect)));
     }
 }
