Files
DimensionalRipper/docs/superpowers/specs/2026-10-13-world-callback-ownership-design.md
T

95 lines
9.9 KiB
Markdown
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.
# 随机刻与服务器 tick 回调归属修复
日期:2026-10-13(Asia/Shanghai)
状态:用户已选择先修复随机刻/邻居通知链和服务器全局 tick 回调。本文为待确认的书面设计;尚未实施代码,也未生成新 jar。
基线:`763c015`,DimensionalRipper 1.1.17,工作分支 `codex/payload-ownership`。
## 1. 目标与范围
修复日志中两条已经定位的错误路径,同时保留其他现有并行能力:
1. 同一世界的不同区块并行随机刻,经同步邻居通知进入 DriveByWire 共享信号网络,触发 `WireNetworkManager.setSource:240` 的 `HashMap.computeIfAbsent` CME。
2. Lootr 在主线程 `ServerTickEvent.Post` 扫描/转换容器,而维度 worker 仍持有世界归属,导致方块实体读取被守卫拒绝。
用户选择的是以上两项,不包括坐服童子采样修复。不修改用户正式服务器、启动器 mods、原存档,不自动推送远端。
验收目标是正确执行而非仅不抛异常:随机刻、信号、箱子转换没有丢失或重复;等待期间 Sable 交接能结束;安全阶段不能错误恢复暂停或停止中的循环。
## 2. 已核对的证据
- 用户 2026-10-13 日志加载 DimensionalRipper 1.1.17、NeoForge 21.1.233、DriveByWire 0.3.0、Lootr 1.21.1-1.11.37.120。
- 三次 CME 分别在 11:02:10、11:02:17、11:02:23;事件总线和 ChunkEnv 各打印一份,不把六份堆栈计作六次独立异常。
- 当前 `ServerChunkCacheMixin` 将 `level.tickChunk` 收集成分片,使用按区块锁后调用自定义 `IChunkEnvTick`。这些锁不保护同一世界的 DriveByWire 网络管理器。
- 本机同版本 DriveByWire 字节码显示 `sourceValues` 是普通 HashMap,`setSource` 的计算函数只新建内部 HashMap。并发写入是强证据支持的原因;尚未捕获用户环境中另一条写线程,不能宣称已有完整双线程现场复现。
- 本机 Lootr 的 `HandleTick.onServerTick(ServerTickEvent.Post)` 直接调用 `BlockEntityTicker.onServerTick(server)`,遍历不同维度的 ticker;不经过公共收包入口。
- 本地 NeoForge 21.1.244 的 `MinecraftServer.tickServer` 在 `tickChildren` 前调用 `fireServerTickPre`,在方法末尾调用 `fireServerTickPost`。用户 21.1.233 必须另外核对注入及运行兼容,不能仅凭版本范围宣称已测试。
## 3. 选择的方案
不选仅替换 DriveByWire 集合:嵌套集合及“读取—更新—通知”仍是复合操作,且无法覆盖其他邻居监听器。
不选关闭所有并行:这是应急措施,会扩大性能回退范围。
采用两项边界调整,共用已有世界执行权协调器。第一项减少维度内不安全并行;第二项让明确的主线程事件边界与维度写入互斥。它不是任意模组线程安全的通用保证。
### 3.1 区块环境 tick 恢复所属维度内串行
- 当前 `tickChunks` 中每次 `level.tickChunk(chunk, randomTickSpeed)` 恢复直接调用原方法,不再投递区块环境子任务或分片。
- 调用在该世界合法 owner 上执行:维度并行启用时是所属维度 worker,原版路径则为服务器主线程。不是把每个区块同步投递到主线程。
- 沿用原 tickChunks 选中的区块、顺序和随机刻速度;同一已选区块只执行一次。由原方法及其模组注入处理随机源、降水、闪电、方块和流体随机刻,不继续绕过原方法手工复刻。
- 方块更新与同步邻居通知保留在同一调用链,不独立排队、不合并、不延后一拍,不调整 `randomTickSpeed`。
- 不允许 `fine on` 或 `fine sharded on` 重新开启旧区块环境并行。第一版不保留“默认关闭但可误开启”的不安全开关;现有实体/方块实体等其他并行开关保持原功能。
- 既有实体/方块实体子任务必须先按原屏障归还执行权,不能与本阶段重叠;实施测试需验证,不以线程名称替代归属检查。
- 修改 README/status 的区块环境并行说明,不能继续宣称此阶段仍分片并行。历史累计指标如保留,必须明确不是当前并行工作量。
范围限制:这一改动消除已知的同世界并行随机刻来源,不代表所有实体、方块实体、异步模组或跨世界共享状态已安全。若真实 DriveByWire 回归暴露其他写入来源,先报告证据并重新确认范围,不能仅压住 CME。
### 3.2 服务器全局 Pre/Post 回调取得执行权
- 只包装 `MinecraftServer.tickServer` 对 `EventHooks.fireServerTickPre` 与 `fireServerTickPost` 的两个原调用点,保留同一 server、BooleanSupplier、原位置及原事件总线顺序,原调用恰好执行一次。
- 复用 `MainThreadPhaseCoordinator.run`:禁止维度续排,等待全部在途及退出中循环完成;期间继续处理使 Sable 交接结束所需的主线程工作;随后进入真实主线程独占阶段执行原事件回调。
- 不包住整个 tickServer,不把两个事件之间全部主线程工作都纳入长期独占,也不移动事件的顺序。
- 不修改全局 `EventBus.post`、通用 `MinecraftServer.execute` 或 `pollTask`;不把 `LevelTickEvent.Pre/Post` 搬到主线程。因而坐服童子的维度事件采样异常仍可能出现。
- 不能只看总开关判断“无并行”;存在尚未归还执行权的循环时仍须等待。没有并行循环的原版路径应保留直接原调用,不创建或启动新循环。
- 独占内 CC 或 payload 嵌套使用既有租约,不递归建立屏障。禁止从正在被维度 worker 同步等待的 Sable 回调创建全局阶段,避免主线程与该 worker 互等。
- 等待不能持有第三方模组锁;超时只做限频诊断,不能授权与 worker 并发写世界。
- 异常保持原对象和传播语义,不吞异常、不重试整个事件、不重放已执行监听器。finally 正确释放本次租约,但不能解除菜单暂停或复活停止状态;阶段内新建循环仍受约束。
全局事件每次进入都需要安全条件,不能根据猜测“这 tick Lootr 没任务”跳过保护;也不为单个模组过滤事件监听器。
## 4. 性能与可观测性
- 第一项取消区块环境细粒度并行;第二项在服务器每 tick 的 Pre/Post 两个边界收口,主线程可能等待最慢维度。因此不再承诺“一个慢维度绝不影响主线程和其他维度”。两次安全阶段之外仍允许原有维度及其他阶段并行。
- 不承诺固定开销比例、20 TPS 或性能提升。先保证正确性,再测量收益;如果安全回退代价不可接受,应基于实测重新设计,而不是放松互斥。
- Pre 与 Post 分开记录次数、最近等待/执行时间、累计或最大耗时;使用有界数据,不按监听器或每 tick 打堆栈。
- 本地原版实现先记录部分 tick 耗时再执行 Post,故 Carpet/原版 MSPT 可能未完整涵盖新增 Post 等待。必须同时查看主线程实际 TPS、维度完成 TPS 和 Pre/Post 独立耗时,不能用低 MSPT 证明不卡。
- 不把本次扩展成通用采样器或故障保护重写。
## 5. 必须验证的项目
先建立能失败的回归,再改变生产行为:
1. 两个以上区块的真实随机刻及邻居监听器,在旧版本记录并行重叠,新版本无同世界重叠且不运行在细粒度子任务上。核对原 `tickChunk` 注入仍被调用、选中区块至多一次、邻居通知同步完成。
2. 实际 DriveByWire 0.3.0 对多个位置的信号更新测试,核对最终信号值及连接状态,不能仅测一个自建 HashMap 或等待偶然 CME。
3. 注册真实 ServerTickEvent.Pre/Post 测试监听器;旧版本在 worker 持有世界时不能合法读取,新版本看到原 BE、真正主线程、全部循环不在途。核对事件顺序和执行次数。
4. 真实 Lootr 扫描/转换的测试容器,核对预期转换结果及内容/战利品表,而非仅注册假监听器。检查未加载位置不会被额外强加载。
5. 等待中的 Sable 回调、嵌套 CC/payload、异常、菜单暂停、动态/退出中的循环、停服状态均不引入死锁、重复回调或错误恢复。
6. 无 DriveByWire/Lootr/Sable 时可启动;分别验证细粒度 on/off、分片 on/off 都不会重开区块环境并行。重复单人世界进入/退出,检查静态诊断引用释放。
7. 重跑已有队列、屏障、CC、payload、实际航空学把手、Create/Jade、水流、区块与实体路由回归。
8. 比较空载、随机刻密集、Lootr 转换负载及一个人为慢维度:报告实际 TPS、阶段等待/执行耗时及随机刻进度,不只看 Carpet MSPT。
9. Java 21 干净构建;校验 jar 元数据、无测试夹具及可选依赖打包,记录 SHA256。运行日志必须具备 PASS 标记且没有非预期服务器异常,不能只看 Gradle 退出码。
真实模组无法运行或完整测试矩阵未通过时,明确保留未验收项目,只能交付对应范围的测试候选,不能宣称两项已全面解决。坐服童子测试应独立,不能让其已知异常混淆这两项因果验证。
## 6. 不在本次范围与交付流程
不修 ServerWarashi 采样栈,不宣称修复任意异步世界访问、其他实体/方块实体并行、跨维度共享集合、C2ME、既有停服超时或所有主线程世界访问。保护两个 tick 事件不等于保护所有主线程指令、函数和第三方后台任务。
用户确认本文后再编写具体实施计划及失败测试拆分,确认计划和执行方式后实施。交付本地候选 jar、测试证据、回退代价及未解决项;不部署或推送。
## 自审
设计与“先修 1 和 2”的范围一致。随机刻选择维度 owner 串行而非 worker 同步请求全局阶段,避免自等待;全局事件仅保护两个明确入口。性能描述明确承认两次全局等待破坏完全维度隔离;验收区分假监听器与真实模组。未将坐服童子或其他已有问题纳入修复承诺。