Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
368 changes: 368 additions & 0 deletions Spigot-Server-Patches/0645-Optimize-Villagers.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,368 @@
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 ineffeciency in
the code rebuilding data that is static every single invocation for 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.

diff --git a/src/main/java/net/minecraft/server/BehaviorFindPosition.java b/src/main/java/net/minecraft/server/BehaviorFindPosition.java
index 63a761ebef80d4af09cdc2682e496d78492c4a3a..6bffce90fa887ef173ecd4260fd30a5e6c0feea5 100644
--- a/src/main/java/net/minecraft/server/BehaviorFindPosition.java
+++ b/src/main/java/net/minecraft/server/BehaviorFindPosition.java
@@ -13,12 +13,12 @@ import java.util.stream.Collectors;

public class BehaviorFindPosition extends Behavior<EntityCreature> {

- private final VillagePlaceType b;
- private final MemoryModuleType<GlobalPos> c;
+ private final VillagePlaceType b; private final VillagePlaceType getPoiType() { return b; } // Paper - OBFHELPER
+ private final MemoryModuleType<GlobalPos> c; private final MemoryModuleType<GlobalPos> getTargetMemoryModuleType() { return c; } // Paper - OBFHELPER
private final boolean d;
- private final Optional<Byte> e;
- private long f;
- private final Long2ObjectMap<BehaviorFindPosition.a> g;
+ private final Optional<Byte> e; private final Optional<Byte> getPoiAcquisitionEvent() { return this.e; } // Paper - OBFHELPER
+ private long f; private final long getPositionExpireTimeLimit() { return f; } // Paper - OBFHELPER
+ private final Long2ObjectMap<BehaviorFindPosition.a> g; private Long2ObjectMap<BehaviorFindPosition.a> getFoundPositionsToExpiry() { return g; } // Paper - OBFHELPER

public BehaviorFindPosition(VillagePlaceType villageplacetype, MemoryModuleType<GlobalPos> memorymoduletype, MemoryModuleType<GlobalPos> memorymoduletype1, boolean flag, Optional<Byte> optional) {
super(a(memorymoduletype, memorymoduletype1));
@@ -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); // Paper
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); // Paper
VillagePlace villageplace = worldserver.y();

- this.g.long2ObjectEntrySet().removeIf((entry) -> {
- return !((BehaviorFindPosition.a) entry.getValue()).b(i);
+ // Paper 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.getPoiType().getCompletionCondition();
+ 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.from(chunkcoordintpair, i1).asLong());
+ if (section == null || !section.isPresent()) continue;
+ for (java.util.Map.Entry<VillagePlaceType, java.util.Set<VillagePlaceRecord>> entry : section.get().getRecords().entrySet()) {
+ if (!predicate.test(entry.getKey())) continue;
+ for (VillagePlaceRecord record : entry.getValue()) {
+ if (!record.hasVacancy()) continue;
+
+ BlockPosition pos = record.getPosition();
+ long key = pos.asLong();
+ if (this.getFoundPositionsToExpiry().containsKey(key)) {
+ continue;
+ }
+ double poiDist = pos.distanceSquared(blockposition2);
+ if (poiDist <= (double) requiredDist) {
+ this.getFoundPositionsToExpiry().put(key, new BehaviorFindPosition.a(java.util.concurrent.ThreadLocalRandom.current(), (long) (this.getPositionExpireTimeLimit() + Math.sqrt(poiDist) * 4))); // use dist instead of 40 to blacklist longer if farther distance
+ ++poiAttempts;
+ PathEntity pathentity = entitycreature.getNavigation().findPathToAny(com.google.common.collect.ImmutableSet.of(pos), 8, false, this.getPoiType().getValidRange());
+
+ if (pathentity != null && pathentity.canReach()) {
+ GlobalPos globalPos = GlobalPos.create(worldserver.getDimensionKey(), pos);
+ entitycreature.getBehaviorController().setMemory(this.getTargetMemoryModuleType(), globalPos);
+ this.getPoiAcquisitionEvent().ifPresent((event) -> worldserver.broadcastEntityEffect(entitycreature, event));
+ break OUT;
+ }
+ if (poiAttempts >= maxPoiAttempts) {
+ break OUT;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ this.getFoundPositionsToExpiry().long2ObjectEntrySet().removeIf((entry) -> {
+ return entry.getValue().getPreviousAttemptAt() < this.getPositionExpireTimeLimit();
});
+ /*
Predicate<BlockPosition> predicate = (blockposition) -> {
BehaviorFindPosition.a behaviorfindposition_a = (BehaviorFindPosition.a) this.g.get(blockposition.asLong());

@@ -103,12 +148,13 @@ public class BehaviorFindPosition extends Behavior<EntityCreature> {
}
}

+ */ // Paper end
}

static class a {

private final Random a;
- private long b;
+ private long b; private final long getPreviousAttemptAt() { return b; } // Paper - OBFHELPER
private long c;
private int d;

diff --git a/src/main/java/net/minecraft/server/NavigationAbstract.java b/src/main/java/net/minecraft/server/NavigationAbstract.java
index 1558c5f8256f50be6850f1d7f70eee3e8ec76496..d97a2f826701d7f0ff36fb5fe6acbbfe8b36ed32 100644
--- a/src/main/java/net/minecraft/server/NavigationAbstract.java
+++ b/src/main/java/net/minecraft/server/NavigationAbstract.java
@@ -110,6 +110,7 @@ public abstract class NavigationAbstract {

@Nullable
// Paper start - Add target
+ protected PathEntity findPathToAny(Set<BlockPosition> set, int i, boolean flag, int j) { return a(set, i, flag, j); } // OBFHELPER
protected PathEntity a(Set<BlockPosition> set, int i, boolean flag, int j) {
return this.a(set, null, i, flag, j);
}
diff --git a/src/main/java/net/minecraft/server/PathEntity.java b/src/main/java/net/minecraft/server/PathEntity.java
index c81a5d50c480b064ab60ed6f25f9e2c0bedb6ece..f633796f754fd1b80047033c37a38b099d9b9a9c 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 this.j(); } // Paper - 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..8c6675db41f2fffdc2fd6f244afd92dc4ad2acc9 100644
--- a/src/main/java/net/minecraft/server/RegionFileSection.java
+++ b/src/main/java/net/minecraft/server/RegionFileSection.java
@@ -25,7 +25,7 @@ public class RegionFileSection<R> extends RegionFileCache implements AutoCloseab

private static final Logger LOGGER = LogManager.getLogger();
// Paper - nuke IOWorker
- private final Long2ObjectMap<Optional<R>> c = new Long2ObjectOpenHashMap();
+ private final Long2ObjectMap<Optional<R>> c = new Long2ObjectOpenHashMap(); private Long2ObjectMap<Optional<R>> getLoadedElements() { return c; } // Paper - OBFHELPER
protected final LongLinkedOpenHashSet d = new LongLinkedOpenHashSet(); // Paper - private -> protected
private final Function<Runnable, Codec<R>> e;
private final Function<Runnable, R> f;
@@ -50,12 +50,15 @@ public class RegionFileSection<R> extends RegionFileCache implements AutoCloseab

}

- @Nullable
+ @Nullable protected Optional<R> getIfLoaded(long i) { return c(i); } @Nullable // Paper - OBFHELPER
protected Optional<R> c(long i) {
- return (Optional) this.c.get(i);
+ return this.getLoadedElements().getOrDefault(i, Optional.empty()); // Paper
}

+ protected final Optional<R> getSection(long i) { return d(i); } // Paper - OBFHELPER
protected Optional<R> d(long i) {
+ // Paper 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.getIfLoaded(i);
+ // Paper 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()); // Paper - 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.getLoadedElements().put(i1, optional); // Paper - NO!!!
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)); // Paper - hush
}
}

diff --git a/src/main/java/net/minecraft/server/SectionPosition.java b/src/main/java/net/minecraft/server/SectionPosition.java
index f95925f1c5d091f1a129d0437bb6e175c6ac080f..5ac0c06315ad75e826e21b7e293e725c5192c70d 100644
--- a/src/main/java/net/minecraft/server/SectionPosition.java
+++ b/src/main/java/net/minecraft/server/SectionPosition.java
@@ -19,6 +19,7 @@ public class SectionPosition extends BaseBlockPosition {
return new SectionPosition(blockposition.getX() >> 4, blockposition.getY() >> 4, blockposition.getZ() >> 4); // Paper
}

+ public static final SectionPosition from(ChunkCoordIntPair chunkPos, int i) { return a(chunkPos, i); } // Paper - OBFHELPER
public static SectionPosition a(ChunkCoordIntPair chunkcoordintpair, int i) {
return new SectionPosition(chunkcoordintpair.x, i, chunkcoordintpair.z);
}
@@ -173,6 +174,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(); } // Paper - 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..ac1a7b7f82436568e10e10b30aab7d1588a8b640 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(); } // Paper - 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(); } // Paper - OBFHELPER
public boolean d() {
return this.c > 0;
}

+ public final boolean isOccupied() { return e(); } // Paper - OBFHELPER
public boolean e() {
return this.c != this.b.b();
}

+ public final BlockPosition getPosition() { return f(); } // Paper - 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..0f27429b960395130410f4d96ff285a59a35d4d6 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; } // Paper - 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) -> { // Paper - 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..a9708470c1d27e5f1a612eee84478fea1973311a 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; // Paper
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);
+ // Paper start
+ if (professionCache == null) {
+ professionCache = new java.util.HashSet<>();
+ for (VillagerProfession profession : IRegistry.VILLAGER_PROFESSION) {
+ professionCache.add(profession.getPlaceType());
+ }
+ }
+ return professionCache.contains(villageplacetype);
+ // Paper end
};
public static final Predicate<VillagePlaceType> b = (villageplacetype) -> {
return true;
@@ -83,10 +92,12 @@ public class VillagePlaceType {
return this.D;
}

+ public final Predicate<VillagePlaceType> getCompletionCondition() { return c(); } // Paper - OBFHELPER
public Predicate<VillagePlaceType> c() {
return this.E;
}

+ public final int getValidRange() { return d(); } // Paper - 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..ffdd9cdde094fcb401cf69daab12aa39623632df 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(); } // Paper - 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; // Paper
return (VillagerProfession) IRegistry.a((IRegistry) IRegistry.VILLAGER_PROFESSION, new MinecraftKey(s), (Object) (new VillagerProfession(s, villageplacetype, immutableset, immutableset1, soundeffect)));
}
}
Loading