From c538c03f7f574a340db2a410b153a5e931f20be8 Mon Sep 17 00:00:00 2001 From: hayanesuru Date: Wed, 30 Apr 2025 17:07:04 +0800 Subject: [PATCH 1/8] optimize async target finding --- .../features/0154-Async-target-finding.patch | 1025 +++++++++-------- .../leaf/async/ai/AsyncGoalExecutor.java | 77 +- .../dreeam/leaf/async/ai/AsyncGoalThread.java | 37 + .../java/org/dreeam/leaf/async/ai/Waker.java | 17 + .../modules/async/AsyncTargetFinding.java | 38 +- .../dreeam/leaf/util/queue/SpscIntQueue.java | 63 + 6 files changed, 728 insertions(+), 529 deletions(-) create mode 100644 leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalThread.java create mode 100644 leaf-server/src/main/java/org/dreeam/leaf/async/ai/Waker.java create mode 100644 leaf-server/src/main/java/org/dreeam/leaf/util/queue/SpscIntQueue.java diff --git a/leaf-server/minecraft-patches/features/0154-Async-target-finding.patch b/leaf-server/minecraft-patches/features/0154-Async-target-finding.patch index 82309c9a4..047a2838c 100644 --- a/leaf-server/minecraft-patches/features/0154-Async-target-finding.patch +++ b/leaf-server/minecraft-patches/features/0154-Async-target-finding.patch @@ -5,18 +5,25 @@ Subject: [PATCH] Async target finding diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java -index 24926aa7ed5c78b235659daf18b224b14beb744c..2603f5ca5e5f3fd86af76aec7e16039bf9c9292d 100644 +index 24926aa7ed5c78b235659daf18b224b14beb744c..98af1ad020a003db66d7319f33d43deec315aec5 100644 --- a/net/minecraft/server/MinecraftServer.java +++ b/net/minecraft/server/MinecraftServer.java -@@ -1088,6 +1088,15 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop entitiesWithScheduledTasks = java.util.concurrent.ConcurrentHashMap.newKeySet(); // SparklyPaper - skip EntityScheduler's executeTick checks if there isn't any tasks to be run (concurrent because plugins may schedule tasks async) + public java.util.concurrent.Semaphore serverLevelTickingSemaphore = null; // SparklyPaper - parallel world ticking ++ @Nullable public org.dreeam.leaf.async.ai.AsyncGoalThread asyncGoalThread; // Leaf - Async target finding + + public static S spin(Function threadFunction) { + ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.init(); // Paper - rewrite data converter system +@@ -1088,6 +1089,14 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop realPlayers; // Leaves - skip -+ public List asyncAITasks = new it.unimi.dsi.fastutil.objects.ObjectArrayList<>(); // Leaf ++ public final @Nullable org.dreeam.leaf.async.ai.AsyncGoalExecutor asyncGoalExecutor; // Leaf - Async target finding public LevelChunk getChunkIfLoaded(int x, int z) { return this.chunkSource.getChunkAtIfLoadedImmediately(x, z); // Paper - Use getChunkIfLoadedImmediately -@@ -861,6 +871,21 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe - } - ); - this.tickBlockEntities(); -+ // Leaf start - Async target finding -+ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled -+ && org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled -+ ) { -+ final var tasks = this.asyncAITasks; -+ this.asyncAITasks = new it.unimi.dsi.fastutil.objects.ObjectArrayList<>(); -+ org.dreeam.leaf.async.ai.AsyncGoalExecutor.runTasks(tasks); -+ } else -+ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled) { -+ final var tasks = this.asyncAITasks; -+ this.asyncAITasks = new it.unimi.dsi.fastutil.objects.ObjectArrayList<>(); -+ org.dreeam.leaf.async.ai.AsyncGoalExecutor.EXECUTOR.execute( -+ () -> org.dreeam.leaf.async.ai.AsyncGoalExecutor.runTasks(tasks)); -+ } -+ // Leaf end - Async target finding - } +@@ -335,6 +345,12 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + ((ca.spottedleaf.moonrise.patches.chunk_system.server.ChunkSystemMinecraftServer)this.server).moonrise$executeMidTickTasks(); + } + ++ // Leaf start - Async target finding ++ public final void leafMidTickTasks() { ++ if (this.asyncGoalExecutor != null) this.asyncGoalExecutor.midTick(); ++ } ++ // Leaf end - Async target finding ++ + @Override + public final ChunkAccess moonrise$syncLoadNonFull(final int chunkX, final int chunkZ, final net.minecraft.world.level.chunk.status.ChunkStatus status) { + return this.moonrise$getChunkTaskScheduler().syncLoadNonFull(chunkX, chunkZ, status); +@@ -711,6 +727,13 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + this.preciseTime = this.serverLevelData.getDayTime(); // Purpur - Configurable daylight cycle + this.realPlayers = Lists.newArrayList(); // Leaves - skip + this.tickExecutor = java.util.concurrent.Executors.newSingleThreadExecutor(new org.dreeam.leaf.async.world.SparklyPaperServerLevelTickExecutorThreadFactory(getWorld().getName())); // SparklyPaper - parallel world ticking ++ // Leaf start - Async target finding ++ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled) { ++ this.asyncGoalExecutor = new org.dreeam.leaf.async.ai.AsyncGoalExecutor(server.asyncGoalThread, this); ++ } else { ++ this.asyncGoalExecutor = null; ++ } ++ // Leaf end - Async target finding + } - // Paper - rewrite chunk system + // Paper start +@@ -855,6 +878,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + } + this.moonrise$midTickTasks(); // Paper - rewrite chunk system + // Gale end - Airplane - remove lambda from ticking guard - copied from guardEntityTick ++ this.leafMidTickTasks(); // Leaf - Async target finding + } + } + } +@@ -1329,6 +1353,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + // Leaf end - SparklyPaper - parallel world ticking (only run mid-tick at the end of each tick / fixes concurrency bugs related to executeMidTickTasks) + // Paper end - rewrite chunk system + ++ this.leafMidTickTasks(); // Leaf - Async target finding + } + + private void tickBlock(BlockPos pos, Block block) { +@@ -1345,6 +1370,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + // Leaf end - SparklyPaper - parallel world ticking (only run mid-tick at the end of each tick / fixes concurrency bugs related to executeMidTickTasks) + // Paper end - rewrite chunk system + ++ this.leafMidTickTasks(); // Leaf - Async target finding + } + + // Paper start - log detailed entity tick information diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java -index 90879616842cc61d15854b07f56f6fcb89f11074..0ae36986492d98f46ccf409f6b71ed9cfca488e7 100644 +index 90879616842cc61d15854b07f56f6fcb89f11074..f114a8f5143799d72e36e0a535888c5fb25213e1 100644 --- a/net/minecraft/world/entity/Entity.java +++ b/net/minecraft/world/entity/Entity.java @@ -243,6 +243,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess protected Vec3 stuckSpeedMultiplier = Vec3.ZERO; @Nullable private Entity.RemovalReason removalReason; -+ private final java.util.concurrent.atomic.AtomicBoolean isRemoved = new java.util.concurrent.atomic.AtomicBoolean(false); // Leaf - atomic removal check ++ private volatile boolean isRemoved = false; // Leaf - volatile removal check public static final float DEFAULT_BB_WIDTH = 0.6F; public static final float DEFAULT_BB_HEIGHT = 1.8F; public float moveDist; @@ -93,13 +143,13 @@ index 90879616842cc61d15854b07f56f6fcb89f11074..0ae36986492d98f46ccf409f6b71ed9c public final boolean isRemoved() { - return this.removalReason != null; -+ // Leaf start - atomic removal check ++ // Leaf start - volatile removal check + if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled) { -+ return this.isRemoved.getAcquire(); ++ return this.isRemoved; + } else { + return this.removalReason != null; + } -+ // Leaf end - atomic removal check ++ // Leaf end - volatile removal check } @Nullable @@ -107,11 +157,11 @@ index 90879616842cc61d15854b07f56f6fcb89f11074..0ae36986492d98f46ccf409f6b71ed9c final boolean alreadyRemoved = this.removalReason != null; // Paper - Folia schedulers if (this.removalReason == null) { this.removalReason = removalReason; -+ // Leaf start - atomic removal check -+ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled) { -+ this.isRemoved.setRelease(true); ++ // Leaf start - volatile removal check ++ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled && this.removalReason != null) { ++ this.isRemoved = true; + } -+ // Leaf end - atomic removal check ++ // Leaf end - volatile removal check } if (this.removalReason.shouldDestroy()) { @@ -119,28 +169,76 @@ index 90879616842cc61d15854b07f56f6fcb89f11074..0ae36986492d98f46ccf409f6b71ed9c public void unsetRemoved() { this.removalReason = null; -+ // Leaf start - atomic removal check ++ // Leaf start - volatile removal check + if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled) { -+ this.isRemoved.setRelease(false); ++ this.isRemoved = false; + } -+ // Leaf end - atomic removal check ++ // Leaf end - volatile removal check } // Paper start - Folia schedulers +diff --git a/net/minecraft/world/entity/Mob.java b/net/minecraft/world/entity/Mob.java +index 05d5cde42b7011091ef4ee874c0d9d5586ae3f10..555e206c5fafaa801e46625070da2fd3bd603ce4 100644 +--- a/net/minecraft/world/entity/Mob.java ++++ b/net/minecraft/world/entity/Mob.java +@@ -144,6 +144,10 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab + private float restrictRadius = -1.0F; + public boolean aware = true; // CraftBukkit + public int ticksSinceLastInteraction; // Purpur - Entity lifespan ++ public boolean tickingTarget; // Leaf - Async target finding ++ public final org.dreeam.leaf.async.ai.Waker getGoalCtx() { ++ return tickingTarget ? this.targetSelector.ctx : this.goalSelector.ctx; ++ } + + protected Mob(EntityType entityType, Level level) { + super(entityType, level); +@@ -231,6 +235,11 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab + if (this.targetSelector.inactiveTick(this.activatedPriority, true)) { // Pufferfish - pass activated priority + this.targetSelector.tick(); + } ++ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled) { ++ if (this.targetSelector.ctxGoals != null || this.goalSelector.ctxGoals != null) { ++ ((ServerLevel) this.level()).asyncGoalExecutor.submit(this.getId()); ++ } ++ } + } + // Paper end + +@@ -914,17 +923,28 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab + // Paper end - Allow nerfed mobs to jump and float + this.sensing.tick(); + int i = this.tickCount + this.getId(); ++ // Leaf start - Async target finding + if (i % 2 != 0 && this.tickCount > 1) { ++ this.tickingTarget = true; + if (this.targetSelector.inactiveTick(this.activatedPriority, false)) // Pufferfish - use this to alternate ticking + this.targetSelector.tickRunningGoals(false); ++ this.tickingTarget = false; + if (this.goalSelector.inactiveTick(this.activatedPriority, false)) // Pufferfish - use this to alternate ticking + this.goalSelector.tickRunningGoals(false); + } else { ++ this.tickingTarget = true; + if (this.targetSelector.inactiveTick(this.activatedPriority, false)) // Pufferfish - use this to alternate ticking + this.targetSelector.tick(); ++ this.tickingTarget = false; + if (this.goalSelector.inactiveTick(this.activatedPriority, false)) // Pufferfish - use this to alternate ticking + this.goalSelector.tick(); + } ++ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled) { ++ if (this.targetSelector.ctxGoals != null || this.goalSelector.ctxGoals != null) { ++ ((ServerLevel) this.level()).asyncGoalExecutor.submit(this.getId()); ++ } ++ } ++ // Leaf end - Async target finding + + this.navigation.tick(); + this.customServerAiStep((ServerLevel)this.level()); diff --git a/net/minecraft/world/entity/ai/goal/AvoidEntityGoal.java b/net/minecraft/world/entity/ai/goal/AvoidEntityGoal.java -index 4b24bb47a53a46586a642f3fb2656b1b8b670bf2..77e51ffce5afc9089fd3a2e382cdfde7a6cf1c35 100644 +index 4b24bb47a53a46586a642f3fb2656b1b8b670bf2..149672d5aea4e04f74f8fb80a3f4856fb2a7c133 100644 --- a/net/minecraft/world/entity/ai/goal/AvoidEntityGoal.java +++ b/net/minecraft/world/entity/ai/goal/AvoidEntityGoal.java -@@ -65,17 +65,32 @@ public class AvoidEntityGoal extends Goal { - this(mob, entityClassToAvoid, livingEntity -> true, maxDistance, walkSpeedModifier, sprintSpeedModifier, predicateOnAvoidEntity); - } +@@ -67,15 +67,25 @@ public class AvoidEntityGoal extends Goal { -+ // Leaf start - Async Avoid Entity Finding -+ private final java.util.concurrent.atomic.AtomicBoolean isSearching = new java.util.concurrent.atomic.AtomicBoolean(false); -+ private final java.util.concurrent.atomic.AtomicReference pendingTarget = new java.util.concurrent.atomic.AtomicReference<>(null); -+ private static final org.apache.logging.log4j.Logger LOGGER = org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER; -+ // Leaf end - Async Avoid Entity Finding -+ @Override public boolean canUse() { - this.toAvoid = getServerLevel(this.mob) @@ -158,6 +256,7 @@ index 4b24bb47a53a46586a642f3fb2656b1b8b670bf2..77e51ffce5afc9089fd3a2e382cdfde7 + } + if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchEntity) { + getNearestEntityAsync(); ++ return false; + } else { + this.toAvoid = getServerLevel(this.mob) + .getNearestEntity( @@ -173,49 +272,37 @@ index 4b24bb47a53a46586a642f3fb2656b1b8b670bf2..77e51ffce5afc9089fd3a2e382cdfde7 if (this.toAvoid == null) { return false; } else { -@@ -91,6 +106,62 @@ public class AvoidEntityGoal extends Goal { +@@ -91,6 +101,47 @@ public class AvoidEntityGoal extends Goal { } } + // Leaf start - Async Avoid Entity Finding + private boolean poll() { -+ LivingEntity t = pendingTarget.getAndSet(null); -+ if (t == null) { -+ return false; -+ } ++ if (!(this.mob.getGoalCtx().result() instanceof LivingEntity entity)) { return false; } + var serverLevel = getServerLevel(this.mob); -+ if (serverLevel == null || !t.isAlive() || !this.avoidEntityTargeting.test(serverLevel, this.mob, t)) { ++ if (serverLevel == null || !entity.isAlive() || !this.avoidEntityTargeting.test(serverLevel, this.mob, entity)) { + return false; + } -+ this.toAvoid = (T) t; ++ this.toAvoid = (T) entity; + return true; + } + + private void getNearestEntityAsync() { -+ if (!isSearching.compareAndSet(false, true)) { -+ return; -+ } -+ + final var mob = this.mob; -+ -+ if (mob == null || mob.isRemoved() || !mob.isAlive()) { -+ isSearching.setRelease(false); -+ return; -+ } -+ ++ final var ctx = mob.getGoalCtx(); ++ if (!ctx.state) return; + final double x = mob.getX(); + final double y = mob.getEyeY(); + final double z = mob.getZ(); + final var serverLevel = getServerLevel(mob); + final var avoidClass = this.avoidClass; + final var bound = mob.getBoundingBox().inflate(this.maxDist, 3.0, this.maxDist); -+ final var pendingTarget = this.pendingTarget; -+ serverLevel.asyncAITasks.add(() -> { ++ ctx.wake = () -> { + try { + if (mob == null || mob.isRemoved() || !mob.isAlive() || mob.level() != serverLevel) { + return; + } -+ var avoid = serverLevel.getNearestEntity( ++ ctx.result = serverLevel.getNearestEntity( + serverLevel.getEntitiesOfClass(avoidClass, bound, LivingEntity::isAlive), + avoidEntityTargeting, + mob, @@ -223,13 +310,10 @@ index 4b24bb47a53a46586a642f3fb2656b1b8b670bf2..77e51ffce5afc9089fd3a2e382cdfde7 + y, + z + ); -+ pendingTarget.setRelease(avoid); + } catch (Exception e) { -+ LOGGER.error("Exception getting entities", e); -+ } finally { -+ isSearching.setRelease(false); ++ org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER.error("Exception getting entities", e); + } -+ }); ++ }; + } + // Leaf end - Async Avoid Entity Finding + @@ -237,19 +321,16 @@ index 4b24bb47a53a46586a642f3fb2656b1b8b670bf2..77e51ffce5afc9089fd3a2e382cdfde7 public boolean canContinueToUse() { return !this.pathNav.isDone(); diff --git a/net/minecraft/world/entity/ai/goal/BegGoal.java b/net/minecraft/world/entity/ai/goal/BegGoal.java -index 28ef40e8a645989ea181297069cf2bbe571f3082..56a5fda5f1e2482e1c89c7c2f9c34ea85562702e 100644 +index 28ef40e8a645989ea181297069cf2bbe571f3082..5f1a0ccaed29324a24bfbea96c9dc3adfeb11ad5 100644 --- a/net/minecraft/world/entity/ai/goal/BegGoal.java +++ b/net/minecraft/world/entity/ai/goal/BegGoal.java -@@ -27,8 +27,74 @@ public class BegGoal extends Goal { +@@ -27,8 +27,54 @@ public class BegGoal extends Goal { this.setFlags(EnumSet.of(Goal.Flag.LOOK)); } + // Leaf start - Async Target Finding -+ private static final org.apache.logging.log4j.Logger LOGGER = org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER; -+ private final java.util.concurrent.atomic.AtomicBoolean isSearching = new java.util.concurrent.atomic.AtomicBoolean(false); -+ private final java.util.concurrent.atomic.AtomicReference pendingTarget = new java.util.concurrent.atomic.AtomicReference<>(null); + protected boolean poll() { -+ Player t = pendingTarget.getAndSet(null); ++ if (!(this.wolf.getGoalCtx().result() instanceof Player t)) { return false; } + if (t == null) { + return false; + } @@ -262,27 +343,12 @@ index 28ef40e8a645989ea181297069cf2bbe571f3082..56a5fda5f1e2482e1c89c7c2f9c34ea8 + } + + private void findTargetAsync() { -+ if (!isSearching.compareAndSet(false, true)) { -+ return; -+ } -+ -+ // Capture mutable state to avoid race conditions + final Wolf wolf = this.wolf; -+ -+ // Safety check -+ if (wolf == null || wolf.isRemoved() || !wolf.isAlive()) { -+ isSearching.setRelease(false); -+ return; -+ } -+ -+ final double x = wolf.getX(); -+ final double y = wolf.getEyeY(); -+ final double z = wolf.getZ(); ++ final var ctx = wolf.getGoalCtx(); ++ if (!ctx.state) return; + final ServerLevel serverLevel = getServerLevel(wolf); + final TargetingConditions begTargeting = this.begTargeting; -+ final var pendingTarget = this.pendingTarget; -+ -+ serverLevel.asyncAITasks.add(() -> { ++ ctx.wake = () -> { + try { + if (wolf.level() != serverLevel || wolf.isRemoved() || !wolf.isAlive()) { + return; @@ -290,14 +356,12 @@ index 28ef40e8a645989ea181297069cf2bbe571f3082..56a5fda5f1e2482e1c89c7c2f9c34ea8 + + var player = serverLevel.getNearestPlayer(begTargeting, wolf); + if (player != null && playerHoldingInteresting(player)) { -+ pendingTarget.setRelease(player); ++ ctx.result = player; + } + } catch (Exception e) { -+ LOGGER.error("Exception getting player", e); -+ } finally { -+ isSearching.setRelease(false); ++ org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER.error("Exception getting entities", e); + } -+ }); ++ }; + } + // Leaf end - Async Target Finding + @@ -307,7 +371,7 @@ index 28ef40e8a645989ea181297069cf2bbe571f3082..56a5fda5f1e2482e1c89c7c2f9c34ea8 + if (poll()) { + return true; + } -+ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchPlayer) { ++ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchEntity) { + findTargetAsync(); + return false; + } @@ -315,7 +379,7 @@ index 28ef40e8a645989ea181297069cf2bbe571f3082..56a5fda5f1e2482e1c89c7c2f9c34ea8 this.player = this.level.getNearestPlayer(this.begTargeting, this.wolf); return this.player != null && this.playerHoldingInteresting(this.player); } -@@ -59,14 +125,16 @@ public class BegGoal extends Goal { +@@ -59,14 +105,16 @@ public class BegGoal extends Goal { this.lookTime--; } @@ -382,42 +446,27 @@ index 9954f49bc364969c7ccb37f4186fa2ab8710f6ae..b0a93215a7d5e8fe58331f261d7b4f87 + // Leaf end - Async search block } diff --git a/net/minecraft/world/entity/ai/goal/FollowBoatGoal.java b/net/minecraft/world/entity/ai/goal/FollowBoatGoal.java -index 1fe4d1877ed52017d4d22ddb9b77f78e2b93dff9..1f478e26a743fe6fdeccbe2c1f0efed0387fcb1e 100644 +index 1fe4d1877ed52017d4d22ddb9b77f78e2b93dff9..ec52e6c8110876eb915485c54a8102c4afe460f6 100644 --- a/net/minecraft/world/entity/ai/goal/FollowBoatGoal.java +++ b/net/minecraft/world/entity/ai/goal/FollowBoatGoal.java -@@ -23,8 +23,71 @@ public class FollowBoatGoal extends Goal { +@@ -23,8 +23,54 @@ public class FollowBoatGoal extends Goal { this.mob = mob; } + // Leaf start - Async Target Finding -+ private static final org.apache.logging.log4j.Logger LOGGER = org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER; -+ private final java.util.concurrent.atomic.AtomicBoolean isSearching = new java.util.concurrent.atomic.AtomicBoolean(false); -+ private final java.util.concurrent.atomic.AtomicReference pendingTarget = new java.util.concurrent.atomic.AtomicReference<>(null); -+ + private boolean poll() { -+ Player t = pendingTarget.getAndSet(null); -+ if (t == null) { -+ return false; -+ } ++ if (!(this.mob.getGoalCtx().result() instanceof Player t)) { return false; } + var serverLevel = getServerLevel(this.mob); + return serverLevel != null && t.isAlive() && !t.isSpectator() && (Mth.abs(t.xxa) > 0.0F || Mth.abs(t.zza) > 0.0F); + } + + private void findTargetAsync() { -+ if (!isSearching.compareAndSet(false, true)) { -+ return; -+ } -+ + final PathfinderMob mob = this.mob; -+ if (mob == null || mob.isRemoved() || !mob.isAlive()) { -+ isSearching.setRelease(false); -+ return; -+ } -+ ++ final var ctx = mob.getGoalCtx(); ++ if (!ctx.state) return; + final var bound = mob.getBoundingBox().inflate(5.0); + final var serverLevel = getServerLevel(mob); -+ final var pendingTarget = this.pendingTarget; -+ serverLevel.asyncAITasks.add(() -> { ++ ctx.wake = () -> { + try { + if (mob.level() != serverLevel || mob.isRemoved() || !mob.isAlive()) { + return; @@ -428,16 +477,14 @@ index 1fe4d1877ed52017d4d22ddb9b77f78e2b93dff9..1f478e26a743fe6fdeccbe2c1f0efed0 + Entity controllingPassenger = abstractBoat.getControllingPassenger(); + if (controllingPassenger instanceof Player player + && (Mth.abs(player.xxa) > 0.0F || Mth.abs(player.zza) > 0.0F)) { -+ pendingTarget.setRelease(player); ++ ctx.result = player; + break; + } + } + } catch (Exception e) { -+ LOGGER.error("Exception getting entities", e); -+ } finally { -+ isSearching.setRelease(false); ++ org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER.error("Exception getting entities", e); + } -+ }); ++ }; + } + // Leaf end - Async Target Finding @Override @@ -457,7 +504,7 @@ index 1fe4d1877ed52017d4d22ddb9b77f78e2b93dff9..1f478e26a743fe6fdeccbe2c1f0efed0 List entitiesOfClass = this.mob.level().getEntitiesOfClass(AbstractBoat.class, this.mob.getBoundingBox().inflate(5.0)); boolean flag = false; -@@ -37,7 +100,7 @@ public class FollowBoatGoal extends Goal { +@@ -37,7 +83,7 @@ public class FollowBoatGoal extends Goal { } } @@ -467,19 +514,11 @@ index 1fe4d1877ed52017d4d22ddb9b77f78e2b93dff9..1f478e26a743fe6fdeccbe2c1f0efed0 @Override diff --git a/net/minecraft/world/entity/ai/goal/FollowMobGoal.java b/net/minecraft/world/entity/ai/goal/FollowMobGoal.java -index c9cf985173d3da9e84ce178f161e2fd5fb8ce472..48708da53db05f2668b4e1eb52cd3effa49ea983 100644 +index c9cf985173d3da9e84ce178f161e2fd5fb8ce472..0d123e6bac5748b6c5efd76f4c1e086cbf749783 100644 --- a/net/minecraft/world/entity/ai/goal/FollowMobGoal.java +++ b/net/minecraft/world/entity/ai/goal/FollowMobGoal.java -@@ -36,8 +36,23 @@ public class FollowMobGoal extends Goal { - } - } +@@ -38,6 +38,15 @@ public class FollowMobGoal extends Goal { -+ // Leaf start - Async Follow Mob Finding -+ private final java.util.concurrent.atomic.AtomicBoolean isSearching = new java.util.concurrent.atomic.AtomicBoolean(false); -+ private final java.util.concurrent.atomic.AtomicReference pendingTarget = new java.util.concurrent.atomic.AtomicReference<>(null); -+ private static final org.apache.logging.log4j.Logger LOGGER = org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER; -+ // Leaf end - Async Follow Mob Finding -+ @Override public boolean canUse() { + // Leaf start - Async Follow Mob Finding @@ -494,16 +533,13 @@ index c9cf985173d3da9e84ce178f161e2fd5fb8ce472..48708da53db05f2668b4e1eb52cd3eff List entitiesOfClass = this.mob.level().getEntitiesOfClass(Mob.class, this.mob.getBoundingBox().inflate(this.areaSize), this.followPredicate); if (!entitiesOfClass.isEmpty()) { for (Mob mob : entitiesOfClass) { -@@ -51,6 +66,62 @@ public class FollowMobGoal extends Goal { +@@ -51,6 +60,45 @@ public class FollowMobGoal extends Goal { return false; } + // Leaf start - Async Follow Mob Finding + private boolean poll() { -+ var t = pendingTarget.getAndSet(null); -+ if (t == null) { -+ return false; -+ } ++ if (!(this.mob.getGoalCtx().result() instanceof Mob t)) { return false; } + var serverLevel = getServerLevel(this.mob); + if (serverLevel == null || !t.isAlive() || t.isInvisible()) { + return false; @@ -513,25 +549,13 @@ index c9cf985173d3da9e84ce178f161e2fd5fb8ce472..48708da53db05f2668b4e1eb52cd3eff + } + + private void getFollowingMobAsync() { -+ if (!isSearching.compareAndSet(false, true)) { -+ return; -+ } -+ + final var mob = this.mob; -+ -+ if (mob == null || mob.isRemoved() || !mob.isAlive()) { -+ isSearching.setRelease(false); -+ return; -+ } -+ -+ final double x = mob.getX(); -+ final double y = mob.getEyeY(); -+ final double z = mob.getZ(); ++ final var ctx = this.mob.getGoalCtx(); ++ if (!ctx.state) return; + final var serverLevel = getServerLevel(mob); + final var bound = this.mob.getBoundingBox().inflate(this.areaSize); + final var followPredicate = this.followPredicate; -+ final var pendingTarget = this.pendingTarget; -+ serverLevel.asyncAITasks.add(() -> { ++ ctx.wake = () -> { + try { + if (mob == null || mob.isRemoved() || !mob.isAlive() || mob.level() != serverLevel) { + return; @@ -540,17 +564,15 @@ index c9cf985173d3da9e84ce178f161e2fd5fb8ce472..48708da53db05f2668b4e1eb52cd3eff + if (!entitiesOfClass.isEmpty()) { + for (final Mob follow : entitiesOfClass) { + if (!follow.isInvisible()) { -+ pendingTarget.setRelease(follow); ++ ctx.result = follow; + return; + } + } + } + } catch (Exception e) { -+ LOGGER.error("Exception getting entities", e); -+ } finally { -+ isSearching.setRelease(false); ++ org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER.error("Exception getting entities", e); + } -+ }); ++ }; + } + // Leaf end - Async Follow Mob Finding + @@ -558,22 +580,16 @@ index c9cf985173d3da9e84ce178f161e2fd5fb8ce472..48708da53db05f2668b4e1eb52cd3eff public boolean canContinueToUse() { return this.followingMob != null && !this.navigation.isDone() && this.mob.distanceToSqr(this.followingMob) > this.stopDistance * this.stopDistance; diff --git a/net/minecraft/world/entity/ai/goal/FollowParentGoal.java b/net/minecraft/world/entity/ai/goal/FollowParentGoal.java -index 3093f03d4f298bf39fec8bad2b6c22518774aea8..7d97345e8a5c630bf53cce3bd543e46e0b596237 100644 +index 3093f03d4f298bf39fec8bad2b6c22518774aea8..ee42674761653cacc5045fcb60e314fbe25f390f 100644 --- a/net/minecraft/world/entity/ai/goal/FollowParentGoal.java +++ b/net/minecraft/world/entity/ai/goal/FollowParentGoal.java -@@ -19,11 +19,84 @@ public class FollowParentGoal extends Goal { +@@ -19,11 +19,68 @@ public class FollowParentGoal extends Goal { this.speedModifier = speedModifier; } + // Leaf start - Async Target Finding -+ private static final org.apache.logging.log4j.Logger LOGGER = org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER; -+ private final java.util.concurrent.atomic.AtomicBoolean isSearching = new java.util.concurrent.atomic.AtomicBoolean(false); -+ private final java.util.concurrent.atomic.AtomicReference pendingTarget = new java.util.concurrent.atomic.AtomicReference<>(null); + protected boolean poll() { -+ var t = pendingTarget.getAndSet(null); -+ if (t == null) { -+ return false; -+ } ++ if (!(this.animal.getGoalCtx().result() instanceof Animal t)) { return false; } + var serverLevel = getServerLevel(animal); + if (serverLevel == null || !t.isAlive() || animal.distanceToSqr(t) < 9.0) { + return false; @@ -583,22 +599,14 @@ index 3093f03d4f298bf39fec8bad2b6c22518774aea8..7d97345e8a5c630bf53cce3bd543e46e + } + + private void findTargetAsync() { -+ if (!isSearching.compareAndSet(false, true)) { -+ return; -+ } -+ + final Animal animal = this.animal; -+ if (animal == null || animal.isRemoved() || !animal.isAlive()) { -+ isSearching.setRelease(false); -+ return; -+ } -+ ++ final var ctx = animal.getGoalCtx(); ++ if (!ctx.state) return; + final var targetType = animal.getClass(); + final var bound = animal.getBoundingBox().inflate(8.0, 4.0, 8.0); + final var serverLevel = getServerLevel(animal); + final var pos = animal.position(); -+ final var pendingTarget = this.pendingTarget; -+ serverLevel.asyncAITasks.add(() -> { ++ ctx.wake = () -> { + try { + if (animal.level() != serverLevel || animal.isRemoved() || !animal.isAlive()) { + return; @@ -618,14 +626,12 @@ index 3093f03d4f298bf39fec8bad2b6c22518774aea8..7d97345e8a5c630bf53cce3bd543e46e + } + } + if (target != null) { -+ pendingTarget.setRelease(target); ++ ctx.result = target; + } + } catch (Exception e) { -+ LOGGER.error("Exception getting entities", e); -+ } finally { -+ isSearching.setRelease(false); ++ org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER.error("Exception getting entities", e); + } -+ }); ++ }; + } + // Leaf end - Async Target Finding + @@ -646,7 +652,7 @@ index 3093f03d4f298bf39fec8bad2b6c22518774aea8..7d97345e8a5c630bf53cce3bd543e46e List entitiesOfClass = this.animal .level() .getEntitiesOfClass((Class)this.animal.getClass(), this.animal.getBoundingBox().inflate(8.0, 4.0, 8.0)); -@@ -43,6 +116,7 @@ public class FollowParentGoal extends Goal { +@@ -43,6 +100,7 @@ public class FollowParentGoal extends Goal { if (animal == null) { return false; } else if (d < 9.0) { @@ -654,23 +660,151 @@ index 3093f03d4f298bf39fec8bad2b6c22518774aea8..7d97345e8a5c630bf53cce3bd543e46e return false; } else { this.parent = animal; +diff --git a/net/minecraft/world/entity/ai/goal/GoalSelector.java b/net/minecraft/world/entity/ai/goal/GoalSelector.java +index e82e32407cec6109b9c3b0106295217f4a3f4aa2..9d0d0a788b435f014496e37372730258e3e33014 100644 +--- a/net/minecraft/world/entity/ai/goal/GoalSelector.java ++++ b/net/minecraft/world/entity/ai/goal/GoalSelector.java +@@ -3,7 +3,6 @@ package net.minecraft.world.entity.ai.goal; + import com.google.common.annotations.VisibleForTesting; + import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet; + import java.util.EnumMap; +-import java.util.EnumSet; + import java.util.Map; + import java.util.Set; + import java.util.function.Predicate; +@@ -26,6 +25,13 @@ public class GoalSelector { + private final ca.spottedleaf.moonrise.common.set.OptimizedSmallEnumSet goalTypes = new ca.spottedleaf.moonrise.common.set.OptimizedSmallEnumSet<>(Goal.Flag.class); // Paper - remove streams from GoalSelector + private int curRate; // Paper - EAR 2 + ++ // Leaf start - Async target finding ++ public WrappedGoal @org.jetbrains.annotations.Nullable[] ctxGoals = null; ++ private int ctxIndex = 0; ++ private int ctxState = 0; ++ public final org.dreeam.leaf.async.ai.Waker ctx = new org.dreeam.leaf.async.ai.Waker(); ++ // Leaf end - Async target finding ++ + public void addGoal(int priority, Goal goal) { + this.availableGoals.add(new WrappedGoal(priority, goal)); + } +@@ -85,7 +91,99 @@ public class GoalSelector { + return true; + } + ++ public final boolean poll() { ++ if (this.ctxGoals == null || ctx.wake != null) { ++ return false; ++ } ++ if (ctxState == 0) { ++ while (ctxIndex < this.ctxGoals.length) { ++ WrappedGoal goal = this.ctxGoals[ctxIndex]; ++ // todo TemptGoal ++ if (goal.isRunning() && (goalContainsAnyFlags(goal, this.goalTypes) || !goal.canContinueToUse())) { ++ goal.stop(); ++ } ++ ctxIndex++; ++ } ++ ++ this.lockedFlags.entrySet().removeIf(entry -> !entry.getValue().isRunning()); ++ ++ ctxIndex = 0; ++ ctx.state = true; ++ ctxState = 1; ++ } ++ if (ctxState == 1) { ++ while (ctxIndex < this.ctxGoals.length) { ++ WrappedGoal goal = this.ctxGoals[ctxIndex]; ++ if (!goal.isRunning()) { ++ if (!goalContainsAnyFlags(goal, this.goalTypes) ++ && goalCanBeReplacedForAllFlags(goal, this.lockedFlags)) { ++ if (goal.canUse()) { ++ long flagIterator = goal.getFlags().getBackingSet(); ++ int wrappedGoalSize = goal.getFlags().size(); ++ for (int i = 0; i < wrappedGoalSize; ++i) { ++ final Goal.Flag flag = GOAL_FLAG_VALUES[Long.numberOfTrailingZeros(flagIterator)]; ++ flagIterator ^= ca.spottedleaf.concurrentutil.util.IntegerUtil.getTrailingBit(flagIterator); ++ // Paper end ++ WrappedGoal wrappedGoal1 = this.lockedFlags.getOrDefault(flag, NO_GOAL); ++ wrappedGoal1.stop(); ++ this.lockedFlags.put(flag, goal); ++ } ++ ++ goal.start(); ++ } ++ ctx.state = false; ++ if (ctx.wake != null) { ++ return true; ++ } ++ } ++ } else if (!ctx.state) { ++ if (goal.getGoal() instanceof net.minecraft.world.entity.ai.goal.target.ResetUniversalAngerTargetGoal resetUniversalAngerTargetGoal) { ++ resetUniversalAngerTargetGoal.poll(); ++ } else if (goal.getGoal() instanceof net.minecraft.world.entity.ai.goal.target.HurtByTargetGoal hurtByTargetGoal) { ++ hurtByTargetGoal.poll(); ++ } ++ } ++ ctxIndex++; ++ ctx.state = true; ++ } ++ ++ ctxIndex = 0; ++ ctx.state = true; ++ ctxState = 2; ++ } ++ if (ctxState == 2) { ++ while (ctxIndex < this.ctxGoals.length) { ++ WrappedGoal goal = this.ctxGoals[ctxIndex]; ++ if (goal.isRunning()) { ++ goal.tick(); ++ } ++ ctxIndex++; ++ } ++ ++ ctxGoals = null; ++ ctxState = 0; ++ ctxIndex = 0; ++ ctx.state = true; ++ } ++ return false; ++ } ++ ++ public final void wake() { ++ Runnable wake = ctx.wake; ++ if (wake != null) { ++ wake.run(); ++ } ++ ctx.wake = null; ++ } ++ + public void tick() { ++ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled) { ++ if (this.ctxGoals == null) { ++ this.ctxGoals = this.availableGoals.toArray(new WrappedGoal[0]); ++ } ++ return; ++ } ++ + for (WrappedGoal wrappedGoal : this.availableGoals) { + if (wrappedGoal.isRunning() && (goalContainsAnyFlags(wrappedGoal, this.goalTypes) || !wrappedGoal.canContinueToUse())) { // Paper - Perf: optimize goal types by removing streams + wrappedGoal.stop(); +@@ -94,6 +192,7 @@ public class GoalSelector { + + this.lockedFlags.entrySet().removeIf(entry -> !entry.getValue().isRunning()); + ++ + for (WrappedGoal wrappedGoalx : this.availableGoals) { + // Paper start + if (!wrappedGoalx.isRunning() && !goalContainsAnyFlags(wrappedGoalx, this.goalTypes) && goalCanBeReplacedForAllFlags(wrappedGoalx, this.lockedFlags) && wrappedGoalx.canUse()) { diff --git a/net/minecraft/world/entity/ai/goal/LlamaFollowCaravanGoal.java b/net/minecraft/world/entity/ai/goal/LlamaFollowCaravanGoal.java -index be59d0c27a83b329ec3f97c029cfb9c114e22472..23c5e933ccb98a38228810953a7e2447d5115c84 100644 +index be59d0c27a83b329ec3f97c029cfb9c114e22472..239567f1ac82e17ec8f0624f1a33c1faa7ad13fe 100644 --- a/net/minecraft/world/entity/ai/goal/LlamaFollowCaravanGoal.java +++ b/net/minecraft/world/entity/ai/goal/LlamaFollowCaravanGoal.java -@@ -20,19 +20,110 @@ public class LlamaFollowCaravanGoal extends Goal { +@@ -20,19 +20,94 @@ public class LlamaFollowCaravanGoal extends Goal { this.setFlags(EnumSet.of(Goal.Flag.MOVE)); } + // Leaf start - Async Target Finding -+ private static final org.apache.logging.log4j.Logger LOGGER = org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER; -+ private final java.util.concurrent.atomic.AtomicBoolean isSearching = new java.util.concurrent.atomic.AtomicBoolean(false); -+ private final java.util.concurrent.atomic.AtomicReference pendingTarget = new java.util.concurrent.atomic.AtomicReference<>(null); + private @javax.annotation.Nullable Llama poll() { -+ var t = pendingTarget.getAndSet(null); -+ if (t == null) { -+ return null; -+ } ++ if (!(this.llama.getGoalCtx().result() instanceof Llama t)) { return null; } + var serverLevel = getServerLevel(this.llama); + if (serverLevel == null || !t.isAlive()) { + return null; @@ -679,21 +813,13 @@ index be59d0c27a83b329ec3f97c029cfb9c114e22472..23c5e933ccb98a38228810953a7e2447 + } + + private void findTargetAsync() { -+ if (!isSearching.compareAndSet(false, true)) { -+ return; -+ } -+ + final Llama llama = this.llama; -+ if (llama == null || llama.isRemoved() || !llama.isAlive()) { -+ isSearching.setRelease(false); -+ return; -+ } -+ ++ final var ctx = llama.getGoalCtx(); ++ if (!ctx.state) return; + final var bound = llama.getBoundingBox().inflate(9.0, 4.0, 9.0); + final var serverLevel = getServerLevel(llama); + final var pos = llama.position(); -+ final var pendingTarget = this.pendingTarget; -+ serverLevel.asyncAITasks.add(() -> { ++ ctx.wake = () -> { + try { + if (llama.level() != serverLevel || llama.isRemoved() || !llama.isAlive()) { + return; @@ -730,14 +856,12 @@ index be59d0c27a83b329ec3f97c029cfb9c114e22472..23c5e933ccb98a38228810953a7e2447 + } + } + if (target != null) { -+ pendingTarget.setRelease(target); ++ ctx.result = target; + } + } catch (Exception e) { -+ LOGGER.error("Exception getting entities", e); -+ } finally { -+ isSearching.setRelease(false); ++ org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER.error("Exception getting entities", e); + } -+ }); ++ }; + } + // Leaf end - Async Target Finding @Override @@ -771,7 +895,7 @@ index be59d0c27a83b329ec3f97c029cfb9c114e22472..23c5e933ccb98a38228810953a7e2447 if (llama1.inCaravan() && !llama1.hasCaravanTail()) { double d1 = this.llama.distanceToSqr(llama1); if (!(d1 > d)) { -@@ -45,6 +136,7 @@ public class LlamaFollowCaravanGoal extends Goal { +@@ -45,6 +120,7 @@ public class LlamaFollowCaravanGoal extends Goal { if (llama == null) { for (Entity entityx : entities) { Llama llama1 = (Llama)entityx; @@ -779,7 +903,7 @@ index be59d0c27a83b329ec3f97c029cfb9c114e22472..23c5e933ccb98a38228810953a7e2447 if (llama1.isLeashed() && !llama1.hasCaravanTail()) { double d1 = this.llama.distanceToSqr(llama1); if (!(d1 > d)) { -@@ -54,6 +146,7 @@ public class LlamaFollowCaravanGoal extends Goal { +@@ -54,6 +130,7 @@ public class LlamaFollowCaravanGoal extends Goal { } } } @@ -788,10 +912,10 @@ index be59d0c27a83b329ec3f97c029cfb9c114e22472..23c5e933ccb98a38228810953a7e2447 if (llama == null) { return false; diff --git a/net/minecraft/world/entity/ai/goal/LookAtPlayerGoal.java b/net/minecraft/world/entity/ai/goal/LookAtPlayerGoal.java -index 6463c3c9b08d6058f2843c225b08a40fc30a960b..0f39eca165f3eae1b05d1e0548f63467bc21bbf5 100644 +index 6463c3c9b08d6058f2843c225b08a40fc30a960b..819f7947b0c9c546cf45b2effe72cfb597a99ce6 100644 --- a/net/minecraft/world/entity/ai/goal/LookAtPlayerGoal.java +++ b/net/minecraft/world/entity/ai/goal/LookAtPlayerGoal.java -@@ -48,32 +48,123 @@ public class LookAtPlayerGoal extends Goal { +@@ -48,32 +48,94 @@ public class LookAtPlayerGoal extends Goal { @Override public boolean canUse() { @@ -802,19 +926,34 @@ index 6463c3c9b08d6058f2843c225b08a40fc30a960b..0f39eca165f3eae1b05d1e0548f63467 + if (this.mob.getRandom().nextFloat() >= this.probability) { return false; -- } else { -- if (this.mob.getTarget() != null) { -- this.lookAt = this.mob.getTarget(); + } -+ if (this.lookAtType == Player.class) { -+ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchPlayer) { -+ getLookAsync(); -+ return false; - } -+ } else if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchEntity) { ++ if (this.mob.getTarget() != null) { ++ this.lookAt = this.mob.getTarget(); ++ } ++ ++ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchEntity) { + getLookAsync(); + return false; + } ++ ++ ServerLevel serverLevel = getServerLevel(this.mob); ++ if (this.lookAtType == Player.class) { ++ this.lookAt = serverLevel.getNearestPlayer(this.lookAtContext, this.mob, this.mob.getX(), this.mob.getEyeY(), this.mob.getZ()); + } else { +- if (this.mob.getTarget() != null) { +- this.lookAt = this.mob.getTarget(); +- } ++ this.lookAt = serverLevel.getNearestEntity( ++ this.mob ++ .level() ++ .getEntitiesOfClass(this.lookAtType, this.mob.getBoundingBox().inflate(this.lookDistance, 3.0, this.lookDistance), livingEntity -> true), ++ this.lookAtContext, ++ this.mob, ++ this.mob.getX(), ++ this.mob.getEyeY(), ++ this.mob.getZ() ++ ); ++ } - ServerLevel serverLevel = getServerLevel(this.mob); - if (this.lookAtType == Player.class) { @@ -831,40 +970,14 @@ index 6463c3c9b08d6058f2843c225b08a40fc30a960b..0f39eca165f3eae1b05d1e0548f63467 - this.mob.getZ() - ); - } -+ if (this.mob.getTarget() != null) { -+ this.lookAt = this.mob.getTarget(); -+ } -+ -+ ServerLevel serverLevel = getServerLevel(this.mob); -+ if (this.lookAtType == Player.class) { -+ this.lookAt = serverLevel.getNearestPlayer(this.lookAtContext, this.mob, this.mob.getX(), this.mob.getEyeY(), this.mob.getZ()); -+ } else { -+ this.lookAt = serverLevel.getNearestEntity( -+ this.mob -+ .level() -+ .getEntitiesOfClass(this.lookAtType, this.mob.getBoundingBox().inflate(this.lookDistance, 3.0, this.lookDistance), livingEntity -> true), -+ this.lookAtContext, -+ this.mob, -+ this.mob.getX(), -+ this.mob.getEyeY(), -+ this.mob.getZ() -+ ); -+ } -+ + return this.lookAt != null; + // Leaf end - Async look finding + } - return this.lookAt != null; + // Leaf start - Async look finding -+ private final java.util.concurrent.atomic.AtomicBoolean isSearching = new java.util.concurrent.atomic.AtomicBoolean(false); -+ private final java.util.concurrent.atomic.AtomicReference pendingTarget = new java.util.concurrent.atomic.AtomicReference<>(null); -+ private static final org.apache.logging.log4j.Logger LOGGER = org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER; + protected boolean poll() { -+ LivingEntity t = pendingTarget.getAndSet(null); -+ if (t == null) { -+ return false; -+ } ++ if (!(this.mob.getGoalCtx().result() instanceof LivingEntity t)) { return false; } + if (this.mob.getTarget() != null) { + this.lookAt = this.mob.getTarget(); + return true; @@ -872,47 +985,32 @@ index 6463c3c9b08d6058f2843c225b08a40fc30a960b..0f39eca165f3eae1b05d1e0548f63467 + ServerLevel serverLevel = getServerLevel(this.mob); + if (!t.isAlive() || !this.lookAtContext.test(serverLevel, this.mob, t)) { + return false; -+ } + } + this.lookAt = t; + return true; + } + + protected void getLookAsync() { -+ if (!isSearching.compareAndSet(false, true)) { -+ return; - } -+ + final var mob = this.mob; -+ -+ if (mob == null || mob.isRemoved() || !mob.isAlive()) { -+ isSearching.setRelease(false); -+ return; -+ } -+ ++ final var ctx = mob.getGoalCtx(); ++ if (!ctx.state) return; + final double x = mob.getX(); + final double y = mob.getEyeY(); + final double z = mob.getZ(); + final var serverLevel = getServerLevel(mob); + final var bound = mob.getBoundingBox().inflate(this.lookDistance, 3.0, this.lookDistance); -+ final var target = mob.getTarget(); + final var lookAtContext = this.lookAtContext; + final var lookAtType = this.lookAtType; -+ final var pendingTarget = this.pendingTarget; -+ serverLevel.asyncAITasks.add(() -> { ++ ctx.wake = () -> { + try { + if (mob == null || !mob.isAlive() || mob.level() != serverLevel) { + return; + } -+ if (target != null) { -+ pendingTarget.setRelease(target); -+ return; -+ } + + if (lookAtType == Player.class) { -+ var result = serverLevel.getNearestPlayer(lookAtContext, mob, x, y, z); -+ pendingTarget.setRelease(result); ++ ctx.result = serverLevel.getNearestPlayer(lookAtContext, mob, x, y, z); + } else { -+ var result = serverLevel.getNearestEntity( ++ ctx.result = serverLevel.getNearestEntity( + serverLevel + .getEntitiesOfClass(lookAtType, bound, livingEntity -> true), + lookAtContext, @@ -921,36 +1019,27 @@ index 6463c3c9b08d6058f2843c225b08a40fc30a960b..0f39eca165f3eae1b05d1e0548f63467 + y, + z + ); -+ pendingTarget.setRelease(result); + } + } catch (Exception e) { -+ LOGGER.error("Exception getting entities", e); -+ } finally { -+ isSearching.setRelease(false); ++ org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER.error("Exception getting entities", e); + } -+ }); ++ }; } + // Leaf end - Async look finding @Override public boolean canContinueToUse() { diff --git a/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java b/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java -index 3f080b15543bf8c5fa0774b62d7f12e13b82511a..6ff92396c6c97f4185e287f04eb197842d8b5fec 100644 +index 3f080b15543bf8c5fa0774b62d7f12e13b82511a..99e6e7bac2b5af1b1abb1de6b51aed1f44e5e8b0 100644 --- a/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java +++ b/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java -@@ -41,8 +41,90 @@ public abstract class MoveToBlockGoal extends Goal { +@@ -41,8 +41,69 @@ public abstract class MoveToBlockGoal extends Goal { this.setFlags(EnumSet.of(Goal.Flag.MOVE, Goal.Flag.JUMP)); } + // Leaf start - Async search block -+ private final java.util.concurrent.atomic.AtomicBoolean isSearching = new java.util.concurrent.atomic.AtomicBoolean(false); -+ private final java.util.concurrent.atomic.AtomicReference pendingBlock = new java.util.concurrent.atomic.AtomicReference<>(null); -+ private static final org.apache.logging.log4j.Logger LOGGER = org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER; + protected boolean poll() { -+ BlockPos blockPos1 = pendingBlock.getAndSet(null); -+ if (blockPos1 == null) { -+ return false; -+ } ++ if (!(this.mob.getGoalCtx().result() instanceof BlockPos blockPos1)) { return false; } + if (!this.mob.level().hasChunkAt(blockPos1) + || !this.mob.isWithinRestriction(blockPos1) + || !this.isValidTarget(this.mob.level(), blockPos1)) { @@ -962,23 +1051,10 @@ index 3f080b15543bf8c5fa0774b62d7f12e13b82511a..6ff92396c6c97f4185e287f04eb19784 + } + + protected void getBlockAsync() { -+ if (!isSearching.compareAndSet(false, true)) { -+ return; -+ } -+ + final var mob = this.mob; -+ -+ if (mob == null || mob.isRemoved() || !mob.isAlive()) { -+ isSearching.setRelease(false); -+ return; -+ } -+ -+ final double x = mob.getX(); -+ final double y = mob.getEyeY(); -+ final double z = mob.getZ(); ++ final var ctx = mob.getGoalCtx(); ++ if (!ctx.state) return; + final var serverLevel = getServerLevel(mob); -+ -+ final java.util.concurrent.atomic.AtomicReference pendingBlock = this.pendingBlock; + final TypeToCheck ty = this.typeToCheck(); + final net.minecraft.world.level.block.Block toRemove; + if (this instanceof RemoveBlockGoal removeBlockGoal) { @@ -992,18 +1068,16 @@ index 3f080b15543bf8c5fa0774b62d7f12e13b82511a..6ff92396c6c97f4185e287f04eb19784 + final BlockPos blockPos = mob.blockPosition(); + final float restrictRadius = mob.getRestrictRadius(); + final BlockPos restrictCenter = mob.getRestrictCenter(); -+ serverLevel.asyncAITasks.add(() -> { ++ ctx.wake = () -> { + try { + if (mob == null || !mob.isAlive() || mob.level() != serverLevel) { + return; + } -+ findNearestBlockAsync(pendingBlock, ty, toRemove, mob, serverLevel, verticalSearchStart, searchRange, verticalSearchRange, blockPos, restrictRadius, restrictCenter); ++ findNearestBlockAsync(ctx, ty, toRemove, mob, serverLevel, verticalSearchStart, searchRange, verticalSearchRange, blockPos, restrictRadius, restrictCenter); + } catch (Exception e) { -+ LOGGER.error("Exception getting block", e); -+ } finally { -+ isSearching.setRelease(false); ++ org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER.error("Exception getting block", e); + } -+ }); ++ }; + } + + protected enum TypeToCheck { @@ -1029,7 +1103,7 @@ index 3f080b15543bf8c5fa0774b62d7f12e13b82511a..6ff92396c6c97f4185e287f04eb19784 if (this.nextStartTick > 0) { this.nextStartTick--; return false; -@@ -109,6 +191,10 @@ public abstract class MoveToBlockGoal extends Goal { +@@ -109,6 +170,10 @@ public abstract class MoveToBlockGoal extends Goal { } protected boolean findNearestBlock() { @@ -1040,12 +1114,12 @@ index 3f080b15543bf8c5fa0774b62d7f12e13b82511a..6ff92396c6c97f4185e287f04eb19784 int i = this.searchRange; int i1 = this.verticalSearchRange; BlockPos blockPos = this.mob.blockPosition(); -@@ -133,5 +219,104 @@ public abstract class MoveToBlockGoal extends Goal { +@@ -133,5 +198,104 @@ public abstract class MoveToBlockGoal extends Goal { return false; } + protected static boolean findNearestBlockAsync( -+ final java.util.concurrent.atomic.AtomicReference pendingBlock, ++ final org.dreeam.leaf.async.ai.Waker ctx, + final TypeToCheck ty, + @org.jetbrains.annotations.Nullable final net.minecraft.world.level.block.Block toRemove, + final PathfinderMob mob, @@ -1066,7 +1140,7 @@ index 3f080b15543bf8c5fa0774b62d7f12e13b82511a..6ff92396c6c97f4185e287f04eb19784 + if (!serverLevel.hasChunkAt(mutableBlockPos)) continue; + if (isWithinRestriction(restrictRadius, restrictCenter, mutableBlockPos) + && isValidTargetAsync(ty, toRemove, mob, serverLevel, mutableBlockPos)) { -+ pendingBlock.setRelease(mutableBlockPos.immutable()); ++ ctx.result = mutableBlockPos.immutable(); + return true; + } + } @@ -1146,7 +1220,7 @@ index 3f080b15543bf8c5fa0774b62d7f12e13b82511a..6ff92396c6c97f4185e287f04eb19784 + } } diff --git a/net/minecraft/world/entity/ai/goal/OfferFlowerGoal.java b/net/minecraft/world/entity/ai/goal/OfferFlowerGoal.java -index 3c274d917bca9de87abfb842f5f332e112a7a2d7..f001bd12a2ec5696b8ee628484597de09b2eed32 100644 +index 3c274d917bca9de87abfb842f5f332e112a7a2d7..aa743d8f4ec0c1d78e5e58a7b5e36bca08b8b2ef 100644 --- a/net/minecraft/world/entity/ai/goal/OfferFlowerGoal.java +++ b/net/minecraft/world/entity/ai/goal/OfferFlowerGoal.java @@ -19,10 +19,20 @@ public class OfferFlowerGoal extends Goal { @@ -1162,29 +1236,23 @@ index 3c274d917bca9de87abfb842f5f332e112a7a2d7..f001bd12a2ec5696b8ee628484597de0 + return true; + } + if (this.golem.getRandom().nextInt(8000) != 0) { - return false; ++ return false; + } + if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchEntity) { + getVillagerAsync(); -+ return false; + return false; + // Leaf end - Async offer flower finding } else { this.villager = getServerLevel(this.golem) .getNearestEntity( -@@ -38,6 +48,65 @@ public class OfferFlowerGoal extends Goal { +@@ -38,6 +48,47 @@ public class OfferFlowerGoal extends Goal { } } + + // Leaf start - Async look finding -+ private final java.util.concurrent.atomic.AtomicBoolean isSearching = new java.util.concurrent.atomic.AtomicBoolean(false); -+ private final java.util.concurrent.atomic.AtomicReference pendingTarget = new java.util.concurrent.atomic.AtomicReference<>(null); -+ private static final org.apache.logging.log4j.Logger LOGGER = org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER; + protected boolean poll() { -+ Villager t = pendingTarget.getAndSet(null); -+ if (t == null) { -+ return false; -+ } ++ if (!(this.golem.getGoalCtx().result() instanceof Villager t)) { return false; } + var serverLevel = getServerLevel(this.golem); + if (!t.isAlive() || !OFFER_TARGER_CONTEXT.test(serverLevel, this.golem, t)) { + return false; @@ -1194,29 +1262,20 @@ index 3c274d917bca9de87abfb842f5f332e112a7a2d7..f001bd12a2ec5696b8ee628484597de0 + } + + protected void getVillagerAsync() { -+ if (!isSearching.compareAndSet(false, true)) { -+ return; -+ } -+ + final var golem = this.golem; -+ -+ if (golem == null || golem.isRemoved() || !golem.isAlive()) { -+ isSearching.setRelease(false); -+ return; -+ } -+ ++ final var ctx = golem.getGoalCtx(); ++ if (!ctx.state) return; + final double x = golem.getX(); + final double y = golem.getEyeY(); + final double z = golem.getZ(); + final var serverLevel = getServerLevel(golem); + final var bound = golem.getBoundingBox().inflate(6.0, 2.0, 6.0); -+ final var pendingTarget = this.pendingTarget; -+ serverLevel.asyncAITasks.add(() -> { ++ ctx.wake = () -> { + try { + if (golem == null || !golem.isAlive() || golem.level() != serverLevel) { + return; + } -+ var result = serverLevel.getNearestEntity( ++ ctx.result = serverLevel.getNearestEntity( + serverLevel.getEntitiesOfClass(Villager.class, bound, livingEntity -> true), + OFFER_TARGER_CONTEXT, + golem, @@ -1224,13 +1283,10 @@ index 3c274d917bca9de87abfb842f5f332e112a7a2d7..f001bd12a2ec5696b8ee628484597de0 + y, + z + ); -+ pendingTarget.setRelease(result); + } catch (Exception e) { -+ LOGGER.error("Exception getting entities", e); -+ } finally { -+ isSearching.setRelease(false); ++ org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER.error("Exception getting entities", e); + } -+ }); ++ }; + } + // Leaf end - Async look finding + @@ -1286,28 +1342,18 @@ index 95fa516910a3834bbd4db6d11279e13a1f0dac41..f7ddae601abbe9e22a35c7cb4f9763e6 + // Leaf end - Async search block } diff --git a/net/minecraft/world/entity/ai/goal/TemptGoal.java b/net/minecraft/world/entity/ai/goal/TemptGoal.java -index f88f618d34fb343b31de3af1a875d6633703df71..7c6d90ed1878b29904ceaca1089eaadd05a0d570 100644 +index f88f618d34fb343b31de3af1a875d6633703df71..d7b026018adcc8d0c88f8a0561bdc13d0fbdeebc 100644 --- a/net/minecraft/world/entity/ai/goal/TemptGoal.java +++ b/net/minecraft/world/entity/ai/goal/TemptGoal.java -@@ -36,14 +36,84 @@ public class TemptGoal extends Goal { +@@ -36,14 +36,68 @@ public class TemptGoal extends Goal { this.targetingConditions = TEMPT_TARGETING.copy().selector((entity, level) -> this.shouldFollow(entity)); } + // Leaf start - Async Tempt Finding -+ private final java.util.concurrent.atomic.AtomicBoolean isSearching = new java.util.concurrent.atomic.AtomicBoolean(false); -+ private final java.util.concurrent.atomic.AtomicReference pendingTarget = new java.util.concurrent.atomic.AtomicReference<>(null); -+ private static final org.apache.logging.log4j.Logger LOGGER = org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER; -+ + private boolean poll() { -+ LivingEntity t = pendingTarget.getAndSet(null); -+ if (t == null) { -+ t = this.player; -+ } -+ if (t == null) { -+ return false; -+ } ++ if (!(this.mob.getGoalCtx().result() instanceof Player t)) { return false; } + var serverLevel = getServerLevel(this.mob); -+ if (serverLevel == null || !t.isAlive() || !this.targetingConditions.range(this.mob.getAttributeValue(Attributes.TEMPT_RANGE)).test(serverLevel, this.mob, t)) { ++ if (!t.isAlive() || !this.targetingConditions.range(this.mob.getAttributeValue(Attributes.TEMPT_RANGE)).test(serverLevel, this.mob, t)) { + return false; + } + this.player = t; @@ -1315,38 +1361,43 @@ index f88f618d34fb343b31de3af1a875d6633703df71..7c6d90ed1878b29904ceaca1089eaadd + } + + private void getNearestPlayerAsync() { -+ if (!isSearching.compareAndSet(false, true)) { -+ return; -+ } -+ + final var mob = this.mob; -+ -+ if (mob == null || mob.isRemoved() || !mob.isAlive()) { -+ isSearching.setRelease(false); -+ return; -+ } -+ ++ final var ctx = mob.getGoalCtx(); ++ if (!ctx.state) return; + final var serverLevel = getServerLevel(mob); -+ final var pendingTarget = this.pendingTarget; + final var conditions = this.targetingConditions + .range(mob.getAttributeValue(Attributes.TEMPT_RANGE)) + .copy(); -+ serverLevel.asyncAITasks.add(() -> { ++ ctx.wake = () -> { + try { + if (mob == null || mob.isRemoved() || !mob.isAlive() || mob.level() != serverLevel) { + return; + } -+ pendingTarget.setRelease(serverLevel.getNearestPlayer(conditions, mob)); ++ ctx.result = serverLevel.getNearestPlayer(conditions, mob); + } catch (Exception e) { -+ LOGGER.error("Exception getting player", e); -+ } finally { -+ isSearching.setRelease(false); ++ org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER.error("Exception getting player", e); + } -+ }); ++ }; + } + // Leaf end - Async Tempt Finding @Override public boolean canUse() { ++ // Leaf start - Async Tempt Finding ++ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchEntity) { ++ if (poll()) { ++ if (this.player != null) { ++ org.bukkit.event.entity.EntityTargetLivingEntityEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTargetLivingEvent(this.mob, this.player, org.bukkit.event.entity.EntityTargetEvent.TargetReason.TEMPT); ++ if (event.isCancelled()) { ++ return false; ++ } ++ this.player = (event.getTarget() == null) ? null : ((org.bukkit.craftbukkit.entity.CraftLivingEntity) event.getTarget()).getHandle(); ++ } ++ if (this.player != null) { ++ return true; ++ } ++ } ++ } ++ // Leaf end - Async Tempt Finding if (this.calmDown > 0) { this.calmDown--; return false; @@ -1354,20 +1405,9 @@ index f88f618d34fb343b31de3af1a875d6633703df71..7c6d90ed1878b29904ceaca1089eaadd - this.player = getServerLevel(this.mob) - .getNearestPlayer(this.targetingConditions.range(this.mob.getAttributeValue(Attributes.TEMPT_RANGE)), this.mob); + // Leaf start - Async Tempt Finding -+ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchPlayerTempt) { -+ if (poll()) { -+ if (this.player != null) { -+ org.bukkit.event.entity.EntityTargetLivingEntityEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTargetLivingEvent(this.mob, this.player, org.bukkit.event.entity.EntityTargetEvent.TargetReason.TEMPT); -+ if (event.isCancelled()) { -+ return false; -+ } -+ this.player = (event.getTarget() == null) ? null : ((org.bukkit.craftbukkit.entity.CraftLivingEntity) event.getTarget()).getHandle(); -+ } -+ return this.player != null; -+ } else { -+ getNearestPlayerAsync(); -+ return false; -+ } ++ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchEntity) { ++ getNearestPlayerAsync(); ++ return false; + } else { + this.player = getServerLevel(this.mob) + .getNearestPlayer(this.targetingConditions.range(this.mob.getAttributeValue(Attributes.TEMPT_RANGE)), this.mob); @@ -1377,7 +1417,7 @@ index f88f618d34fb343b31de3af1a875d6633703df71..7c6d90ed1878b29904ceaca1089eaadd if (this.player != null) { org.bukkit.event.entity.EntityTargetLivingEntityEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTargetLivingEvent(this.mob, this.player, org.bukkit.event.entity.EntityTargetEvent.TargetReason.TEMPT); diff --git a/net/minecraft/world/entity/ai/goal/target/DefendVillageTargetGoal.java b/net/minecraft/world/entity/ai/goal/target/DefendVillageTargetGoal.java -index 4644f3f7af89623ca6218c0dd24bb6cd67db553b..04bc007a138d5f5875fa89f52d573693fa96c656 100644 +index 4644f3f7af89623ca6218c0dd24bb6cd67db553b..921418da5f026edad4c78ba51127bb758c34bb9a 100644 --- a/net/minecraft/world/entity/ai/goal/target/DefendVillageTargetGoal.java +++ b/net/minecraft/world/entity/ai/goal/target/DefendVillageTargetGoal.java @@ -16,7 +16,7 @@ public class DefendVillageTargetGoal extends TargetGoal { @@ -1389,19 +1429,13 @@ index 4644f3f7af89623ca6218c0dd24bb6cd67db553b..04bc007a138d5f5875fa89f52d573693 public DefendVillageTargetGoal(IronGolem golem) { super(golem, false, true); -@@ -24,8 +24,82 @@ public class DefendVillageTargetGoal extends TargetGoal { +@@ -24,8 +24,63 @@ public class DefendVillageTargetGoal extends TargetGoal { this.setFlags(EnumSet.of(Goal.Flag.TARGET)); } + // Leaf start - Async Target Finding -+ private static final org.apache.logging.log4j.Logger LOGGER = org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER; -+ private final java.util.concurrent.atomic.AtomicBoolean isSearching = new java.util.concurrent.atomic.AtomicBoolean(false); -+ private final java.util.concurrent.atomic.AtomicReference pendingTarget = new java.util.concurrent.atomic.AtomicReference<>(null); + protected boolean poll() { -+ LivingEntity t = pendingTarget.getAndSet(null); -+ if (t == null) { -+ return false; -+ } ++ if (!(this.mob.getGoalCtx().result() instanceof LivingEntity t)) { return false; } + ServerLevel serverLevel = getServerLevel(this.mob); + if (serverLevel == null || !t.isAlive() || !attackTargeting.test(serverLevel, golem, t)) { + return false; @@ -1415,24 +1449,12 @@ index 4644f3f7af89623ca6218c0dd24bb6cd67db553b..04bc007a138d5f5875fa89f52d573693 + } + + private void findTargetAsync() { -+ if (!isSearching.compareAndSet(false, true)) { -+ return; -+ } -+ + final IronGolem mob = this.golem; -+ if (mob == null || mob.isRemoved() || !mob.isAlive()) { -+ isSearching.setRelease(false); -+ return; -+ } -+ -+ final double x = mob.getX(); -+ final double y = mob.getEyeY(); -+ final double z = mob.getZ(); ++ final var ctx = mob.getGoalCtx(); ++ if (!ctx.state) return; + AABB bound = this.golem.getBoundingBox().inflate(10.0, 8.0, 10.0); + final ServerLevel serverLevel = getServerLevel(mob); -+ final var pendingTarget = this.pendingTarget; -+ -+ serverLevel.asyncAITasks.add(() -> { ++ ctx.wake = () -> { + try { + if (mob.level() != serverLevel || !mob.isAlive()) { + return; @@ -1445,16 +1467,15 @@ index 4644f3f7af89623ca6218c0dd24bb6cd67db553b..04bc007a138d5f5875fa89f52d573693 + for (Player player : nearbyPlayers) { + int playerReputation = villager.getPlayerReputation(player); + if (playerReputation <= -100) { -+ pendingTarget.setRelease(player); ++ ctx.result = player; ++ break; + } + } + } + } catch (Exception e) { -+ LOGGER.error("Exception getting entities", e); -+ } finally { -+ isSearching.setRelease(false); ++ org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER.error("Exception getting entities", e); + } -+ }); ++ }; + } + // Leaf end - Async Target Finding + @@ -1472,7 +1493,7 @@ index 4644f3f7af89623ca6218c0dd24bb6cd67db553b..04bc007a138d5f5875fa89f52d573693 AABB aabb = this.golem.getBoundingBox().inflate(10.0, 8.0, 10.0); ServerLevel serverLevel = getServerLevel(this.golem); List nearbyEntities = serverLevel.getNearbyEntities(Villager.class, this.attackTargeting, this.golem, aabb); -@@ -42,6 +116,7 @@ public class DefendVillageTargetGoal extends TargetGoal { +@@ -42,6 +97,7 @@ public class DefendVillageTargetGoal extends TargetGoal { } } @@ -1481,10 +1502,10 @@ index 4644f3f7af89623ca6218c0dd24bb6cd67db553b..04bc007a138d5f5875fa89f52d573693 && (!(this.potentialTarget instanceof Player) || !this.potentialTarget.isSpectator() && !((Player)this.potentialTarget).isCreative()); } diff --git a/net/minecraft/world/entity/ai/goal/target/HurtByTargetGoal.java b/net/minecraft/world/entity/ai/goal/target/HurtByTargetGoal.java -index 25fe78116ce01eeefe5c958423734195d27302eb..50228ccc92181d021b2f1cf164671ce4b2095c75 100644 +index 25fe78116ce01eeefe5c958423734195d27302eb..e19f2cb98c9669deb3a19114264668fbb29d3cc5 100644 --- a/net/minecraft/world/entity/ai/goal/target/HurtByTargetGoal.java +++ b/net/minecraft/world/entity/ai/goal/target/HurtByTargetGoal.java -@@ -73,6 +73,59 @@ public class HurtByTargetGoal extends TargetGoal { +@@ -73,6 +73,56 @@ public class HurtByTargetGoal extends TargetGoal { protected void alertOthers() { double followDistance = this.getFollowDistance(); AABB aabb = AABB.unitCubeFromLowerCorner(this.mob.position()).inflate(followDistance, 10.0, followDistance); @@ -1492,12 +1513,13 @@ index 25fe78116ce01eeefe5c958423734195d27302eb..50228ccc92181d021b2f1cf164671ce4 + // Leaf start - async alert other + if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.alertOther) { + final var self = this.mob; ++ final var ctx = self.getGoalCtx(); ++ if (!ctx.state) return; + final var serverLevel = getServerLevel(self); -+ final var alertTarget = this.mob.getLastHurtByMob(); + final var toIgnoreAlert = this.toIgnoreAlert; -+ serverLevel.asyncAITasks.add(() -> { ++ ctx.wake = () -> { + try { -+ if (alertTarget == null || self == null || self.level() != serverLevel) { ++ if (self == null || self.level() != serverLevel) { + return; + } + var toAlert = new java.util.ArrayList(); @@ -1528,15 +1550,11 @@ index 25fe78116ce01eeefe5c958423734195d27302eb..50228ccc92181d021b2f1cf164671ce4 + toAlert.add(mob); + } + } -+ serverLevel.getServer().execute(() -> { -+ for (var livingEntity : toAlert) { -+ alertOther(livingEntity, alertTarget); -+ } -+ }); ++ ctx.result = toAlert; + } catch (Exception e) { + org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER.error("Exception alertOthers", e); + } -+ }); ++ }; + return; + } + // Leaf end - async alert other @@ -1544,7 +1562,7 @@ index 25fe78116ce01eeefe5c958423734195d27302eb..50228ccc92181d021b2f1cf164671ce4 List entitiesOfClass = this.mob .level() .getEntitiesOfClass((Class)this.mob.getClass(), aabb, EntitySelector.NO_SPECTATORS); -@@ -86,6 +139,7 @@ public class HurtByTargetGoal extends TargetGoal { +@@ -86,6 +136,7 @@ public class HurtByTargetGoal extends TargetGoal { } mob = (Mob)var5.next(); @@ -1552,7 +1570,7 @@ index 25fe78116ce01eeefe5c958423734195d27302eb..50228ccc92181d021b2f1cf164671ce4 if (this.mob != mob && mob.getTarget() == null && (!(this.mob instanceof TamableAnimal) || ((TamableAnimal)this.mob).getOwner() == ((TamableAnimal)mob).getOwner()) -@@ -96,6 +150,7 @@ public class HurtByTargetGoal extends TargetGoal { +@@ -96,6 +147,7 @@ public class HurtByTargetGoal extends TargetGoal { boolean flag = false; @@ -1560,23 +1578,33 @@ index 25fe78116ce01eeefe5c958423734195d27302eb..50228ccc92181d021b2f1cf164671ce4 for (Class clazz : this.toIgnoreAlert) { if (mob.getClass() == clazz) { flag = true; +@@ -113,6 +165,15 @@ public class HurtByTargetGoal extends TargetGoal { + } + } + ++ // Leaf start - async alert other ++ public void poll() { ++ if (!(this.mob.getGoalCtx().result() instanceof List toAlert)) { return; } ++ for (var livingEntity : toAlert) { ++ alertOther((Mob) livingEntity, this.mob.getLastHurtByMob()); ++ } ++ } ++ // Leaf end - async alert other ++ + protected void alertOther(Mob mob, LivingEntity target) { + mob.setTarget(target, org.bukkit.event.entity.EntityTargetEvent.TargetReason.TARGET_ATTACKED_NEARBY_ENTITY, true); // CraftBukkit - reason + } diff --git a/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java b/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java -index 41ee3cdc45ecc8376a2203ed588bb544ed377294..24c7567ffe88d4767371ac0082aa26cf5d2c2ed8 100644 +index 41ee3cdc45ecc8376a2203ed588bb544ed377294..f07d0db481aecb8260c09eafef2c1cd582a8502c 100644 --- a/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java +++ b/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java -@@ -41,8 +41,73 @@ public class NearestAttackableTargetGoal extends TargetG +@@ -41,8 +41,53 @@ public class NearestAttackableTargetGoal extends TargetG this.targetConditions = TargetingConditions.forCombat().range(this.getFollowDistance()).selector(selector); } + // Leaf start - Async Target Finding -+ private static final org.apache.logging.log4j.Logger LOGGER = org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER; -+ private final java.util.concurrent.atomic.AtomicBoolean isSearching = new java.util.concurrent.atomic.AtomicBoolean(false); -+ private final java.util.concurrent.atomic.AtomicReference pendingTarget = new java.util.concurrent.atomic.AtomicReference<>(null); + protected boolean poll() { -+ LivingEntity t = pendingTarget.getAndSet(null); -+ if (t == null) { -+ return false; -+ } ++ if (!(this.mob.getGoalCtx().result() instanceof LivingEntity t)) { return false; } + ServerLevel serverLevel = getServerLevel(this.mob); + if (serverLevel == null || !t.isAlive() || !this.getTargetConditions().test(serverLevel, this.mob, t)) { + return false; @@ -1586,16 +1614,9 @@ index 41ee3cdc45ecc8376a2203ed588bb544ed377294..24c7567ffe88d4767371ac0082aa26cf + } + + private void findTargetAsync() { -+ if (!isSearching.compareAndSet(false, true)) { -+ return; -+ } -+ + final Mob mob = this.mob; -+ if (mob == null || mob.isRemoved() || !mob.isAlive()) { -+ isSearching.setRelease(false); -+ return; -+ } -+ ++ final var ctx = mob.getGoalCtx(); ++ if (!ctx.state) return; + final double x = mob.getX(); + final double y = mob.getEyeY(); + final double z = mob.getZ(); @@ -1603,28 +1624,21 @@ index 41ee3cdc45ecc8376a2203ed588bb544ed377294..24c7567ffe88d4767371ac0082aa26cf + final Class targetType = this.targetType; + final AABB targetSearch = getTargetSearchArea(this.getFollowDistance()); + final ServerLevel serverLevel = getServerLevel(mob); -+ final var pendingTarget = this.pendingTarget; -+ -+ serverLevel.asyncAITasks.add(() -> { ++ ctx.wake = () -> { + try { + if (mob.level() != serverLevel || mob.isRemoved() || !mob.isAlive()) { + return; + } + + if (targetType != Player.class && targetType != ServerPlayer.class) { -+ final java.util.List entities = mob.level().getEntitiesOfClass(targetType, targetSearch, entity -> entity != null && entity != mob && entity.isAlive()); -+ var result = serverLevel.getNearestEntity(entities, targetConditions, mob, x,y,z); -+ pendingTarget.setRelease(result); ++ ctx.result = serverLevel.getNearestEntity(mob.level().getEntitiesOfClass(targetType, targetSearch, entity -> entity != null && entity != mob && entity.isAlive()), targetConditions, mob, x,y,z); + } else { -+ var result = serverLevel.getNearestPlayer(targetConditions, mob, x, y, z); -+ pendingTarget.setRelease(result); ++ ctx.result = serverLevel.getNearestPlayer(targetConditions, mob, x, y, z); + } + } catch (Exception e) { -+ LOGGER.error("Exception getting entities", e); -+ } finally { -+ isSearching.setRelease(false); ++ org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER.error("Exception getting entities", e); + } -+ }); ++ }; + } + // Leaf end - Async Target Finding + @@ -1638,19 +1652,13 @@ index 41ee3cdc45ecc8376a2203ed588bb544ed377294..24c7567ffe88d4767371ac0082aa26cf if (this.randomInterval > 0 && this.mob.getRandom().nextInt(this.randomInterval) != 0) { return false; } else { -@@ -57,6 +122,21 @@ public class NearestAttackableTargetGoal extends TargetG +@@ -57,6 +102,15 @@ public class NearestAttackableTargetGoal extends TargetG protected void findTarget() { ServerLevel serverLevel = getServerLevel(this.mob); + + // Leaf start - Async Target Finding -+ if (targetType == Player.class) { -+ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchPlayer) { -+ this.findTargetAsync(); -+ this.target = null; -+ return; -+ } -+ } else if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchEntity) { ++ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchEntity) { + this.findTargetAsync(); + this.target = null; + return; @@ -1660,7 +1668,7 @@ index 41ee3cdc45ecc8376a2203ed588bb544ed377294..24c7567ffe88d4767371ac0082aa26cf if (this.targetType != Player.class && this.targetType != ServerPlayer.class) { this.target = serverLevel.getNearestEntity( this.mob.level().getEntitiesOfClass(this.targetType, this.getTargetSearchArea(this.getFollowDistance()), entity -> true), -@@ -81,7 +161,7 @@ public class NearestAttackableTargetGoal extends TargetG +@@ -81,7 +135,7 @@ public class NearestAttackableTargetGoal extends TargetG this.target = target; } @@ -1712,20 +1720,22 @@ index abf57494950f55bbd75f335f26736cb9e703c197..683722fbd07fcae9cdd72928ed25fd08 } } diff --git a/net/minecraft/world/entity/ai/goal/target/ResetUniversalAngerTargetGoal.java b/net/minecraft/world/entity/ai/goal/target/ResetUniversalAngerTargetGoal.java -index 61a7bb5f1760d47a13b6aafc123bcf3dff420860..8db8dc0b9a79bc56568e22a8e99354de599ed23b 100644 +index 61a7bb5f1760d47a13b6aafc123bcf3dff420860..93da18d921a1cf7d72f441d9415435ff9c417f1f 100644 --- a/net/minecraft/world/entity/ai/goal/target/ResetUniversalAngerTargetGoal.java +++ b/net/minecraft/world/entity/ai/goal/target/ResetUniversalAngerTargetGoal.java -@@ -37,6 +37,36 @@ public class ResetUniversalAngerTargetGoal extends G +@@ -37,6 +37,34 @@ public class ResetUniversalAngerTargetGoal extends G this.lastHurtByPlayerTimestamp = this.mob.getLastHurtByMobTimestamp(); this.mob.forgetCurrentTargetAndRefreshUniversalAnger(); if (this.alertOthersOfSameType) { + // Leaf start - async alert other + if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.alertOther) { + final var mob = this.mob; ++ final var ctx = mob.getGoalCtx(); ++ if (!ctx.state) return; + final var serverLevel = getServerLevel(mob); + final double followRange = this.mob.getAttributeValue(Attributes.FOLLOW_RANGE); + final AABB aabb = AABB.unitCubeFromLowerCorner(this.mob.position()).inflate(followRange, 10.0, followRange); -+ serverLevel.asyncAITasks.add(() -> { ++ ctx.wake = () -> { + try { + if (mob == null || serverLevel != mob.level() || !mob.isAlive()) { + return; @@ -1737,24 +1747,30 @@ index 61a7bb5f1760d47a13b6aafc123bcf3dff420860..8db8dc0b9a79bc56568e22a8e99354de + toStop.add((NeutralMob) entity); + } + } -+ serverLevel.getServer().execute(() -> { -+ for (NeutralMob neutralMob : toStop) { -+ neutralMob.forgetCurrentTargetAndRefreshUniversalAnger(); -+ } -+ }); ++ ctx.result = toStop; + } catch (Exception e) { + org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER.error("Exception alertOthers forgetCurrentTargetAndRefreshUniversalAnger", e); + } -+ }); ++ }; + return; + } + // Leaf end - async alert other this.getNearbyMobsOfSameType() .stream() .filter(mob -> mob != this.mob) -@@ -48,6 +78,7 @@ public class ResetUniversalAngerTargetGoal extends G +@@ -47,7 +75,17 @@ public class ResetUniversalAngerTargetGoal extends G + super.start(); } ++ // Leaf start - async alert other ++ public void poll() { ++ if (!(this.mob.getGoalCtx().result() instanceof List toStop)) { return; } ++ for (var neutralMob : toStop) { ++ ((NeutralMob)neutralMob).forgetCurrentTargetAndRefreshUniversalAnger(); ++ } ++ } ++ // Leaf end - async alert other ++ private List getNearbyMobsOfSameType() { + // Leaf - async alert other double attributeValue = this.mob.getAttributeValue(Attributes.FOLLOW_RANGE); @@ -1854,10 +1870,10 @@ index 90452f0945e761077608692877677f522d38bccd..9e380f5b4dcedb115f8893dd382f28ea class FoxFloatGoal extends FloatGoal { diff --git a/net/minecraft/world/entity/animal/Panda.java b/net/minecraft/world/entity/animal/Panda.java -index 0a6a3060f3690ab2d8439d66e6fd6f0c5dc20688..050b060c711ea33d413fba506653618d8c4c4bd4 100644 +index 0a6a3060f3690ab2d8439d66e6fd6f0c5dc20688..d3466ebc9d0a57d0dcefa98d65fe42d8c827e6d3 100644 --- a/net/minecraft/world/entity/animal/Panda.java +++ b/net/minecraft/world/entity/animal/Panda.java -@@ -991,31 +991,44 @@ public class Panda extends Animal { +@@ -991,31 +991,39 @@ public class Panda extends Animal { @Override public boolean canUse() { @@ -1887,12 +1903,7 @@ index 0a6a3060f3690ab2d8439d66e6fd6f0c5dc20688..050b060c711ea33d413fba506653618d - ); - } + } -+ if (this.lookAtType == Player.class) { -+ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchPlayer) { -+ getLookAsync(); -+ return false; -+ } -+ } else if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchEntity) { ++ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchEntity) { + getLookAsync(); + return false; + } @@ -2065,3 +2076,31 @@ index ae4ee948971e931e4fdc4ec2187f5182195c626c..7b878761eefc41fa735ef67c4a6dcbe9 } static class StriderPathNavigation extends GroundPathNavigation { +diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java +index ac7729d1caa80155697bfe0e8646e4eda5d1780e..d2851eb4c3e7eb2a1976075e2df0edb33b1c411d 100644 +--- a/net/minecraft/world/level/Level.java ++++ b/net/minecraft/world/level/Level.java +@@ -1548,6 +1548,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + this.moonrise$midTickTasks(); + } + // Leaf end - SparklyPaper - parallel world ticking (only run mid-tick at the end of each tick / fixes concurrency bugs related to executeMidTickTasks) - do not bother with condition work / make configurable ++ ((ServerLevel) this).leafMidTickTasks(); // Leaf - Async target finding + // Paper end - rewrite chunk system + } + } +@@ -1817,9 +1818,12 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + + @Override + public List getEntities(@Nullable Entity entity, AABB boundingBox, Predicate predicate) { +- if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) // Leaf start - SparklyPaper - parallel world ticking mod (make configurable) +- ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread((ServerLevel)this, boundingBox, "Cannot getEntities asynchronously"); // SparklyPaper - parallel world ticking (additional concurrency issues logs) +- List list = Lists.newArrayList(); ++ // Leaf start - Async target finding ++ // if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) // Leaf start - SparklyPaper - parallel world ticking mod (make configurable) ++ // ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread((ServerLevel)this, boundingBox, "Cannot getEntities asynchronously"); // SparklyPaper - parallel world ticking (additional concurrency issues logs) ++ // Leaf end - Async target finding ++ ++ // List list = Lists.newArrayList(); // Leaf - unused + + // Paper start - rewrite chunk system + final List ret = new java.util.ArrayList<>(); diff --git a/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalExecutor.java b/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalExecutor.java index d27be8992..74311455d 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalExecutor.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalExecutor.java @@ -1,19 +1,82 @@ package org.dreeam.leaf.async.ai; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.Mob; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.dreeam.leaf.config.modules.async.AsyncTargetFinding; +import org.dreeam.leaf.util.queue.SpscIntQueue; -import java.util.List; -import java.util.concurrent.ExecutorService; +import java.util.concurrent.locks.LockSupport; public class AsyncGoalExecutor { - public static ExecutorService EXECUTOR; + public static final Logger LOGGER = LogManager.getLogger("Leaf Async Goal"); + final SpscIntQueue queue; + final SpscIntQueue wake; + private final AsyncGoalThread thread; + private final ServerLevel serverLevel; + private boolean dirty = false; + private long tickCount = 0L; - public static final Logger LOGGER = LogManager.getLogger("Leaf Async Entity Lookup"); + public AsyncGoalExecutor(AsyncGoalThread thread, ServerLevel serverLevel) { + this.serverLevel = serverLevel; + queue = new SpscIntQueue(AsyncTargetFinding.queueSize); + wake = new SpscIntQueue(AsyncTargetFinding.queueSize); + this.thread = thread; + } + + boolean wake(int id) { + Entity entity = this.serverLevel.getEntities().get(id); + if (entity == null || entity.isRemoved() || !(entity instanceof Mob m)) { + return false; + } + m.goalSelector.wake(); + m.targetSelector.wake(); + return true; + } - public static void runTasks(List tasks) { - for (Runnable task : tasks) { - task.run(); + public final void submit(int entityId) { + if (!this.queue.send(entityId)) { + while (wake(entityId)) { + if (!poll(entityId)) { + break; + } + Thread.onSpinWait(); + } } + dirty = true; + } + + public final void unpark() { + if (dirty) LockSupport.unpark(thread); + dirty = false; + } + + public final void midTick() { + while (true) { + int id = this.wake.recv(); + if (id == Integer.MAX_VALUE) { + break; + } + if (poll(id)) { + submit(id); + } + } + if ((tickCount & 3L) == 0L) unpark(); + tickCount += 1; + } + + private boolean poll(int id) { + Entity entity = this.serverLevel.getEntities().get(id); + if (entity == null || !entity.isAlive() || !(entity instanceof Mob mob)) { + return false; + } + mob.tickingTarget = true; + boolean a = mob.targetSelector.poll(); + mob.tickingTarget = false; + boolean b = mob.goalSelector.poll(); + return a || b; } } + diff --git a/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalThread.java b/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalThread.java new file mode 100644 index 000000000..9d8abe8cd --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalThread.java @@ -0,0 +1,37 @@ +package org.dreeam.leaf.async.ai; + +import net.minecraft.Util; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; + +import java.util.concurrent.locks.LockSupport; + +public class AsyncGoalThread extends Thread { + public AsyncGoalThread(final MinecraftServer server) { + super(() -> run(server), "Leaf Async Goal Thread"); + this.setDaemon(true); + this.setUncaughtExceptionHandler(Util::onThreadException); + this.setPriority(Thread.NORM_PRIORITY - 1); + this.start(); + } + + private static void run(MinecraftServer server) { + while (server.isRunning()) { + LockSupport.park(); + for (ServerLevel level : server.getAllLevels()) { + var exec = level.asyncGoalExecutor; + while (true) { + int id = exec.queue.recv(); + if (id == Integer.MAX_VALUE) { + break; + } + if (exec.wake(id)) { + while (!exec.wake.send(id)) { + Thread.onSpinWait(); + } + } + } + } + } + } +} diff --git a/leaf-server/src/main/java/org/dreeam/leaf/async/ai/Waker.java b/leaf-server/src/main/java/org/dreeam/leaf/async/ai/Waker.java new file mode 100644 index 000000000..82560379d --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/async/ai/Waker.java @@ -0,0 +1,17 @@ +package org.dreeam.leaf.async.ai; + +import org.jetbrains.annotations.Nullable; + +public class Waker { + @Nullable + public volatile Runnable wake = null; + @Nullable + public volatile Object result = null; + public volatile boolean state = true; + + public final @Nullable Object result() { + Object result = this.result; + this.result = null; + return result; + } +} diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncTargetFinding.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncTargetFinding.java index dc6442c42..9519291bb 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncTargetFinding.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncTargetFinding.java @@ -1,16 +1,10 @@ package org.dreeam.leaf.config.modules.async; -import com.google.common.util.concurrent.ThreadFactoryBuilder; -import org.dreeam.leaf.async.ai.AsyncGoalExecutor; import org.dreeam.leaf.config.ConfigModules; import org.dreeam.leaf.config.EnumConfigCategory; import org.dreeam.leaf.config.annotations.Experimental; -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; - public class AsyncTargetFinding extends ConfigModules { public String getBasePath() { @@ -20,18 +14,17 @@ public String getBasePath() { @Experimental public static boolean enabled = false; public static boolean alertOther = true; - public static boolean searchBlock = false; + public static boolean searchBlock = true; public static boolean searchEntity = true; - public static boolean searchPlayer = false; - public static boolean searchPlayerTempt = false; + public static int queueSize = 100_000; private static boolean asyncTargetFindingInitialized; @Override public void onLoaded() { config.addCommentRegionBased(getBasePath(), """ **Experimental feature** - This moves the expensive entity target search calculations to a background thread while - keeping the actual entity validation on the main thread.""", + This moves the expensive entity and block search calculations to background thread while + keeping the actual validation on the main thread.""", """ **实验性功能** 这会将昂贵的实体目标搜索计算移至后台线程, 同时在主线程上保持实际的实体验证."""); @@ -44,29 +37,16 @@ public void onLoaded() { enabled = config.getBoolean(getBasePath() + ".enabled", enabled); alertOther = config.getBoolean(getBasePath() + ".async-alert-other", true); - searchBlock = config.getBoolean(getBasePath() + ".async-search-block", false); + searchBlock = config.getBoolean(getBasePath() + ".async-search-block", true); searchEntity = config.getBoolean(getBasePath() + ".async-search-entity", true); - searchPlayer = config.getBoolean(getBasePath() + ".async-search-player", false); - searchPlayerTempt = config.getBoolean(getBasePath() + ".async-search-player-tempt", false); + queueSize = config.getInt(getBasePath() + ".queue-size", 100_000); + if (queueSize <= 0) { + queueSize = 100_000; + } if (!enabled) { alertOther = false; searchEntity = false; searchBlock = false; - searchPlayer = false; - searchPlayerTempt = false; - return; } - AsyncGoalExecutor.EXECUTOR = new ThreadPoolExecutor( - 1, - 1, - 0L, - TimeUnit.MILLISECONDS, - new ArrayBlockingQueue<>(128), - new ThreadFactoryBuilder() - .setNameFormat("Leaf Async Target Finding Thread") - .setDaemon(true) - .setPriority(Thread.NORM_PRIORITY - 2) - .build(), - new ThreadPoolExecutor.CallerRunsPolicy()); } } diff --git a/leaf-server/src/main/java/org/dreeam/leaf/util/queue/SpscIntQueue.java b/leaf-server/src/main/java/org/dreeam/leaf/util/queue/SpscIntQueue.java new file mode 100644 index 000000000..9afc72dff --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/util/queue/SpscIntQueue.java @@ -0,0 +1,63 @@ +package org.dreeam.leaf.util.queue; + +/// Lock-free Single Producer Single Consumer Queue +public class SpscIntQueue { + private final int[] data; + private final PaddedAtomicInteger producerIdx = new PaddedAtomicInteger(); + private final PaddedAtomicInteger producerCachedIdx = new PaddedAtomicInteger(); + private final PaddedAtomicInteger consumerIdx = new PaddedAtomicInteger(); + private final PaddedAtomicInteger consumerCachedIdx = new PaddedAtomicInteger(); + + public SpscIntQueue(int size) { + this.data = new int[size + 1]; + } + + public final boolean send(int e) { + final int idx = producerIdx.getOpaque(); + int nextIdx = idx + 1; + if (nextIdx == data.length) { + nextIdx = 0; + } + int cachedIdx = consumerCachedIdx.getPlain(); + if (nextIdx == cachedIdx) { + cachedIdx = consumerIdx.getAcquire(); + consumerCachedIdx.setPlain(cachedIdx); + if (nextIdx == cachedIdx) { + return false; + } + } + data[idx] = e; + producerIdx.setRelease(nextIdx); + return true; + } + + + public final int recv() { + final int idx = consumerIdx.getOpaque(); + int cachedIdx = producerCachedIdx.getPlain(); + if (idx == cachedIdx) { + cachedIdx = producerIdx.getAcquire(); + producerCachedIdx.setPlain(cachedIdx); + if (idx == cachedIdx) { + return Integer.MAX_VALUE; + } + } + int e = data[idx]; + int nextIdx = idx + 1; + if (nextIdx == data.length) { + nextIdx = 0; + } + consumerIdx.setRelease(nextIdx); + return e; + } + + public final int size() { + return this.data.length; + } + + static class PaddedAtomicInteger extends java.util.concurrent.atomic.AtomicInteger { + @SuppressWarnings("unused") + private int i1, i2, i3, i4, i5, i6, i7, i8, + i9, i10, i11, i12, i13, i14, i15; + } +} From 71744b54cd4b6bb58c2da16f282d84a20a91fb5d Mon Sep 17 00:00:00 2001 From: hayanesuru Date: Wed, 30 Apr 2025 17:32:05 +0800 Subject: [PATCH 2/8] fix canContinueToUse --- .../features/0154-Async-target-finding.patch | 76 +++++++++---------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/leaf-server/minecraft-patches/features/0154-Async-target-finding.patch b/leaf-server/minecraft-patches/features/0154-Async-target-finding.patch index 047a2838c..79f091f93 100644 --- a/leaf-server/minecraft-patches/features/0154-Async-target-finding.patch +++ b/leaf-server/minecraft-patches/features/0154-Async-target-finding.patch @@ -661,18 +661,10 @@ index 3093f03d4f298bf39fec8bad2b6c22518774aea8..ee42674761653cacc5045fcb60e314fb } else { this.parent = animal; diff --git a/net/minecraft/world/entity/ai/goal/GoalSelector.java b/net/minecraft/world/entity/ai/goal/GoalSelector.java -index e82e32407cec6109b9c3b0106295217f4a3f4aa2..9d0d0a788b435f014496e37372730258e3e33014 100644 +index e82e32407cec6109b9c3b0106295217f4a3f4aa2..32463fae3d3a58e71c4aae2d81b2a1b60fee7d69 100644 --- a/net/minecraft/world/entity/ai/goal/GoalSelector.java +++ b/net/minecraft/world/entity/ai/goal/GoalSelector.java -@@ -3,7 +3,6 @@ package net.minecraft.world.entity.ai.goal; - import com.google.common.annotations.VisibleForTesting; - import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet; - import java.util.EnumMap; --import java.util.EnumSet; - import java.util.Map; - import java.util.Set; - import java.util.function.Predicate; -@@ -26,6 +25,13 @@ public class GoalSelector { +@@ -26,6 +26,13 @@ public class GoalSelector { private final ca.spottedleaf.moonrise.common.set.OptimizedSmallEnumSet goalTypes = new ca.spottedleaf.moonrise.common.set.OptimizedSmallEnumSet<>(Goal.Flag.class); // Paper - remove streams from GoalSelector private int curRate; // Paper - EAR 2 @@ -686,7 +678,7 @@ index e82e32407cec6109b9c3b0106295217f4a3f4aa2..9d0d0a788b435f014496e37372730258 public void addGoal(int priority, Goal goal) { this.availableGoals.add(new WrappedGoal(priority, goal)); } -@@ -85,7 +91,99 @@ public class GoalSelector { +@@ -85,7 +92,107 @@ public class GoalSelector { return true; } @@ -697,11 +689,17 @@ index e82e32407cec6109b9c3b0106295217f4a3f4aa2..9d0d0a788b435f014496e37372730258 + if (ctxState == 0) { + while (ctxIndex < this.ctxGoals.length) { + WrappedGoal goal = this.ctxGoals[ctxIndex]; -+ // todo TemptGoal ++ // entity tempt + if (goal.isRunning() && (goalContainsAnyFlags(goal, this.goalTypes) || !goal.canContinueToUse())) { ++ ctx.state = false; ++ if (ctx.wake != null) { ++ return true; ++ } + goal.stop(); + } ++ + ctxIndex++; ++ ctx.state = true; + } + + this.lockedFlags.entrySet().removeIf(entry -> !entry.getValue().isRunning()); @@ -713,33 +711,35 @@ index e82e32407cec6109b9c3b0106295217f4a3f4aa2..9d0d0a788b435f014496e37372730258 + if (ctxState == 1) { + while (ctxIndex < this.ctxGoals.length) { + WrappedGoal goal = this.ctxGoals[ctxIndex]; -+ if (!goal.isRunning()) { -+ if (!goalContainsAnyFlags(goal, this.goalTypes) -+ && goalCanBeReplacedForAllFlags(goal, this.lockedFlags)) { -+ if (goal.canUse()) { -+ long flagIterator = goal.getFlags().getBackingSet(); -+ int wrappedGoalSize = goal.getFlags().size(); -+ for (int i = 0; i < wrappedGoalSize; ++i) { -+ final Goal.Flag flag = GOAL_FLAG_VALUES[Long.numberOfTrailingZeros(flagIterator)]; -+ flagIterator ^= ca.spottedleaf.concurrentutil.util.IntegerUtil.getTrailingBit(flagIterator); -+ // Paper end -+ WrappedGoal wrappedGoal1 = this.lockedFlags.getOrDefault(flag, NO_GOAL); -+ wrappedGoal1.stop(); -+ this.lockedFlags.put(flag, goal); -+ } -+ -+ goal.start(); -+ } -+ ctx.state = false; -+ if (ctx.wake != null) { -+ return true; ++ // entity and block ++ if (!goal.isRunning() ++ && !goalContainsAnyFlags(goal, this.goalTypes) ++ && goalCanBeReplacedForAllFlags(goal, this.lockedFlags) ++ ) { ++ if (goal.canUse()) { ++ long flagIterator = goal.getFlags().getBackingSet(); ++ int wrappedGoalSize = goal.getFlags().size(); ++ for (int i = 0; i < wrappedGoalSize; ++i) { ++ final Goal.Flag flag = GOAL_FLAG_VALUES[Long.numberOfTrailingZeros(flagIterator)]; ++ flagIterator ^= ca.spottedleaf.concurrentutil.util.IntegerUtil.getTrailingBit(flagIterator); ++ WrappedGoal wrappedGoal1 = this.lockedFlags.getOrDefault(flag, NO_GOAL); ++ wrappedGoal1.stop(); ++ this.lockedFlags.put(flag, goal); + } ++ ++ goal.start(); + } -+ } else if (!ctx.state) { -+ if (goal.getGoal() instanceof net.minecraft.world.entity.ai.goal.target.ResetUniversalAngerTargetGoal resetUniversalAngerTargetGoal) { -+ resetUniversalAngerTargetGoal.poll(); -+ } else if (goal.getGoal() instanceof net.minecraft.world.entity.ai.goal.target.HurtByTargetGoal hurtByTargetGoal) { -+ hurtByTargetGoal.poll(); ++ ctx.state = false; ++ if (ctx.wake != null) { ++ return true; ++ } ++ } ++ // entity alert other ++ if (!ctx.state) { ++ switch (goal.getGoal()) { ++ case net.minecraft.world.entity.ai.goal.target.HurtByTargetGoal t -> t.poll(); ++ case net.minecraft.world.entity.ai.goal.target.ResetUniversalAngerTargetGoal t -> t.poll(); ++ default -> {} + } + } + ctxIndex++; @@ -786,7 +786,7 @@ index e82e32407cec6109b9c3b0106295217f4a3f4aa2..9d0d0a788b435f014496e37372730258 for (WrappedGoal wrappedGoal : this.availableGoals) { if (wrappedGoal.isRunning() && (goalContainsAnyFlags(wrappedGoal, this.goalTypes) || !wrappedGoal.canContinueToUse())) { // Paper - Perf: optimize goal types by removing streams wrappedGoal.stop(); -@@ -94,6 +192,7 @@ public class GoalSelector { +@@ -94,6 +201,7 @@ public class GoalSelector { this.lockedFlags.entrySet().removeIf(entry -> !entry.getValue().isRunning()); From 248306212ed117c0c39a61cd22fd3736a6c13df8 Mon Sep 17 00:00:00 2001 From: hayanesuru Date: Wed, 30 Apr 2025 18:30:48 +0800 Subject: [PATCH 3/8] fix data race --- .../features/0154-Async-target-finding.patch | 15 +++++++--- .../leaf/async/ai/AsyncGoalExecutor.java | 28 ++++++++----------- 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/leaf-server/minecraft-patches/features/0154-Async-target-finding.patch b/leaf-server/minecraft-patches/features/0154-Async-target-finding.patch index 79f091f93..63c5d0310 100644 --- a/leaf-server/minecraft-patches/features/0154-Async-target-finding.patch +++ b/leaf-server/minecraft-patches/features/0154-Async-target-finding.patch @@ -46,7 +46,7 @@ index 33dd16a26edd2974f04d9a868d3e58e8e3060032..0817ad4e16d8858b7d92e028d551e918 // Gale start - Pufferfish - SIMD support diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java -index 90bdcd168ad5b1a940f81b191bd59a34d3a33070..565adb702daf4b88f057b9dbb4bc864fd0b7ace3 100644 +index 90bdcd168ad5b1a940f81b191bd59a34d3a33070..fbfb35dad8b07c31f967d33fb04cfcfc94557d72 100644 --- a/net/minecraft/server/level/ServerLevel.java +++ b/net/minecraft/server/level/ServerLevel.java @@ -177,7 +177,16 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe @@ -102,7 +102,7 @@ index 90bdcd168ad5b1a940f81b191bd59a34d3a33070..565adb702daf4b88f057b9dbb4bc864f } // Paper start -@@ -855,6 +878,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -855,12 +878,14 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe } this.moonrise$midTickTasks(); // Paper - rewrite chunk system // Gale end - Airplane - remove lambda from ticking guard - copied from guardEntityTick @@ -110,7 +110,14 @@ index 90bdcd168ad5b1a940f81b191bd59a34d3a33070..565adb702daf4b88f057b9dbb4bc864f } } } -@@ -1329,6 +1353,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + } + ); + this.tickBlockEntities(); ++ if (this.asyncGoalExecutor != null) this.asyncGoalExecutor.unpark(); // Leaf - Async target finding + } + + // Paper - rewrite chunk system +@@ -1329,6 +1354,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe // Leaf end - SparklyPaper - parallel world ticking (only run mid-tick at the end of each tick / fixes concurrency bugs related to executeMidTickTasks) // Paper end - rewrite chunk system @@ -118,7 +125,7 @@ index 90bdcd168ad5b1a940f81b191bd59a34d3a33070..565adb702daf4b88f057b9dbb4bc864f } private void tickBlock(BlockPos pos, Block block) { -@@ -1345,6 +1370,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -1345,6 +1371,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe // Leaf end - SparklyPaper - parallel world ticking (only run mid-tick at the end of each tick / fixes concurrency bugs related to executeMidTickTasks) // Paper end - rewrite chunk system diff --git a/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalExecutor.java b/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalExecutor.java index 74311455d..a14c27e87 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalExecutor.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalExecutor.java @@ -38,10 +38,8 @@ boolean wake(int id) { public final void submit(int entityId) { if (!this.queue.send(entityId)) { - while (wake(entityId)) { - if (!poll(entityId)) { - break; - } + LockSupport.unpark(thread); + while (!this.queue.send(entityId)) { Thread.onSpinWait(); } } @@ -59,24 +57,20 @@ public final void midTick() { if (id == Integer.MAX_VALUE) { break; } - if (poll(id)) { + Entity entity = this.serverLevel.getEntities().get(id); + if (entity == null || !entity.isAlive() || !(entity instanceof Mob mob)) { + continue; + } + mob.tickingTarget = true; + boolean a = mob.targetSelector.poll(); + mob.tickingTarget = false; + boolean b = mob.goalSelector.poll(); + if (a || b) { submit(id); } } if ((tickCount & 3L) == 0L) unpark(); tickCount += 1; } - - private boolean poll(int id) { - Entity entity = this.serverLevel.getEntities().get(id); - if (entity == null || !entity.isAlive() || !(entity instanceof Mob mob)) { - return false; - } - mob.tickingTarget = true; - boolean a = mob.targetSelector.poll(); - mob.tickingTarget = false; - boolean b = mob.goalSelector.poll(); - return a || b; - } } From 5faac4eee7f1aa84dfb78de82b84188b33ad1df3 Mon Sep 17 00:00:00 2001 From: hayanesuru Date: Thu, 1 May 2025 20:28:26 +0800 Subject: [PATCH 4/8] fix TemptGoal start before search entity --- .../features/0154-Async-target-finding.patch | 46 ++++++++----------- 1 file changed, 19 insertions(+), 27 deletions(-) diff --git a/leaf-server/minecraft-patches/features/0154-Async-target-finding.patch b/leaf-server/minecraft-patches/features/0154-Async-target-finding.patch index 63c5d0310..8d35cf3d7 100644 --- a/leaf-server/minecraft-patches/features/0154-Async-target-finding.patch +++ b/leaf-server/minecraft-patches/features/0154-Async-target-finding.patch @@ -1349,10 +1349,10 @@ index 95fa516910a3834bbd4db6d11279e13a1f0dac41..f7ddae601abbe9e22a35c7cb4f9763e6 + // Leaf end - Async search block } diff --git a/net/minecraft/world/entity/ai/goal/TemptGoal.java b/net/minecraft/world/entity/ai/goal/TemptGoal.java -index f88f618d34fb343b31de3af1a875d6633703df71..d7b026018adcc8d0c88f8a0561bdc13d0fbdeebc 100644 +index f88f618d34fb343b31de3af1a875d6633703df71..37aa00347e9cf391e447cff775e4493b36e05beb 100644 --- a/net/minecraft/world/entity/ai/goal/TemptGoal.java +++ b/net/minecraft/world/entity/ai/goal/TemptGoal.java -@@ -36,14 +36,68 @@ public class TemptGoal extends Goal { +@@ -36,12 +36,62 @@ public class TemptGoal extends Goal { this.targetingConditions = TEMPT_TARGETING.copy().selector((entity, level) -> this.shouldFollow(entity)); } @@ -1389,40 +1389,32 @@ index f88f618d34fb343b31de3af1a875d6633703df71..d7b026018adcc8d0c88f8a0561bdc13d + // Leaf end - Async Tempt Finding @Override public boolean canUse() { -+ // Leaf start - Async Tempt Finding -+ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchEntity) { -+ if (poll()) { -+ if (this.player != null) { -+ org.bukkit.event.entity.EntityTargetLivingEntityEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTargetLivingEvent(this.mob, this.player, org.bukkit.event.entity.EntityTargetEvent.TargetReason.TEMPT); -+ if (event.isCancelled()) { -+ return false; -+ } -+ this.player = (event.getTarget() == null) ? null : ((org.bukkit.craftbukkit.entity.CraftLivingEntity) event.getTarget()).getHandle(); -+ } -+ if (this.player != null) { -+ return true; -+ } -+ } -+ } -+ // Leaf end - Async Tempt Finding if (this.calmDown > 0) { this.calmDown--; return false; } else { -- this.player = getServerLevel(this.mob) -- .getNearestPlayer(this.targetingConditions.range(this.mob.getAttributeValue(Attributes.TEMPT_RANGE)), this.mob); + // Leaf start - Async Tempt Finding + if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchEntity) { -+ getNearestPlayerAsync(); -+ return false; -+ } else { -+ this.player = getServerLevel(this.mob) -+ .getNearestPlayer(this.targetingConditions.range(this.mob.getAttributeValue(Attributes.TEMPT_RANGE)), this.mob); ++ if (poll()) { ++ if (this.player != null) { ++ org.bukkit.event.entity.EntityTargetLivingEntityEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTargetLivingEvent(this.mob, this.player, org.bukkit.event.entity.EntityTargetEvent.TargetReason.TEMPT); ++ if (event.isCancelled()) { ++ return false; ++ } ++ this.player = (event.getTarget() == null) ? null : ((org.bukkit.craftbukkit.entity.CraftLivingEntity) event.getTarget()).getHandle(); ++ } ++ if (this.player != null) { ++ return true; ++ } ++ } else { ++ getNearestPlayerAsync(); ++ return false; ++ } + } + // Leaf end - Async Tempt Finding + this.player = getServerLevel(this.mob) + .getNearestPlayer(this.targetingConditions.range(this.mob.getAttributeValue(Attributes.TEMPT_RANGE)), this.mob); // CraftBukkit start - if (this.player != null) { - org.bukkit.event.entity.EntityTargetLivingEntityEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTargetLivingEvent(this.mob, this.player, org.bukkit.event.entity.EntityTargetEvent.TargetReason.TEMPT); diff --git a/net/minecraft/world/entity/ai/goal/target/DefendVillageTargetGoal.java b/net/minecraft/world/entity/ai/goal/target/DefendVillageTargetGoal.java index 4644f3f7af89623ca6218c0dd24bb6cd67db553b..921418da5f026edad4c78ba51127bb758c34bb9a 100644 --- a/net/minecraft/world/entity/ai/goal/target/DefendVillageTargetGoal.java From cce8c3b9936a6ef39383cc16e70d9f3238bf6aa6 Mon Sep 17 00:00:00 2001 From: hayanesuru Date: Thu, 1 May 2025 21:31:58 +0800 Subject: [PATCH 5/8] fix AvoidEntityGoal doesn't create path --- .../features/0154-Async-target-finding.patch | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/leaf-server/minecraft-patches/features/0154-Async-target-finding.patch b/leaf-server/minecraft-patches/features/0154-Async-target-finding.patch index 8d35cf3d7..ea082b856 100644 --- a/leaf-server/minecraft-patches/features/0154-Async-target-finding.patch +++ b/leaf-server/minecraft-patches/features/0154-Async-target-finding.patch @@ -241,10 +241,10 @@ index 05d5cde42b7011091ef4ee874c0d9d5586ae3f10..555e206c5fafaa801e46625070da2fd3 this.navigation.tick(); this.customServerAiStep((ServerLevel)this.level()); diff --git a/net/minecraft/world/entity/ai/goal/AvoidEntityGoal.java b/net/minecraft/world/entity/ai/goal/AvoidEntityGoal.java -index 4b24bb47a53a46586a642f3fb2656b1b8b670bf2..149672d5aea4e04f74f8fb80a3f4856fb2a7c133 100644 +index 4b24bb47a53a46586a642f3fb2656b1b8b670bf2..5239f4dfeaed8adccceb2a6d6578308261f46628 100644 --- a/net/minecraft/world/entity/ai/goal/AvoidEntityGoal.java +++ b/net/minecraft/world/entity/ai/goal/AvoidEntityGoal.java -@@ -67,15 +67,25 @@ public class AvoidEntityGoal extends Goal { +@@ -67,15 +67,24 @@ public class AvoidEntityGoal extends Goal { @Override public boolean canUse() { @@ -258,12 +258,11 @@ index 4b24bb47a53a46586a642f3fb2656b1b8b670bf2..149672d5aea4e04f74f8fb80a3f4856f - this.mob.getZ() - ); + // Leaf start - Async Avoid Entity Finding -+ if (poll()) { -+ return true; -+ } + if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchEntity) { -+ getNearestEntityAsync(); -+ return false; ++ if (!poll()) { ++ getNearestEntityAsync(); ++ return false; ++ } + } else { + this.toAvoid = getServerLevel(this.mob) + .getNearestEntity( @@ -279,7 +278,7 @@ index 4b24bb47a53a46586a642f3fb2656b1b8b670bf2..149672d5aea4e04f74f8fb80a3f4856f if (this.toAvoid == null) { return false; } else { -@@ -91,6 +101,47 @@ public class AvoidEntityGoal extends Goal { +@@ -91,6 +100,47 @@ public class AvoidEntityGoal extends Goal { } } @@ -310,7 +309,7 @@ index 4b24bb47a53a46586a642f3fb2656b1b8b670bf2..149672d5aea4e04f74f8fb80a3f4856f + return; + } + ctx.result = serverLevel.getNearestEntity( -+ serverLevel.getEntitiesOfClass(avoidClass, bound, LivingEntity::isAlive), ++ serverLevel.getEntitiesOfClass(avoidClass, bound, livingEntity -> true), + avoidEntityTargeting, + mob, + x, From 9b76be10f910f1b4b32d417faff1f65c1c75155a Mon Sep 17 00:00:00 2001 From: hayanesuru Date: Thu, 1 May 2025 21:53:19 +0800 Subject: [PATCH 6/8] fix inactiveTick tickingTarget --- .../features/0154-Async-target-finding.patch | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/leaf-server/minecraft-patches/features/0154-Async-target-finding.patch b/leaf-server/minecraft-patches/features/0154-Async-target-finding.patch index ea082b856..63d3116c4 100644 --- a/leaf-server/minecraft-patches/features/0154-Async-target-finding.patch +++ b/leaf-server/minecraft-patches/features/0154-Async-target-finding.patch @@ -185,7 +185,7 @@ index 90879616842cc61d15854b07f56f6fcb89f11074..f114a8f5143799d72e36e0a535888c5f // Paper start - Folia schedulers diff --git a/net/minecraft/world/entity/Mob.java b/net/minecraft/world/entity/Mob.java -index 05d5cde42b7011091ef4ee874c0d9d5586ae3f10..555e206c5fafaa801e46625070da2fd3bd603ce4 100644 +index 05d5cde42b7011091ef4ee874c0d9d5586ae3f10..bf021604e0c345ef72cbc55d4f8fe996d2d561f7 100644 --- a/net/minecraft/world/entity/Mob.java +++ b/net/minecraft/world/entity/Mob.java @@ -144,6 +144,10 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab @@ -199,19 +199,29 @@ index 05d5cde42b7011091ef4ee874c0d9d5586ae3f10..555e206c5fafaa801e46625070da2fd3 protected Mob(EntityType entityType, Level level) { super(entityType, level); -@@ -231,6 +235,11 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab +@@ -225,12 +229,21 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab + } + // Paper end - Skip AI during inactive ticks for non-aware mobs + boolean isThrottled = org.dreeam.leaf.config.modules.opt.ThrottleInactiveGoalSelectorTick.enabled && _pufferfish_inactiveTickDisableCounter++ % 20 != 0; // Pufferfish - throttle inactive goal selector ticking ++ this.tickingTarget = false; // Leaf + if (this.goalSelector.inactiveTick(this.activatedPriority, true) && !isThrottled) { // Pufferfish - pass activated priroity // Pufferfish - throttle inactive goal selector ticking + this.goalSelector.tick(); + } ++ this.tickingTarget = true; // Leaf if (this.targetSelector.inactiveTick(this.activatedPriority, true)) { // Pufferfish - pass activated priority this.targetSelector.tick(); } ++ // Leaf start - Async target finding + if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled) { + if (this.targetSelector.ctxGoals != null || this.goalSelector.ctxGoals != null) { + ((ServerLevel) this.level()).asyncGoalExecutor.submit(this.getId()); + } + } ++ // Leaf start - Async target finding } // Paper end -@@ -914,17 +923,28 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab +@@ -914,17 +927,28 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab // Paper end - Allow nerfed mobs to jump and float this.sensing.tick(); int i = this.tickCount + this.getId(); From a8ce9870246334a75fdb06206e7c3beac4cbae11 Mon Sep 17 00:00:00 2001 From: hayanesuru Date: Thu, 1 May 2025 22:02:57 +0800 Subject: [PATCH 7/8] default queueSize to 0 --- .../dreeam/leaf/config/modules/async/AsyncTargetFinding.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncTargetFinding.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncTargetFinding.java index 9519291bb..45f549ecb 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncTargetFinding.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncTargetFinding.java @@ -16,7 +16,7 @@ public String getBasePath() { public static boolean alertOther = true; public static boolean searchBlock = true; public static boolean searchEntity = true; - public static int queueSize = 100_000; + public static int queueSize = 0; private static boolean asyncTargetFindingInitialized; @Override From 9a50196a53ab457fd7062dbb2b2b7879c94f631a Mon Sep 17 00:00:00 2001 From: hayanesuru Date: Thu, 1 May 2025 22:24:53 +0800 Subject: [PATCH 8/8] default queueSize to 4096 --- .../dreeam/leaf/config/modules/async/AsyncTargetFinding.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncTargetFinding.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncTargetFinding.java index 45f549ecb..ea66a4d56 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncTargetFinding.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncTargetFinding.java @@ -16,7 +16,7 @@ public String getBasePath() { public static boolean alertOther = true; public static boolean searchBlock = true; public static boolean searchEntity = true; - public static int queueSize = 0; + public static int queueSize = 4096; private static boolean asyncTargetFindingInitialized; @Override @@ -41,7 +41,7 @@ public void onLoaded() { searchEntity = config.getBoolean(getBasePath() + ".async-search-entity", true); queueSize = config.getInt(getBasePath() + ".queue-size", 100_000); if (queueSize <= 0) { - queueSize = 100_000; + queueSize = 4096; } if (!enabled) { alertOther = false;