diff --git a/Spigot-Server-Patches/MC-Dev-fixes.patch b/Spigot-Server-Patches/MC-Dev-fixes.patch index 10621de25..0750a9857 100644 --- a/Spigot-Server-Patches/MC-Dev-fixes.patch +++ b/Spigot-Server-Patches/MC-Dev-fixes.patch @@ -49,6 +49,19 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 } else { System.arraycopy(this.b, 0, au, 0, this.c); if (au.length > this.c) { +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 { + villageplace.a(this.a.c(), (blockposition1) -> { + return blockposition1.equals(blockposition); + }, blockposition, 1); +- entitycreature.getBehaviorController().setMemory(this.b, (Object) GlobalPos.create(worldserver.getWorldProvider().getDimensionManager(), blockposition)); ++ entitycreature.getBehaviorController().setMemory(this.b, GlobalPos.create(worldserver.getWorldProvider().getDimensionManager(), blockposition)); + PacketDebug.c(worldserver, blockposition); + }); + } else if (this.f < 5) { diff --git a/src/main/java/net/minecraft/server/BiomeBase.java b/src/main/java/net/minecraft/server/BiomeBase.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/server/BiomeBase.java diff --git a/Spigot-Server-Patches/MC-Utils.patch b/Spigot-Server-Patches/MC-Utils.patch index b979f707c..fbf230afb 100644 --- a/Spigot-Server-Patches/MC-Utils.patch +++ b/Spigot-Server-Patches/MC-Utils.patch @@ -2283,6 +2283,18 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 public double a() { double d0 = this.b(); double d1 = this.c(); +diff --git a/src/main/java/net/minecraft/server/BaseBlockPosition.java b/src/main/java/net/minecraft/server/BaseBlockPosition.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/server/BaseBlockPosition.java ++++ b/src/main/java/net/minecraft/server/BaseBlockPosition.java +@@ -0,0 +0,0 @@ public class BaseBlockPosition implements Comparable { + return this.distanceSquared(iposition.getX(), iposition.getY(), iposition.getZ(), true) < d0 * d0; + } + ++ public final double distanceSquared(BaseBlockPosition baseblockposition) { return m(baseblockposition); } // Paper - OBFHELPER + public double m(BaseBlockPosition baseblockposition) { + return this.distanceSquared((double) baseblockposition.getX(), (double) baseblockposition.getY(), (double) baseblockposition.getZ(), true); + } diff --git a/src/main/java/net/minecraft/server/BlockAccessAir.java b/src/main/java/net/minecraft/server/BlockAccessAir.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/server/BlockAccessAir.java diff --git a/Spigot-Server-Patches/Optimize-Villagers.patch b/Spigot-Server-Patches/Optimize-Villagers.patch new file mode 100644 index 000000000..7e04e431a --- /dev/null +++ b/Spigot-Server-Patches/Optimize-Villagers.patch @@ -0,0 +1,272 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +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 { + + 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 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 section = villageplace.getSection(SectionPosition.a(chunkcoordintpair, i1).v()); ++ if (section == null || !section.isPresent()) continue; ++ for (java.util.Map.Entry> 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 predicate = (blockposition) -> { + long j = blockposition.asLong(); + +@@ -0,0 +0,0 @@ public class BehaviorFindPosition extends Behavior { + 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 extends RegionFi + + @Nullable + protected Optional c(long i) { +- return (Optional) this.c.get(i); ++ return this.c.getOrDefault(i, Optional.empty()); // Paper + } + ++ protected final Optional getSection(long i) { return d(i); } // Paper - OBFHELPER + protected Optional d(long i) { +- SectionPosition sectionposition = SectionPosition.a(i); +- +- if (this.b(sectionposition)) { +- return Optional.empty(); +- } else { +- Optional 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 extends RegionFi + private void a(ChunkCoordIntPair chunkcoordintpair, DynamicOps 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 dynamic = new Dynamic(dynamicops, t0); +@@ -0,0 +0,0 @@ public class RegionFileSection 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 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 b = new Short2ObjectOpenHashMap(); +- private final Map> c = Maps.newHashMap(); ++ private final Map> c = Maps.newHashMap(); public final Map> 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 professionCache; // Paper + private static final Predicate 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 a = (villageplacetype) -> { + return true; +@@ -0,0 +0,0 @@ public class VillagePlaceType { + } + + private static VillagePlaceType a(String s, Set 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 set, int i, Predicate 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 immutableset, ImmutableSet 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))); + } + }