fix: improve threaded world compatibility and chunk reads in 1.1.15
Add regression tests and per-dimension progress diagnostics. Document the unresolved C2ME random-initialization redirect conflict.
This commit is contained in:
1 parent
74cae0b581
commit
32b11aee87
42 files changed
+1506
-381
No files matched your search
@@ -3,12 +3,12 @@ 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.dimthread.TpsTracker;
|
||||
import dev.modzuozhi.core.filter.SerDesFilter;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.commands.arguments.ResourceLocationArgument;
|
||||
@@ -37,6 +37,7 @@ public final class 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)))
|
||||
@@ -67,12 +68,8 @@ public final class MasterCommand {
|
||||
ModZuozhi.LOGGER.info("[ModZuozhi] 全局随 /dimensionalripper fine on 自动恢复为开启");
|
||||
}
|
||||
FineGrainScheduler.setEnabled(enabled);
|
||||
if (!enabled) {
|
||||
// 关闭细粒度后必须先排空 SUB_POOL 残留子任务:此时维度 loop 已退化为串行 tick,
|
||||
// 残留实体子任务会与它并发改 EntitySection(靠 isLockDisabled 的残留保护仍持锁互斥,
|
||||
// 见 EntityTickParallel),等排空后锁才回落到零开销纯串行。
|
||||
FineGrainScheduler.awaitIdle(5000L);
|
||||
}
|
||||
// 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();
|
||||
}
|
||||
@@ -117,6 +114,38 @@ public final class MasterCommand {
|
||||
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 ? "开启" : "关闭";
|
||||
@@ -126,25 +155,54 @@ public final class MasterCommand {
|
||||
|
||||
ServerLevel level = ctx.getSource().getLevel();
|
||||
ResourceKey<Level> dimension = level.dimension();
|
||||
// 维度并行激活时:维度 loop 的真实 MSPT(recordDimension 持续更新);
|
||||
// 全局被 FaultGuard 降级关闭(退回原版串行,维度 loop 已停、recordDimension 不再更新)时:
|
||||
// 用主线程 tick 间隔样本,它才是当前真实 tick 周期。
|
||||
double tps = DimThreadCore.MANAGER.isActive(level.getServer())
|
||||
? TpsTracker.tps(dimension)
|
||||
: TpsTracker.tps();
|
||||
|
||||
String msg = String.format(
|
||||
"modzuozhi 状态:细粒度=%s%s,分片并行=%s,禁碰撞=%s,故障原因:%s\n"
|
||||
+ "当前维度:%s,TPS:%.1f,掉拍分片累计:实体 %d / TE %d / 区块环境 %d",
|
||||
+ "兼容状态:%s\n"
|
||||
+ "当前维度:%s,掉拍分片累计:实体 %d / TE %d / 区块环境 %d",
|
||||
fine, fineMark,
|
||||
EntityTickParallel.isSharded() ? "开启" : "关闭",
|
||||
noCollide, reason,
|
||||
dimensionName(dimension), tps,
|
||||
SableMainThreadBridge.statusReason(),
|
||||
dimensionName(dimension),
|
||||
EntityTickParallel.missedShards(), ShardGate.teMissed(), ShardGate.chunkMissed());
|
||||
ctx.getSource().sendSuccess(() -> Component.literal(msg), 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()) {
|
||||
@@ -154,4 +212,4 @@ public final class MasterCommand {
|
||||
default -> dimension.location().toString();
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user