mirror of
https://github.com/Winds-Studio/Leaf.git
synced 2025-12-26 18:39:23 +00:00
Update changes from ver/1.21.4 branch
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
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.concurrent.locks.LockSupport;
|
||||
|
||||
public class AsyncGoalExecutor {
|
||||
|
||||
public static final Logger LOGGER = LogManager.getLogger("Leaf Async Goal");
|
||||
|
||||
protected final SpscIntQueue queue;
|
||||
protected final SpscIntQueue wake;
|
||||
private final AsyncGoalThread thread;
|
||||
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;
|
||||
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);
|
||||
if (entity == null || entity.isRemoved() || !(entity instanceof Mob mob)) {
|
||||
return false;
|
||||
}
|
||||
mob.goalSelector.wake();
|
||||
mob.targetSelector.wake();
|
||||
return true;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public final void unpark() {
|
||||
if (dirty) LockSupport.unpark(thread);
|
||||
dirty = false;
|
||||
}
|
||||
|
||||
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;
|
||||
boolean b = mob.goalSelector.poll();
|
||||
if (a || b) {
|
||||
submit(id);
|
||||
}
|
||||
}
|
||||
if (didWork || (tickCount & 15L) == 0L) unpark();
|
||||
tickCount += 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
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 {
|
||||
|
||||
private static final int SPIN_TRIES = 1000;
|
||||
|
||||
public AsyncGoalThread(final MinecraftServer server) {
|
||||
super(() -> run(server), "Leaf Async Goal Thread");
|
||||
this.setDaemon(false);
|
||||
this.setUncaughtExceptionHandler(Util::onThreadException);
|
||||
this.setPriority(Thread.NORM_PRIORITY - 1);
|
||||
this.start();
|
||||
}
|
||||
|
||||
private static void run(MinecraftServer server) {
|
||||
int emptySpins = 0;
|
||||
|
||||
while (server.isRunning()) {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,8 @@ import java.util.Locale;
|
||||
|
||||
public enum PathfindTaskRejectPolicy {
|
||||
FLUSH_ALL,
|
||||
CALLER_RUNS;
|
||||
CALLER_RUNS,
|
||||
DISCARD;
|
||||
|
||||
public static PathfindTaskRejectPolicy fromString(String policy) {
|
||||
try {
|
||||
|
||||
@@ -46,6 +46,13 @@ public abstract class ConfigModules extends LeafConfig {
|
||||
for (ConfigModules module : MODULES) {
|
||||
module.onPostLoaded();
|
||||
}
|
||||
|
||||
// Save config to disk
|
||||
try {
|
||||
LeafConfig.config().saveConfig();
|
||||
} catch (Exception e) {
|
||||
LeafConfig.LOGGER.error("Failed to save config file!", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static List<Field> getAnnotatedStaticFields(Class<?> clazz, Class<? extends Annotation> annotation) {
|
||||
|
||||
@@ -58,6 +58,7 @@ public class LeafConfig {
|
||||
|
||||
/* Load & Reload */
|
||||
|
||||
// Reload config (async)
|
||||
public static @NotNull CompletableFuture<Void> reloadAsync(CommandSender sender) {
|
||||
return CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
@@ -76,6 +77,7 @@ public class LeafConfig {
|
||||
}, Util.ioPool());
|
||||
}
|
||||
|
||||
// Init config
|
||||
public static void loadConfig() {
|
||||
try {
|
||||
long begin = System.nanoTime();
|
||||
@@ -100,9 +102,6 @@ public class LeafConfig {
|
||||
|
||||
// Load config modules
|
||||
ConfigModules.initModules();
|
||||
|
||||
// Save config to disk
|
||||
leafGlobalConfig.saveConfig();
|
||||
}
|
||||
|
||||
public static LeafGlobalConfig config() {
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
package org.dreeam.leaf.config.modules.async;
|
||||
|
||||
import org.dreeam.leaf.config.ConfigModules;
|
||||
import org.dreeam.leaf.config.EnumConfigCategory;
|
||||
|
||||
public class AsyncBlockFinding extends ConfigModules {
|
||||
|
||||
public String getBasePath() {
|
||||
return EnumConfigCategory.ASYNC.getBaseKeyName() + ".async-block-finding";
|
||||
}
|
||||
|
||||
public static boolean enabled = false;
|
||||
public static boolean asyncBlockFindingInitialized;
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
config.addCommentRegionBased(getBasePath(), """
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,8 +13,7 @@ public class AsyncChunkSend extends ConfigModules {
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
config.addCommentRegionBased(getBasePath(),
|
||||
"""
|
||||
config.addCommentRegionBased(getBasePath(), """
|
||||
Makes chunk packet preparation and sending asynchronous to improve server performance.
|
||||
This can significantly reduce main thread load when many players are loading chunks.""",
|
||||
"""
|
||||
|
||||
@@ -10,7 +10,7 @@ public class AsyncMobSpawning extends ConfigModules {
|
||||
}
|
||||
|
||||
public static boolean enabled = true;
|
||||
public static boolean asyncMobSpawningInitialized;
|
||||
private static boolean asyncMobSpawningInitialized;
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
@@ -22,13 +22,16 @@ public class AsyncMobSpawning extends ConfigModules {
|
||||
This just offloads some expensive calculations that are required for mob spawning.""",
|
||||
"""
|
||||
是否异步化生物生成.
|
||||
在实体较多的服务器上, 异步生成可最高带来15%的性能提升.
|
||||
在实体较多的服务器上, 异步生成可最高带来 15% 的性能提升.
|
||||
须在Paper配置文件中打开 per-player-mob-spawns 才能生效.""");
|
||||
|
||||
// This prevents us from changing the value during a reload.
|
||||
if (!asyncMobSpawningInitialized) {
|
||||
asyncMobSpawningInitialized = true;
|
||||
enabled = config.getBoolean(getBasePath() + ".enabled", enabled);
|
||||
if (asyncMobSpawningInitialized) {
|
||||
config.getConfigSection(getBasePath());
|
||||
return;
|
||||
}
|
||||
asyncMobSpawningInitialized = true;
|
||||
|
||||
enabled = config.getBoolean(getBasePath() + ".enabled", enabled);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,9 +16,27 @@ public class AsyncPathfinding extends ConfigModules {
|
||||
public static int asyncPathfindingKeepalive = 60;
|
||||
public static int asyncPathfindingQueueSize = 0;
|
||||
public static PathfindTaskRejectPolicy asyncPathfindingRejectPolicy = PathfindTaskRejectPolicy.FLUSH_ALL;
|
||||
private static boolean asyncPathfindingInitialized;
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
config.addCommentRegionBased(getBasePath() + ".reject-policy", """
|
||||
The policy to use when the queue is full and a new task is submitted.
|
||||
FLUSH_ALL: All pending tasks will be run on server thread.
|
||||
CALLER_RUNS: Newly submitted task will be run on server thread.
|
||||
DISCARD: Newly submitted task will be dropped directly.""",
|
||||
"""
|
||||
当队列满时, 新提交的任务将使用以下策略处理.
|
||||
FLUSH_ALL: 所有等待中的任务都将在主线程上运行.
|
||||
CALLER_RUNS: 新提交的任务将在主线程上运行.
|
||||
DISCARD: 新提交的任务会被直接丢弃."""
|
||||
);
|
||||
if (asyncPathfindingInitialized) {
|
||||
config.getConfigSection(getBasePath());
|
||||
return;
|
||||
}
|
||||
asyncPathfindingInitialized = true;
|
||||
|
||||
final int availableProcessors = Runtime.getRuntime().availableProcessors();
|
||||
enabled = config.getBoolean(getBasePath() + ".enabled", enabled);
|
||||
asyncPathfindingMaxThreads = config.getInt(getBasePath() + ".max-threads", asyncPathfindingMaxThreads);
|
||||
@@ -37,15 +55,10 @@ public class AsyncPathfinding extends ConfigModules {
|
||||
if (asyncPathfindingQueueSize <= 0)
|
||||
asyncPathfindingQueueSize = asyncPathfindingMaxThreads * 256;
|
||||
|
||||
asyncPathfindingRejectPolicy = PathfindTaskRejectPolicy.fromString(config.getString(getBasePath() + ".reject-policy", availableProcessors >= 12 && asyncPathfindingQueueSize < 512 ? PathfindTaskRejectPolicy.FLUSH_ALL.toString() : PathfindTaskRejectPolicy.CALLER_RUNS.toString(), config.pickStringRegionBased(
|
||||
"""
|
||||
The policy to use when the queue is full and a new task is submitted.
|
||||
FLUSH_ALL: All pending tasks will be run on server thread.
|
||||
CALLER_RUNS: Newly submitted task will be run on server thread.""",
|
||||
"""
|
||||
当队列满时, 新提交的任务将使用以下策略处理.
|
||||
FLUSH_ALL: 所有等待中的任务都将在主线程上运行.
|
||||
CALLER_RUNS: 新提交的任务将在主线程上运行."""
|
||||
)));
|
||||
asyncPathfindingRejectPolicy = PathfindTaskRejectPolicy.fromString(config.getString(getBasePath() + ".reject-policy",
|
||||
availableProcessors >= 12 && asyncPathfindingQueueSize < 512
|
||||
? PathfindTaskRejectPolicy.FLUSH_ALL.toString()
|
||||
: PathfindTaskRejectPolicy.CALLER_RUNS.toString())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,17 +10,13 @@ public class AsyncPlayerDataSave extends ConfigModules {
|
||||
return EnumConfigCategory.ASYNC.getBaseKeyName() + ".async-playerdata-save";
|
||||
}
|
||||
|
||||
@Experimental
|
||||
public static boolean enabled = false;
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
config.addCommentRegionBased(getBasePath(),
|
||||
"""
|
||||
**Experimental feature, may have data lost in some circumstances!**
|
||||
config.addCommentRegionBased(getBasePath(), """
|
||||
Make PlayerData saving asynchronously.""",
|
||||
"""
|
||||
**实验性功能, 在部分场景下可能丢失玩家数据!**
|
||||
异步保存玩家数据.""");
|
||||
|
||||
enabled = config.getBoolean(getBasePath() + ".enabled", enabled);
|
||||
|
||||
@@ -11,22 +11,40 @@ public class AsyncTargetFinding extends ConfigModules {
|
||||
return EnumConfigCategory.ASYNC.getBaseKeyName() + ".async-target-finding";
|
||||
}
|
||||
|
||||
@Experimental
|
||||
public static boolean enabled = false;
|
||||
public static boolean asyncTargetFindingInitialized;
|
||||
public static boolean alertOther = true;
|
||||
public static boolean searchBlock = true;
|
||||
public static boolean searchEntity = true;
|
||||
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.""",
|
||||
"""
|
||||
这会将昂贵的实体目标搜索计算移至后台线程, 同时在主线程上保持实际的实体验证.""");
|
||||
|
||||
if (!asyncTargetFindingInitialized) {
|
||||
asyncTargetFindingInitialized = true;
|
||||
enabled = config.getBoolean(getBasePath() + ".enabled", enabled);
|
||||
if (asyncTargetFindingInitialized) {
|
||||
config.getConfigSection(getBasePath());
|
||||
return;
|
||||
}
|
||||
asyncTargetFindingInitialized = true;
|
||||
|
||||
enabled = config.getBoolean(getBasePath() + ".enabled", enabled);
|
||||
alertOther = config.getBoolean(getBasePath() + ".async-alert-other", true);
|
||||
searchBlock = config.getBoolean(getBasePath() + ".async-search-block", true);
|
||||
searchEntity = config.getBoolean(getBasePath() + ".async-search-entity", true);
|
||||
queueSize = config.getInt(getBasePath() + ".queue-size", 4096);
|
||||
|
||||
if (queueSize <= 0) {
|
||||
queueSize = 4096;
|
||||
}
|
||||
if (!enabled) {
|
||||
alertOther = false;
|
||||
searchEntity = false;
|
||||
searchBlock = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ public class MultithreadedTracker extends ConfigModules {
|
||||
public static int asyncEntityTrackerMaxThreads = 0;
|
||||
public static int asyncEntityTrackerKeepalive = 60;
|
||||
public static int asyncEntityTrackerQueueSize = 0;
|
||||
private static boolean asyncMultithreadedTrackerInitialized;
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
@@ -24,15 +25,22 @@ public class MultithreadedTracker extends ConfigModules {
|
||||
"""
|
||||
异步实体跟踪,
|
||||
在实体数量多且密集的情况下效果明显.""");
|
||||
|
||||
enabled = config.getBoolean(getBasePath() + ".enabled", enabled);
|
||||
compatModeEnabled = config.getBoolean(getBasePath() + ".compat-mode", compatModeEnabled, config.pickStringRegionBased("""
|
||||
config.addCommentRegionBased(getBasePath() + ".compat-mode", """
|
||||
Enable compat mode ONLY if Citizens or NPC plugins using real entity has installed,
|
||||
Compat mode fixed visible issue with player type NPCs of Citizens,
|
||||
But still recommend to use packet based / virtual entity NPC plugin, e.g. ZNPC Plus, Adyeshach, Fancy NPC or else.""",
|
||||
"""
|
||||
是否启用兼容模式,
|
||||
如果你的服务器安装了 Citizens 或其他类似非发包 NPC 插件, 请开启此项."""));
|
||||
如果你的服务器安装了 Citizens 或其他类似非发包 NPC 插件, 请开启此项.""");
|
||||
|
||||
if (asyncMultithreadedTrackerInitialized) {
|
||||
config.getConfigSection(getBasePath());
|
||||
return;
|
||||
}
|
||||
asyncMultithreadedTrackerInitialized = true;
|
||||
|
||||
enabled = config.getBoolean(getBasePath() + ".enabled", enabled);
|
||||
compatModeEnabled = config.getBoolean(getBasePath() + ".compat-mode", compatModeEnabled);
|
||||
asyncEntityTrackerMaxThreads = config.getInt(getBasePath() + ".max-threads", asyncEntityTrackerMaxThreads);
|
||||
asyncEntityTrackerKeepalive = config.getInt(getBasePath() + ".keepalive", asyncEntityTrackerKeepalive);
|
||||
asyncEntityTrackerQueueSize = config.getInt(getBasePath() + ".queue-size", asyncEntityTrackerQueueSize);
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.dreeam.leaf.config.modules.async;
|
||||
|
||||
import org.dreeam.leaf.config.ConfigModules;
|
||||
import org.dreeam.leaf.config.EnumConfigCategory;
|
||||
import org.dreeam.leaf.config.LeafConfig;
|
||||
import org.dreeam.leaf.config.annotations.Experimental;
|
||||
|
||||
public class SparklyPaperParallelWorldTicking extends ConfigModules {
|
||||
@@ -19,22 +20,29 @@ public class SparklyPaperParallelWorldTicking extends ConfigModules {
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
config.addCommentRegionBased(getBasePath(),
|
||||
"""
|
||||
config.addCommentRegionBased(getBasePath(), """
|
||||
**Experimental feature**
|
||||
Enables parallel world ticking to improve performance on multi-core systems..""",
|
||||
Enables parallel world ticking to improve performance on multi-core systems.""",
|
||||
"""
|
||||
**实验性功能**
|
||||
启用并行世界处理以提高多核系统的性能.""");
|
||||
启用并行世界处理以提高多核 CPU 使用率.""");
|
||||
|
||||
enabled = config.getBoolean(getBasePath() + ".enabled", enabled);
|
||||
threads = config.getInt(getBasePath() + ".threads", threads);
|
||||
threads = enabled ? threads : 0;
|
||||
if (enabled) {
|
||||
if (threads <= 0) threads = 8;
|
||||
} else {
|
||||
threads = 0;
|
||||
}
|
||||
logContainerCreationStacktraces = config.getBoolean(getBasePath() + ".log-container-creation-stacktraces", logContainerCreationStacktraces);
|
||||
logContainerCreationStacktraces = enabled && logContainerCreationStacktraces;
|
||||
disableHardThrow = config.getBoolean(getBasePath() + ".disable-hard-throw", disableHardThrow);
|
||||
disableHardThrow = enabled && disableHardThrow;
|
||||
runAsyncTasksSync = config.getBoolean(getBasePath() + ".run-async-tasks-sync", runAsyncTasksSync);
|
||||
runAsyncTasksSync = enabled && runAsyncTasksSync;
|
||||
|
||||
if (enabled) {
|
||||
LeafConfig.LOGGER.info("Using {} threads for Parallel World Ticking", threads);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package org.dreeam.leaf.config.modules.gameplay;
|
||||
|
||||
import org.dreeam.leaf.config.ConfigModules;
|
||||
import org.dreeam.leaf.config.EnumConfigCategory;
|
||||
|
||||
public class ConfigurableInventoryOverflowEvent extends ConfigModules {
|
||||
|
||||
public String getBasePath() {
|
||||
return EnumConfigCategory.GAMEPLAY.getBaseKeyName() + ".inventory-overflow-event";
|
||||
}
|
||||
|
||||
public static boolean enabled = false;
|
||||
public static String listenerClass = "com.example.package.PlayerInventoryOverflowEvent" ;
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
enabled = config.getBoolean(getBasePath() + ".enabled", enabled, config.pickStringRegionBased("""
|
||||
The event called when used plugin to Inventory#addItem
|
||||
into player's inventory, and the inventory is full.
|
||||
This is not recommended to use, please re-design to use the
|
||||
returned map of Inventory#addItem method as soon as possible!""",
|
||||
"""
|
||||
此事件将在插件使用 Inventory#addItem 方法
|
||||
添加物品到玩家背包, 但是背包已满时调用.
|
||||
不建议使用此事件,请尽快迁移至使用 Inventory#addItem 方法
|
||||
返回的 map"""));
|
||||
listenerClass = config.getString(getBasePath() + ".listener-class", listenerClass, config.pickStringRegionBased("""
|
||||
The full class name of the listener which listens to this inventory overflow event.""",
|
||||
"""
|
||||
监听此物品栏物品溢出事件的完整类名."""));
|
||||
}
|
||||
}
|
||||
@@ -15,8 +15,7 @@ public class SmoothTeleport extends ConfigModules {
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
enabled = config.getBoolean(getBasePath(), enabled, config.pickStringRegionBased(
|
||||
"""
|
||||
enabled = config.getBoolean(getBasePath(), enabled, config.pickStringRegionBased("""
|
||||
**Experimental feature**
|
||||
Whether to make a "smooth teleport" when players changing dimension.
|
||||
This requires original world and target world have same logical height to work.""",
|
||||
|
||||
@@ -10,7 +10,7 @@ public class ServerBrand extends ConfigModules {
|
||||
}
|
||||
|
||||
public static String serverModName = io.papermc.paper.ServerBuildInfo.buildInfo().brandName();
|
||||
public static String serverGUIName = "Leaf Console";
|
||||
public static String serverGUIName = io.papermc.paper.ServerBuildInfo.buildInfo().brandName() + " Console";
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
|
||||
@@ -17,8 +17,7 @@ public class ChatMessageSignature extends ConfigModules {
|
||||
Whether or not enable chat message signature,
|
||||
disable will prevent players to report chat messages.
|
||||
And also disables the popup when joining a server without
|
||||
'secure chat', such as offline-mode servers.
|
||||
""",
|
||||
'secure chat', such as offline-mode servers.""",
|
||||
"""
|
||||
是否启用聊天签名, 禁用后玩家无法进行聊天举报."""));
|
||||
}
|
||||
|
||||
@@ -20,8 +20,7 @@ public class OptimizeNonFlushPacketSending extends ConfigModules {
|
||||
Optimizes non-flush packet sending by using Netty's lazyExecute method to avoid
|
||||
expensive thread wakeup calls when scheduling packet operations.
|
||||
|
||||
Requires server restart to take effect.
|
||||
""",
|
||||
Requires server restart to take effect.""",
|
||||
"""
|
||||
警告: 此选项与 ProtocolLib 不兼容, 并可能导致与其他修改数据包
|
||||
处理的插件出现问题.
|
||||
@@ -29,7 +28,6 @@ public class OptimizeNonFlushPacketSending extends ConfigModules {
|
||||
通过使用 Netty 的 lazyExecute 方法来优化非刷新数据包的发送,
|
||||
避免在调度数据包操作时进行昂贵的线程唤醒调用.
|
||||
|
||||
需要重启服务器才能生效.
|
||||
"""));
|
||||
需要重启服务器才能生效."""));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,8 +15,7 @@ public class DontSaveEntity extends ConfigModules {
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
dontSavePrimedTNT = config.getBoolean(getBasePath() + ".dont-save-primed-tnt", dontSavePrimedTNT,
|
||||
config.pickStringRegionBased(
|
||||
"""
|
||||
config.pickStringRegionBased("""
|
||||
Disable save primed tnt on chunk unloads.
|
||||
Useful for redstone/technical servers, can prevent machines from being exploded by TNT,
|
||||
when player disconnected caused by Internet issue.""",
|
||||
|
||||
@@ -67,8 +67,8 @@ public class FastRNG extends ConfigModules {
|
||||
Use direct random implementation instead of delegating to Java's RandomGenerator.
|
||||
This may improve performance but potentially changes RNG behavior.""",
|
||||
"""
|
||||
使用直接随机实现而不是委托给Java的RandomGenerator.
|
||||
这可能会提高性能,但可能会改变RNG行为。"""));
|
||||
使用直接随机实现而不是委派给 RandomGenerator.
|
||||
这可能会提高性能, 但可能会改变 RNG 行为."""));
|
||||
|
||||
if (enabled) {
|
||||
try {
|
||||
|
||||
@@ -10,7 +10,7 @@ public class ThrottleHopperWhenFull extends ConfigModules {
|
||||
}
|
||||
|
||||
public static boolean enabled = false;
|
||||
public static int skipTicks = 0;
|
||||
public static int skipTicks = 8;
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
package org.dreeam.leaf.util.map;
|
||||
|
||||
import com.google.common.collect.AbstractIterator;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.BlockPos.MutableBlockPos;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
@NullMarked
|
||||
public final class BlockPosIterator extends AbstractIterator<BlockPos> {
|
||||
|
||||
private final int startX;
|
||||
private final int startY;
|
||||
private final int startZ;
|
||||
private final int endX;
|
||||
private final int endY;
|
||||
private final int endZ;
|
||||
private @Nullable MutableBlockPos pos = null;
|
||||
|
||||
public static Iterable<BlockPos> iterable(AABB bb) {
|
||||
return () -> new BlockPosIterator(bb);
|
||||
}
|
||||
|
||||
public static Iterable<BlockPos> traverseArea(Vec3 vec, AABB boundingBox) {
|
||||
double toTravel = Math.min(16.0 / vec.length(), 1.0);
|
||||
Vec3 movement = vec.scale(toTravel);
|
||||
AABB fromBB = boundingBox.move(-vec.x, -vec.y, -vec.z);
|
||||
AABB searchArea = fromBB.expandTowards(movement);
|
||||
return BlockPosIterator.iterable(searchArea);
|
||||
}
|
||||
|
||||
public BlockPosIterator(AABB bb) {
|
||||
this.startX = Mth.floor(bb.minX);
|
||||
this.startY = Mth.floor(bb.minY);
|
||||
this.startZ = Mth.floor(bb.minZ);
|
||||
this.endX = Mth.floor(bb.maxX);
|
||||
this.endY = Mth.floor(bb.maxY);
|
||||
this.endZ = Mth.floor(bb.maxZ);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BlockPos computeNext() {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
package org.dreeam.leaf.util.map;
|
||||
|
||||
import it.unimi.dsi.fastutil.longs.*;
|
||||
import it.unimi.dsi.fastutil.longs.LongCollection;
|
||||
import it.unimi.dsi.fastutil.longs.LongIterator;
|
||||
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
||||
import it.unimi.dsi.fastutil.longs.LongSet;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -20,20 +20,18 @@ public class FasterRandomSource implements BitRandomSource {
|
||||
private static final RandomGeneratorFactory<RandomGenerator> RANDOM_GENERATOR_FACTORY = RandomGeneratorFactory.of(FastRNG.randomGenerator);
|
||||
private static final boolean isSplittableGenerator = RANDOM_GENERATOR_FACTORY.isSplittable();
|
||||
private long seed;
|
||||
private boolean useDirectImpl;
|
||||
private static final boolean useDirectImpl = FastRNG.useDirectImpl;
|
||||
private RandomGenerator randomGenerator;
|
||||
public static final FasterRandomSource SHARED_INSTANCE = new FasterRandomSource(ThreadLocalRandom.current().nextLong());
|
||||
|
||||
public FasterRandomSource(long seed) {
|
||||
this.seed = seed;
|
||||
this.randomGenerator = RANDOM_GENERATOR_FACTORY.create(seed);
|
||||
this.useDirectImpl = FastRNG.useDirectImpl; // Get the value from config
|
||||
}
|
||||
|
||||
private FasterRandomSource(long seed, RandomGenerator.SplittableGenerator randomGenerator) {
|
||||
this.seed = seed;
|
||||
this.randomGenerator = randomGenerator;
|
||||
this.useDirectImpl = FastRNG.useDirectImpl;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -59,7 +57,6 @@ public class FasterRandomSource implements BitRandomSource {
|
||||
@Override
|
||||
public final int next(int bits) {
|
||||
if (useDirectImpl) {
|
||||
// Direct
|
||||
return (int) ((seed = seed * MULTIPLIER + INCREMENT & SEED_MASK) >>> (INT_BITS - bits));
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user