diff --git a/leaf-server/src/main/java/org/dreeam/leaf/async/path/AsyncPath.java b/leaf-server/src/main/java/org/dreeam/leaf/async/path/AsyncPath.java index 9afa9c8d..fac99840 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/async/path/AsyncPath.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/async/path/AsyncPath.java @@ -18,6 +18,10 @@ import java.util.function.Supplier; */ public class AsyncPath extends Path { + private volatile boolean fastPath = false; + + private volatile boolean processingFinished = false; + /** * marks whether this async path has been processed */ @@ -26,7 +30,7 @@ public class AsyncPath extends Path { /** * runnables waiting for this to be processed */ - private final List postProcessing = new ArrayList<>(0); + private final List postProcessing = new ArrayList<>(2); /** * a list of positions that this path could path towards @@ -78,17 +82,35 @@ public class AsyncPath extends Path { @Override public boolean isProcessed() { - return this.processState == PathProcessState.COMPLETED; + return fastPath || (this.processState == PathProcessState.COMPLETED); } /** * returns the future representing the processing state of this path */ - public synchronized void postProcessing(@NotNull Runnable runnable) { + public void postProcessing(@NotNull Runnable runnable) { if (isProcessed()) { runnable.run(); - } else { - this.postProcessing.add(runnable); + return; + } + + boolean finished = this.processingFinished; + if (finished) { + runnable.run(); + return; + } + + boolean shouldRun = false; + synchronized (this) { + if (isProcessed()) { + shouldRun = true; + } else { + this.postProcessing.add(runnable); + } + } + + if (shouldRun) { + runnable.run(); } } @@ -103,40 +125,79 @@ public class AsyncPath extends Path { return false; } + // For single position (common case), do direct comparison + if (positions.size() == 1 && this.positions.size() == 1) { + return this.positions.iterator().next().equals(positions.iterator().next()); + } + return this.positions.containsAll(positions); } /** * starts processing this path */ - public synchronized void process() { - if (this.processState == PathProcessState.COMPLETED || - this.processState == PathProcessState.PROCESSING) { + public void process() { + // Single check - if not WAITING, we're either COMPLETED or PROCESSING + if (this.processState != PathProcessState.WAITING) { return; } - processState = PathProcessState.PROCESSING; + synchronized (this) { + // Double-check after acquiring lock + if (this.processState != PathProcessState.WAITING) { + return; + } - final Path bestPath = this.pathSupplier.get(); + processState = PathProcessState.PROCESSING; + } - this.nodes.addAll(bestPath.nodes); // we mutate this list to reuse the logic in Path - this.target = bestPath.getTarget(); - this.distToTarget = bestPath.getDistToTarget(); - this.canReach = bestPath.canReach(); + // computation outside synchronized block + final Path bestPath; + try { + bestPath = this.pathSupplier.get(); + } catch (Exception e) { + // Handle pathfinding failures gracefully + synchronized (this) { + processState = PathProcessState.COMPLETED; + this.processingFinished = true; - processState = PathProcessState.COMPLETED; + // Still run callbacks even if pathfinding failed + for (Runnable runnable : this.postProcessing) { + runnable.run(); + } + this.postProcessing.clear(); + } + return; + } - for (Runnable runnable : this.postProcessing) { + // Final state update - minimal synchronization + List callbacksToRun; + synchronized (this) { + this.nodes.addAll(bestPath.nodes); + this.target = bestPath.getTarget(); + this.distToTarget = bestPath.getDistToTarget(); + this.canReach = bestPath.canReach(); + + processState = PathProcessState.COMPLETED; + this.processingFinished = true; // Mark as finished for postProcessing + + // Copy callbacks to run outside synchronized block + callbacksToRun = new ArrayList<>(this.postProcessing); + this.postProcessing.clear(); + } + + for (Runnable runnable : callbacksToRun) { runnable.run(); - } // Run tasks after processing + } } /** * if this path is accessed while it hasn't processed, just process it in-place */ private void checkProcessed() { - if (this.processState == PathProcessState.WAITING || - this.processState == PathProcessState.PROCESSING) { // Block if we are on processing + // Use single volatile read instead of multiple comparisons + PathProcessState state = this.processState; + if (state != PathProcessState.COMPLETED) { this.process(); } } diff --git a/leaf-server/src/main/java/org/dreeam/leaf/async/path/AsyncPathProcessor.java b/leaf-server/src/main/java/org/dreeam/leaf/async/path/AsyncPathProcessor.java index a3ab0bac..8502b8d3 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/async/path/AsyncPathProcessor.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/async/path/AsyncPathProcessor.java @@ -31,6 +31,7 @@ public class AsyncPathProcessor { private static final Logger LOGGER = LogManager.getLogger(THREAD_PREFIX); private static long lastWarnMillis = System.currentTimeMillis(); public static ThreadPoolExecutor PATH_PROCESSING_EXECUTOR = null; + private static long lastWarnNanos = System.nanoTime(); public static void init() { if (PATH_PROCESSING_EXECUTOR == null) { @@ -124,9 +125,10 @@ public class AsyncPathProcessor { } } - if (System.currentTimeMillis() - lastWarnMillis > 30000L) { - LOGGER.warn("Async pathfinding processor is busy! Pathfinding tasks will be treated as policy defined in config. Increasing max-threads in Leaf config may help."); - lastWarnMillis = System.currentTimeMillis(); + long currentNanos = System.nanoTime(); + if (currentNanos - lastWarnNanos > 30_000_000_000L) { + LOGGER.warn("Async pathfinding processor is busy! Consider increasing max-threads."); + lastWarnNanos = currentNanos; } }; } diff --git a/leaf-server/src/main/java/org/dreeam/leaf/async/path/NodeEvaluatorCache.java b/leaf-server/src/main/java/org/dreeam/leaf/async/path/NodeEvaluatorCache.java index c98a0e2b..92cc3e9b 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/async/path/NodeEvaluatorCache.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/async/path/NodeEvaluatorCache.java @@ -14,8 +14,12 @@ public class NodeEvaluatorCache { private static final Map> threadLocalNodeEvaluators = new ConcurrentHashMap<>(); private static final Map nodeEvaluatorToGenerator = new ConcurrentHashMap<>(); - private static @NotNull Queue getQueueForFeatures(@NotNull NodeEvaluatorFeatures nodeEvaluatorFeatures) { - return threadLocalNodeEvaluators.computeIfAbsent(nodeEvaluatorFeatures, key -> new MultiThreadedQueue<>()); + private static @NotNull Queue getQueueForFeatures(@NotNull NodeEvaluatorFeatures features) { + Queue queue = threadLocalNodeEvaluators.get(features); + if (queue == null) { + queue = threadLocalNodeEvaluators.computeIfAbsent(features, k -> new MultiThreadedQueue<>()); + } + return queue; } public static @NotNull NodeEvaluator takeNodeEvaluator(@NotNull NodeEvaluatorGenerator generator, @NotNull NodeEvaluator localNodeEvaluator) {