feat: 公开宿主组合加载 API、Forge 风格依赖版本区间与加载顺序、程序集解析策略
- ShrinkModComponentDiscovery.Discover 公开:只发现、不执行生命周期 - ShrinkModLoader.LoadComposed / ShrinkModCordisRuntime.ApplyComposedAsync:宿主提供元数据组合加载,支持纯数据包与 IShrinkModWrapper 公开真实实例 - 公开 TryAttachEventBusInstance,随生命周期逆操作释放 - 依赖新增 VersionRange(Maven/Forge 区间语法)与 Ordering(Before/After) - AssemblyResolvePolicy 可限制跨模组程序集解析
This commit is contained in:
1 parent
5398036d3b
commit
d15bf00512
29 files changed
+917
-132
No files matched your search
@@ -54,6 +54,114 @@ ShrinkModLoader.LoadAuthorized(settings, authorizedDllPaths);
|
||||
|
||||
`ShrinkModBootstrap` 仍然保留,作为手动覆盖或调试入口,但不是必需品。
|
||||
|
||||
## 宿主清单组合(0.3.8)
|
||||
|
||||
Unity 宿主可以先发现授权 DLL,再用清单替换元数据和生命周期工厂,最后提交完整组合。
|
||||
`Discover` 不构造模组、不运行生命周期、不改变已提交的 current revision;DLL 会进入 AppDomain,
|
||||
外部源的 `Revision` 仍为 `SHA-256:入口类型全名`。工程内源仍按 `assemblyNamePrefixes` 筛选。
|
||||
|
||||
```csharp
|
||||
var discovered = ShrinkModComponentDiscovery.Discover(settings, authorizedDllPaths);
|
||||
var original = discovered.Single(source => source.Info.ModId == "demo.code");
|
||||
var info = new ShrinkModInfo("demo.code", "代码包", "1.2.0", 0,
|
||||
original.Info.EntryType, original.Info.AutoApplyHarmonyPatches,
|
||||
new[] { new ShrinkModDependency("demo.data", "1.0", optional: false) });
|
||||
var code = original.WithMetadata(info, original.Revision,
|
||||
() => new PackageWrapper(original.Factory()));
|
||||
var dataInfo = new ShrinkModInfo("demo.data", "数据包", "1.0", 0,
|
||||
entryType: null, autoApplyHarmonyPatches: false,
|
||||
dependencies: Array.Empty<ShrinkModDependency>());
|
||||
var data = ShrinkModComponentSource.CreateDataOnly(dataInfo, "manifest-sha256");
|
||||
ShrinkModLoader.LoadComposed(settings, new[] { code, data });
|
||||
|
||||
sealed class PackageWrapper : ShrinkModBase, IShrinkModWrapper
|
||||
{
|
||||
public PackageWrapper(IShrinkMod instance) => PublicInstance = instance;
|
||||
public IShrinkMod PublicInstance { get; }
|
||||
public override void OnConstruct(ShrinkModContext ctx) => PublicInstance.OnConstruct(ctx);
|
||||
public override void OnRegisterContent(ShrinkModContext ctx) => PublicInstance.OnRegisterContent(ctx);
|
||||
public override void OnInitialize(ShrinkModContext ctx) => PublicInstance.OnInitialize(ctx);
|
||||
public override void OnReady(ShrinkModContext ctx) => PublicInstance.OnReady(ctx);
|
||||
}
|
||||
```
|
||||
|
||||
包装器可以在各阶段加入宿主作用域、数据注册和 `EffectInverse`。`Handle.Instance` 保持原有
|
||||
`IShrinkMod` 类型,返回 `IShrinkModWrapper.PublicInstance`,自动 EventBus 绑定也使用这个真实实例。
|
||||
`CreateDataOnly` 的默认工厂不执行业务,公开实例为 null;传入自定义 `Func<IShrinkMod>` 可处理数据注册。
|
||||
数据包的 `EntryType` 必须为 null,框架不会尝试为它应用 Harmony 补丁。
|
||||
|
||||
`LoadComposed` 始终使用可逆 ContextHost,与 `useContextHost` 的旧 Loader 开关无关。每个模组按
|
||||
依赖/排序关系执行 Construct → RegisterContent → Initialize → Ready;成功后设置 `IsLoaded`,
|
||||
对新建或重建的 handle 发送 `OnModReady`,再发送 `OnAllModsReady`。失败恢复旧组合及 DLL revision。
|
||||
相同 revision、元数据和排序前驱保持幂等;工厂行为或清单数据改变时,宿主应更新 revision。
|
||||
`WithMetadata` 保留发现快照,修改外部源时应使用它。不要把不同扫描中同一路径的不同 revision 混进一次提交。
|
||||
|
||||
热更新时重新发现、重新按清单组合,再调用 `LoadComposed`;删除源即卸载,空列表卸载全部实例。
|
||||
宿主组合模式下 `LoadNewExternalMods` 保留当前组合,内置 watcher 不会丢弃清单元数据;宿主负责触发重组。
|
||||
已有 `LoadAuthorized`、普通 ContextHost 和旧 Loader 的入口保持可用,不混用旧 Loader 实例与宿主组合。
|
||||
高级异步宿主可调用 `ShrinkModCordisRuntime.ApplyComposedAsync(settings, sources)`,它与既有
|
||||
`ApplyDiscoveredAsync` 一样仅负责根事务;需要 Loader 状态与静态通知时使用 `LoadComposed`。
|
||||
|
||||
### 依赖区间和排序
|
||||
|
||||
```csharp
|
||||
[ShrinkModDependency("demo.core", optional: false,
|
||||
VersionRange = "[1.0,2.0)", Ordering = ShrinkModDependencyOrdering.After)]
|
||||
[ShrinkModDependency("demo.ui", optional: true,
|
||||
Ordering = ShrinkModDependencyOrdering.Before)]
|
||||
public sealed class MyMod : ShrinkModBase { }
|
||||
|
||||
var dependency = new ShrinkModDependency("demo.core", minimumVersion: null,
|
||||
optional: true, ordering: ShrinkModDependencyOrdering.After, versionRange: "[1.2,)");
|
||||
bool matches = ShrinkModVersionRange.Parse("[1.0,2.0)").Contains("1.5.0");
|
||||
```
|
||||
|
||||
区间支持开闭边界、无上/下界、精确值 `[1.2]` 和并集 `(,1.0],[1.2,)`;裸版本 `1.0`
|
||||
表示 `>= 1.0`。数字版本的尾部零等价(`1.2 == 1.2.0`),带后缀版本沿用框架版本比较规则,
|
||||
不承诺 Maven 的全部 qualifier 排序规则。无效区间、范围不满足、必选依赖缺失和排序环均明确报错。
|
||||
|
||||
`optional` 只决定目标能否缺席;目标存在时仍检查区间。`None` 不添加先后边,`Before` 让本模组
|
||||
先于目标,`After` 让目标先于本模组。若只想排序且不要求安装目标,使用 `optional: true`。
|
||||
原 `minimumVersion` 参数与三参数构造函数保留,未指定排序时仍为 `After`;`VersionRange` 优先于
|
||||
`MinimumVersion`。旧 Loader 的增量路径无法把新模组插到已运行模组之前,会明确要求改用 ContextHost。
|
||||
|
||||
### 按清单约束程序集解析
|
||||
|
||||
在发现之前设置策略;宿主把程序集简单名称、授权绝对路径映射回自己的 packageId:
|
||||
|
||||
```csharp
|
||||
ShrinkModLoader.AssemblyResolvePolicy = (requestingAssembly, requestedName, candidatePath) =>
|
||||
{
|
||||
if (requestingAssembly == null) return false;
|
||||
if (!assemblyOwners.TryGetValue(requestingAssembly.GetName().Name, out var requesterId)) return false;
|
||||
if (!pathOwners.TryGetValue(candidatePath, out var candidateId)) return false;
|
||||
return requesterId == candidateId || declaredDependencies[requesterId].Contains(candidateId);
|
||||
};
|
||||
```
|
||||
|
||||
`assemblyOwners`、`pathOwners` 和 `declaredDependencies` 由宿主清单建立。回调在返回缓存候选或加载
|
||||
候选字节前执行,返回 false 拒绝本次解析;候选始终来自授权白名单,策略不能扩大白名单。
|
||||
未设置策略时保持原行为。入口 DLL 文件名应与程序集简单名称一致。策略仅约束框架的
|
||||
`AssemblyResolve` 回调,不能拦截 CLR 已解析的程序集引用,也不是不可信代码沙箱。
|
||||
|
||||
### 显式绑定生成订阅
|
||||
|
||||
宿主组合在 RegisterContent 后自动为公开实例调用以下入口,并将 Dispose 登记为可逆效应;
|
||||
Initialize/Ready 失败、替换或卸载都会退订。自动路径不需要包装器再手动绑定同一个实例。
|
||||
如果需要手动绑定其它订阅对象:
|
||||
|
||||
```csharp
|
||||
var binding = ShrinkModOptionalRuntimeIntegration.TryAttachEventBusInstance(
|
||||
subscriber.GetType(), subscriber, context.ModInfo.ModId, verboseLogging: false);
|
||||
if (binding != null) context.EffectInverse(binding.Dispose);
|
||||
```
|
||||
|
||||
返回值为 `IDisposable`;没有生成合同或可选绑定失败时返回 null。它使用 `ShrinkBusKey.Mod(modId)`,
|
||||
不会反射枚举订阅方法。手动调用方负责释放;ContextHost 自动路径还会在退役时移除对应 Mod Bus。
|
||||
|
||||
共享 NuGet `ShrinkSDK.ModFramework` 0.3.8 包含依赖元数据和范围解析;上述 Unity 发现/ContextHost API
|
||||
属于 UPM Runtime。Godot 适配包独立版本为 0.1.7,本次仅更新它对共享包的发布引用。
|
||||
|
||||
## 配置文件
|
||||
|
||||
菜单 `ShrinkSDK/模组/创建设置` 会在
|
||||
@@ -678,9 +786,9 @@ public sealed partial class DemoFullMod : ShrinkModBase
|
||||
|
||||
## 设计约束
|
||||
|
||||
- 一个模组类必须有 `[ShrinkMod]`
|
||||
- 自动发现的模组类必须有 `[ShrinkMod]`;宿主组合使用显式元数据和工厂
|
||||
- 一个模组类必须实现 `IShrinkMod`
|
||||
- 一个模组类必须能被无参构造
|
||||
- 自动发现的模组类必须能被无参构造;宿主工厂可自行构造包装器
|
||||
- 模组 ID 必须全局唯一
|
||||
- 必选依赖缺失或版本不满足时,装载直接失败
|
||||
- 检测到循环依赖时,装载直接失败
|
||||
|
||||
Reference in new issue
Block a user