mirror of
https://github.com/PaperMC/Paper.git
synced 2025-01-07 19:12:22 +01:00
131b67c38a
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.
272 lines
14 KiB
Diff
272 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
--- a/src/main/java/net/minecraft/server/BehaviorFindPosition.java
|
|
+++ b/src/main/java/net/minecraft/server/BehaviorFindPosition.java
|
|
@@ -0,0 +0,0 @@ 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 (poiAttempts <= maxPoiAttempts && 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) {
|
|
+ this.e.long2LongEntrySet().removeIf((entry) -> entry.getLongValue() < this.d);
|
|
+ break OUT;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ /*
|
|
Predicate<BlockPosition> predicate = (blockposition) -> {
|
|
long j = blockposition.asLong();
|
|
|
|
@@ -0,0 +0,0 @@ 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
--- a/src/main/java/net/minecraft/server/RegionFileSection.java
|
|
+++ b/src/main/java/net/minecraft/server/RegionFileSection.java
|
|
@@ -0,0 +0,0 @@ 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) {
|
|
@@ -0,0 +0,0 @@ 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);
|
|
@@ -0,0 +0,0 @@ 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) {
|
|
@@ -0,0 +0,0 @@ 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
--- a/src/main/java/net/minecraft/server/VillagePlaceRecord.java
|
|
+++ b/src/main/java/net/minecraft/server/VillagePlaceRecord.java
|
|
@@ -0,0 +0,0 @@ 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;
|
|
@@ -0,0 +0,0 @@ public class VillagePlaceRecord implements MinecraftSerializable {
|
|
}
|
|
}
|
|
|
|
+ protected final boolean increaseVacancy() { return c(); } // Paper - OBFHELPER
|
|
protected boolean c() {
|
|
if (this.c >= this.b.b()) {
|
|
return false;
|
|
@@ -0,0 +0,0 @@ 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
--- a/src/main/java/net/minecraft/server/VillagePlaceSection.java
|
|
+++ b/src/main/java/net/minecraft/server/VillagePlaceSection.java
|
|
@@ -0,0 +0,0 @@ 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
--- a/src/main/java/net/minecraft/server/VillagePlaceType.java
|
|
+++ b/src/main/java/net/minecraft/server/VillagePlaceType.java
|
|
@@ -0,0 +0,0 @@ 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;
|
|
@@ -0,0 +0,0 @@ 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
--- a/src/main/java/net/minecraft/server/VillagerProfession.java
|
|
+++ b/src/main/java/net/minecraft/server/VillagerProfession.java
|
|
@@ -0,0 +0,0 @@ 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)));
|
|
}
|
|
}
|