274 lines
14 KiB
Diff
274 lines
14 KiB
Diff
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
|
From: Aikar <aikar@aikar.co>
|
|
Date: Tue, 26 May 2020 21:32:05 -0400
|
|
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 35eb3a5a61145e94d5b0c77c0eb13bfa46fac23b..6861b1a345496a83900b0ef702ba34315a030ef6 100644
|
|
--- a/src/main/java/net/minecraft/server/BehaviorFindPosition.java
|
|
+++ b/src/main/java/net/minecraft/server/BehaviorFindPosition.java
|
|
@@ -29,8 +29,55 @@ public class BehaviorFindPosition extends Behavior<EntityCreature> {
|
|
|
|
protected void a(WorldServer worldserver, EntityCreature entitycreature, long i) {
|
|
this.f = 0;
|
|
- this.d = worldserver.getTime() + (long) worldserver.getRandom().nextInt(20);
|
|
+ this.d = worldserver.getTime() + (long) java.util.concurrent.ThreadLocalRandom.current().nextInt(20); // Paper
|
|
VillagePlace villageplace = worldserver.B();
|
|
+
|
|
+ // Paper start - replace implementation completely
|
|
+ BlockPosition blockposition2 = new BlockPosition(entitycreature);
|
|
+ int dist = 48;
|
|
+ int requiredDist = dist * dist;
|
|
+ int cdist = Math.floorDiv(dist, 16);
|
|
+ Predicate<VillagePlaceType> predicate = this.a.c();
|
|
+ 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).v());
|
|
+ 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.e.containsKey(key)) {
|
|
+ continue;
|
|
+ }
|
|
+ double poiDist = pos.distanceSquared(blockposition2);
|
|
+ if (poiDist <= (double) requiredDist) {
|
|
+ this.e.put(key, (long) (this.d + 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.a.d());
|
|
+
|
|
+ if (pathentity != null && pathentity.h()) {
|
|
+ record.decreaseVacancy();
|
|
+ GlobalPos globalPos = GlobalPos.create(worldserver.getWorldProvider().getDimensionManager(), pos);
|
|
+ entitycreature.getBehaviorController().setMemory(this.b, globalPos);
|
|
+ break OUT;
|
|
+ }
|
|
+ if (poiAttempts >= maxPoiAttempts) {
|
|
+ break OUT;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ // Clean up - vanilla does this only when it runs out, but that would push it to try farther POI's...
|
|
+ this.e.long2LongEntrySet().removeIf((entry) -> entry.getLongValue() < this.d);
|
|
+ /*
|
|
Predicate<BlockPosition> predicate = (blockposition) -> {
|
|
long j = blockposition.asLong();
|
|
|
|
@@ -61,6 +108,6 @@ public class BehaviorFindPosition extends Behavior<EntityCreature> {
|
|
return entry.getLongValue() < this.d;
|
|
});
|
|
}
|
|
-
|
|
+ */ // Paper end
|
|
}
|
|
}
|
|
diff --git a/src/main/java/net/minecraft/server/RegionFileSection.java b/src/main/java/net/minecraft/server/RegionFileSection.java
|
|
index a6d8ef5eb44f3f851a3a1be4032ca21ab1d7f2b2..c3e8a0145d63843736d2060f978cdf38df359563 100644
|
|
--- a/src/main/java/net/minecraft/server/RegionFileSection.java
|
|
+++ b/src/main/java/net/minecraft/server/RegionFileSection.java
|
|
@@ -51,29 +51,15 @@ public class RegionFileSection<R extends MinecraftSerializable> extends RegionFi
|
|
|
|
@Nullable
|
|
protected Optional<R> c(long i) {
|
|
- return (Optional) this.c.get(i);
|
|
+ return this.c.getOrDefault(i, Optional.empty()); // Paper
|
|
}
|
|
|
|
+ protected final Optional<R> getSection(long i) { return d(i); } // Paper - OBFHELPER
|
|
protected Optional<R> d(long i) {
|
|
- SectionPosition sectionposition = SectionPosition.a(i);
|
|
-
|
|
- if (this.b(sectionposition)) {
|
|
- return Optional.empty();
|
|
- } else {
|
|
- Optional<R> optional = this.c(i);
|
|
-
|
|
- if (optional != null) {
|
|
- return optional;
|
|
- } else {
|
|
- this.b(sectionposition.u());
|
|
- optional = this.c(i);
|
|
- if (optional == null) {
|
|
- throw (IllegalStateException) SystemUtils.c(new IllegalStateException());
|
|
- } else {
|
|
- return optional;
|
|
- }
|
|
- }
|
|
- }
|
|
+ // Paper start - replace method - never load POI data sync, we load this in chunk load already, reduce ops
|
|
+ // If it's an unloaded chunk, well too bad.
|
|
+ return this.c(i);
|
|
+ // Paper end
|
|
}
|
|
|
|
protected boolean b(SectionPosition sectionposition) {
|
|
@@ -117,7 +103,7 @@ public class RegionFileSection<R extends MinecraftSerializable> extends RegionFi
|
|
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).v(), Optional.empty());
|
|
+ //this.c.put(SectionPosition.a(chunkcoordintpair, i).v(), Optional.empty()); // Paper - NO!!!
|
|
}
|
|
} else {
|
|
Dynamic<T> dynamic = new Dynamic(dynamicops, t0);
|
|
@@ -135,7 +121,7 @@ public class RegionFileSection<R extends MinecraftSerializable> extends RegionFi
|
|
}, dynamic2);
|
|
});
|
|
|
|
- this.c.put(i1, optional);
|
|
+ if (optional.isPresent()) this.c.put(i1, optional); // Paper - NO!!!
|
|
optional.ifPresent((minecraftserializable) -> {
|
|
this.b(i1);
|
|
if (flag) {
|
|
@@ -199,7 +185,7 @@ public class RegionFileSection<R extends MinecraftSerializable> extends RegionFi
|
|
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/VillagePlaceRecord.java b/src/main/java/net/minecraft/server/VillagePlaceRecord.java
|
|
index 1e9d7a3f902eb4571b93bb0e58cba966365f07b8..44535a3e2c3320aac472c5a7ee557fac7bab2530 100644
|
|
--- a/src/main/java/net/minecraft/server/VillagePlaceRecord.java
|
|
+++ b/src/main/java/net/minecraft/server/VillagePlaceRecord.java
|
|
@@ -32,6 +32,7 @@ public class VillagePlaceRecord implements MinecraftSerializable {
|
|
return dynamicops.createMap(ImmutableMap.of(dynamicops.createString("pos"), this.a.a(dynamicops), dynamicops.createString("type"), dynamicops.createString(IRegistry.POINT_OF_INTEREST_TYPE.getKey(this.b).toString()), dynamicops.createString("free_tickets"), dynamicops.createInt(this.c)));
|
|
}
|
|
|
|
+ protected final boolean decreaseVacancy() { return b(); } // Paper - OBFHELPER
|
|
protected boolean b() {
|
|
if (this.c <= 0) {
|
|
return false;
|
|
@@ -42,6 +43,7 @@ public class VillagePlaceRecord implements MinecraftSerializable {
|
|
}
|
|
}
|
|
|
|
+ protected final boolean increaseVacancy() { return c(); } // Paper - OBFHELPER
|
|
protected boolean c() {
|
|
if (this.c >= this.b.b()) {
|
|
return false;
|
|
@@ -52,14 +54,17 @@ public class VillagePlaceRecord implements MinecraftSerializable {
|
|
}
|
|
}
|
|
|
|
+ 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
|
|
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 3f2602dbe0995f8d01d4a1428d919405d711a205..436b064c3b277143075386fc9a71027fb5962681 100644
|
|
--- a/src/main/java/net/minecraft/server/VillagePlaceSection.java
|
|
+++ b/src/main/java/net/minecraft/server/VillagePlaceSection.java
|
|
@@ -23,7 +23,7 @@ public class VillagePlaceSection implements MinecraftSerializable {
|
|
|
|
private static final Logger LOGGER = LogManager.getLogger();
|
|
private final Short2ObjectMap<VillagePlaceRecord> b = new Short2ObjectOpenHashMap();
|
|
- private final Map<VillagePlaceType, Set<VillagePlaceRecord>> c = Maps.newHashMap();
|
|
+ private final Map<VillagePlaceType, Set<VillagePlaceRecord>> c = Maps.newHashMap(); public final Map<VillagePlaceType, Set<VillagePlaceRecord>> getRecords() { return c; } // Paper - OBFHELPER
|
|
private final Runnable d;
|
|
private boolean e;
|
|
|
|
diff --git a/src/main/java/net/minecraft/server/VillagePlaceType.java b/src/main/java/net/minecraft/server/VillagePlaceType.java
|
|
index ab3e054cd2f38756a5d802d4d981022318ab047d..c1f293fc98d3efb4665cfb9036f208b842fc8e36 100644
|
|
--- a/src/main/java/net/minecraft/server/VillagePlaceType.java
|
|
+++ b/src/main/java/net/minecraft/server/VillagePlaceType.java
|
|
@@ -12,8 +12,14 @@ import java.util.stream.Stream;
|
|
|
|
public class VillagePlaceType {
|
|
|
|
+ static Set<VillagePlaceType> professionCache; // Paper
|
|
private static final Predicate<VillagePlaceType> v = (villageplacetype) -> {
|
|
- return ((Set) IRegistry.VILLAGER_PROFESSION.d().map(VillagerProfession::b).collect(Collectors.toSet())).contains(villageplacetype);
|
|
+ // Paper start
|
|
+ if (professionCache == null) {
|
|
+ professionCache = IRegistry.VILLAGER_PROFESSION.d().map(VillagerProfession::b).collect(Collectors.toSet());
|
|
+ }
|
|
+ return professionCache.contains(villageplacetype);
|
|
+ // Paper end
|
|
};
|
|
public static final Predicate<VillagePlaceType> a = (villageplacetype) -> {
|
|
return true;
|
|
@@ -89,11 +95,11 @@ public class VillagePlaceType {
|
|
}
|
|
|
|
private static VillagePlaceType a(String s, Set<IBlockData> set, int i, int j) {
|
|
- return a((VillagePlaceType) IRegistry.POINT_OF_INTEREST_TYPE.a(new MinecraftKey(s), (Object) (new VillagePlaceType(s, set, i, j))));
|
|
+ return a((VillagePlaceType) IRegistry.POINT_OF_INTEREST_TYPE.a(new MinecraftKey(s), (new VillagePlaceType(s, set, i, j)))); // Paper - decompile error
|
|
}
|
|
|
|
private static VillagePlaceType a(String s, Set<IBlockData> set, int i, Predicate<VillagePlaceType> predicate, int j) {
|
|
- return a((VillagePlaceType) IRegistry.POINT_OF_INTEREST_TYPE.a(new MinecraftKey(s), (Object) (new VillagePlaceType(s, set, i, predicate, j))));
|
|
+ return a((VillagePlaceType) IRegistry.POINT_OF_INTEREST_TYPE.a(new MinecraftKey(s), (new VillagePlaceType(s, set, i, predicate, j)))); // Paper - decompile error
|
|
}
|
|
|
|
private static VillagePlaceType a(VillagePlaceType villageplacetype) {
|
|
diff --git a/src/main/java/net/minecraft/server/VillagerProfession.java b/src/main/java/net/minecraft/server/VillagerProfession.java
|
|
index c38296165b33698bc15fe49a2de0d0d19cfb910a..f9d7a16c79a4e3ffe8b6e7ed469236a93892f01d 100644
|
|
--- a/src/main/java/net/minecraft/server/VillagerProfession.java
|
|
+++ b/src/main/java/net/minecraft/server/VillagerProfession.java
|
|
@@ -61,6 +61,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)));
|
|
}
|
|
}
|