From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: AlphaKR93 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 . 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 postProcessors = new ArrayList<>(0); + private final Set positions; + private final Supplier pathSupplier; + private final Runnable postSupplier; + private final List nodes; + + private @Nullable BlockPos target; + private float distToTarget = 0; + private boolean canReach = true; + + @SuppressWarnings("DataFlowIssue") + public AsyncPath( + final @NotNull List emptyNodeList, + final @NotNull Set positions, + final @NotNull Supplier 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 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 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> threadLocalEvaluators = new ConcurrentHashMap<>(); + private static final Map evaluatorGenerators = new ConcurrentHashMap<>(); + + private static Queue 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 ecd1bbd17cb0134cf1f4e99a3fea9e205d38f46b..a064bca7d59dc645b28d5c4d92611d7aff5d3c7d 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 @@ -76,6 +76,35 @@ public class AcquirePoi { io.papermc.paper.util.PoiAccess.findNearestPoiPositions(poiManager, poiPredicate, predicate2, entity.blockPosition(), world.purpurConfig.villagerAcquirePoiSearchRadius, world.purpurConfig.villagerAcquirePoiSearchRadius*world.purpurConfig.villagerAcquirePoiSearchRadius, PoiManager.Occupancy.HAS_SPACE, false, 5, poiposes); // Purpur Set, BlockPos>> set = new java.util.HashSet<>(poiposes); // 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, BlockPos> pair : set) long2ObjectMap.computeIfAbsent( + pair.getSecond().asLong(), + ignored -> new 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 { @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 { 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 { @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 optional = entity.getBrain().getMemory(MemoryModuleType.WALK_TARGET); boolean bl = optional.map(MoveToTargetSink::isWalkTargetSpectator).orElse(false); @@ -95,12 +98,67 @@ public class MoveToTargetSink extends Behavior { @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 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 { 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> 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 74aca307b4ebffe4e33c4fca3e07c23ca87622ac..a1c99a611c9b0c99d851393c668fe02c5a31536f 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() && groundPathNavigation.canOpenDoors()) { + if (path != null && path.isProcessed() && !path.isDone() && groundPathNavigation.canOpenDoors()) { // 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 ee38e447a810094d2253b85714b74282a4b4c2bc..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,12 +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 a3e0c5af4cc9323c02e88e768cbda9e46854aea1..d68e97084ba6bc97312fc5b62ffcb6e6b21b056e 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,12 +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) { @@ -50,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 62634bedd97c5be9ecce24ab0cff205715a68da8..d7bbb9ddb97b336a2198b07cb2395f1b8b8e9d30 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 @@ -23,12 +23,26 @@ 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 544920a31b649985333f82beafa94a3392f5853e..05715fa17caf8d8eece85b7ad752f57890b04716 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 @@ -152,6 +152,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 { @@ -177,7 +178,23 @@ public abstract class PathNavigation { int i = (int)(followRange + (float)range); 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); - //this.level.getProfiler().pop(); // Purpur + // 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 if (path != null && path.getTarget() != null) { this.targetPos = path.getTarget(); this.reachRange = distance; @@ -233,8 +250,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; @@ -257,6 +274,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()) { @@ -282,7 +300,9 @@ public abstract class PathNavigation { return this.level.getBlockState(blockPos.below()).isAir() ? pos.y : WalkNodeEvaluator.getFloorLevel(this.level, blockPos); } + @SuppressWarnings("DataFlowIssue") // Plazma - Process pathfinding asynchronously 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(); @@ -439,7 +459,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 ce7398a617abe6e800c1e014b3ac5c970eb15c8a..8e9a00ee90f97d2684f72eca5006568aa2a858da 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,12 +15,26 @@ 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; + }; + @Override protected PathFinder createPathFinder(int range) { this.allowBreaching = this.mob.getType() == EntityType.DOLPHIN; this.nodeEvaluator = new SwimNodeEvaluator(this.allowBreaching); + 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/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 { java.util.List, 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> 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 9a6ec5e465684be7f0d54b1f7e66bdf52603e442..d1a31e04899e3bce3f884f60916abd4e25dd3afd 100644 --- a/src/main/java/net/minecraft/world/entity/animal/Bee.java +++ b/src/main/java/net/minecraft/world/entity/animal/Bee.java @@ -1147,7 +1147,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) { @@ -1209,7 +1209,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 651f30b4fb02dc03fabad34b62d7d86fa0889754..2fb5e4fc7422c81c67a026143c92f5e787d864fb 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 @@ -491,11 +491,25 @@ public class Frog extends Animal implements VariantHolder> { 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; + }; + @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); return new PathFinder(this.nodeEvaluator, range); } + // Plazma end - Process pathfinding asynchronously } } 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 94de51bf9acb32421838ffe54602310f0263b3c4..1afa876e6abac59320c10d37ec8664db1af751b2 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Drowned.java +++ b/src/main/java/net/minecraft/world/entity/monster/Drowned.java @@ -296,7 +296,7 @@ public class Drowned extends Zombie implements RangedAttackMob { protected boolean closeToNextPos() { Path pathentity = this.getNavigation().getPath(); - if (pathentity != null) { + if (pathentity != null && pathentity.isProcessed()) { // Plazma - Process pathfinding asynchronously BlockPos blockposition = pathentity.getTarget(); if (blockposition != null) { 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 70650cc6f76bed79a31a9e8c86205910994a920f..ca521f6ddaf898f17473e9b87e302ddaf9f30380 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Strider.java +++ b/src/main/java/net/minecraft/world/entity/monster/Strider.java @@ -610,10 +610,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 c0398f21cb2ecc9e07f01163a4432a603ff26f03..78a5631753e5bdc16b786ac6951c8d39c4337767 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 @@ -634,6 +634,15 @@ public class Warden extends Monster implements VibrationSystem { protected PathFinder createPathFinder(int range) { this.nodeEvaluator = new WalkNodeEvaluator(); this.nodeEvaluator.setCanPassDoors(true); + // 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 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 404080976208c30e9e95e5bee47c2a749e709a45..e31ee7508a1af51bec628141c3a153997dc75672 100644 --- a/src/main/java/net/minecraft/world/level/pathfinder/PathFinder.java +++ b/src/main/java/net/minecraft/world/level/pathfinder/PathFinder.java @@ -16,6 +16,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; @@ -24,91 +25,138 @@ public class PathFinder { public final NodeEvaluator nodeEvaluator; private static final boolean DEBUG = false; private final BinaryHeap openSet = new BinaryHeap(); + // Plazma start - Process pathfinding asynchronously + private static final org.slf4j.Logger LOGGER = com.mojang.logging.LogUtils.getLogger(); + private final @Nullable NodeEvaluatorGenerator evaluatorGenerator; public PathFinder(NodeEvaluator pathNodeMaker, int range) { + this(pathNodeMaker, range, null); + } + + public PathFinder(NodeEvaluator pathNodeMaker, int range, @Nullable NodeEvaluatorGenerator evaluatorGenerator) { this.nodeEvaluator = pathNodeMaker; this.maxVisitedNodes = range; + this.evaluatorGenerator = evaluatorGenerator; } + @SuppressWarnings("ConstantValue") @Nullable - public Path findPath(PathNavigationRegion world, Mob mob, Set positions, float followRange, int distance, float rangeMultiplier) { - this.openSet.clear(); - this.nodeEvaluator.prepare(world, mob); - Node node = this.nodeEvaluator.getStart(); + public Path findPath( + PathNavigationRegion world, + Mob mob, + Set positions, + float followRange, + int distance, + float rangeMultiplier + ) { + if (!org.plazmamc.plazma.configurations.GlobalConfiguration.get().entity.asyncPathProcess.enabled) this.openSet.clear(); + NodeEvaluator evaluator = this.evaluatorGenerator == null + ? this.nodeEvaluator + : NodeEvaluatorCache.take(this.evaluatorGenerator, this.nodeEvaluator); + + evaluator.prepare(world, mob); + Node node = evaluator.getStart(); + if (node == null) { + NodeEvaluatorCache.remove(evaluator); return null; - } else { - // Paper start - Perf: remove streams and optimize collection - List> 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(world.getProfiler(), node, map, followRange, distance, rangeMultiplier); - this.nodeEvaluator.done(); - return path; } + + List> 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 (this.evaluatorGenerator == null) { + NodeEvaluatorCache.remove(evaluator); + return this.findPath(node, map, followRange, distance, rangeMultiplier); + } + + return new AsyncPath( + Lists.newArrayList(), + positions, + () -> this.processPath(evaluator, node, map, followRange, distance, rangeMultiplier), + () -> { + evaluator.done(); + NodeEvaluatorCache.returnEvaluator(evaluator); + } + ); } @Nullable // Paper start - Perf: remove streams and optimize collection - private Path findPath(ProfilerFiller profiler, Node startNode, List> positions, float followRange, int distance, float rangeMultiplier) { - //profiler.push("find_path"); // Purpur - //profiler.markForCharting(MetricCategory.PATH_FINDING); // Purpur - // Set set = positions.keySet(); + private Path findPath( + Node startNode, + List> 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(); + } + } + + @SuppressWarnings("DataFlowIssue") + @org.jetbrains.annotations.NotNull + private synchronized Path processPath( + NodeEvaluator nodeEvaluator, + Node startNode, + List> 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); // Paper - optimize collection startNode.f = startNode.h; this.openSet.clear(); this.openSet.insert(startNode); - // Set set2 = ImmutableSet.of(); // Paper - unused - diff on change + int i = 0; List> entryList = Lists.newArrayListWithExpectedSize(positions.size()); // Paper - optimize collection int j = (int)((float)this.maxVisitedNodes * rangeMultiplier); while (!this.openSet.isEmpty()) { - if (++i >= j) { - break; - } + if (++i >= j) break; Node node = this.openSet.pop(); node.closed = true; - // Paper start - optimize collection - for (int i1 = 0; i1 < positions.size(); i1++) { - final Map.Entry entry = positions.get(i1); + for (final Map.Entry entry : positions) { Target target = entry.getKey(); - if (node.distanceManhattan(target) <= (float)distance) { + if (node.distanceManhattan(target) <= (float) distance) { target.setReached(); entryList.add(entry); - // Paper end - Perf: remove streams and optimize collection } } - if (!entryList.isEmpty()) { // Paper - Perf: remove streams and optimize collection; rename - break; - } + if (!entryList.isEmpty()) break; + if (node.distanceTo(startNode) >= followRange) continue; + + int k = 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)) continue; - 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); - } - } + 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); + continue; } + node2.f = node2.g + node2.h; + this.openSet.insert(node2); } } @@ -126,6 +174,7 @@ public class PathFinder { return best; // Paper end - Perf: remove streams and optimize collection } + // Plazma end - Process pathfinding asynchronously protected float distance(Node a, Node b) { return a.distanceTo(b); 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 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 aa8b0a9b3d98e2c2fb1bd3c374cd2e742e6e17cf..516a4545b67a4bf0d37cee3d1e26772831944fe6 100644 --- a/src/main/java/org/plazmamc/plazma/configurations/GlobalConfiguration.java +++ b/src/main/java/org/plazmamc/plazma/configurations/GlobalConfiguration.java @@ -60,6 +60,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() { }