feat: 公开宿主组合加载 API、Forge 风格依赖版本区间与加载顺序、程序集解析策略
Publish UPM package / publish (push) Successful in 3s
Publish NuGet packages / publish (push) Successful in 2m18s

- ShrinkModComponentDiscovery.Discover 公开:只发现、不执行生命周期
- ShrinkModLoader.LoadComposed / ShrinkModCordisRuntime.ApplyComposedAsync:宿主提供元数据组合加载,支持纯数据包与 IShrinkModWrapper 公开真实实例
- 公开 TryAttachEventBusInstance,随生命周期逆操作释放
- 依赖新增 VersionRange(Maven/Forge 区间语法)与 Ordering(Before/After)
- AssemblyResolvePolicy 可限制跨模组程序集解析
This commit is contained in:
cneicy committed 2026-11-10 07:39:22 +08:00
1 parent 5398036d3b
commit d15bf00512
29 files changed
+917 -132

No files matched your search

@@ -0,0 +1,59 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace ShrinkModFramework
{
internal static class ShrinkModDependencyGraph
{
internal static void ValidateVersion(ShrinkModInfo owner, ShrinkModDependency dependency, string actual)
{
if (!string.IsNullOrWhiteSpace(dependency.VersionRange) && !ShrinkModVersionRange.Parse(dependency.VersionRange).Contains(actual))
throw new InvalidOperationException($"Mod {owner.ModId} requires {dependency.ModId} in range {dependency.VersionRange}, but found {actual}.");
}
internal static IEnumerable<string> Predecessors(string id, IEnumerable<ShrinkModInfo> infos)
{
var map = infos.ToDictionary(info => info.ModId, StringComparer.Ordinal);
foreach (var owner in map.Values)
foreach (var dependency in owner.Dependencies)
{
if (!map.ContainsKey(dependency.ModId)) continue;
if (owner.ModId == id && dependency.Ordering == ShrinkModDependencyOrdering.After)
yield return dependency.ModId;
if (dependency.ModId == id && dependency.Ordering == ShrinkModDependencyOrdering.Before)
yield return owner.ModId;
}
}
internal static IReadOnlyList<ShrinkModInfo> Order(IEnumerable<ShrinkModInfo> infos)
{
var map = infos.ToDictionary(info => info.ModId, StringComparer.Ordinal);
foreach (var owner in map.Values)
foreach (var dependency in owner.Dependencies)
{
if (map.TryGetValue(dependency.ModId, out var target)) ValidateVersion(owner, dependency, target.Version);
else if (!dependency.Optional)
throw new InvalidOperationException($"Mod {owner.ModId} is missing required dependency {dependency.ModId}.");
}
var result = new List<ShrinkModInfo>();
var visiting = new HashSet<string>(StringComparer.Ordinal);
var visited = new HashSet<string>(StringComparer.Ordinal);
void Visit(string id)
{
if (visited.Contains(id)) return;
if (!visiting.Add(id)) throw new InvalidOperationException($"Circular mod dependency/Before/After ordering includes {id}.");
foreach (var predecessor in Predecessors(id, map.Values).OrderBy(value => value, StringComparer.Ordinal)) Visit(predecessor);
visiting.Remove(id); visited.Add(id); result.Add(map[id]);
}
foreach (var info in map.Values.OrderBy(info => info.LoadOrder).ThenBy(info => info.ModId, StringComparer.Ordinal)) Visit(info.ModId);
return result;
}
internal static bool SameMetadata(ShrinkModInfo a, ShrinkModInfo b) =>
a.ModId == b.ModId && a.DisplayName == b.DisplayName && a.Version == b.Version &&
a.LoadOrder == b.LoadOrder && a.EntryType == b.EntryType && a.AutoApplyHarmonyPatches == b.AutoApplyHarmonyPatches &&
a.Dependencies.Count == b.Dependencies.Count && a.Dependencies.Zip(b.Dependencies, (x, y) =>
x.ModId == y.ModId && x.VersionRange == y.VersionRange && x.Optional == y.Optional && x.Ordering == y.Ordering).All(equal => equal);
}
}