mirror of
https://github.com/Winds-Studio/Leaf.git
synced 2025-12-25 09:59:15 +00:00
Reduce AsyncGoal content switching
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
package org.dreeam.leaf.async;
|
||||
|
||||
import net.minecraft.Util;
|
||||
import org.dreeam.leaf.config.modules.async.AsyncPlayerDataSave;
|
||||
|
||||
import java.util.Optional;
|
||||
@@ -17,7 +18,7 @@ public class AsyncPlayerDataSaving {
|
||||
new com.google.common.util.concurrent.ThreadFactoryBuilder()
|
||||
.setPriority(Thread.NORM_PRIORITY - 2)
|
||||
.setNameFormat("Leaf IO Thread")
|
||||
.setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(net.minecraft.server.MinecraftServer.LOGGER))
|
||||
.setUncaughtExceptionHandler(Util::onThreadException)
|
||||
.build(),
|
||||
new ThreadPoolExecutor.DiscardPolicy()
|
||||
);
|
||||
|
||||
@@ -17,20 +17,19 @@ public class AsyncGoalExecutor {
|
||||
protected final SpscIntQueue queue;
|
||||
protected final SpscIntQueue wake;
|
||||
private final AsyncGoalThread thread;
|
||||
private final ServerLevel serverLevel;
|
||||
private final ServerLevel world;
|
||||
private boolean dirty = false;
|
||||
private long tickCount = 0L;
|
||||
private static final int SPIN_LIMIT = 100;
|
||||
|
||||
public AsyncGoalExecutor(AsyncGoalThread thread, ServerLevel serverLevel) {
|
||||
this.serverLevel = serverLevel;
|
||||
public AsyncGoalExecutor(AsyncGoalThread thread, ServerLevel world) {
|
||||
this.world = world;
|
||||
this.queue = new SpscIntQueue(AsyncTargetFinding.queueSize);
|
||||
this.wake = new SpscIntQueue(AsyncTargetFinding.queueSize);
|
||||
this.thread = thread;
|
||||
}
|
||||
|
||||
boolean wake(int id) {
|
||||
Entity entity = this.serverLevel.getEntities().get(id);
|
||||
Entity entity = this.world.getEntities().get(id);
|
||||
if (entity == null || entity.isRemoved() || !(entity instanceof Mob mob)) {
|
||||
return false;
|
||||
}
|
||||
@@ -40,19 +39,13 @@ public class AsyncGoalExecutor {
|
||||
}
|
||||
|
||||
public final void submit(int entityId) {
|
||||
if (!this.queue.send(entityId)) {
|
||||
int spinCount = 0;
|
||||
while (!this.queue.send(entityId)) {
|
||||
spinCount++;
|
||||
// Unpark the thread after some spinning to help clear the queue
|
||||
if (spinCount > SPIN_LIMIT) {
|
||||
unpark();
|
||||
spinCount = 0;
|
||||
}
|
||||
Thread.onSpinWait();
|
||||
}
|
||||
}
|
||||
dirty = true;
|
||||
if (!this.queue.send(entityId)) {
|
||||
unpark();
|
||||
do {
|
||||
wake(entityId);
|
||||
} while (poll(entityId));
|
||||
}
|
||||
}
|
||||
|
||||
public final void unpark() {
|
||||
@@ -61,27 +54,31 @@ public class AsyncGoalExecutor {
|
||||
}
|
||||
|
||||
public final void midTick() {
|
||||
boolean didWork = false;
|
||||
while (true) {
|
||||
int id = this.wake.recv();
|
||||
if (id == Integer.MAX_VALUE) {
|
||||
Integer id = this.wake.recv();
|
||||
if (id == null) {
|
||||
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;
|
||||
boolean b = mob.goalSelector.poll();
|
||||
if (a || b) {
|
||||
if (poll(id)) {
|
||||
submit(id);
|
||||
}
|
||||
}
|
||||
if (didWork || (tickCount & 15L) == 0L) unpark();
|
||||
if ((tickCount & 7L) == 7L) {
|
||||
unpark();
|
||||
}
|
||||
tickCount += 1;
|
||||
}
|
||||
|
||||
private boolean poll(int id) {
|
||||
Entity entity = this.world.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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,8 +8,6 @@ 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(false);
|
||||
@@ -19,38 +17,27 @@ public class AsyncGoalThread extends Thread {
|
||||
}
|
||||
|
||||
private static void run(MinecraftServer server) {
|
||||
int emptySpins = 0;
|
||||
|
||||
while (server.isRunning()) {
|
||||
boolean didWork = false;
|
||||
boolean retry = 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) {
|
||||
Integer id = exec.queue.recv();
|
||||
if (id == null) {
|
||||
break;
|
||||
}
|
||||
levelWork = true;
|
||||
retry = 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
|
||||
if (retry) {
|
||||
Thread.yield();
|
||||
} 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
|
||||
}
|
||||
LockSupport.park();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package org.dreeam.leaf.util.queue;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/// Lock-free Single Producer Single Consumer Queue
|
||||
public class SpscIntQueue {
|
||||
|
||||
@@ -33,14 +35,14 @@ public class SpscIntQueue {
|
||||
}
|
||||
|
||||
|
||||
public final int recv() {
|
||||
public final @Nullable Integer 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;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
int e = data[idx];
|
||||
|
||||
Reference in New Issue
Block a user