Repository navigation
Closed
Labels
area/apiPublic API, contracts, or compatibility surface.Public API, contracts, or compatibility surface.priority/p3Low priority or backlog.Low priority or backlog.severity/minorMinor defect, polish issue, or low-impact bug.Minor defect, polish issue, or low-impact bug.status/needs-triageNeeds maintainer or analyzer triage.Needs maintainer or analyzer triage.
Description
模块范围
Public API(公开 API)
严重程度
Minor(轻微)
发生了什么?
ModelCapabilities 的读路径和写路径对「persistence 尚未注册」这一前置条件处理不一致,导致 API 处于半可用状态。
读路径(CreateCollection,src/Models/Capabilities/ModelCapabilities.cs:90-97)把它当 no-op:
private static ModelCapabilitySet CreateCollection(AbstractModel model)
{
var collection = new ModelCapabilitySet(model);
collection.Load(null);
if (IsInitialized) // ← 未初始化就跳过,不抛
ModelSavedDataRuntime.GetBag(model).Set(SavedDataSlotKey, new ModelCapabilitySaveDocument(), false);
return collection;
}写路径(MarkSavedDataDirty,同文件 :72-79)直接抛:
internal static void MarkSavedDataDirty(AbstractModel model)
{
if (!IsInitialized)
throw new InvalidOperationException(
"ModelCapabilities persistence has not been registered. Register a model capability or default capability modifier during mod initialization before mutating model capabilities.");
ModelSavedDataRuntime.GetBag(model).Set(SavedDataSlotKey, new ModelCapabilitySaveDocument());
}后果:调用方可以在 mod 初始化早期成功 Get() 到 capability set,然后在第一次修改能力时崩溃。
触发条件与真实调用方(已核实)
EnsureInitialized() 由 ModContentRegistry 的 3 处调用(:887 RegisterModelCapability、:923、:941)。也就是说:
- 只要 mod 注册过任意一个 model capability,
IsInitialized就为 true,本条不触发。 - 触发条件是:mod 先对某个 model 调
ModelCapabilities.Get()/Get<T>()/ 尝试修改,之后才注册第一个 capability(或者永远不注册)。
真实调用方确实存在(git grep 确认):
| 位置 | 用途 |
|---|---|
src/Interactions/RightClick/ModRightClickRegistry.cs:736 |
右键交互取能力集 |
src/Diagnostics/DebugTools/RitsuDebugCapabilityActions.cs:304、:356 |
调试工具 |
src/Diagnostics/DebugTools/RitsuDebugStatePresetActions.cs:580 |
状态预设 |
src/Models/Capabilities/ModelCapabilities.cs:140、:150、:160 |
公开 API 包装 |
所以这是一个顺序依赖缺陷,不是"必崩"——但它让 API 在早期初始化阶段的表现不可预测,且失败信息("persistence has not been registered")没有指出"你应该先注册 capability"。
复现步骤
- 在一个 mod 的早期初始化阶段(
ModContentRegistry注册 capability 之前) - 对任意
AbstractModel调用ModelCapabilities.Get(model)→ 成功返回 - 对该 set 做任意修改,触发
MarkSavedDataDirty→InvalidOperationException
期望行为
两条路径对同一前置条件行为一致。要么都抛(明确「必须先注册」),要么都 no-op(先允许内存态存在,注册后再补持久化)。
从 CreateCollection 的写法看,作者的意图似乎是后者。
实际行为
读成功、写崩溃。
建议修法
若采纳 no-op 语义:
internal static void MarkSavedDataDirty(AbstractModel model)
{
if (!IsInitialized)
return; // 与 CreateCollection 保持一致
ModelSavedDataRuntime.GetBag(model).Set(SavedDataSlotKey, new ModelCapabilitySaveDocument());
}版本
- 复核基准:
main@2332d9d0(v0.6.7,2026-10-08) dev分支另有 2 个未发布提交(d99863e6、1fc1bff9),仅改NModExtraHand.cs/FreePlayBindingRegistry.cs/RitsuLibFramework.cs,与本问题无关
Metadata
Metadata
Assignees
Labels
area/apiPublic API, contracts, or compatibility surface.Public API, contracts, or compatibility surface.priority/p3Low priority or backlog.Low priority or backlog.severity/minorMinor defect, polish issue, or low-impact bug.Minor defect, polish issue, or low-impact bug.status/needs-triageNeeds maintainer or analyzer triage.Needs maintainer or analyzer triage.
Activity
fix(capabilities): register persistence when capabilities are used