8000 optimize async target finding by hayanesuru · Pull Request #303 · Winds-Studio/Leaf · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

optimize async target finding #303

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
May 1, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,023 changes: 535 additions & 488 deletions leaf-server/minecraft-patches/features/0154-Async-target-finding.patch

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,19 +1,76 @@
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 final void submit(int entityId) {
if (!this.queue.send(entityId)) {
LockSupport.unpark(thread);
while (!this.queue.send(entityId)) {
Thread.onSpinWait();
}
}
dirty = true;
}

public static void runTasks(List<Runnable> tasks) {
for (Runnable task : tasks) {
task.run();
public final void unpark() {
if (dirty) LockSupport.unpark(thread);
10000 dirty = false;
}

public final void midTick() {
while (true) {
int id = this.wake.recv();
if (id == Integer.MAX_VALUE) {
break;
}
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;
}
}

Original file line number Diff line number Diff line change
@@ -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();
}
}
}
}
}
}
}
17 changes: 17 additions & 0 deletions leaf-server/src/main/java/org/dreeam/leaf/async/ai/Waker.java
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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() {
Expand All @@ -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 = 4096;
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.""",
"""
**实验性功能**
这会将昂贵的实体目标搜索计算移至后台线程, 同时在主线程上保持实际的实体验证.""");
Expand All @@ -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 = 4096;
}
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());
}
}
7E42
Original file line number Diff line number Diff line change
@@ -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;
}
}
0