diff --git a/leaf-server/minecraft-patches/features/0157-AsyncBlockFinding.patch b/leaf-server/minecraft-patches/features/0157-AsyncBlockFinding.patch new file mode 100644 index 00000000..b19ba862 --- /dev/null +++ b/leaf-server/minecraft-patches/features/0157-AsyncBlockFinding.patch @@ -0,0 +1,138 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Taiyou06 +Date: Sun, 23 Mar 2025 11:51:44 +0100 +Subject: [PATCH] AsyncBlockFinding + + +diff --git a/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java b/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java +index 3f080b15543bf8c5fa0774b62d7f12e13b82511a..51a949dbb88d7fff076379962000b70acecacce3 100644 +--- a/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java ++++ b/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java +@@ -1,9 +1,16 @@ + package net.minecraft.world.entity.ai.goal; + + import java.util.EnumSet; ++import java.util.ArrayList; ++import java.util.List; ++import java.util.concurrent.ConcurrentLinkedQueue; ++import java.util.concurrent.ExecutorService; ++import java.util.concurrent.Executors; ++import com.google.common.util.concurrent.ThreadFactoryBuilder; + import net.minecraft.core.BlockPos; + import net.minecraft.world.entity.PathfinderMob; + import net.minecraft.world.level.LevelReader; ++import org.dreeam.leaf.config.modules.async.AsyncBlockFinding; + + public abstract class MoveToBlockGoal extends Goal { + private static final int GIVE_UP_TICKS = 1200; +@@ -20,6 +27,17 @@ public abstract class MoveToBlockGoal extends Goal { + private final int verticalSearchRange; + protected int verticalSearchStart; + ++ private static final ExecutorService BLOCK_FINDER_EXECUTOR = ++ Executors.newSingleThreadExecutor( ++ new ThreadFactoryBuilder() ++ .setNameFormat("block-finder-%d") ++ .setDaemon(true) ++ .build()); ++ ++ private final ConcurrentLinkedQueue candidateBlocks = new ConcurrentLinkedQueue<>(); ++ private boolean asyncSearchInProgress = false; ++ private boolean searchComplete = false; ++ + public MoveToBlockGoal(PathfinderMob mob, double speedModifier, int searchRange) { + this(mob, speedModifier, searchRange, 1); + } +@@ -109,6 +127,92 @@ public abstract class MoveToBlockGoal extends Goal { + } + + protected boolean findNearestBlock() { ++ // Check if async is enabled, if not use the original implementation ++ if (!AsyncBlockFinding.enabled) { ++ return findNearestBlockSync(); ++ } ++ ++ // Check if we're done with the current search ++ if (searchComplete) { ++ searchComplete = false; ++ asyncSearchInProgress = false; ++ candidateBlocks.clear(); ++ return false; ++ } ++ while (!candidateBlocks.isEmpty()) { ++ BlockPos pos = candidateBlocks.poll(); ++ ++ if (pos != null && this.mob.level().hasChunkAt(pos) && ++ this.mob.isWithinRestriction(pos) && ++ this.isValidTarget(this.mob.level(), pos)) { ++ ++ this.blockPos = pos; ++ this.mob.movingTarget = pos == BlockPos.ZERO ? null : pos; ++ searchComplete = true; ++ return true; ++ } ++ } ++ ++ // If no candidates are left and async search is done ++ if (candidateBlocks.isEmpty() && !asyncSearchInProgress) { ++ searchComplete = true; ++ return false; ++ } ++ ++ // Start async search if needed ++ if (!asyncSearchInProgress && candidateBlocks.isEmpty()) { ++ asyncSearchInProgress = true; ++ ++ // Get necessary data from main thread ++ final BlockPos centerPos = this.mob.blockPosition().immutable(); ++ final int searchRange = this.searchRange; ++ final int verticalRange = this.verticalSearchRange; ++ final int verticalStart = this.verticalSearchStart; ++ BLOCK_FINDER_EXECUTOR.execute(() -> { ++ try { ++ generateCandidateBlocks(centerPos, searchRange, verticalRange, verticalStart); ++ } catch (Exception e) { ++ e.printStackTrace(); ++ } finally { ++ asyncSearchInProgress = false; ++ } ++ }); ++ } ++ ++ return false; ++ } ++ ++ // Generate candidate blocks in a spiral pattern ++ private void generateCandidateBlocks(BlockPos center, int searchRange, int verticalRange, int verticalStart) { ++ // Pre-calculate a prioritized list of positions ++ List positions = new ArrayList<>(); ++ ++ for (int i2 = verticalStart; i2 <= verticalRange; i2 = i2 > 0 ? -i2 : 1 - i2) { ++ for (int i3 = 0; i3 < searchRange; i3++) { ++ for (int i4 = 0; i4 <= i3; i4 = i4 > 0 ? -i4 : 1 - i4) { ++ for (int i5 = i4 < i3 && i4 > -i3 ? i3 : 0; i5 <= i3; i5 = i5 > 0 ? -i5 : 1 - i5) { ++ BlockPos pos = center.offset(i4, i2 - 1, i5); ++ positions.add(pos.immutable()); ++ } ++ } ++ } ++ } ++ ++ // Sort by distance to center (closest first) ++ positions.sort((p1, p2) -> { ++ double d1 = p1.distSqr(center); ++ double d2 = p2.distSqr(center); ++ return Double.compare(d1, d2); ++ }); ++ ++ // Add to candidate queue ++ for (BlockPos pos : positions) { ++ candidateBlocks.add(pos); ++ } ++ } ++ ++ // The original method renamed ++ protected boolean findNearestBlockSync() { + int i = this.searchRange; + int i1 = this.verticalSearchRange; + BlockPos blockPos = this.mob.blockPosition(); diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncBlockFinding.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncBlockFinding.java new file mode 100644 index 00000000..063a0b90 --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncBlockFinding.java @@ -0,0 +1,32 @@ +package org.dreeam.leaf.config.modules.async; + +import org.dreeam.leaf.config.ConfigModules; +import org.dreeam.leaf.config.EnumConfigCategory; +import org.dreeam.leaf.config.annotations.Experimental; + +public class AsyncBlockFinding extends ConfigModules { + + public String getBasePath() { + return EnumConfigCategory.ASYNC.getBaseKeyName() + ".async-block-finding"; + } + + @Experimental + public static boolean enabled = false; + + public static boolean asyncBlockFindingInitialized; + + @Override + public void onLoaded() { + config.addCommentRegionBased(getBasePath(), """ + **Experimental feature** + This moves the expensive search calculations to a background thread while + keeping the actual block validation on the main thread.""", + """ + 这会将昂贵的搜索计算移至后台线程, 同时在主线程上保持实际的方块验证."""); + + if (!asyncBlockFindingInitialized) { + asyncBlockFindingInitialized = true; + enabled = config.getBoolean(getBasePath() + ".enabled", enabled); + } + } +}