Compare commits

...

1 Commits

Author SHA1 Message Date
Sofiane H. Djerbi
a3a6c53f66 Pathfinding fixes
Fix shulker boxes breaking pathfinding
Fix petal memory leak
Remove the thread queue
2023-08-05 02:59:48 +02:00

View File

@@ -5,7 +5,7 @@ Subject: [PATCH] Async path processing
diff --git a/src/main/java/dev/kaiijumc/kaiiju/KaiijuConfig.java b/src/main/java/dev/kaiijumc/kaiiju/KaiijuConfig.java diff --git a/src/main/java/dev/kaiijumc/kaiiju/KaiijuConfig.java b/src/main/java/dev/kaiijumc/kaiiju/KaiijuConfig.java
index ebfa9e1dcca5ea8272e796f0409902d92b59ee76..6be4abdd16f2d57a80dbe175a91ff304fd17a7db 100644 index 6df1720159383c2f536b40ded1092a437c1a20af..fc88b9f1e7e8f5858a91deeca2a5d51266a79a93 100644
--- a/src/main/java/dev/kaiijumc/kaiiju/KaiijuConfig.java --- a/src/main/java/dev/kaiijumc/kaiiju/KaiijuConfig.java
+++ b/src/main/java/dev/kaiijumc/kaiiju/KaiijuConfig.java +++ b/src/main/java/dev/kaiijumc/kaiiju/KaiijuConfig.java
@@ -12,6 +12,7 @@ import org.bukkit.configuration.file.YamlConfiguration; @@ -12,6 +12,7 @@ import org.bukkit.configuration.file.YamlConfiguration;
@@ -16,14 +16,13 @@ index ebfa9e1dcca5ea8272e796f0409902d92b59ee76..6be4abdd16f2d57a80dbe175a91ff304
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
@@ -213,12 +214,28 @@ public class KaiijuConfig { @@ -218,12 +219,26 @@ public class KaiijuConfig {
public static boolean disablePlayerStats = false; public static boolean disablePlayerStats = false;
public static boolean disableArmSwingEvent = false; public static boolean disableArmSwingEvent = false;
public static boolean disableEnsureTickThreadChecks = false; public static boolean disableEnsureTickThreadChecks = false;
+ public static boolean asyncPathProcessing = false; + public static boolean asyncPathProcessing = false;
+ public static int asyncPathProcessingMaxThreads = 0; + public static int asyncPathProcessingMaxThreads = 0;
+ public static int asyncPathProcessingKeepalive = 60; + public static int asyncPathProcessingKeepalive = 60;
+ public static int asyncPathProcessingQueueCapacity = 4096;
private static void optimizationSettings() { private static void optimizationSettings() {
disableVanishApi = getBoolean("optimization.disable-vanish-api", disableVanishApi); disableVanishApi = getBoolean("optimization.disable-vanish-api", disableVanishApi);
@@ -33,7 +32,6 @@ index ebfa9e1dcca5ea8272e796f0409902d92b59ee76..6be4abdd16f2d57a80dbe175a91ff304
+ asyncPathProcessing = getBoolean("optimization.async-path-processing.enable", asyncPathProcessing); + asyncPathProcessing = getBoolean("optimization.async-path-processing.enable", asyncPathProcessing);
+ asyncPathProcessingMaxThreads = getInt("optimization.async-path-processing.max-threads", asyncPathProcessingMaxThreads); + asyncPathProcessingMaxThreads = getInt("optimization.async-path-processing.max-threads", asyncPathProcessingMaxThreads);
+ asyncPathProcessingKeepalive = getInt("optimization.async-path-processing.keepalive", asyncPathProcessingKeepalive); + asyncPathProcessingKeepalive = getInt("optimization.async-path-processing.keepalive", asyncPathProcessingKeepalive);
+ asyncPathProcessingQueueCapacity = getInt("optimization.async-path-processing.queue-capacity", asyncPathProcessingQueueCapacity);
+ if (asyncPathProcessingMaxThreads < 0) + if (asyncPathProcessingMaxThreads < 0)
+ asyncPathProcessingMaxThreads = Math.max(Runtime.getRuntime().availableProcessors() + asyncPathProcessingMaxThreads, 1); + asyncPathProcessingMaxThreads = Math.max(Runtime.getRuntime().availableProcessors() + asyncPathProcessingMaxThreads, 1);
+ else if (asyncPathProcessingMaxThreads == 0) + else if (asyncPathProcessingMaxThreads == 0)
@@ -340,10 +338,10 @@ index 0000000000000000000000000000000000000000..6b91852238f80d236fc44f766b115267
+} +}
diff --git a/src/main/java/dev/kaiijumc/kaiiju/path/AsyncPathProcessor.java b/src/main/java/dev/kaiijumc/kaiiju/path/AsyncPathProcessor.java diff --git a/src/main/java/dev/kaiijumc/kaiiju/path/AsyncPathProcessor.java b/src/main/java/dev/kaiijumc/kaiiju/path/AsyncPathProcessor.java
new file mode 100644 new file mode 100644
index 0000000000000000000000000000000000000000..a6de8906d1629c60523c02681379ccdcaa22b588 index 0000000000000000000000000000000000000000..acdb210e706ea85355f971d73aed92b071410abf
--- /dev/null --- /dev/null
+++ b/src/main/java/dev/kaiijumc/kaiiju/path/AsyncPathProcessor.java +++ b/src/main/java/dev/kaiijumc/kaiiju/path/AsyncPathProcessor.java
@@ -0,0 +1,53 @@ @@ -0,0 +1,48 @@
+package dev.kaiijumc.kaiiju.path; +package dev.kaiijumc.kaiiju.path;
+ +
+import com.google.common.util.concurrent.ThreadFactoryBuilder; +import com.google.common.util.concurrent.ThreadFactoryBuilder;
@@ -362,12 +360,7 @@ index 0000000000000000000000000000000000000000..a6de8906d1629c60523c02681379ccdc
+ */ + */
+public class AsyncPathProcessor { +public class AsyncPathProcessor {
+ +
+ private static final Executor pathProcessingExecutor = new ThreadPoolExecutor( + private static final Executor pathProcessingExecutor = Executors.newCachedThreadPool(
+ 1,
+ dev.kaiijumc.kaiiju.KaiijuConfig.asyncPathProcessingMaxThreads,
+ dev.kaiijumc.kaiiju.KaiijuConfig.asyncPathProcessingKeepalive,
+ TimeUnit.SECONDS,
+ new ArrayBlockingQueue<>(dev.kaiijumc.kaiiju.KaiijuConfig.asyncPathProcessingQueueCapacity),
+ new ThreadFactoryBuilder() + new ThreadFactoryBuilder()
+ .setNameFormat("petal-path-processor-%d") + .setNameFormat("petal-path-processor-%d")
+ .setPriority(Thread.NORM_PRIORITY - 2) + .setPriority(Thread.NORM_PRIORITY - 2)
@@ -399,10 +392,10 @@ index 0000000000000000000000000000000000000000..a6de8906d1629c60523c02681379ccdc
+} +}
diff --git a/src/main/java/dev/kaiijumc/kaiiju/path/NodeEvaluatorCache.java b/src/main/java/dev/kaiijumc/kaiiju/path/NodeEvaluatorCache.java diff --git a/src/main/java/dev/kaiijumc/kaiiju/path/NodeEvaluatorCache.java b/src/main/java/dev/kaiijumc/kaiiju/path/NodeEvaluatorCache.java
new file mode 100644 new file mode 100644
index 0000000000000000000000000000000000000000..121eda164714650f76bb6c8495ef375d8a00d812 index 0000000000000000000000000000000000000000..3213fed7cea3ebfc364f4d6603b95f4263222c76
--- /dev/null --- /dev/null
+++ b/src/main/java/dev/kaiijumc/kaiiju/path/NodeEvaluatorCache.java +++ b/src/main/java/dev/kaiijumc/kaiiju/path/NodeEvaluatorCache.java
@@ -0,0 +1,42 @@ @@ -0,0 +1,45 @@
+package dev.kaiijumc.kaiiju.path; +package dev.kaiijumc.kaiiju.path;
+ +
+import net.minecraft.world.level.pathfinder.NodeEvaluator; +import net.minecraft.world.level.pathfinder.NodeEvaluator;
@@ -419,13 +412,13 @@ index 0000000000000000000000000000000000000000..121eda164714650f76bb6c8495ef375d
+ private static final Map<NodeEvaluatorFeatures, ConcurrentLinkedQueue<NodeEvaluator>> threadLocalNodeEvaluators = new ConcurrentHashMap<>(); + private static final Map<NodeEvaluatorFeatures, ConcurrentLinkedQueue<NodeEvaluator>> threadLocalNodeEvaluators = new ConcurrentHashMap<>();
+ private static final Map<NodeEvaluator, NodeEvaluatorGenerator> nodeEvaluatorToGenerator = new ConcurrentHashMap<>(); + private static final Map<NodeEvaluator, NodeEvaluatorGenerator> nodeEvaluatorToGenerator = new ConcurrentHashMap<>();
+ +
+ private static @NotNull Queue<NodeEvaluator> getDequeForGenerator(@NotNull NodeEvaluatorFeatures nodeEvaluatorFeatures) { + private static @NotNull Queue<NodeEvaluator> getQueueForFeatures(@NotNull NodeEvaluatorFeatures nodeEvaluatorFeatures) {
+ return threadLocalNodeEvaluators.computeIfAbsent(nodeEvaluatorFeatures, (key) -> new ConcurrentLinkedQueue<>()); + return threadLocalNodeEvaluators.computeIfAbsent(nodeEvaluatorFeatures, (key) -> new ConcurrentLinkedQueue<>());
+ } + }
+ +
+ public static @NotNull NodeEvaluator takeNodeEvaluator(@NotNull NodeEvaluatorGenerator generator, @NotNull NodeEvaluator localNodeEvaluator) { + public static @NotNull NodeEvaluator takeNodeEvaluator(@NotNull NodeEvaluatorGenerator generator, @NotNull NodeEvaluator localNodeEvaluator) {
+ final NodeEvaluatorFeatures nodeEvaluatorFeatures = NodeEvaluatorFeatures.fromNodeEvaluator(localNodeEvaluator); + final NodeEvaluatorFeatures nodeEvaluatorFeatures = NodeEvaluatorFeatures.fromNodeEvaluator(localNodeEvaluator);
+ NodeEvaluator nodeEvaluator = getDequeForGenerator(nodeEvaluatorFeatures).poll(); + NodeEvaluator nodeEvaluator = getQueueForFeatures(nodeEvaluatorFeatures).poll();
+ +
+ if (nodeEvaluator == null) { + if (nodeEvaluator == null) {
+ nodeEvaluator = generator.generate(nodeEvaluatorFeatures); + nodeEvaluator = generator.generate(nodeEvaluatorFeatures);
@@ -437,13 +430,16 @@ index 0000000000000000000000000000000000000000..121eda164714650f76bb6c8495ef375d
+ } + }
+ +
+ public static void returnNodeEvaluator(@NotNull NodeEvaluator nodeEvaluator) { + public static void returnNodeEvaluator(@NotNull NodeEvaluator nodeEvaluator) {
+ final NodeEvaluatorFeatures nodeEvaluatorFeatures = NodeEvaluatorFeatures.fromNodeEvaluator(nodeEvaluator); + final NodeEvaluatorGenerator generator = nodeEvaluatorToGenerator.remove(nodeEvaluator);
+ final var generator = nodeEvaluatorToGenerator.remove(nodeEvaluator);
+ Validate.notNull(generator, "NodeEvaluator already returned"); + Validate.notNull(generator, "NodeEvaluator already returned");
+ +
+ getDequeForGenerator(nodeEvaluatorFeatures).offer(nodeEvaluator); + final NodeEvaluatorFeatures nodeEvaluatorFeatures = NodeEvaluatorFeatures.fromNodeEvaluator(nodeEvaluator);
+ getQueueForFeatures(nodeEvaluatorFeatures).offer(nodeEvaluator);
+ } + }
+ +
+ public static void removeNodeEvaluator(@NotNull NodeEvaluator nodeEvaluator) {
+ nodeEvaluatorToGenerator.remove(nodeEvaluator);
+ }
+} +}
diff --git a/src/main/java/dev/kaiijumc/kaiiju/path/NodeEvaluatorFeatures.java b/src/main/java/dev/kaiijumc/kaiiju/path/NodeEvaluatorFeatures.java diff --git a/src/main/java/dev/kaiijumc/kaiiju/path/NodeEvaluatorFeatures.java b/src/main/java/dev/kaiijumc/kaiiju/path/NodeEvaluatorFeatures.java
new file mode 100644 new file mode 100644
@@ -1171,10 +1167,10 @@ index 2a335f277bd0e4b8ad0f60d8226eb8aaa80a871f..b2c3c459fae7d0cb5ef0fcbc2ff0e61c
return false; return false;
} else if (o.nodes.size() != this.nodes.size()) { } else if (o.nodes.size() != this.nodes.size()) {
diff --git a/src/main/java/net/minecraft/world/level/pathfinder/PathFinder.java b/src/main/java/net/minecraft/world/level/pathfinder/PathFinder.java diff --git a/src/main/java/net/minecraft/world/level/pathfinder/PathFinder.java b/src/main/java/net/minecraft/world/level/pathfinder/PathFinder.java
index d23481453717f715124156b5d83f6448f720d049..d4cc2a5f99f8445be9a63c279e28032f11a91304 100644 index d23481453717f715124156b5d83f6448f720d049..bab740b11e4cc20f924fcf9e33779cdc811b6e8f 100644
--- a/src/main/java/net/minecraft/world/level/pathfinder/PathFinder.java --- a/src/main/java/net/minecraft/world/level/pathfinder/PathFinder.java
+++ b/src/main/java/net/minecraft/world/level/pathfinder/PathFinder.java +++ b/src/main/java/net/minecraft/world/level/pathfinder/PathFinder.java
@@ -24,37 +24,77 @@ public class PathFinder { @@ -24,37 +24,82 @@ public class PathFinder {
public final NodeEvaluator nodeEvaluator; public final NodeEvaluator nodeEvaluator;
private static final boolean DEBUG = false; private static final boolean DEBUG = false;
private final BinaryHeap openSet = new BinaryHeap(); private final BinaryHeap openSet = new BinaryHeap();
@@ -1207,11 +1203,7 @@ index d23481453717f715124156b5d83f6448f720d049..d4cc2a5f99f8445be9a63c279e28032f
+ Node node = nodeEvaluator.getStart(); + Node node = nodeEvaluator.getStart();
+ // Kaiiju end + // Kaiiju end
if (node == null) { if (node == null) {
+ // Kaiiju start - petal - handle nodeEvaluatorGenerator + dev.kaiijumc.kaiiju.path.NodeEvaluatorCache.removeNodeEvaluator(nodeEvaluator); // Kaiiju - petal - handle nodeEvaluatorGenerator
+ if (this.nodeEvaluatorGenerator != null) {
+ dev.kaiijumc.kaiiju.path.NodeEvaluatorCache.returnNodeEvaluator(nodeEvaluator);
+ }
+ // Kaiiju end
return null; return null;
} else { } else {
// Paper start - remove streams - and optimize collection // Paper start - remove streams - and optimize collection
@@ -1227,13 +1219,22 @@ index d23481453717f715124156b5d83f6448f720d049..d4cc2a5f99f8445be9a63c279e28032f
+ // Kaiiju start - petal - async path processing + // Kaiiju start - petal - async path processing
+ if (this.nodeEvaluatorGenerator == null) { + if (this.nodeEvaluatorGenerator == null) {
+ // run sync :( + // run sync :(
+ return this.findPath(nodeEvaluator, world.getProfiler(), node, map, followRange, distance, rangeMultiplier); + dev.kaiijumc.kaiiju.path.NodeEvaluatorCache.removeNodeEvaluator(nodeEvaluator);
+ return this.findPath(world.getProfiler(), node, map, followRange, distance, rangeMultiplier);
+ } + }
+ +
+ if (mob != null && mob.hasCustomName() && mob.getCustomName().getString().contains("1o")) org.bukkit.Bukkit.getLogger().info("Positions " + positions);
+ return new dev.kaiijumc.kaiiju.path.AsyncPath(Lists.newArrayList(), positions, () -> { + return new dev.kaiijumc.kaiiju.path.AsyncPath(Lists.newArrayList(), positions, () -> {
+ try { + try {
+ return this.processPath(nodeEvaluator, node, map, followRange, distance, rangeMultiplier); + Path path = this.processPath(nodeEvaluator, node, map, followRange, distance, rangeMultiplier);
+ if (mob != null && mob.hasCustomName() && mob.getCustomName().getString().contains("1o")) org.bukkit.Bukkit.getLogger().info("path " + path);
+ return path;
+ } catch (Exception e) {
+ if (mob != null && mob.hasCustomName() && mob.getCustomName().getString().contains("1o")) org.bukkit.Bukkit.getLogger().info("error path: " + e);
+ e.printStackTrace();
+ return null;
+ } finally { + } finally {
+ if (mob != null && mob.hasCustomName() && mob.getCustomName().getString().contains("1o")) org.bukkit.Bukkit.getLogger().info("NE " + nodeEvaluator);
+ nodeEvaluator.done(); + nodeEvaluator.done();
+ dev.kaiijumc.kaiiju.path.NodeEvaluatorCache.returnNodeEvaluator(nodeEvaluator); + dev.kaiijumc.kaiiju.path.NodeEvaluatorCache.returnNodeEvaluator(nodeEvaluator);
+ } + }
@@ -1245,24 +1246,23 @@ index d23481453717f715124156b5d83f6448f720d049..d4cc2a5f99f8445be9a63c279e28032f
- @Nullable - @Nullable
+ //@Nullable // Kaiiju - Always not null + //@Nullable // Kaiiju - Always not null
// Paper start - optimize collection // Paper start - optimize collection
- private Path findPath(ProfilerFiller profiler, Node startNode, List<Map.Entry<Target, BlockPos>> positions, float followRange, int distance, float rangeMultiplier) { private Path findPath(ProfilerFiller profiler, Node startNode, List<Map.Entry<Target, BlockPos>> positions, float followRange, int distance, float rangeMultiplier) {
+ private Path findPath(NodeEvaluator nodeEvaluator, ProfilerFiller profiler, Node startNode, List<Map.Entry<Target, BlockPos>> positions, float followRange, int distance, float rangeMultiplier) {
profiler.push("find_path"); profiler.push("find_path");
profiler.markForCharting(MetricCategory.PATH_FINDING); profiler.markForCharting(MetricCategory.PATH_FINDING);
+ // Kaiiju start - petal - split pathfinding into the original sync method for compat and processing for delaying + // Kaiiju start - petal - split pathfinding into the original sync method for compat and processing for delaying
+ try { + try {
+ return this.processPath(this.nodeEvaluator, startNode, positions, followRange, distance, rangeMultiplier); + return this.processPath(this.nodeEvaluator, startNode, positions, followRange, distance, rangeMultiplier);
+ } finally { + } finally {
+ nodeEvaluator.done(); + this.nodeEvaluator.done();
+ } + }
+ } + }
+ private synchronized Path processPath(NodeEvaluator nodeEvaluator, Node startNode, List<Map.Entry<Target, BlockPos>> positions, float followRange, int distance, float rangeMultiplier) { // sync to only use the caching functions in this class on a single thread + private synchronized @org.jetbrains.annotations.NotNull Path processPath(NodeEvaluator nodeEvaluator, Node startNode, List<Map.Entry<Target, BlockPos>> positions, float followRange, int distance, float rangeMultiplier) { // sync to only use the caching functions in this class on a single thread
+ org.apache.commons.lang3.Validate.isTrue(!positions.isEmpty()); // ensure that we have at least one position, which means we'll always return a path + org.apache.commons.lang3.Validate.isTrue(!positions.isEmpty()); // ensure that we have at least one position, which means we'll always return a path
+ // Kaiiju end + // Kaiiju end
// Set<Target> set = positions.keySet(); // Set<Target> set = positions.keySet();
startNode.g = 0.0F; startNode.g = 0.0F;
startNode.h = this.getBestH(startNode, positions); // Paper - optimize collection startNode.h = this.getBestH(startNode, positions); // Paper - optimize collection
@@ -91,7 +131,7 @@ public class PathFinder { @@ -91,7 +136,7 @@ public class PathFinder {
} }
if (!(node.distanceTo(startNode) >= followRange)) { if (!(node.distanceTo(startNode) >= followRange)) {
@@ -1271,6 +1271,14 @@ index d23481453717f715124156b5d83f6448f720d049..d4cc2a5f99f8445be9a63c279e28032f
for(int l = 0; l < k; ++l) { for(int l = 0; l < k; ++l) {
Node node2 = this.neighbors[l]; Node node2 = this.neighbors[l];
@@ -123,6 +168,7 @@ public class PathFinder {
if (best == null || comparator.compare(path, best) < 0)
best = path;
}
+ //noinspection ConstantConditions // Kaiiju - petal - ignore this warning, we know that the above loop always runs at least once since positions is not empty
return best;
// Paper end
}
diff --git a/src/main/java/net/minecraft/world/level/pathfinder/SwimNodeEvaluator.java b/src/main/java/net/minecraft/world/level/pathfinder/SwimNodeEvaluator.java diff --git a/src/main/java/net/minecraft/world/level/pathfinder/SwimNodeEvaluator.java b/src/main/java/net/minecraft/world/level/pathfinder/SwimNodeEvaluator.java
index 0e2b14e7dfedf209d63279c81723fd7955122d78..079b278e2e262af433bb5bd0c12b3d8db4fa12fc 100644 index 0e2b14e7dfedf209d63279c81723fd7955122d78..079b278e2e262af433bb5bd0c12b3d8db4fa12fc 100644
--- a/src/main/java/net/minecraft/world/level/pathfinder/SwimNodeEvaluator.java --- a/src/main/java/net/minecraft/world/level/pathfinder/SwimNodeEvaluator.java