Files
DimensionalRipper/src/main/java/dev/modzuozhi/command/MasterCommand.java
T

220 lines
13 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package dev.modzuozhi.command;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.context.CommandContext;
import dev.modzuozhi.ModZuozhi;
import dev.modzuozhi.core.compat.SableMainThreadBridge;
import dev.modzuozhi.core.dimthread.DimThreadCore;
import dev.modzuozhi.core.dimthread.EntityTickParallel;
import dev.modzuozhi.core.dimthread.FaultGuard;
import dev.modzuozhi.core.dimthread.FineGrainScheduler;
import dev.modzuozhi.core.dimthread.ShardGate;
import dev.modzuozhi.core.filter.SerDesFilter;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.arguments.ResourceLocationArgument;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.Level;
import static net.minecraft.commands.Commands.literal;
/**
* modzuozhi 管理命令。
* <p>
* 全局多线程优化<b>默认启用</b>,不提供全局开关(运行时切换全局并行风险高,容易引发
* 状态不一致问题)。若触发自动降级(见 {@link FaultGuard}),默认退回原版串行,重启游戏
* 即恢复启用,也可执行 {@code /dimensionalripper fine on} 手动复位。
* <p>
* /dimensionalripper fine on|off - 单独开关"细粒度并行"(维度内 TE/区块/实体并行,默认开启)。
* /dimensionalripper nocollide on|off - 单独开关"禁用实体间碰撞"(Mob Collision Off 模式,默认开启)。
* /dimensionalripper status - 查看当前运行状态、降级情况与故障原因。
*/
public final class MasterCommand {
private MasterCommand() {
}
public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
dispatcher.register(
literal("dimensionalripper")
.then(literal("flowstatus").executes(MasterCommand::flowStatus))
.then(literal("status").executes(MasterCommand::status))
.then(literal("fine")
.then(literal("on").executes(ctx -> setFine(ctx, true)))
.then(literal("off").executes(ctx -> setFine(ctx, false)))
.then(literal("sharded")
.then(literal("on").executes(ctx -> setSharded(ctx, true)))
.then(literal("off").executes(ctx -> setSharded(ctx, false)))))
.then(literal("nocollide")
.then(literal("on").executes(ctx -> setNoCollide(ctx, true)))
.then(literal("off").executes(ctx -> setNoCollide(ctx, false))))
.then(literal("tewhitelist")
.then(net.minecraft.commands.Commands.argument("type", net.minecraft.commands.arguments.ResourceLocationArgument.id())
.executes(MasterCommand::teWhitelist)))
.then(literal("teunwhitelist")
.then(net.minecraft.commands.Commands.argument("type", net.minecraft.commands.arguments.ResourceLocationArgument.id())
.executes(MasterCommand::teUnwhitelist)))
.then(literal("status").executes(MasterCommand::status)));
}
private static int setFine(CommandContext<CommandSourceStack> ctx, boolean enabled) {
if (enabled && !ModZuozhi.isEnabled()) {
// 全局关闭时单独开细粒度没有意义(维度 loop 已停、并行入口被 isActive 拦截),
// 还会留下"全局关但细粒度开"的不一致状态。这里自动恢复全局开启,下一 tick
// 主线程会重新拉起维度 loop(killAllLoops 已清空 loops map,computeIfAbsent 重建)。
ModZuozhi.ENABLED = true;
ctx.getSource().sendSuccess(() ->
Component.literal("全局此前已关闭,已随细粒度一并恢复为开启"), false);
ModZuozhi.LOGGER.info("[ModZuozhi] 全局随 /dimensionalripper fine on 自动恢复为开启");
}
FineGrainScheduler.setEnabled(enabled);
// Barriers drain each current phase; keep the server event loop available
// for any in-flight task that depends on a server callback.
if (enabled) {
FaultGuard.reset();
}
ctx.getSource().sendSuccess(() ->
Component.literal("细粒度并行已" + (enabled ? "开启" : "关闭")), false);
return 1;
}
private static int setSharded(CommandContext<CommandSourceStack> ctx, boolean enabled) {
EntityTickParallel.setSharded(enabled);
ctx.getSource().sendSuccess(() ->
Component.literal("分片并行(实体/TE/区块环境)已" + (enabled ? "开启" : "关闭")), false);
return 1;
}
private static int setNoCollide(CommandContext<CommandSourceStack> ctx, boolean enabled) {
ModZuozhi.MOB_COLLISION_OFF = enabled;
ctx.getSource().sendSuccess(() ->
Component.literal("实体间碰撞已" + (enabled ? "禁用" : "恢复")), false);
ModZuozhi.LOGGER.info("[ModZuozhi] 实体间碰撞已切换为 {}", enabled ? "禁用" : "恢复");
return 1;
}
/** 将指定 TE 注册名加入 modded 并行白名单(仅影响该 TE,不改全局 lockModded 保守策略)。 */
private static int teWhitelist(CommandContext<CommandSourceStack> ctx) {
String type = ResourceLocationArgument.getId(ctx, "type").toString();
SerDesFilter.whitelistModded(type);
ctx.getSource().sendSuccess(() ->
Component.literal("TE 白名单已添加: " + type + "(清单: " + SerDesFilter.moddedWhitelist() + ")"), false);
ModZuozhi.LOGGER.info("[ModZuozhi] TE 白名单新增 {}", type);
return 1;
}
/** 将指定 TE 注册名移出 modded 并行白名单(恢复该 modded TE 默认的强制串行)。 */
private static int teUnwhitelist(CommandContext<CommandSourceStack> ctx) {
String type = ResourceLocationArgument.getId(ctx, "type").toString();
boolean removed = SerDesFilter.unwhitelistModded(type);
ctx.getSource().sendSuccess(() ->
Component.literal("TE 白名单已" + (removed ? "移除" : "移除(原本不在白名单中)") + ": " + type
+ "(清单: " + SerDesFilter.moddedWhitelist() + ")"), false);
ModZuozhi.LOGGER.info("[ModZuozhi] TE 白名单移除 {}", type);
return 1;
}
private static int flowStatus(CommandContext<CommandSourceStack> ctx) {
var player = ctx.getSource().getPlayer();
if (player == null) {
ctx.getSource().sendFailure(Component.literal("请由玩家站在不流动的水附近执行。"));
return 0;
}
dev.modzuozhi.core.compat.SableAssemblyDispatch.dispatch(player, () -> {
var level = player.serverLevel();
var hit = player.pick(8.0, 0.0F, true);
var pos = hit instanceof net.minecraft.world.phys.BlockHitResult blockHit
&& hit.getType() == net.minecraft.world.phys.HitResult.Type.BLOCK
? blockHit.getBlockPos() : player.blockPosition();
var targetChunk = new net.minecraft.world.level.ChunkPos(pos);
long chunk = targetChunk.toLong();
String message = "水流诊断:维度=" + level.dimension().location()
+ ",目标=" + pos.toShortString() + ",区块=" + targetChunk
+ ",世界时间=" + level.getGameTime()
+ ",正常刻=" + level.tickRateManager().runsNormally()
+ ",调试世界=" + level.isDebug()
+ ",区块可更新=" + level.getChunkSource().isPositionTicking(chunk)
+ ",实体已加载=" + level.areEntitiesLoaded(chunk)
+ ",目标流体=" + level.getFluidState(pos).getType()
+ ",待处理流体刻=" + level.getFluidTicks().count()
+ ",待处理方块刻=" + level.getBlockTicks().count();
ctx.getSource().sendSuccess(() -> Component.literal(message), false);
String detail = ((dev.modzuozhi.core.compat.ScheduledTickDiagnostics) level.getFluidTicks())
.dimensionalripper$describeTicks(pos, level.getGameTime());
ctx.getSource().sendSuccess(() -> Component.literal("流体调度明细:" + detail), false);
});
return 1;
}
private static int status(CommandContext<CommandSourceStack> ctx) {
String fine = FineGrainScheduler.isEnabled() ? "开启" : "关闭";
String noCollide = ModZuozhi.MOB_COLLISION_OFF ? "开启" : "关闭";
String fineMark = FaultGuard.isFineDegraded() ? "(已自动降级)" : "";
// 全局始终默认启用,不提供开关;FaultGuard 若触发全局降级,其信息已写入 reason。
String reason = FaultGuard.getReason();
ServerLevel level = ctx.getSource().getLevel();
ResourceKey<Level> dimension = level.dimension();
String msg = String.format(
"modzuozhi 状态:细粒度=%s%s,分片并行=%s,禁碰撞=%s,故障原因:%s\n"
+ "兼容状态:%s\n"
+ "当前维度:%s,掉拍分片累计:实体 %d / TE %d / 区块环境 %d",
fine, fineMark,
EntityTickParallel.isSharded() ? "开启" : "关闭",
noCollide, reason,
SableMainThreadBridge.statusReason(),
dimensionName(dimension),
EntityTickParallel.missedShards(), ShardGate.teMissed(), ShardGate.chunkMissed());
ctx.getSource().sendSuccess(() -> Component.literal(msg), false);
ctx.getSource().sendSuccess(() -> Component.literal("线程交接:"
+ dev.modzuozhi.core.compat.ThreadOwnershipDiagnostics.timing()), false);
ctx.getSource().sendSuccess(() -> Component.literal(
dev.modzuozhi.core.compat.PayloadDiagnostics.snapshot(ctx.getSource().getServer())), false);
var loops = DimThreadCore.MANAGER.loopsSnapshot();
if (loops.isEmpty()) {
ctx.getSource().sendSuccess(() -> Component.literal("维度循环:无数据(尚未启动或已停止),不按 20 TPS 显示。"), false);
} else {
ctx.getSource().sendSuccess(() -> Component.literal(
"维度实测:完成TPS按最近10秒计数(启动不足10秒用已观测时间,含暂停时间);"
+ "工作MSPT为最近100次均值/最近一次,包含交接等待,不含末尾区块队列轮询。"), false);
long now = System.nanoTime();
loops.entrySet().stream().sorted(java.util.Comparator.comparing(e -> e.getKey().dimension().location().toString()))
.forEach(entry -> {
var loop = entry.getValue();
var p = loop.progress(now);
String state = !loop.isRunning() ? "已停止" : loop.isPaused()
? (loop.isTicking() ? "暂停收口中" : "已暂停")
: !loop.isScheduled() ? "未启动"
: p.windowSeconds() == 0 ? "无采样"
: p.runningMs() >= 1000 ? "当前Tick超过1秒"
: p.sinceCompletedMs() >= 1000 ? "超过1秒无完成"
: Double.isNaN(p.sinceCompletedMs()) ? "尚无成功完成" : "运行中";
String line = String.format(java.util.Locale.ROOT,
"%s [%s] 完成TPS=%s (窗口%.1fs),工作MSPT(均/末)=%s/%s ms,当前Tick=%s ms,距上次完成=%s ms,失败Tick=%d",
entry.getKey().dimension().location(), state,
loop.isPaused() || !loop.isRunning() ? "—" : metric(p.tps()),
p.windowSeconds(),
metric(p.averageWorkMs()), metric(p.lastWorkMs()),
Double.isNaN(p.runningMs()) ? "未执行" : metric(p.runningMs()),
Double.isNaN(p.sinceCompletedMs()) ? "无记录" : metric(p.sinceCompletedMs()), p.failures());
ctx.getSource().sendSuccess(() -> Component.literal(line), false);
});
}
return 1;
}
private static String metric(double value) {
return Double.isNaN(value) ? "无数据" : String.format(java.util.Locale.ROOT, "%.1f", value);
}
/** 把维度 key 映射成玩家可读的中文名。 */
private static String dimensionName(ResourceKey<Level> dimension) {
return switch (dimension.location().getPath()) {
case "overworld" -> "主世界";
case "the_nether" -> "下界";
case "the_end" -> "末地";
default -> dimension.location().toString();
};
}
}