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 a14c27e8..5ac7e6cb 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 @@ -18,6 +18,7 @@ public class AsyncGoalExecutor { private final ServerLevel serverLevel; private boolean dirty = false; private long tickCount = 0L; + private static final int SPIN_LIMIT = 100; public AsyncGoalExecutor(AsyncGoalThread thread, ServerLevel serverLevel) { this.serverLevel = serverLevel; @@ -38,8 +39,14 @@ public class AsyncGoalExecutor { public final void submit(int entityId) { if (!this.queue.send(entityId)) { - LockSupport.unpark(thread); + int spinCount = 0; while (!this.queue.send(entityId)) { + spinCount++; + // Unpark thread after some spinning to help clear the queue + if (spinCount > SPIN_LIMIT) { + unpark(); + spinCount = 0; + } Thread.onSpinWait(); } } @@ -52,15 +59,18 @@ public class AsyncGoalExecutor { } public final void midTick() { + boolean didWork = false; while (true) { int id = this.wake.recv(); if (id == Integer.MAX_VALUE) { break; } + didWork = true; 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; @@ -69,8 +79,7 @@ public class AsyncGoalExecutor { submit(id); } } - if ((tickCount & 3L) == 0L) unpark(); + if (didWork || (tickCount & 15L) == 0L) unpark(); tickCount += 1; } } - 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 index 9d8abe8c..7cab43cc 100644 --- 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 @@ -7,6 +7,8 @@ import net.minecraft.server.level.ServerLevel; import java.util.concurrent.locks.LockSupport; public class AsyncGoalThread extends Thread { + private static final int SPIN_TRIES = 1000; + public AsyncGoalThread(final MinecraftServer server) { super(() -> run(server), "Leaf Async Goal Thread"); this.setDaemon(true); @@ -16,21 +18,38 @@ public class AsyncGoalThread extends Thread { } private static void run(MinecraftServer server) { + int emptySpins = 0; + while (server.isRunning()) { - LockSupport.park(); + boolean didWork = false; for (ServerLevel level : server.getAllLevels()) { var exec = level.asyncGoalExecutor; + boolean levelWork = false; while (true) { int id = exec.queue.recv(); if (id == Integer.MAX_VALUE) { break; } + levelWork = true; if (exec.wake(id)) { while (!exec.wake.send(id)) { Thread.onSpinWait(); } } } + didWork |= levelWork; + } + // Adaptive parking + if (didWork) { + emptySpins = 0; // Reset counter when work was done + } else { + emptySpins++; + if (emptySpins > SPIN_TRIES) { + LockSupport.park(); // Only park after several empty spins + emptySpins = 0; + } else { + Thread.onSpinWait(); // Yield to other threads but don't park + } } } }