9
0
mirror of https://github.com/Winds-Studio/Leaf.git synced 2025-12-29 03:49:21 +00:00

Merge branch 'ver/1.21.4' into dev/random-tick

This commit is contained in:
hayanesuru
2025-06-13 18:19:09 +09:00
121 changed files with 778 additions and 336 deletions

View File

@@ -13,7 +13,7 @@ public class AsyncExecutor implements Runnable {
private final Logger LOGGER = LogManager.getLogger("Leaf");
private final PriorityQueue<Runnable> jobs = PriorityQueues.synchronize(new ObjectArrayFIFOQueue<>());
private final Thread thread;
public final Thread thread;
private volatile boolean killswitch = false;
public AsyncExecutor(String threadName) {
@@ -28,10 +28,10 @@ public class AsyncExecutor implements Runnable {
thread.start();
}
public void kill() throws InterruptedException {
public void join(long millis) throws InterruptedException {
killswitch = true;
LockSupport.unpark(thread);
thread.join();
thread.join(millis);
}
public void submit(Runnable runnable) {

View File

@@ -12,20 +12,30 @@ import java.util.concurrent.TimeUnit;
public class AsyncPlayerDataSaving {
public static final ExecutorService IO_POOL = new ThreadPoolExecutor(
1, 1, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(),
new com.google.common.util.concurrent.ThreadFactoryBuilder()
.setPriority(Thread.NORM_PRIORITY - 2)
.setNameFormat("Leaf IO Thread")
.setUncaughtExceptionHandler(Util::onThreadException)
.build(),
new ThreadPoolExecutor.DiscardPolicy()
);
public static ExecutorService IO_POOL = null;
private AsyncPlayerDataSaving() {
}
public static void init() {
if (IO_POOL == null) {
IO_POOL = new ThreadPoolExecutor(
1,
1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(),
new com.google.common.util.concurrent.ThreadFactoryBuilder()
.setPriority(Thread.NORM_PRIORITY - 2)
.setNameFormat("Leaf IO Thread")
.setUncaughtExceptionHandler(Util::onThreadException)
.build(),
new ThreadPoolExecutor.DiscardPolicy()
);
} else {
throw new IllegalStateException();
}
}
public static Optional<Future<?>> submit(Runnable runnable) {
if (!AsyncPlayerDataSave.enabled) {
runnable.run();

View File

@@ -1,37 +1,59 @@
package org.dreeam.leaf.async;
import net.minecraft.server.MinecraftServer;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dreeam.leaf.async.ai.AsyncGoalThread;
import org.dreeam.leaf.async.path.AsyncPathProcessor;
import org.dreeam.leaf.async.tracker.MultithreadedTracker;
public class ShutdownExecutors {
public static void shutdown(MinecraftServer server) {
import java.util.concurrent.TimeUnit;
if (server.mobSpawnExecutor != null) {
public class ShutdownExecutors {
public static final Logger LOGGER = LogManager.getLogger("Leaf");
public static void shutdown(MinecraftServer server) {
if (server.mobSpawnExecutor != null && server.mobSpawnExecutor.thread.isAlive()) {
LOGGER.info("Waiting for mob spawning thread to shutdown...");
try {
server.mobSpawnExecutor.kill();
server.mobSpawnExecutor.join(3000L);
} catch (InterruptedException ignored) {
}
}
if (AsyncPlayerDataSaving.IO_POOL != null) {
LOGGER.info("Waiting for player I/O executor to shutdown...");
AsyncPlayerDataSaving.IO_POOL.shutdown();
try {
AsyncPlayerDataSaving.IO_POOL.awaitTermination(300L, java.util.concurrent.TimeUnit.SECONDS);
AsyncPlayerDataSaving.IO_POOL.awaitTermination(60L, TimeUnit.SECONDS);
} catch (InterruptedException ignored) {
}
}
if (server.asyncGoalThread != null) {
LOGGER.info("Waiting for mob target finding thread to shutdown...");
AsyncGoalThread.RUNNING = false;
try {
server.asyncGoalThread.join();
server.asyncGoalThread.join(3000L);
} catch (InterruptedException ignored) {
}
}
if (MultithreadedTracker.TRACKER_EXECUTOR != null) {
LOGGER.info("Waiting for mob tracker executor to shutdown...");
MultithreadedTracker.TRACKER_EXECUTOR.shutdown();
try {
MultithreadedTracker.TRACKER_EXECUTOR.awaitTermination(10L, java.util.concurrent.TimeUnit.SECONDS);
MultithreadedTracker.TRACKER_EXECUTOR.awaitTermination(10L, TimeUnit.SECONDS);
} catch (InterruptedException ignored) {
}
}
if (AsyncPathProcessor.PATH_PROCESSING_EXECUTOR != null) {
LOGGER.info("Waiting for mob pathfinding executor to shutdown...");
AsyncPathProcessor.PATH_PROCESSING_EXECUTOR.shutdown();
try {
AsyncPathProcessor.PATH_PROCESSING_EXECUTOR.awaitTermination(10L, TimeUnit.SECONDS);
} catch (InterruptedException ignored) {
}
}

View File

@@ -9,6 +9,7 @@ import java.util.concurrent.locks.LockSupport;
public class AsyncGoalThread extends Thread {
public static volatile boolean RUNNING = true;
public AsyncGoalThread(final MinecraftServer server) {
super(() -> run(server), "Leaf Async Goal Thread");
this.setDaemon(false);
@@ -18,7 +19,7 @@ public class AsyncGoalThread extends Thread {
}
private static void run(MinecraftServer server) {
while (server.isRunning()) {
while (RUNNING) {
boolean retry = false;
for (ServerLevel level : server.getAllLevels()) {
var exec = level.asyncGoalExecutor;

View File

@@ -1,6 +1,7 @@
package org.dreeam.leaf.async.path;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import net.minecraft.Util;
import net.minecraft.server.MinecraftServer;
import net.minecraft.world.level.pathfinder.Path;
import org.apache.logging.log4j.LogManager;
@@ -15,6 +16,7 @@ import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
@@ -28,49 +30,25 @@ public class AsyncPathProcessor {
private static final String THREAD_PREFIX = "Leaf Async Pathfinding";
private static final Logger LOGGER = LogManager.getLogger(THREAD_PREFIX);
private static long lastWarnMillis = System.currentTimeMillis();
private static final ThreadPoolExecutor pathProcessingExecutor = new ThreadPoolExecutor(
1,
AsyncPathfinding.asyncPathfindingMaxThreads,
AsyncPathfinding.asyncPathfindingKeepalive, TimeUnit.SECONDS,
getQueueImpl(),
new ThreadFactoryBuilder()
.setNameFormat(THREAD_PREFIX + " Thread - %d")
.setPriority(Thread.NORM_PRIORITY - 2)
.build(),
new RejectedTaskHandler()
);
public static ThreadPoolExecutor PATH_PROCESSING_EXECUTOR = null;
private static class RejectedTaskHandler implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable rejectedTask, ThreadPoolExecutor executor) {
BlockingQueue<Runnable> workQueue = executor.getQueue();
if (!executor.isShutdown()) {
switch (AsyncPathfinding.asyncPathfindingRejectPolicy) {
case FLUSH_ALL -> {
if (!workQueue.isEmpty()) {
List<Runnable> pendingTasks = new ArrayList<>(workQueue.size());
workQueue.drainTo(pendingTasks);
for (Runnable pendingTask : pendingTasks) {
pendingTask.run();
}
}
rejectedTask.run();
}
case CALLER_RUNS -> rejectedTask.run();
}
}
if (System.currentTimeMillis() - lastWarnMillis > 30000L) {
LOGGER.warn("Async pathfinding processor is busy! Pathfinding tasks will be treated as policy defined in config. Increasing max-threads in Leaf config may help.");
lastWarnMillis = System.currentTimeMillis();
}
public static void init() {
if (PATH_PROCESSING_EXECUTOR == null) {
PATH_PROCESSING_EXECUTOR = new ThreadPoolExecutor(
getCorePoolSize(),
getMaxPoolSize(),
getKeepAliveTime(), TimeUnit.SECONDS,
getQueueImpl(),
getThreadFactory(),
getRejectedPolicy()
);
} else {
throw new IllegalStateException();
}
}
protected static CompletableFuture<Void> queue(@NotNull AsyncPath path) {
return CompletableFuture.runAsync(path::process, pathProcessingExecutor)
return CompletableFuture.runAsync(path::process, PATH_PROCESSING_EXECUTOR)
.orTimeout(60L, TimeUnit.SECONDS)
.exceptionally(throwable -> {
if (throwable instanceof TimeoutException e) {
@@ -98,9 +76,57 @@ public class AsyncPathProcessor {
}
}
private static int getCorePoolSize() {
return 1;
}
private static int getMaxPoolSize() {
return AsyncPathfinding.asyncPathfindingMaxThreads;
}
private static long getKeepAliveTime() {
return AsyncPathfinding.asyncPathfindingKeepalive;
}
private static BlockingQueue<Runnable> getQueueImpl() {
final int queueCapacity = AsyncPathfinding.asyncPathfindingQueueSize;
return new LinkedBlockingQueue<>(queueCapacity);
}
private static @NotNull ThreadFactory getThreadFactory() {
return new ThreadFactoryBuilder()
.setNameFormat(THREAD_PREFIX + " Thread - %d")
.setPriority(Thread.NORM_PRIORITY - 2)
.setUncaughtExceptionHandler(Util::onThreadException)
.build();
}
private static @NotNull RejectedExecutionHandler getRejectedPolicy() {
return (Runnable rejectedTask, ThreadPoolExecutor executor) -> {
BlockingQueue<Runnable> workQueue = executor.getQueue();
if (!executor.isShutdown()) {
switch (AsyncPathfinding.asyncPathfindingRejectPolicy) {
case FLUSH_ALL -> {
if (!workQueue.isEmpty()) {
List<Runnable> pendingTasks = new ArrayList<>(workQueue.size());
workQueue.drainTo(pendingTasks);
for (Runnable pendingTask : pendingTasks) {
pendingTask.run();
}
}
rejectedTask.run();
}
case CALLER_RUNS -> rejectedTask.run();
}
}
if (System.currentTimeMillis() - lastWarnMillis > 30000L) {
LOGGER.warn("Async pathfinding processor is busy! Pathfinding tasks will be treated as policy defined in config. Increasing max-threads in Leaf config may help.");
lastWarnMillis = System.currentTimeMillis();
}
};
}
}

View File

@@ -60,5 +60,9 @@ public class AsyncPathfinding extends ConfigModules {
? PathfindTaskRejectPolicy.FLUSH_ALL.toString()
: PathfindTaskRejectPolicy.CALLER_RUNS.toString())
);
if (enabled) {
org.dreeam.leaf.async.path.AsyncPathProcessor.init();
}
}
}

View File

@@ -19,5 +19,9 @@ public class AsyncPlayerDataSave extends ConfigModules {
异步保存玩家数据.""");
enabled = config.getBoolean(getBasePath() + ".enabled", enabled);
if (enabled) {
org.dreeam.leaf.async.AsyncPlayerDataSaving.init();
}
}
}

View File

@@ -57,6 +57,7 @@ public class MultithreadedTracker extends ConfigModules {
if (asyncEntityTrackerQueueSize <= 0)
asyncEntityTrackerQueueSize = asyncEntityTrackerMaxThreads * 384;
if (enabled) {
org.dreeam.leaf.async.tracker.MultithreadedTracker.init();
}

View File

@@ -1,18 +0,0 @@
package org.dreeam.leaf.config.modules.opt;
import org.dreeam.leaf.config.ConfigModules;
import org.dreeam.leaf.config.EnumConfigCategory;
public class OptimiseBlockEntities extends ConfigModules {
public String getBasePath() {
return EnumConfigCategory.PERF.getBaseKeyName();
}
public static boolean enabled = true;
@Override
public void onLoaded() {
enabled = config.getBoolean(getBasePath() + ".optimise-block-entities", enabled);
}
}

View File

@@ -0,0 +1,24 @@
package org.dreeam.leaf.config.modules.opt;
import org.dreeam.leaf.config.ConfigModules;
import org.dreeam.leaf.config.EnumConfigCategory;
public class OptimizeBlockEntities extends ConfigModules {
public String getBasePath() {
return EnumConfigCategory.PERF.getBaseKeyName();
}
public static boolean enabled = true;
@Override
public void onLoaded() {
// Transfer old config
Boolean optimiseBlockEntities = config.getBoolean(getBasePath() + ".optimise-block-entities");
if (optimiseBlockEntities != null && optimiseBlockEntities) {
enabled = true;
}
enabled = config.getBoolean(getBasePath() + ".optimize-block-entities", enabled);
}
}

View File

@@ -17,6 +17,5 @@ public class OptimizePlayerMovementProcessing extends ConfigModules {
Whether to optimize player movement processing by skipping unnecessary edge checks and avoiding redundant view distance updates.""",
"""
是否优化玩家移动处理,跳过不必要的边缘检查并避免冗余的视距更新。"""));
}
}

View File

@@ -1,17 +0,0 @@
package org.dreeam.leaf.config.modules.opt;
import org.dreeam.leaf.config.ConfigModules;
import org.dreeam.leaf.config.EnumConfigCategory;
public class PreloadNaturalMobSpawning extends ConfigModules {
public String getBasePath() {
return EnumConfigCategory.PERF.getBaseKeyName() + ".preload-mob-spawning-position";
}
public static boolean enabled = false;
@Override
public void onLoaded() {
enabled = config.getBoolean(getBasePath() + ".enabled", enabled);
}
}

View File

@@ -15,7 +15,7 @@ public class ReduceChunkSourceUpdates extends ConfigModules {
public void onLoaded() {
enabled = config.getBoolean(getBasePath() + ".enabled", enabled,
config.pickStringRegionBased(
"Reduces chunk source updates on inter-chunk player moves. (Recommended to enable)",
"Reduces chunk source updates on inter-chunk player moves.",
"减少玩家跨区块移动时的区块源更新。"
)
);

View File

@@ -46,25 +46,25 @@ public final class BlockPosIterator extends AbstractIterator<BlockPos> {
MutableBlockPos pos = this.pos;
if (pos == null) {
return this.pos = new MutableBlockPos(this.startX, this.startY, this.startZ);
} else {
int x = pos.getX();
int y = pos.getY();
int z = pos.getZ();
if (y < this.endY) {
y += 1;
} else if (x < this.endX) {
x += 1;
y = this.startY;
} else if (z < this.endZ) {
z += 1;
x = this.startX;
} else {
return this.endOfData();
}
pos.set(x, y, z);
return pos;
}
int x = pos.getX();
int y = pos.getY();
int z = pos.getZ();
if (y < this.endY) {
y += 1;
} else if (x < this.endX) {
x += 1;
y = this.startY;
} else if (z < this.endZ) {
z += 1;
x = this.startX;
y = this.startY; // Reset y also!
} else {
return this.endOfData();
}
pos.set(x, y, z);
return pos;
}
}

View File

@@ -1,5 +1,6 @@
package org.dreeam.leaf.world.block;
import it.unimi.dsi.fastutil.objects.Object2BooleanOpenHashMap;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.level.Level;
@@ -8,8 +9,6 @@ import net.minecraft.world.level.block.PoweredRailBlock;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.RailShape;
import java.util.HashMap;
import static net.minecraft.world.level.block.Block.*;
import static net.minecraft.world.level.block.PoweredRailBlock.POWERED;
import static net.minecraft.world.level.block.PoweredRailBlock.SHAPE;
@@ -23,6 +22,8 @@ public class OptimizedPoweredRails {
private static int RAIL_POWER_LIMIT = 8;
private static final Object2BooleanOpenHashMap<BlockPos> CHECKED_POS_POOL = new Object2BooleanOpenHashMap<>();
private static void giveShapeUpdate(Level level, BlockState state, BlockPos pos, BlockPos fromPos, Direction direction) {
BlockState oldState = level.getBlockState(pos);
Block.updateOrDestroy(
@@ -45,8 +46,8 @@ public class OptimizedPoweredRails {
public static void updateState(PoweredRailBlock self, BlockState state, Level level, BlockPos pos) {
boolean shouldBePowered = level.hasNeighborSignal(pos) ||
self.findPoweredRailSignal(level, pos, state, true, 0) ||
self.findPoweredRailSignal(level, pos, state, false, 0);
findPoweredRailSignalFaster(self, level, pos, state, true, 0, CHECKED_POS_POOL) ||
findPoweredRailSignalFaster(self, level, pos, state, false, 0, CHECKED_POS_POOL);
if (shouldBePowered != state.getValue(POWERED)) {
RailShape railShape = state.getValue(SHAPE);
if (railShape.isSlope()) {
@@ -63,9 +64,9 @@ public class OptimizedPoweredRails {
private static boolean findPoweredRailSignalFaster(PoweredRailBlock self, Level world, BlockPos pos,
boolean bl, int distance, RailShape shape,
HashMap<BlockPos, Boolean> checkedPos) {
Object2BooleanOpenHashMap<BlockPos> checkedPos) {
BlockState blockState = world.getBlockState(pos);
boolean speedCheck = checkedPos.containsKey(pos) && checkedPos.get(pos);
boolean speedCheck = checkedPos.containsKey(pos) && checkedPos.getBoolean(pos);
if (speedCheck) {
return world.hasNeighborSignal(pos) ||
findPoweredRailSignalFaster(self, world, pos, blockState, bl, distance + 1, checkedPos);
@@ -95,7 +96,7 @@ public class OptimizedPoweredRails {
private static boolean findPoweredRailSignalFaster(PoweredRailBlock self, Level level,
BlockPos pos, BlockState state, boolean bl, int distance,
HashMap<BlockPos, Boolean> checkedPos) {
Object2BooleanOpenHashMap<BlockPos> checkedPos) {
if (distance >= RAIL_POWER_LIMIT - 1) return false;
int i = pos.getX();
int j = pos.getY();
@@ -165,7 +166,8 @@ public class OptimizedPoweredRails {
private static void powerLane(PoweredRailBlock self, Level world, BlockPos pos,
BlockState mainState, RailShape railShape) {
world.setBlock(pos, mainState.setValue(POWERED, true), UPDATE_FORCE_PLACE);
HashMap<BlockPos, Boolean> checkedPos = new HashMap<>();
Object2BooleanOpenHashMap<BlockPos> checkedPos = CHECKED_POS_POOL;
checkedPos.clear();
checkedPos.put(pos, true);
int[] count = new int[2];
if (railShape == RailShape.NORTH_SOUTH) { // Order: +z, -z
@@ -179,6 +181,7 @@ public class OptimizedPoweredRails {
}
updateRails(self, true, world, pos, mainState, count);
}
checkedPos.clear();
}
private static void dePowerLane(PoweredRailBlock self, Level world, BlockPos pos,
@@ -199,39 +202,46 @@ public class OptimizedPoweredRails {
}
private static void setRailPositionsPower(PoweredRailBlock self, Level world, BlockPos pos,
HashMap<BlockPos, Boolean> checkedPos, int[] count, int i, Direction dir) {
Object2BooleanOpenHashMap<BlockPos> checkedPos, int[] count, int i, Direction dir) {
for (int z = 1; z < RAIL_POWER_LIMIT; z++) {
BlockPos newPos = pos.relative(dir, z);
BlockState state = world.getBlockState(newPos);
if (checkedPos.containsKey(newPos)) {
if (!checkedPos.get(newPos)) break;
if (!checkedPos.getBoolean(newPos))
break;
count[i]++;
} else if (!state.is(self) || state.getValue(POWERED) || !(
world.hasNeighborSignal(newPos) ||
} else if (!state.is(self) || state.getValue(POWERED) || !(world.hasNeighborSignal(newPos) ||
findPoweredRailSignalFaster(self, world, newPos, state, true, 0, checkedPos) ||
findPoweredRailSignalFaster(self, world, newPos, state, false, 0, checkedPos)
)) {
findPoweredRailSignalFaster(self, world, newPos, state, false, 0, checkedPos))) {
checkedPos.put(newPos, false);
break;
} else {
checkedPos.put(newPos, true);
world.setBlock(newPos, state.setValue(POWERED, true), UPDATE_FORCE_PLACE);
if (!state.getValue(POWERED)) {
world.setBlock(newPos, state.setValue(POWERED, true), UPDATE_FORCE_PLACE);
}
count[i]++;
}
}
}
private static void setRailPositionsDePower(PoweredRailBlock self, Level world, BlockPos pos,
int[] count, int i, Direction dir) {
int[] count, int i, Direction dir) {
Object2BooleanOpenHashMap<BlockPos> checkedPos = CHECKED_POS_POOL;
checkedPos.clear();
for (int z = 1; z < RAIL_POWER_LIMIT; z++) {
BlockPos newPos = pos.relative(dir, z);
BlockState state = world.getBlockState(newPos);
if (!state.is(self) || !state.getValue(POWERED) || world.hasNeighborSignal(newPos) ||
self.findPoweredRailSignal(world, newPos, state, true, 0) ||
self.findPoweredRailSignal(world, newPos, state, false, 0)) break;
world.setBlock(newPos, state.setValue(POWERED, false), UPDATE_FORCE_PLACE);
findPoweredRailSignalFaster(self, world, newPos, state, true, 0, checkedPos) ||
findPoweredRailSignalFaster(self, world, newPos, state, false, 0, checkedPos))
break;
if (state.getValue(POWERED)) {
world.setBlock(newPos, state.setValue(POWERED, false), UPDATE_FORCE_PLACE);
}
count[i]++;
}
checkedPos.clear();
}
private static void shapeUpdateEnd(PoweredRailBlock self, Level world, BlockPos pos, BlockState mainState,