Theseus 模组文档
API 参考 / 入口与生命周期

生命周期:四个阶段


宿主按下列顺序推进生命周期,全部回调在 Mod 中都有默认空实现,模组按需覆写:

阶段何时调用允许做什么禁止做什么抛异常后果
onPreInit(ModContext)模组加载时最先调用,注册表开放经 ctx.registries() 注册全部自有内容(音符类型 / 游戏模式 / 判定等级 / 谱面扩展段 / 渲染滤镜 / 背景渲染器 / 打击特效样式 / 判定策略 / 判定调整器 / 收集品 / 皮肤等)把注册写在其它回调或运行期该模组不被激活,记入加载失败(LOAD_FAILED),其余模组继续
宿主冻结注册表全部模组的 onPreInit 结束后立即执行宿主内部动作模组不得依赖此动作作为回调不适用(宿主动作)
onInit(ModContext)注册表已冻结、全部模组的 onPreInit 已完成之后读配置、建缓存、订阅事件、创建运行期资源、读取其它模组已注册的内容(此时注册表内容已定型,可安全遍历)任何注册写入(抛 RegistryFrozenException)该模组不被激活,记入加载失败
onPostInit(ModContext)全部模组的 onInit 已完成之后发布初始事件、自检、建立跨模组交互(此时可确信依赖模组已完成初始化)、读取自有译文任何注册写入保持激活,仅记录错误(此时运行期订阅已经建立,判成失败会让订阅变孤儿)
运行期onPostInit 之后直至卸载事件回调、渲染、快捷键、网络等任何注册写入不在本节讨论范围
onUnload()模组卸载时调用一次释放资源—异常被宿主隔离,仅记录日志

要点补充:

阶段推进粒度:宿主在每一个阶段内部都按依赖拓扑顺序遍历全部模组,即「全部模组的 onPreInit(按依赖序)→ 冻结 → 全部 onInit(按依赖序)→ 全部 onPostInit(按依赖序)」,而不是「逐个模组跑完三段」。因此 onInit 里可以确信所有模组的注册内容都已定型,onPostInit 里可以确信所有模组的 onInit 都已完成。

语言文件加载时机:宿主在本模组的 onInit 返回之后才自动扫描加载其 jar 内的语言文件。因此在 onInit 中读取自己的译文会读到空值,读取自有译文应放到 onPostInit。

事件订阅不在入口接口里:统一走 ctx.eventBus(),在 onInit 中订阅即可收到游玩事件与全局事件,不再有「游玩总线 / 全局总线」之分。GameplayMod 中历史上的注册型钩子(渲染滤镜、背景渲染器、打击特效样式、判定策略、判定调整器)已全部下线,统一改为在 onPreInit 内经 ctx.registries() 注册,不再有 per-模组 的 registerXxx(registry) 回调。