Files
PlazmaBukkitMC/patches/server/features/0043-Process-pathfinding-asynchronously.patch
2025-02-23 20:24:18 +09:00

1334 lines
65 KiB
Diff

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: AlphaKR93 <dev@alpha93.kr>
Date: Sun, 5 May 2024 10:28:26 +0900
Subject: [PATCH] Process pathfinding asynchronously
Based on Leaf, Original by Kaiiju & Petal
[Leaf]
Copyright (C) 2024 Winds Studio, Licensed under MIT License.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
[Kaiiju & Petal]
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
[Kaiiju & Petal]
Kaiiju - Copyright (C) 2024 KaiijuMC, Licensed under GNU GPL v3.
Petal - Copyright (C) 2024 Bloom Host, Licensed under GNU GPL v3.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
diff --git a/src/main/java/dev/kaiijumc/kaiiju/path/AsyncPath.java b/src/main/java/dev/kaiijumc/kaiiju/path/AsyncPath.java
new file mode 100644
index 0000000000000000000000000000000000000000..d32433253c7e3aeac728eb78b03b0e6b04d4f8ca
--- /dev/null
+++ b/src/main/java/dev/kaiijumc/kaiiju/path/AsyncPath.java
@@ -0,0 +1,201 @@
+package dev.kaiijumc.kaiiju.path;
+
+import net.minecraft.core.BlockPos;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.level.pathfinder.Node;
+import net.minecraft.world.level.pathfinder.Path;
+import net.minecraft.world.phys.Vec3;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Supplier;
+
+@SuppressWarnings("DataFlowIssue")
+public class AsyncPath extends Path {
+
+ private volatile boolean processed = false;
+ private final List<Runnable> postProcessors = new ArrayList<>(0);
+ private final Set<BlockPos> positions;
+ private final Supplier<Path> pathSupplier;
+ private final Runnable postSupplier;
+ private final List<Node> nodes;
+
+ private @Nullable BlockPos target;
+ private float distToTarget = 0;
+ private boolean canReach = true;
+
+ @SuppressWarnings("DataFlowIssue")
+ public AsyncPath(
+ final @NotNull List<Node> emptyNodeList,
+ final @NotNull Set<BlockPos> positions,
+ final @NotNull Supplier<Path> pathSupplier,
+ final @NotNull Runnable postSupplier
+ ) {
+ super(emptyNodeList, null, false);
+
+ this.nodes = emptyNodeList;
+ this.positions = positions;
+ this.pathSupplier = pathSupplier;
+ this.postSupplier = postSupplier;
+
+ AsyncPathProcessor.queue(this);
+ }
+
+ public synchronized void process() {
+ if (this.processed) return;
+
+ final Path best = this.pathSupplier.get();
+ this.postSupplier.run();
+
+ this.nodes.addAll(best.nodes);
+ this.target = best.getTarget();
+ this.distToTarget = best.getDistToTarget();
+ this.canReach = best.canReach();
+ this.processed = true;
+
+ for (Runnable runnable : this.postProcessors) runnable.run();
+ }
+
+ public synchronized void postProcessing(final @NotNull Runnable runnable) {
+ if (this.processed) runnable.run();
+ else this.postProcessors.add(runnable);
+ }
+
+ private void checkProcessed() {
+ if (!this.processed) this.process();
+ }
+
+ public boolean hasSamePosition(final Set<BlockPos> positions) {
+ if (this.positions.size() != positions.size()) return false;
+ return this.positions.containsAll(positions);
+ }
+
+ @Override
+ public boolean isProcessed() {
+ return this.processed;
+ }
+
+ @Override
+ public boolean isDone() {
+ return this.isProcessed() && super.isDone();
+ }
+
+ @Override
+ public BlockPos getTarget() {
+ this.checkProcessed();
+ return this.target;
+ }
+
+ @Override
+ public float getDistToTarget() {
+ this.checkProcessed();
+ return this.distToTarget;
+ }
+
+ @Override
+ public boolean canReach() {
+ this.checkProcessed();
+ return this.canReach;
+ }
+
+ @Override
+ public void advance() {
+ this.checkProcessed();
+ super.advance();
+ }
+
+ @Override
+ public boolean notStarted() {
+ this.checkProcessed();
+ return super.notStarted();
+ }
+
+ @Override
+ @Nullable
+ public Node getEndNode() {
+ this.checkProcessed();
+ return super.getEndNode();
+ }
+
+ @Override
+ public Node getNode(int index) {
+ this.checkProcessed();
+ return super.getNode(index);
+ }
+
+ @Override
+ public void truncateNodes(int index) {
+ this.checkProcessed();
+ super.truncateNodes(index);
+ }
+
+ @Override
+ public void replaceNode(int index, Node node) {
+ this.checkProcessed();
+ super.replaceNode(index, node);
+ }
+
+ @Override
+ public int getNodeCount() {
+ this.checkProcessed();
+ return super.getNodeCount();
+ }
+
+ @Override
+ public int getNextNodeIndex() {
+ this.checkProcessed();
+ return super.getNextNodeIndex();
+ }
+
+ @Override
+ public void setNextNodeIndex(int nodeIndex) {
+ this.checkProcessed();
+ super.setNextNodeIndex(nodeIndex);
+ }
+
+ @Override
+ public Vec3 getEntityPosAtNode(Entity entity, int index) {
+ this.checkProcessed();
+ return super.getEntityPosAtNode(entity, index);
+ }
+
+ @Override
+ public BlockPos getNodePos(int index) {
+ this.checkProcessed();
+ return super.getNodePos(index);
+ }
+
+ @Override
+ public Vec3 getNextEntityPos(Entity entity) {
+ this.checkProcessed();
+ return super.getNextEntityPos(entity);
+ }
+
+ @Override
+ public BlockPos getNextNodePos() {
+ this.checkProcessed();
+ return super.getNextNodePos();
+ }
+
+ @Override
+ public Node getNextNode() {
+ this.checkProcessed();
+ return super.getNextNode();
+ }
+
+ @Override
+ @Nullable
+ public Node getPreviousNode() {
+ this.checkProcessed();
+ return super.getPreviousNode();
+ }
+
+ @Override
+ public boolean hasNext() {
+ this.checkProcessed();
+ return super.hasNext();
+ }
+
+}
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
index 0000000000000000000000000000000000000000..d3724643812c46b1b803ccbdfce753b4302637ae
--- /dev/null
+++ b/src/main/java/dev/kaiijumc/kaiiju/path/AsyncPathProcessor.java
@@ -0,0 +1,55 @@
+package dev.kaiijumc.kaiiju.path;
+
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import com.mojang.logging.LogUtils;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.level.pathfinder.Path;
+import org.checkerframework.framework.qual.DefaultQualifier;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.slf4j.Logger;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+
+import static org.plazmamc.plazma.configurations.GlobalConfiguration.get;
+
+@DefaultQualifier(NotNull.class)
+public class AsyncPathProcessor {
+
+ private static final Logger LOGGER = LogUtils.getClassLogger();
+ private static @Nullable ThreadPoolExecutor EXECUTOR = generateExecutor();
+
+ private static @Nullable ThreadPoolExecutor generateExecutor() {
+ if (EXECUTOR != null) EXECUTOR.shutdownNow();
+ if (!get().entity.asyncPathProcess.enabled) return null;
+ LOGGER.info("Using async pathfinding with a maximum of {} threads", get().entity.asyncPathProcess.maxThreadSize());
+ return new ThreadPoolExecutor(
+ 1,
+ get().entity.asyncPathProcess.maxThreadSize(),
+ get().entity.asyncPathProcess.keepAliveTime, TimeUnit.SECONDS,
+ new LinkedBlockingQueue<>(),
+ new ThreadFactoryBuilder().setNameFormat("async-pathfinding-thread-%d").setPriority(Thread.NORM_PRIORITY - 2).build()
+ );
+ }
+
+ public static void updateExecutor() {
+ EXECUTOR = generateExecutor();
+ }
+
+ protected static CompletableFuture<Void> queue(AsyncPath path) {
+ return CompletableFuture.runAsync(path::process, EXECUTOR);
+ }
+
+ public static void awaitProcess(Entity entity, @Nullable Path path, Consumer<@Nullable Path> after) {
+ if (path == null || path.isProcessed() || !(path instanceof AsyncPath async)) {
+ after.accept(path);
+ return;
+ }
+
+ async.postProcessing(() -> entity.getBukkitEntity().taskScheduler.schedule(ignored -> after.accept(path), null, 1));
+ }
+
+}
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
index 0000000000000000000000000000000000000000..53b4a1004607304b48e69785f5fe2a4faffb6039
--- /dev/null
+++ b/src/main/java/dev/kaiijumc/kaiiju/path/NodeEvaluatorCache.java
@@ -0,0 +1,45 @@
+package dev.kaiijumc.kaiiju.path;
+
+import ca.spottedleaf.concurrentutil.util.Validate;
+import net.minecraft.world.level.pathfinder.NodeEvaluator;
+import org.checkerframework.framework.qual.DefaultQualifier;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import java.util.Map;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+@DefaultQualifier(NotNull.class)
+public class NodeEvaluatorCache {
+
+ private static final Map<NodeEvaluatorFeatures, ConcurrentLinkedQueue<NodeEvaluator>> threadLocalEvaluators = new ConcurrentHashMap<>();
+ private static final Map<NodeEvaluator, NodeEvaluatorGenerator> evaluatorGenerators = new ConcurrentHashMap<>();
+
+ private static Queue<NodeEvaluator> getQueue(NodeEvaluatorFeatures features) {
+ return threadLocalEvaluators.computeIfAbsent(features, ignored -> new ConcurrentLinkedQueue<>());
+ }
+
+ public static NodeEvaluator take(NodeEvaluatorGenerator generator, NodeEvaluator local) {
+ final NodeEvaluatorFeatures features = NodeEvaluatorFeatures.from(local);
+ @Nullable NodeEvaluator evaluator = getQueue(features).poll();
+
+ if (evaluator == null) evaluator = generator.generate(features);
+
+ evaluatorGenerators.put(evaluator, generator);
+ return evaluator;
+ }
+
+ public static void returnEvaluator(NodeEvaluator evaluator) {
+ final NodeEvaluatorGenerator generator = evaluatorGenerators.remove(evaluator);
+ Validate.notNull(generator, "NodeEvaluator already returned");
+
+ final NodeEvaluatorFeatures features = NodeEvaluatorFeatures.from(evaluator);
+ getQueue(features).offer(evaluator);
+ }
+
+ public static void remove(NodeEvaluator evaluator) {
+ evaluatorGenerators.remove(evaluator);
+ }
+
+}
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
index 0000000000000000000000000000000000000000..dff101cc09d5f50958425fea42d0e4fc1dbb531b
--- /dev/null
+++ b/src/main/java/dev/kaiijumc/kaiiju/path/NodeEvaluatorFeatures.java
@@ -0,0 +1,28 @@
+package dev.kaiijumc.kaiiju.path;
+
+import net.minecraft.world.level.pathfinder.NodeEvaluator;
+import net.minecraft.world.level.pathfinder.SwimNodeEvaluator;
+
+public record NodeEvaluatorFeatures(
+ NodeEvaluatorType type,
+ boolean canPassDoors,
+ boolean canFloat,
+ boolean canWalkOverFences,
+ boolean canOpenDoors,
+ boolean allowBreaching
+) {
+
+ public static NodeEvaluatorFeatures from(NodeEvaluator evaluator) {
+
+ return new NodeEvaluatorFeatures(
+ NodeEvaluatorType.from(evaluator),
+ evaluator.canPassDoors(),
+ evaluator.canFloat(),
+ evaluator.canWalkOverFences(),
+ evaluator.canOpenDoors(),
+ evaluator instanceof SwimNodeEvaluator swim && swim.allowBreaching
+ );
+
+ }
+
+}
diff --git a/src/main/java/dev/kaiijumc/kaiiju/path/NodeEvaluatorGenerator.java b/src/main/java/dev/kaiijumc/kaiiju/path/NodeEvaluatorGenerator.java
new file mode 100644
index 0000000000000000000000000000000000000000..6049d53a37ee1203a9982ebbd7637425617fff97
--- /dev/null
+++ b/src/main/java/dev/kaiijumc/kaiiju/path/NodeEvaluatorGenerator.java
@@ -0,0 +1,11 @@
+package dev.kaiijumc.kaiiju.path;
+
+import net.minecraft.world.level.pathfinder.NodeEvaluator;
+import org.jetbrains.annotations.NotNull;
+
+public interface NodeEvaluatorGenerator {
+
+ @NotNull
+ NodeEvaluator generate(NodeEvaluatorFeatures features);
+
+}
diff --git a/src/main/java/dev/kaiijumc/kaiiju/path/NodeEvaluatorType.java b/src/main/java/dev/kaiijumc/kaiiju/path/NodeEvaluatorType.java
new file mode 100644
index 0000000000000000000000000000000000000000..3456f38d381643b0461669b0b5fec6bf32799bc3
--- /dev/null
+++ b/src/main/java/dev/kaiijumc/kaiiju/path/NodeEvaluatorType.java
@@ -0,0 +1,25 @@
+package dev.kaiijumc.kaiiju.path;
+
+import net.minecraft.world.level.pathfinder.AmphibiousNodeEvaluator;
+import net.minecraft.world.level.pathfinder.FlyNodeEvaluator;
+import net.minecraft.world.level.pathfinder.NodeEvaluator;
+import net.minecraft.world.level.pathfinder.SwimNodeEvaluator;
+
+public enum NodeEvaluatorType {
+
+ WALK, SWIM, FLY, AMPHIBIOUS;
+
+ public static NodeEvaluatorType from(NodeEvaluator evaluator) {
+
+ return switch (evaluator) {
+
+ case AmphibiousNodeEvaluator ignored -> AMPHIBIOUS;
+ case SwimNodeEvaluator ignored -> SWIM;
+ case FlyNodeEvaluator ignored -> FLY;
+ default -> WALK;
+
+ };
+
+ }
+
+}
diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/AcquirePoi.java b/src/main/java/net/minecraft/world/entity/ai/behavior/AcquirePoi.java
index 273ba657926ce72a7c82861e880a82bf7f322a0b..f90a14860e4c9a11a972edc3cea011e1cf67b4eb 100644
--- a/src/main/java/net/minecraft/world/entity/ai/behavior/AcquirePoi.java
+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/AcquirePoi.java
@@ -94,6 +94,35 @@ public class AcquirePoi {
}
}
// Paper end - optimise POI access
+ // Plazma start - Process Pathfinding Asynchronously
+ if (org.plazmamc.plazma.configurations.GlobalConfiguration.get().entity.asyncPathProcess.enabled) {
+ Path possible = findPathToPois(entity, set);
+ dev.kaiijumc.kaiiju.path.AsyncPathProcessor.awaitProcess(entity, possible, path -> {
+ if (path == null || !path.canReach()) {
+ for (Pair<Holder<PoiType>, BlockPos> pair : set) long2ObjectMap.computeIfAbsent(
+ pair.getSecond().asLong(),
+ ignored -> new net.minecraft.world.entity.ai.behavior.AcquirePoi.JitteredLinearRetry(entity.level().random, time)
+ );
+ return;
+ }
+
+ BlockPos pos = path.getTarget();
+ poiManager.getType(pos).ifPresent(poiType -> {
+ poiManager.take(
+ poiPredicate,
+ (holder, blockPos) -> blockPos.equals(pos),
+ pos,
+ 1
+ );
+ queryResult.set(GlobalPos.of(world.dimension(), pos));
+ entityStatus.ifPresent(status -> world.broadcastEntityEvent(entity, status));
+ long2ObjectMap.clear();
+ DebugPackets.sendPoiTicketCountPacket(world, pos);
+ });
+ });
+ return true;
+ }
+ // Plazma end - Process Pathfinding Asynchronously
Path path = findPathToPois(entity, set);
if (path != null && path.canReach()) {
BlockPos blockPos = path.getTarget();
diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/MoveToTargetSink.java b/src/main/java/net/minecraft/world/entity/ai/behavior/MoveToTargetSink.java
index 2a7a26ca447cc78f24e61a2bf557411c31eb16b2..551cbc7c06b9c3f4ebefcef4eca6a1ee0afa890b 100644
--- a/src/main/java/net/minecraft/world/entity/ai/behavior/MoveToTargetSink.java
+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/MoveToTargetSink.java
@@ -24,6 +24,7 @@ public class MoveToTargetSink extends Behavior<Mob> {
@Nullable
private BlockPos lastTargetPos;
private float speedModifier;
+ private boolean finishedProcessing; // Plazma - Process pathfinding asynchronously
public MoveToTargetSink() {
this(150, 250);
@@ -53,9 +54,10 @@ public class MoveToTargetSink extends Behavior<Mob> {
Brain<?> brain = entity.getBrain();
WalkTarget walkTarget = brain.getMemory(MemoryModuleType.WALK_TARGET).get();
boolean bl = this.reachedTarget(entity, walkTarget);
- if (!bl && this.tryComputePath(entity, walkTarget, world.getGameTime())) {
+ if (!org.plazmamc.plazma.configurations.GlobalConfiguration.get().entity.asyncPathProcess.enabled && !bl && this.tryComputePath(entity, walkTarget, world.getGameTime())) { // Plazma - Process pathfinding asynchronously
this.lastTargetPos = walkTarget.getTarget().currentBlockPosition();
return true;
+ } else if (org.plazmamc.plazma.configurations.GlobalConfiguration.get().entity.asyncPathProcess.enabled && !bl) { return true; // Plazma - Process pathfinding asynchronously
} else {
brain.eraseMemory(MemoryModuleType.WALK_TARGET);
if (bl) {
@@ -69,6 +71,7 @@ public class MoveToTargetSink extends Behavior<Mob> {
@Override
protected boolean canStillUse(ServerLevel world, Mob entity, long time) {
+ if (org.plazmamc.plazma.configurations.GlobalConfiguration.get().entity.asyncPathProcess.enabled && !this.finishedProcessing) return true; // Plazma - Process pathfinding asynchronously
if (this.path != null && this.lastTargetPos != null) {
Optional<WalkTarget> optional = entity.getBrain().getMemory(MemoryModuleType.WALK_TARGET);
boolean bl = optional.map(MoveToTargetSink::isWalkTargetSpectator).orElse(false);
@@ -95,12 +98,67 @@ public class MoveToTargetSink extends Behavior<Mob> {
@Override
protected void start(ServerLevel serverLevel, Mob mob, long l) {
+ // Plazma start - Process pathfinding asynchronously
+ if (org.plazmamc.plazma.configurations.GlobalConfiguration.get().entity.asyncPathProcess.enabled) {
+ Brain<?> brain = mob.getBrain();
+ WalkTarget target = brain.getMemory(MemoryModuleType.WALK_TARGET).get();
+
+ this.finishedProcessing = false;
+ this.lastTargetPos = target.getTarget().currentBlockPosition();
+ this.path = this.computePath(mob, target);
+ return;
+ }
+ // Plazma end - Process pathfinding asynchronously
mob.getBrain().setMemory(MemoryModuleType.PATH, this.path);
mob.getNavigation().moveTo(this.path, (double)this.speedModifier);
}
@Override
protected void tick(ServerLevel serverLevel, Mob mob, long l) {
+ // Plazma start - Process pathfinding asynchronously
+ if (org.plazmamc.plazma.configurations.GlobalConfiguration.get().entity.asyncPathProcess.enabled) {
+ if (this.path != null && !this.path.isProcessed()) return;
+
+ if (!this.finishedProcessing) {
+ this.finishedProcessing = true;
+
+ Brain<?> brain = mob.getBrain();
+ boolean canReach = this.path != null && this.path.canReach();
+
+ if (canReach)
+ brain.eraseMemory(MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE);
+ else if (!brain.hasMemoryValue(MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE))
+ brain.setMemory(MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE, l);
+
+ if (!canReach) {
+ Optional<WalkTarget> target = brain.getMemory(MemoryModuleType.WALK_TARGET);
+ if (target.isEmpty()) return;
+
+ BlockPos pos = target.get().getTarget().currentBlockPosition();
+ Vec3 vec3 = DefaultRandomPos.getPosTowards((PathfinderMob)mob, 10, 7, Vec3.atBottomCenterOf(pos), (float) (Math.PI / 2));
+ if (vec3 != null) {
+ this.path = mob.getNavigation().createPath(vec3.x, vec3.y, vec3.z, 0);
+ this.finishedProcessing = false;
+ return;
+ }
+ }
+
+ mob.getBrain().setMemory(MemoryModuleType.PATH, this.path);
+ mob.getNavigation().moveTo(this.path, this.speedModifier);
+ }
+
+ Path path = mob.getNavigation().getPath();
+ Brain<?> brain = mob.getBrain();
+
+ if (path == null || this.lastTargetPos == null || !brain.hasMemoryValue(MemoryModuleType.WALK_TARGET)) return;
+
+ WalkTarget target = brain.getMemory(MemoryModuleType.WALK_TARGET).get();
+ if (target.getTarget().currentBlockPosition().distSqr(this.lastTargetPos) <= 4.0D) return;
+
+ this.start(serverLevel, mob, l);
+ return;
+ }
+ // Plazma end - Process pathfinding asynchronously
Path path = mob.getNavigation().getPath();
Brain<?> brain = mob.getBrain();
if (this.path != path) {
@@ -154,4 +212,16 @@ public class MoveToTargetSink extends Behavior<Mob> {
private static boolean isWalkTargetSpectator(WalkTarget target) {
return target.getTarget() instanceof EntityTracker entityTracker && entityTracker.getEntity().isSpectator();
}
+
+ // Plazma start - Process pathfinding asynchronously
+ @Nullable
+ private Path computePath(Mob mob, WalkTarget target) {
+ BlockPos pos = target.getTarget().currentBlockPosition();
+ this.speedModifier = target.getSpeedModifier();
+ Brain<?> brain = mob.getBrain();
+ if (this.reachedTarget(mob, target)) brain.eraseMemory(MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE);
+ return mob.getNavigation().createPath(pos, 0);
+ }
+ // Plazma end - Process pathfinding asynchronously
+
}
diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/SetClosestHomeAsWalkTarget.java b/src/main/java/net/minecraft/world/entity/ai/behavior/SetClosestHomeAsWalkTarget.java
index 6802e0c4d331c7125114dd86409f6a110465ab82..0b8973765bcaa77e70ac7afe5b6a99ab9ff24a52 100644
--- a/src/main/java/net/minecraft/world/entity/ai/behavior/SetClosestHomeAsWalkTarget.java
+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/SetClosestHomeAsWalkTarget.java
@@ -60,6 +60,27 @@ public class SetClosestHomeAsWalkTarget {
poiType -> poiType.is(PoiTypes.HOME), predicate, entity.blockPosition(), 48, PoiManager.Occupancy.ANY
)
.collect(Collectors.toSet());
+ // Plazma start - Process Pathfinding asynchronously
+ if (org.plazmamc.plazma.configurations.GlobalConfiguration.get().entity.asyncPathProcess.enabled) {
+ Path possible = AcquirePoi.findPathToPois(entity, set);
+
+ dev.kaiijumc.kaiiju.path.AsyncPathProcessor.awaitProcess(entity, possible, path -> {
+ if (path == null || !path.canReach() || mutableInt.getValue() < 5) {
+ long2LongMap.long2LongEntrySet().removeIf(entry -> entry.getLongValue() < mutableLong.getValue());
+ return;
+ }
+
+ BlockPos pos = path.getTarget();
+ Optional<Holder<PoiType>> poiType = poiManager.getType(pos);
+ if (poiType.isEmpty()) return;
+
+ walkTarget.set(new WalkTarget(pos, speed, 1));
+ DebugPackets.sendPoiTicketCountPacket(world, pos);
+ });
+
+ return true;
+ }
+ // Plazma end - Process Pathfinding asynchronously
Path path = AcquirePoi.findPathToPois(entity, set);
if (path != null && path.canReach()) {
BlockPos blockPos = path.getTarget();
diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/DoorInteractGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/DoorInteractGoal.java
index 2846790fcd00788cf0284c348161ee1aee415f13..069e27041434c11ae5235ccb0e17544fe4a14e12 100644
--- a/src/main/java/net/minecraft/world/entity/ai/goal/DoorInteractGoal.java
+++ b/src/main/java/net/minecraft/world/entity/ai/goal/DoorInteractGoal.java
@@ -56,7 +56,7 @@ public abstract class DoorInteractGoal extends Goal {
} else {
GroundPathNavigation groundPathNavigation = (GroundPathNavigation)this.mob.getNavigation();
Path path = groundPathNavigation.getPath();
- if (path != null && !path.isDone()) {
+ if (path != null && path.isProcessed() && !path.isDone()) { // Plazma - Process Pathfinding asynchronously
for (int i = 0; i < Math.min(path.getNextNodeIndex() + 2, path.getNodeCount()); i++) {
Node node = path.getNode(i);
this.doorPos = new BlockPos(node.x, node.y + 1, node.z);
diff --git a/src/main/java/net/minecraft/world/entity/ai/navigation/AmphibiousPathNavigation.java b/src/main/java/net/minecraft/world/entity/ai/navigation/AmphibiousPathNavigation.java
index 29b852c3262c9cd0d2c77a93c01a386a2c184742..8361b230963469d564d6280414ea083e975f9cf7 100644
--- a/src/main/java/net/minecraft/world/entity/ai/navigation/AmphibiousPathNavigation.java
+++ b/src/main/java/net/minecraft/world/entity/ai/navigation/AmphibiousPathNavigation.java
@@ -12,11 +12,26 @@ public class AmphibiousPathNavigation extends PathNavigation {
super(mob, world);
}
+ // Plazma start - Process pathfinding asynchronously
+ private static final dev.kaiijumc.kaiiju.path.NodeEvaluatorGenerator GENERATOR =
+ (dev.kaiijumc.kaiiju.path.NodeEvaluatorFeatures features) -> {
+ AmphibiousNodeEvaluator evaluator = new AmphibiousNodeEvaluator(false);
+ evaluator.setCanPassDoors(features.canOpenDoors());
+ evaluator.setCanOpenDoors(features.canOpenDoors());
+ evaluator.setCanFloat(features.canFloat());
+ evaluator.setCanWalkOverFences(features.canWalkOverFences());
+ return evaluator;
+ };
+
@Override
protected PathFinder createPathFinder(int range) {
this.nodeEvaluator = new AmphibiousNodeEvaluator(false);
+ this.nodeEvaluator.setCanPassDoors(true);
+ if (org.plazmamc.plazma.configurations.GlobalConfiguration.get().entity.asyncPathProcess.enabled)
+ return new PathFinder(this.nodeEvaluator, range, GENERATOR);
return new PathFinder(this.nodeEvaluator, range);
}
+ // Plazma end - Process pathfinding asynchronously
@Override
protected boolean canUpdatePath() {
diff --git a/src/main/java/net/minecraft/world/entity/ai/navigation/FlyingPathNavigation.java b/src/main/java/net/minecraft/world/entity/ai/navigation/FlyingPathNavigation.java
index 2bd66da93227d4e4fc2ec4df47ae94b17f4d39d3..ee395676b5eb14cd24b7251a83be7154d71b7b20 100644
--- a/src/main/java/net/minecraft/world/entity/ai/navigation/FlyingPathNavigation.java
+++ b/src/main/java/net/minecraft/world/entity/ai/navigation/FlyingPathNavigation.java
@@ -16,11 +16,26 @@ public class FlyingPathNavigation extends PathNavigation {
super(entity, world);
}
+ // Plazma start - Process pathfinding asynchronously
+ private static final dev.kaiijumc.kaiiju.path.NodeEvaluatorGenerator GENERATOR =
+ (dev.kaiijumc.kaiiju.path.NodeEvaluatorFeatures features) -> {
+ FlyNodeEvaluator evaluator = new FlyNodeEvaluator();
+ evaluator.setCanPassDoors(features.canPassDoors());
+ evaluator.setCanOpenDoors(features.canOpenDoors());
+ evaluator.setCanFloat(features.canFloat());
+ evaluator.setCanWalkOverFences(features.canWalkOverFences());
+ return evaluator;
+ };
+
@Override
protected PathFinder createPathFinder(int range) {
this.nodeEvaluator = new FlyNodeEvaluator();
+ this.nodeEvaluator.setCanPassDoors(true);
+ if (org.plazmamc.plazma.configurations.GlobalConfiguration.get().entity.asyncPathProcess.enabled)
+ return new PathFinder(this.nodeEvaluator, range, GENERATOR);
return new PathFinder(this.nodeEvaluator, range);
}
+ // Plazma end - Process pathfinding asynchronously
@Override
protected boolean canMoveDirectly(Vec3 origin, Vec3 target) {
@@ -49,6 +64,7 @@ public class FlyingPathNavigation extends PathNavigation {
this.recomputePath();
}
+ if (this.path != null && !this.path.isProcessed()) return; // Plazma - Process pathfinding asynchronously
if (!this.isDone()) {
if (this.canUpdatePath()) {
this.followThePath();
diff --git a/src/main/java/net/minecraft/world/entity/ai/navigation/GroundPathNavigation.java b/src/main/java/net/minecraft/world/entity/ai/navigation/GroundPathNavigation.java
index 2796df7af365c452b28373adfd7daf1d6730bac5..921b0f792fbb3e7a0bb0275d8494a19830a08eae 100644
--- a/src/main/java/net/minecraft/world/entity/ai/navigation/GroundPathNavigation.java
+++ b/src/main/java/net/minecraft/world/entity/ai/navigation/GroundPathNavigation.java
@@ -24,11 +24,25 @@ public class GroundPathNavigation extends PathNavigation {
super(entity, world);
}
+ // Plazma start - Process pathfinding asynchronously
+ protected static final dev.kaiijumc.kaiiju.path.NodeEvaluatorGenerator GENERATOR = // AT - (private -> protected) because warden
+ (dev.kaiijumc.kaiiju.path.NodeEvaluatorFeatures features) -> {
+ WalkNodeEvaluator evaluator = new WalkNodeEvaluator();
+ evaluator.setCanPassDoors(features.canPassDoors());
+ evaluator.setCanOpenDoors(features.canOpenDoors());
+ evaluator.setCanFloat(features.canFloat());
+ evaluator.setCanWalkOverFences(features.canWalkOverFences());
+ return evaluator;
+ };
+
@Override
protected PathFinder createPathFinder(int range) {
this.nodeEvaluator = new WalkNodeEvaluator();
+ this.nodeEvaluator.setCanPassDoors(true);
+ if (org.plazmamc.plazma.configurations.GlobalConfiguration.get().entity.asyncPathProcess.enabled) return new PathFinder(this.nodeEvaluator, range, GENERATOR);
return new PathFinder(this.nodeEvaluator, range);
}
+ // Plazma end - Process pathfinding asynchronously
@Override
protected boolean canUpdatePath() {
diff --git a/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java b/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java
index 48c0de870a5bbf647309e69361dfb10ab56c65ab..99e31c8e8488ce7138c5385575cbbabe0bd7394e 100644
--- a/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java
+++ b/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java
@@ -168,6 +168,7 @@ public abstract class PathNavigation {
return null;
} else if (!this.canUpdatePath()) {
return null;
+ } else if (this.path instanceof dev.kaiijumc.kaiiju.path.AsyncPath async && !async.isProcessed() && async.hasSamePosition(positions)) { return this.path; // Plazma - Process pathfinding asynchronously
} else if (this.path != null && !this.path.isDone() && positions.contains(this.targetPos)) {
return this.path;
} else {
@@ -195,6 +196,23 @@ public abstract class PathNavigation {
PathNavigationRegion pathNavigationRegion = new PathNavigationRegion(this.level, blockPos.offset(-i, -i, -i), blockPos.offset(i, i, i));
Path path = this.pathFinder.findPath(pathNavigationRegion, this.mob, positions, followRange, distance, this.maxVisitedNodesMultiplier);
profilerFiller.pop();
+ // Plazma start - Process pathfinding asynchronously
+ if (org.plazmamc.plazma.configurations.GlobalConfiguration.get().entity.asyncPathProcess.enabled) {
+ if (!positions.isEmpty()) this.targetPos = positions.iterator().next();
+
+ dev.kaiijumc.kaiiju.path.AsyncPathProcessor.awaitProcess(mob, path, processed -> {
+ if (processed != this.path) return;
+
+ if (processed == null || processed.getTarget() == null) return;
+
+ this.targetPos = processed.getTarget();
+ this.reachRange = distance;
+ this.resetStuckTimeout();
+ });
+
+ return path;
+ }
+ // Plazma end - Process pathfinding asynchronously
if (path != null && path.getTarget() != null) {
this.targetPos = path.getTarget();
this.reachRange = distance;
@@ -250,8 +268,8 @@ public abstract class PathNavigation {
if (this.isDone()) {
return false;
} else {
- this.trimPath();
- if (this.path.getNodeCount() <= 0) {
+ if (path.isProcessed()) this.trimPath(); // Plazma - Process pathfinding asynchronously
+ if (path.isProcessed() && this.path.getNodeCount() <= 0) { // Plazma - Process pathfinding asynchronously
return false;
} else {
this.speedModifier = speed;
@@ -274,6 +292,7 @@ public abstract class PathNavigation {
if (this.hasDelayedRecomputation) {
this.recomputePath();
}
+ if (this.path != null && !this.path.isProcessed()) return; // Plazma - Process pathfinding asynchronously
if (!this.isDone()) {
if (this.canUpdatePath()) {
@@ -300,6 +319,7 @@ public abstract class PathNavigation {
}
protected void followThePath() {
+ if (!this.path.isProcessed()) return; // Plazma - Process pathfinding asynchronously
Vec3 vec3 = this.getTempMobPos();
this.maxDistanceToWaypoint = this.mob.getBbWidth() > 0.75F ? this.mob.getBbWidth() / 2.0F : 0.75F - this.mob.getBbWidth() / 2.0F;
Vec3i vec3i = this.path.getNextNodePos();
@@ -456,7 +476,7 @@ public abstract class PathNavigation {
public boolean shouldRecomputePath(BlockPos pos) {
if (this.hasDelayedRecomputation) {
return false;
- } else if (this.path != null && !this.path.isDone() && this.path.getNodeCount() != 0) {
+ } else if (this.path != null && this.path.isProcessed() && !this.path.isDone() && this.path.getNodeCount() != 0) { // Plazma - Process pathfinding asynchronously
Node node = this.path.getEndNode();
Vec3 vec3 = new Vec3(((double)node.x + this.mob.getX()) / 2.0, ((double)node.y + this.mob.getY()) / 2.0, ((double)node.z + this.mob.getZ()) / 2.0);
return pos.closerToCenterThan(vec3, (double)(this.path.getNodeCount() - this.path.getNextNodeIndex()));
diff --git a/src/main/java/net/minecraft/world/entity/ai/navigation/WaterBoundPathNavigation.java b/src/main/java/net/minecraft/world/entity/ai/navigation/WaterBoundPathNavigation.java
index 943c9944ae17fa7cd72e437cce61beaf3fc9505e..77c10a3e9570f53ce73dacb39cb86a00202e6ce6 100644
--- a/src/main/java/net/minecraft/world/entity/ai/navigation/WaterBoundPathNavigation.java
+++ b/src/main/java/net/minecraft/world/entity/ai/navigation/WaterBoundPathNavigation.java
@@ -15,11 +15,23 @@ public class WaterBoundPathNavigation extends PathNavigation {
super(entity, world);
}
+ // Plazma start - Process pathfinding asynchronously
+ private static final dev.kaiijumc.kaiiju.path.NodeEvaluatorGenerator GENERATOR =
+ (dev.kaiijumc.kaiiju.path.NodeEvaluatorFeatures features) -> {
+ SwimNodeEvaluator evaluator = new SwimNodeEvaluator(features.allowBreaching());
+ evaluator.setCanPassDoors(features.canOpenDoors());
+ evaluator.setCanOpenDoors(features.canOpenDoors());
+ evaluator.setCanFloat(features.canFloat());
+ evaluator.setCanWalkOverFences(features.canWalkOverFences());
+ return evaluator;
+ };
+ // Plazma end - Process pathfinding asynchronously
+
@Override
protected PathFinder createPathFinder(int range) {
this.allowBreaching = this.mob.getType() == EntityType.DOLPHIN;
this.nodeEvaluator = new SwimNodeEvaluator(this.allowBreaching);
- this.nodeEvaluator.setCanPassDoors(false);
+ if (org.plazmamc.plazma.configurations.GlobalConfiguration.get().entity.asyncPathProcess.enabled) return new PathFinder(this.nodeEvaluator, range, GENERATOR); // Plazma - Process pathfinding asynchronously
return new PathFinder(this.nodeEvaluator, range);
}
diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java b/src/main/java/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java
index 9104d7010bda6f9f73b478c11490ef9c53f76da2..a53950a6e4cb2e672b6f130461fa630643e3535f 100644
--- a/src/main/java/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java
+++ b/src/main/java/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java
@@ -57,6 +57,25 @@ public class NearestBedSensor extends Sensor<Mob> {
java.util.List<Pair<Holder<PoiType>, BlockPos>> poiposes = new java.util.ArrayList<>();
// don't ask me why it's unbounded. ask mojang.
io.papermc.paper.util.PoiAccess.findAnyPoiPositions(poiManager, type -> type.is(PoiTypes.HOME), predicate, entity.blockPosition(), world.purpurConfig.villagerNearestBedSensorSearchRadius, PoiManager.Occupancy.ANY, false, Integer.MAX_VALUE, poiposes); // Purpur
+ // Plazma start - Process pathfinding asynchronously
+ if (org.plazmamc.plazma.configurations.GlobalConfiguration.get().entity.asyncPathProcess.enabled) {
+ Path possible = AcquirePoi.findPathToPois(entity, new java.util.HashSet<>(poiposes));
+ dev.kaiijumc.kaiiju.path.AsyncPathProcessor.awaitProcess(entity, possible, path -> {
+ if ((path == null || !path.canReach()) && this.triedCount < 5) {
+ this.batchCache.long2LongEntrySet().removeIf(entry -> entry.getLongValue() < this.lastUpdate);
+ return;
+ }
+ if (path == null) return;
+
+ BlockPos pos = path.getTarget();
+ Optional<Holder<PoiType>> poiType = poiManager.getType(pos);
+ if (poiType.isEmpty()) return;
+
+ entity.getBrain().setMemory(MemoryModuleType.NEAREST_BED, pos);
+ });
+ return;
+ }
+ // Plazma end - Process pathfinding asynchronously
Path path = AcquirePoi.findPathToPois(entity, new java.util.HashSet<>(poiposes));
// Paper end - optimise POI access
if (path != null && path.canReach()) {
diff --git a/src/main/java/net/minecraft/world/entity/animal/Bee.java b/src/main/java/net/minecraft/world/entity/animal/Bee.java
index ba0b72e648fad219d7b42f5d489b53ec41046826..d83f75719a3be1b5b2c544d89b63a4df420c73d2 100644
--- a/src/main/java/net/minecraft/world/entity/animal/Bee.java
+++ b/src/main/java/net/minecraft/world/entity/animal/Bee.java
@@ -1243,7 +1243,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal {
} else {
Bee.this.pathfindRandomlyTowards(Bee.this.hivePos);
}
- } else {
+ } else if (navigation.getPath() != null && navigation.getPath().isProcessed()) { // Plazma - Process pathfinding asynchronously
boolean flag = this.pathfindDirectlyTowards(Bee.this.hivePos);
if (!flag) {
@@ -1302,7 +1302,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal {
} else {
Path pathentity = Bee.this.navigation.getPath();
- return pathentity != null && pathentity.getTarget().equals(pos) && pathentity.canReach() && pathentity.isDone();
+ return pathentity != null && pathentity.isProcessed() && pathentity.getTarget().equals(pos) && pathentity.canReach() && pathentity.isDone(); // Plazma - Process pathfinding asynchronously
}
}
}
diff --git a/src/main/java/net/minecraft/world/entity/animal/frog/Frog.java b/src/main/java/net/minecraft/world/entity/animal/frog/Frog.java
index 05e5bb907edb77f2479b29d6e4a15ae446ab0620..963e55584c741a3a3f903f465d897c7ecbf5cd4d 100644
--- a/src/main/java/net/minecraft/world/entity/animal/frog/Frog.java
+++ b/src/main/java/net/minecraft/world/entity/animal/frog/Frog.java
@@ -508,9 +508,23 @@ public class Frog extends Animal implements VariantHolder<Holder<FrogVariant>> {
return nodeType != PathType.WATER_BORDER && super.canCutCorner(nodeType);
}
+ // Plazma start - Process pathfinding asynchronously
+ private static final dev.kaiijumc.kaiiju.path.NodeEvaluatorGenerator GENERATOR =
+ (dev.kaiijumc.kaiiju.path.NodeEvaluatorFeatures features) -> {
+ FrogNodeEvaluator evaluator = new FrogNodeEvaluator(true);
+ evaluator.setCanPassDoors(features.canPassDoors());
+ evaluator.setCanOpenDoors(features.canOpenDoors());
+ evaluator.setCanWalkOverFences(features.canWalkOverFences());
+ evaluator.setCanFloat(features.canFloat());
+ return evaluator;
+ };
+ // Plazma end - Process pathfinding asynchronously
+
@Override
protected PathFinder createPathFinder(int range) {
this.nodeEvaluator = new Frog.FrogNodeEvaluator(true);
+ this.nodeEvaluator.setCanPassDoors(true);
+ if (org.plazmamc.plazma.configurations.GlobalConfiguration.get().entity.asyncPathProcess.enabled) return new PathFinder(this.nodeEvaluator, range, GENERATOR); // Plazma - Process pathfinding asynchronously
return new PathFinder(this.nodeEvaluator, range);
}
}
diff --git a/src/main/java/net/minecraft/world/entity/monster/Drowned.java b/src/main/java/net/minecraft/world/entity/monster/Drowned.java
index 902b4aff751a0f2a4fb8569eb2d88b7ceec7c40f..3235ea771c58ce9c09457a812416346341375613 100644
--- a/src/main/java/net/minecraft/world/entity/monster/Drowned.java
+++ b/src/main/java/net/minecraft/world/entity/monster/Drowned.java
@@ -309,7 +309,7 @@ public class Drowned extends Zombie implements RangedAttackMob {
protected boolean closeToNextPos() {
Path path = this.getNavigation().getPath();
- if (path != null) {
+ if (path != null && path.isProcessed()) { // Plazma - Process pathfinding asynchronously
BlockPos blockPos = path.getTarget();
if (blockPos != null) {
double d = this.distanceToSqr((double)blockPos.getX(), (double)blockPos.getY(), (double)blockPos.getZ());
diff --git a/src/main/java/net/minecraft/world/entity/monster/Strider.java b/src/main/java/net/minecraft/world/entity/monster/Strider.java
index 1cee20de1b691a92bee625a877e0ee9769e30b0b..ec871c81817ee82fa7b4ff929693b5ba045d35fb 100644
--- a/src/main/java/net/minecraft/world/entity/monster/Strider.java
+++ b/src/main/java/net/minecraft/world/entity/monster/Strider.java
@@ -613,9 +613,23 @@ public class Strider extends Animal implements ItemSteerable, Saddleable {
super(entity, world);
}
+ // Plazma start - Process pathfinding asynchronously
+ private static final dev.kaiijumc.kaiiju.path.NodeEvaluatorGenerator GENERATOR =
+ (dev.kaiijumc.kaiiju.path.NodeEvaluatorFeatures features) -> {
+ WalkNodeEvaluator evaluator = new WalkNodeEvaluator();
+ evaluator.setCanPassDoors(features.canPassDoors());
+ evaluator.setCanOpenDoors(features.canOpenDoors());
+ evaluator.setCanWalkOverFences(features.canWalkOverFences());
+ evaluator.setCanFloat(features.canFloat());
+ return evaluator;
+ };
+ // Plazma end - Process pathfinding asynchronously
+
@Override
protected PathFinder createPathFinder(int range) {
this.nodeEvaluator = new WalkNodeEvaluator();
+ this.nodeEvaluator.setCanPassDoors(true);
+ if (org.plazmamc.plazma.configurations.GlobalConfiguration.get().entity.asyncPathProcess.enabled) return new PathFinder(this.nodeEvaluator, range, GENERATOR); // Plazma - Process pathfinding asynchronously
return new PathFinder(this.nodeEvaluator, range);
}
diff --git a/src/main/java/net/minecraft/world/entity/monster/warden/Warden.java b/src/main/java/net/minecraft/world/entity/monster/warden/Warden.java
index 771e1b5ecc1803079bbb9e4233c616cb3075470e..839a5550a6d613abf4567b32b5f1a04700d6ccf4 100644
--- a/src/main/java/net/minecraft/world/entity/monster/warden/Warden.java
+++ b/src/main/java/net/minecraft/world/entity/monster/warden/Warden.java
@@ -637,6 +637,15 @@ public class Warden extends Monster implements VibrationSystem {
@Override
protected PathFinder createPathFinder(int range) {
this.nodeEvaluator = new WalkNodeEvaluator();
+ // Plazma start - Process pathfinding asynchonously
+ if (org.plazmamc.plazma.configurations.GlobalConfiguration.get().entity.asyncPathProcess.enabled)
+ return new PathFinder(this.nodeEvaluator, range, GroundPathNavigation.GENERATOR) {
+ @Override
+ protected float distance(Node a, Node b) {
+ return a.distanceToXZ(b);
+ }
+ };
+ // Plazma end - Process pathfinding asynchonously
return new PathFinder(this.nodeEvaluator, range) { // CraftBukkit - decompile error
@Override
protected float distance(Node a, Node b) {
diff --git a/src/main/java/net/minecraft/world/level/pathfinder/Path.java b/src/main/java/net/minecraft/world/level/pathfinder/Path.java
index d9d0fff9962131808d54cca20f209df50b8e4af1..420dd323790e72aa12c942d31a94bcb8c46e6bde 100644
--- a/src/main/java/net/minecraft/world/level/pathfinder/Path.java
+++ b/src/main/java/net/minecraft/world/level/pathfinder/Path.java
@@ -100,6 +100,7 @@ public class Path {
}
public boolean sameAs(@Nullable Path o) {
+ if (o == this) return true; // Plazma - Process Pathfinding asynchronously
if (o == null) {
return false;
} else if (o.nodes.size() != this.nodes.size()) {
@@ -191,6 +192,8 @@ public class Path {
return path;
}
+ public boolean isProcessed() { return true; } // Plazma - Async Path Processing
+
public static record DebugData(Node[] openSet, Node[] closedSet, Set<Target> targetNodes) {
public void write(FriendlyByteBuf buf) {
buf.writeCollection(this.targetNodes, (bufx, node) -> node.writeToStream(bufx));
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 cc7d94144e39f7dace7b569b4567def98396e8f9..95e71c471904fc54003180632dc85398ae06d241 100644
--- a/src/main/java/net/minecraft/world/level/pathfinder/PathFinder.java
+++ b/src/main/java/net/minecraft/world/level/pathfinder/PathFinder.java
@@ -17,6 +17,7 @@ import net.minecraft.util.profiling.ProfilerFiller;
import net.minecraft.util.profiling.metrics.MetricCategory;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.level.PathNavigationRegion;
+import dev.kaiijumc.kaiiju.path.*; // Plazma - Process pathfinding asynchronously
public class PathFinder {
private static final float FUDGING = 1.5F;
@@ -25,8 +26,20 @@ public class PathFinder {
public final NodeEvaluator nodeEvaluator;
private static final boolean DEBUG = false;
private final BinaryHeap openSet = new BinaryHeap();
+ // Plazma start - Process pathfinding asynchronously
+ public static boolean ASYNC = false;
+ private static final org.slf4j.Logger LOGGER = com.mojang.logging.LogUtils.getLogger();
+ private final @Nullable NodeEvaluatorGenerator evaluatorGenerator;
+ // Plazma end - Process pathfinding asynchronously
public PathFinder(NodeEvaluator pathNodeMaker, int range) {
+ // Plazma start - Process pathfinding asynchronously
+ this(pathNodeMaker, range, null);
+ }
+
+ public PathFinder(NodeEvaluator pathNodeMaker, int range, @Nullable NodeEvaluatorGenerator evaluatorGenerator) {
+ this.evaluatorGenerator = evaluatorGenerator;
+ // Plazma end - Process pathfinding asynchronously
this.nodeEvaluator = pathNodeMaker;
this.maxVisitedNodes = range;
}
@@ -37,100 +50,48 @@ public class PathFinder {
@Nullable
public Path findPath(PathNavigationRegion world, Mob mob, Set<BlockPos> positions, float followRange, int distance, float rangeMultiplier) {
- this.openSet.clear();
- this.nodeEvaluator.prepare(world, mob);
- Node node = this.nodeEvaluator.getStart();
- if (node == null) {
- return null;
- } else {
- // Paper start - Perf: remove streams and optimize collection
- List<Map.Entry<Target, BlockPos>> map = Lists.newArrayList();
- for (final BlockPos pos : positions) {
- map.add(new java.util.AbstractMap.SimpleEntry<>(this.nodeEvaluator.getTarget(pos.getX(), pos.getY(), pos.getZ()), pos));
- }
- // Paper end - Perf: remove streams and optimize collection
- Path path = this.findPath(node, map, followRange, distance, rangeMultiplier);
- this.nodeEvaluator.done();
- return path;
- }
- }
-
- @Nullable
- // Paper start - Perf: remove streams and optimize collection
- private Path findPath(Node startNode, List<Map.Entry<Target, BlockPos>> positions, float followRange, int distance, float rangeMultiplier) {
- ProfilerFiller profilerFiller = Profiler.get();
- profilerFiller.push("find_path");
- profilerFiller.markForCharting(MetricCategory.PATH_FINDING);
- // Set<Target> set = positions.keySet();
- startNode.g = 0.0F;
- startNode.h = this.getBestH(startNode, positions); // Paper - optimize collection
- startNode.f = startNode.h;
- this.openSet.clear();
- this.openSet.insert(startNode);
- // Set<Node> set2 = ImmutableSet.of(); // Paper - unused - diff on change
- int i = 0;
- List<Map.Entry<Target, BlockPos>> entryList = Lists.newArrayListWithExpectedSize(positions.size()); // Paper - optimize collection
- int j = (int)((float)this.maxVisitedNodes * rangeMultiplier);
+ if (ASYNC) this.openSet.clear();
- while (!this.openSet.isEmpty()) {
- if (++i >= j) {
- break;
- }
+ NodeEvaluator evaluator = this.evaluatorGenerator == null ? this.nodeEvaluator : NodeEvaluatorCache.take(this.evaluatorGenerator, this.nodeEvaluator);
+ evaluator.prepare(world, mob);
- Node node = this.openSet.pop();
- node.closed = true;
+ Node node = evaluator.getStart();
+ if ( node == null ) {
+ NodeEvaluatorCache.remove(evaluator);
+ return null; // diff on change
+ }
- // Paper start - optimize collection
- for (int i1 = 0; i1 < positions.size(); i1++) {
- final Map.Entry<Target, BlockPos> entry = positions.get(i1);
- Target target = entry.getKey();
- if (node.distanceManhattan(target) <= (float)distance) {
- target.setReached();
- entryList.add(entry);
- // Paper end - Perf: remove streams and optimize collection
- }
- }
+ List<Map.Entry<Target, BlockPos>> map = Lists.newArrayList();
+ for (final BlockPos pos : positions)
+ map.add(new java.util.AbstractMap.SimpleEntry<>(evaluator.getTarget(pos.getX(), pos.getY(), pos.getZ()), pos));
- if (!entryList.isEmpty()) { // Paper - Perf: remove streams and optimize collection; rename
- break;
- }
+ if (this.evaluatorGenerator == null) {
+ NodeEvaluatorCache.remove(evaluator);
+ return this.findPath(node, map, followRange, distance, rangeMultiplier);
+ }
- if (!(node.distanceTo(startNode) >= followRange)) {
- int k = this.nodeEvaluator.getNeighbors(this.neighbors, node);
-
- for (int l = 0; l < k; l++) {
- Node node2 = this.neighbors[l];
- float f = this.distance(node, node2);
- node2.walkedDistance = node.walkedDistance + f;
- float g = node.g + f + node2.costMalus;
- if (node2.walkedDistance < followRange && (!node2.inOpenSet() || g < node2.g)) {
- node2.cameFrom = node;
- node2.g = g;
- node2.h = this.getBestH(node2, positions) * 1.5F; // Paper - Perf: remove streams and optimize collection
- if (node2.inOpenSet()) {
- this.openSet.changeCost(node2, node2.g + node2.h);
- } else {
- node2.f = node2.g + node2.h;
- this.openSet.insert(node2);
- }
- }
- }
+ return new AsyncPath(
+ Lists.newArrayList(),
+ positions,
+ () -> this.processPath(evaluator, node, map, followRange, distance, rangeMultiplier),
+ () -> {
+ evaluator.done();
+ NodeEvaluatorCache.returnEvaluator(evaluator);
}
- }
+ );
+ }
- // Paper start - Perf: remove streams and optimize collection
- Path best = null;
- boolean entryListIsEmpty = entryList.isEmpty();
- Comparator<Path> comparator = entryListIsEmpty ? Comparator.comparingInt(Path::getNodeCount)
- : Comparator.comparingDouble(Path::getDistToTarget).thenComparingInt(Path::getNodeCount);
- for (Map.Entry<Target, BlockPos> entry : entryListIsEmpty ? positions : entryList) {
- Path path = this.reconstructPath(entry.getKey().getBestNode(), entry.getValue(), !entryListIsEmpty);
- if (best == null || comparator.compare(path, best) < 0)
- best = path;
+ @Nullable
+ // Paper start - Perf: remove streams and optimize collection
+ private Path findPath(Node startNode, List<Map.Entry<Target, BlockPos>> positions, float followRange, int distance, float rangeMultiplier) {
+ try {
+ return this.processPath(this.nodeEvaluator, startNode, positions, followRange, distance, rangeMultiplier);
+ } catch (Exception e) {
+ LOGGER.error("Failed to process path", e);
+ return null;
+ } finally {
+ this.nodeEvaluator.done();
}
- profilerFiller.pop();
- return best;
- // Paper end - Perf: remove streams and optimize collection
}
protected float distance(Node a, Node b) {
@@ -164,4 +125,78 @@ public class PathFinder {
return new Path(list, target, reachesTarget);
}
+
+ @Nullable
+ private Path processPath(NodeEvaluator evaluator, Node startNode, List<Map.Entry<Target, BlockPos>> positions, float followRange, int distance, float rangeMultiplier) {
+ org.apache.commons.lang3.Validate.isTrue(!positions.isEmpty());
+
+ startNode.g = 0.0F;
+ startNode.h = this.getBestH(startNode, positions);
+ startNode.f = startNode.h;
+
+ this.openSet.clear();
+ this.openSet.insert(startNode);
+
+ int i = 0;
+ int j = (int) (this.maxVisitedNodes * rangeMultiplier);
+ List<Map.Entry<Target, BlockPos>> entryList = Lists.newArrayListWithExpectedSize(positions.size());
+
+ while (!this.openSet.isEmpty()) {
+ if (++i >= j) break;
+
+ Node node = this.openSet.pop();
+ node.closed = true;
+
+ for (final Map.Entry<Target, BlockPos> entry : positions) {
+ Target target = entry.getKey();
+ if (node.distanceManhattan(target) > distance) continue;
+
+ target.setReached();
+ entryList.add(entry);
+ }
+
+ if (!entryList.isEmpty()) break;
+ if (node.distanceTo(startNode) >= followRange) continue;
+
+ int k = evaluator.getNeighbors(this.neighbors, node);
+ for (int l = 0; l < k; l++) {
+ Node node2 = this.neighbors[l];
+
+ float f = this.distance(node, node2);
+ float g = node.g + f + node2.costMalus;
+
+ node2.walkedDistance = node.walkedDistance + f;
+ if (node2.walkedDistance >= followRange && !(!node2.inOpenSet() || g < node2.g)) continue;
+
+ node2.cameFrom = node;
+ node2.g = g;
+ node2.h = this.getBestH(node2, positions) * 1.5F;
+
+ if (node2.inOpenSet()) {
+ this.openSet.changeCost(node2, node2.g + node2.h);
+ continue;
+ }
+
+ node2.f = node2.g + node2.h;
+ this.openSet.insert(node2);
+ }
+ }
+
+ Path best = null;
+ boolean isEmpty = entryList.isEmpty();
+
+ Comparator<Path> comparator = isEmpty
+ ? Comparator.comparingInt(Path::getNodeCount)
+ : Comparator.comparingDouble(Path::getDistToTarget).thenComparingInt(Path::getNodeCount);
+
+ for (final Map.Entry<Target, BlockPos> entry : isEmpty ? positions : entryList) {
+ Path path = this.reconstructPath(entry.getKey().getBestNode(), entry.getValue(), !isEmpty);
+ if (best != null && comparator.compare(path, best) >= 0) continue;
+
+ best = path;
+ }
+
+ return best;
+ }
+
}
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 6308822f819d7cb84c8070c8a7eec1a3f822114b..370540e48a5fd5693bf956ffbddccca58a1482f8 100644
--- a/src/main/java/net/minecraft/world/level/pathfinder/SwimNodeEvaluator.java
+++ b/src/main/java/net/minecraft/world/level/pathfinder/SwimNodeEvaluator.java
@@ -15,7 +15,7 @@ import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.material.FluidState;
public class SwimNodeEvaluator extends NodeEvaluator {
- private final boolean allowBreaching;
+ public final boolean allowBreaching; // Plazma - AT (private -> public)
private final Long2ObjectMap<PathType> pathTypesByPosCache = new Long2ObjectOpenHashMap<>();
public SwimNodeEvaluator(boolean canJumpOutOfWater) {
diff --git a/src/main/java/org/plazmamc/plazma/commands/plazma/subcommand/ReloadCommand.java b/src/main/java/org/plazmamc/plazma/commands/plazma/subcommand/ReloadCommand.java
index 1c83926923f50fb4da1a83dc91614c20a831555f..aec2d0f9a957be65d031957dbff874d801e8d15c 100644
--- a/src/main/java/org/plazmamc/plazma/commands/plazma/subcommand/ReloadCommand.java
+++ b/src/main/java/org/plazmamc/plazma/commands/plazma/subcommand/ReloadCommand.java
@@ -26,6 +26,7 @@ public class ReloadCommand implements PlazmaSubCommand {
MinecraftServer server = ((CraftServer) sender.getServer()).getServer();
server.plazmaConfigurations.reloadConfigs(server);
+ dev.kaiijumc.kaiiju.path.AsyncPathProcessor.updateExecutor(); // Process pathfinding asynchronously
server.server.reloadCount++;
Command.broadcastCommandMessage(sender, text("Successfully reloaded Plazma configuration files.", NamedTextColor.GREEN));
diff --git a/src/main/java/org/plazmamc/plazma/configurations/GlobalConfiguration.java b/src/main/java/org/plazmamc/plazma/configurations/GlobalConfiguration.java
index 8914159c17b3c8b0114e88c5317df5f17b05e5d6..92de32ee00bc9c4e8750773bca95a9cf5fbae067 100644
--- a/src/main/java/org/plazmamc/plazma/configurations/GlobalConfiguration.java
+++ b/src/main/java/org/plazmamc/plazma/configurations/GlobalConfiguration.java
@@ -86,6 +86,26 @@ public class GlobalConfiguration extends ConfigurationPart {
public class Entity extends ConfigurationPart {
+ public AsyncPathProcess asyncPathProcess;
+ public class AsyncPathProcess extends ConfigurationPart {
+
+ public boolean enabled = OPTIMIZE;
+
+ int maxThreadSize = 0;
+ public int keepAliveTime = 60;
+
+ public int maxThreadSize() {
+ if (!enabled) return 0;
+ if (maxThreadSize == 0)
+ return Math.max(Runtime.getRuntime().availableProcessors() / 4, 1);
+ else if (maxThreadSize < 0)
+ return Math.max(Runtime.getRuntime().availableProcessors() + maxThreadSize, 1);
+
+ return maxThreadSize;
+ }
+
+ }
+
@PostProcess
public void post() {
}