diff --git a/patches/server/0108-Optimize-entity-pathfinder-selector.patch b/patches/server/0108-Optimize-entity-pathfinder-selector.patch new file mode 100644 index 000000000..3acc4b0ef --- /dev/null +++ b/patches/server/0108-Optimize-entity-pathfinder-selector.patch @@ -0,0 +1,344 @@ +From 1dd35988e94f4edc0fb1eb34cf4adc444621deb7 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Fri, 6 Mar 2020 16:33:58 -0600 +Subject: [PATCH] Optimize entity pathfinder selector + +--- + .../co/aikar/timings/MinecraftTimings.java | 4 + + .../tuinity/util/OptimizedSmallEnumSet.java | 65 +++++++++ + .../net/minecraft/server/PathfinderGoal.java | 6 +- + .../server/PathfinderGoalSelector.java | 132 +++++++++++------- + .../server/PathfinderGoalWrapped.java | 6 +- + 5 files changed, 159 insertions(+), 54 deletions(-) + create mode 100644 src/main/java/com/tuinity/tuinity/util/OptimizedSmallEnumSet.java + +diff --git a/src/main/java/co/aikar/timings/MinecraftTimings.java b/src/main/java/co/aikar/timings/MinecraftTimings.java +index 69e26a826..11b62a2e0 100644 +--- a/src/main/java/co/aikar/timings/MinecraftTimings.java ++++ b/src/main/java/co/aikar/timings/MinecraftTimings.java +@@ -42,6 +42,10 @@ public final class MinecraftTimings { + public static final Timing antiXrayUpdateTimer = Timings.ofSafe("anti-xray - update"); + public static final Timing antiXrayObfuscateTimer = Timings.ofSafe("anti-xray - obfuscate"); + ++ public static final Timing goalCleanup = Timings.ofSafe("PathfinderGoal - Cleanup"); ++ public static final Timing goalUpdate = Timings.ofSafe("PathfinderGoal - Update"); ++ public static final Timing goalTick = Timings.ofSafe("PathfinderGoal - Tick"); ++ + private static final Map, String> taskNameCache = new MapMaker().weakKeys().makeMap(); + + private MinecraftTimings() {} +diff --git a/src/main/java/com/tuinity/tuinity/util/OptimizedSmallEnumSet.java b/src/main/java/com/tuinity/tuinity/util/OptimizedSmallEnumSet.java +new file mode 100644 +index 000000000..68db85888 +--- /dev/null ++++ b/src/main/java/com/tuinity/tuinity/util/OptimizedSmallEnumSet.java +@@ -0,0 +1,65 @@ ++package com.tuinity.tuinity.util; ++ ++import java.util.Collection; ++ ++// containing utils to work on small numbers of enums ++public final class OptimizedSmallEnumSet { ++ ++ private final Class enumClass; ++ private long backingSet; ++ ++ public OptimizedSmallEnumSet(final Class clazz) { ++ if (clazz == null) { ++ throw new IllegalArgumentException("Null class"); ++ } ++ if (!clazz.isEnum()) { ++ throw new IllegalArgumentException("Class must be enum, not " + clazz.getCanonicalName()); ++ } ++ this.enumClass = clazz; ++ } ++ ++ public boolean addUnchecked(final E element) { ++ final int ordinal = element.ordinal(); ++ final long key = 1L << ordinal; ++ ++ final long prev = this.backingSet; ++ this.backingSet = prev | key; ++ ++ return (prev & key) == 0; ++ } ++ ++ public boolean removeUnchecked(final E element) { ++ final int ordinal = element.ordinal(); ++ final long key = 1L << ordinal; ++ ++ final long prev = this.backingSet; ++ this.backingSet = prev & ~key; ++ ++ return (prev & key) != 0; ++ } ++ ++ public void clear() { ++ this.backingSet = 0L; ++ } ++ ++ public int size() { ++ return Long.bitCount(this.backingSet); ++ } ++ ++ public void addAllUnchecked(final Collection enums) { ++ for (final E element : enums) { ++ if (element == null) { ++ throw new NullPointerException("Null element"); ++ } ++ this.backingSet |= (1L << element.ordinal()); ++ } ++ } ++ ++ public long getBackingSet() { ++ return this.backingSet; ++ } ++ ++ public boolean hasCommonElements(final OptimizedSmallEnumSet other) { ++ return (other.backingSet & this.backingSet) != 0; ++ } ++} +diff --git a/src/main/java/net/minecraft/server/PathfinderGoal.java b/src/main/java/net/minecraft/server/PathfinderGoal.java +index e059930ba..6c307a909 100644 +--- a/src/main/java/net/minecraft/server/PathfinderGoal.java ++++ b/src/main/java/net/minecraft/server/PathfinderGoal.java +@@ -4,7 +4,7 @@ import java.util.EnumSet; + + public abstract class PathfinderGoal { + +- private final EnumSet a = EnumSet.noneOf(PathfinderGoal.Type.class); ++ private final com.tuinity.tuinity.util.OptimizedSmallEnumSet a = new com.tuinity.tuinity.util.OptimizedSmallEnumSet<>(PathfinderGoal.Type.class); // Purpur + + public PathfinderGoal() {} + +@@ -30,14 +30,14 @@ public abstract class PathfinderGoal { + public void setTypes(EnumSet types) { this.a(types); } // Purpur - OBFHELPER + public void a(EnumSet enumset) { + this.a.clear(); +- this.a.addAll(enumset); ++ this.a.addAllUnchecked(enumset); // Purpur + } + + public String toString() { + return this.getClass().getSimpleName(); + } + +- public EnumSet i() { ++ public com.tuinity.tuinity.util.OptimizedSmallEnumSet i() { // Purpur + return this.a; + } + +diff --git a/src/main/java/net/minecraft/server/PathfinderGoalSelector.java b/src/main/java/net/minecraft/server/PathfinderGoalSelector.java +index 935136771..b2bea3e24 100644 +--- a/src/main/java/net/minecraft/server/PathfinderGoalSelector.java ++++ b/src/main/java/net/minecraft/server/PathfinderGoalSelector.java +@@ -12,6 +12,7 @@ import org.apache.logging.log4j.Logger; + public class PathfinderGoalSelector { + + private static final Logger LOGGER = LogManager.getLogger(); ++ private static PathfinderGoalWrapped EMPTY_GOAL() { return b; }; // Purpur - OBFHELPER + private static final PathfinderGoalWrapped b = new PathfinderGoalWrapped(Integer.MAX_VALUE, new PathfinderGoal() { + @Override + public boolean a() { +@@ -26,7 +27,7 @@ public class PathfinderGoalSelector { + private final Map c = new EnumMap(PathfinderGoal.Type.class); + private final Set d = Sets.newLinkedHashSet();private Set getTasks() { return d; }// Paper - OBFHELPER + private final GameProfilerFiller e; +- private final EnumSet f = EnumSet.noneOf(PathfinderGoal.Type.class); ++ private final com.tuinity.tuinity.util.OptimizedSmallEnumSet disabledTypes = new com.tuinity.tuinity.util.OptimizedSmallEnumSet<>(PathfinderGoal.Type.class); // Purpur + private int g = 3;private int getTickRate() { return g; } // Paper - OBFHELPER + private int curRate;private int getCurRate() { return curRate; } private void incRate() { this.curRate++; } // Paper TODO + +@@ -58,65 +59,98 @@ public class PathfinderGoalSelector { + // Paper end + + public void a(PathfinderGoal pathfindergoal) { +- this.d.stream().filter((pathfindergoalwrapped) -> { +- return pathfindergoalwrapped.j() == pathfindergoal; +- }).filter(PathfinderGoalWrapped::g).forEach(PathfinderGoalWrapped::d); +- this.d.removeIf((pathfindergoalwrapped) -> { +- return pathfindergoalwrapped.j() == pathfindergoal; +- }); ++ // Purpur start ++ for (java.util.Iterator iterator = getTasks().iterator(); iterator.hasNext();) { ++ PathfinderGoalWrapped wrappedGoal = iterator.next(); ++ if (wrappedGoal.getGoal() != pathfindergoal) { ++ continue; ++ } ++ if (wrappedGoal.isRunning()) { ++ wrappedGoal.d(); // reset goal ++ } ++ iterator.remove(); ++ } ++ // Purpur end + } + ++ private static final PathfinderGoal.Type[] PATHFINDER_GOAL_TYPES = PathfinderGoal.Type.values(); // Purpur ++ + public void doTick() { + this.e.enter("goalCleanup"); +- this.c().filter((pathfindergoalwrapped) -> { +- boolean flag; ++ // Purpur start ++ co.aikar.timings.MinecraftTimings.goalCleanup.startTiming(); ++ for (java.util.Iterator iter = getTasks().iterator(); iter.hasNext();) { ++ PathfinderGoalWrapped wrappedGoal = iter.next(); ++ if (!wrappedGoal.isRunning()) { ++ continue; // goal is not running ++ } ++ if (!this.disabledTypes.hasCommonElements(wrappedGoal.getTypes()) && wrappedGoal.b()) { ++ continue; // goal type is not disabled and should continue running ++ } ++ wrappedGoal.d(); // reset goal ++ } ++ this.c.forEach((type, goal) -> { ++ if (!goal.isRunning()) { ++ this.c.remove(type); // remove locked goal types if goal no longer running ++ } ++ }); ++ co.aikar.timings.MinecraftTimings.goalCleanup.stopTiming(); ++ // Purpur end ++ this.e.exit(); ++ this.e.enter("goalUpdate"); ++ // Purpur start ++ co.aikar.timings.MinecraftTimings.goalUpdate.startTiming(); ++ goal_update_loop: ++ for (java.util.Iterator iterator = getTasks().iterator(); iterator.hasNext();) { ++ PathfinderGoalWrapped wrappedGoal = iterator.next(); ++ if (wrappedGoal.isRunning()) { ++ continue; // goal is already running ++ } ++ ++ com.tuinity.tuinity.util.OptimizedSmallEnumSet wrappedGoalSet = wrappedGoal.getTypes(); + +- if (pathfindergoalwrapped.g()) { +- Stream stream = pathfindergoalwrapped.i().stream(); +- EnumSet enumset = this.f; ++ if (disabledTypes.hasCommonElements(wrappedGoalSet)) { ++ continue; // goal type is disabled ++ } + +- this.f.getClass(); +- if (!stream.anyMatch(enumset::contains) && pathfindergoalwrapped.b()) { +- flag = false; +- return flag; ++ long k = wrappedGoalSet.getBackingSet(); ++ int wrappedGoalSize = wrappedGoalSet.size(); ++ for (int i = 0; i < wrappedGoalSize; ++i) { ++ PathfinderGoal.Type type = PATHFINDER_GOAL_TYPES[Long.numberOfTrailingZeros(k)]; ++ k ^= -k & k; // Tuinity's ca.spottedleaf.concurrentutil.util.IntegerUtil.getTrailingBit ++ if (!c.getOrDefault(type, EMPTY_GOAL()).canBeReplacedBy(wrappedGoal)) { ++ continue goal_update_loop; // goal type(s) is locked by another running goal with same type(s) + } + } + +- flag = true; +- return flag; +- }).forEach(PathfinderGoal::d); +- this.c.forEach((pathfindergoal_type, pathfindergoalwrapped) -> { +- if (!pathfindergoalwrapped.g()) { +- this.c.remove(pathfindergoal_type); ++ if (!wrappedGoal.a()) { ++ continue; // goal failed to run + } + +- }); +- this.e.exit(); +- this.e.enter("goalUpdate"); +- this.d.stream().filter((pathfindergoalwrapped) -> { +- return !pathfindergoalwrapped.g(); +- }).filter((pathfindergoalwrapped) -> { +- Stream stream = pathfindergoalwrapped.i().stream(); +- EnumSet enumset = this.f; +- +- this.f.getClass(); +- return stream.noneMatch(enumset::contains); +- }).filter((pathfindergoalwrapped) -> { +- return pathfindergoalwrapped.i().stream().allMatch((pathfindergoal_type) -> { +- return ((PathfinderGoalWrapped) this.c.getOrDefault(pathfindergoal_type, PathfinderGoalSelector.b)).a(pathfindergoalwrapped); +- }); +- }).filter(PathfinderGoalWrapped::a).forEach((pathfindergoalwrapped) -> { +- pathfindergoalwrapped.i().forEach((pathfindergoal_type) -> { +- PathfinderGoalWrapped pathfindergoalwrapped1 = (PathfinderGoalWrapped) this.c.getOrDefault(pathfindergoal_type, PathfinderGoalSelector.b); +- +- pathfindergoalwrapped1.d(); +- this.c.put(pathfindergoal_type, pathfindergoalwrapped); +- }); +- pathfindergoalwrapped.c(); +- }); ++ k = wrappedGoalSet.getBackingSet(); ++ wrappedGoalSize = wrappedGoalSet.size(); ++ for (int i = 0; i < wrappedGoalSize; ++i) { ++ PathfinderGoal.Type type = PATHFINDER_GOAL_TYPES[Long.numberOfTrailingZeros(k)]; ++ k ^= -k & k; // Tuinity's ca.spottedleaf.concurrentutil.util.IntegerUtil.getTrailingBit ++ c.getOrDefault(type, EMPTY_GOAL()).d(); // reset goal ++ c.put(type, wrappedGoal); ++ } ++ ++ wrappedGoal.c(); // start goal ++ } ++ co.aikar.timings.MinecraftTimings.goalUpdate.stopTiming(); ++ // Purpur end + this.e.exit(); + this.e.enter("goalTick"); +- this.c().forEach(PathfinderGoalWrapped::e); ++ // Purpur start ++ co.aikar.timings.MinecraftTimings.goalTick.startTiming(); ++ for (PathfinderGoalWrapped wrappedGoal : getTasks()) { ++ if (wrappedGoal.isRunning()) { ++ wrappedGoal.e(); // goal tick ++ } ++ } ++ co.aikar.timings.MinecraftTimings.goalTick.stopTiming(); ++ // Purpur end + this.e.exit(); + } + +@@ -125,11 +159,11 @@ public class PathfinderGoalSelector { + } + + public void a(PathfinderGoal.Type pathfindergoal_type) { +- this.f.add(pathfindergoal_type); ++ this.disabledTypes.addUnchecked(pathfindergoal_type); // Purpur + } + + public void b(PathfinderGoal.Type pathfindergoal_type) { +- this.f.remove(pathfindergoal_type); ++ this.disabledTypes.removeUnchecked(pathfindergoal_type); // Purpur + } + + public void a(PathfinderGoal.Type pathfindergoal_type, boolean flag) { +diff --git a/src/main/java/net/minecraft/server/PathfinderGoalWrapped.java b/src/main/java/net/minecraft/server/PathfinderGoalWrapped.java +index 29657fed7..172e74b96 100644 +--- a/src/main/java/net/minecraft/server/PathfinderGoalWrapped.java ++++ b/src/main/java/net/minecraft/server/PathfinderGoalWrapped.java +@@ -14,6 +14,7 @@ public class PathfinderGoalWrapped extends PathfinderGoal { + this.a = pathfindergoal; + } + ++ public boolean canBeReplacedBy(PathfinderGoalWrapped pathfindergoalwrapped) { return a(pathfindergoalwrapped); } // Purpur - OBFHELPER + public boolean a(PathfinderGoalWrapped pathfindergoalwrapped) { + return this.E_() && pathfindergoalwrapped.h() < this.h(); + } +@@ -59,8 +60,8 @@ public class PathfinderGoalWrapped extends PathfinderGoal { + this.a.a(enumset); + } + +- @Override +- public EnumSet i() { ++ public com.tuinity.tuinity.util.OptimizedSmallEnumSet getTypes() { return i(); } // Purpur - OBFHELPER ++ @Override public com.tuinity.tuinity.util.OptimizedSmallEnumSet i() { // Purpur + return this.a.i(); + } + +@@ -73,6 +74,7 @@ public class PathfinderGoalWrapped extends PathfinderGoal { + return this.b; + } + ++ public PathfinderGoal getGoal() { return j(); } // Purpur - OBFHELPER + public PathfinderGoal j() { + return this.a; + } +-- +2.24.0 +