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
@@ -33,6 +33,10 @@ namespace ShrinkModFramework
|
||||
private long _generation;
|
||||
private int _catalogGeneration;
|
||||
private bool _applying;
|
||||
private IReadOnlyList<ShrinkModComponentSource> _buildingSources = Array.Empty<ShrinkModComponentSource>();
|
||||
|
||||
internal IEnumerable<string> GetPredecessors(string modId) =>
|
||||
ShrinkModDependencyGraph.Predecessors(modId, _buildingSources.Select(source => source.Info));
|
||||
|
||||
public ShrinkModContextHost(bool enableHarmonyPatching = true, bool verboseLogging = false)
|
||||
{
|
||||
@@ -64,7 +68,9 @@ namespace ShrinkModFramework
|
||||
return "shrink.mod/" + modId.Trim();
|
||||
}
|
||||
|
||||
public async UniTask ApplyAsync(IReadOnlyList<ShrinkModComponentSource> desiredSources)
|
||||
public UniTask ApplyAsync(IReadOnlyList<ShrinkModComponentSource> desiredSources) => ApplyAsync(desiredSources, null);
|
||||
|
||||
internal async UniTask ApplyAsync(IReadOnlyList<ShrinkModComponentSource> desiredSources, Action beforeRestore)
|
||||
{
|
||||
if (desiredSources == null)
|
||||
throw new ArgumentNullException(nameof(desiredSources));
|
||||
@@ -95,8 +101,11 @@ namespace ShrinkModFramework
|
||||
catch (Exception applyError)
|
||||
{
|
||||
Exception restoreError = null;
|
||||
_currentSources.Clear();
|
||||
_currentSources.AddRange(previous);
|
||||
try
|
||||
{
|
||||
beforeRestore?.Invoke();
|
||||
await Loader.ApplyAsync(BuildEntries(previous));
|
||||
EnsureActive(previous);
|
||||
}
|
||||
@@ -143,6 +152,7 @@ namespace ShrinkModFramework
|
||||
|
||||
private IReadOnlyList<ShrinkLoaderEntry> BuildEntries(IEnumerable<ShrinkModComponentSource> sources)
|
||||
{
|
||||
_buildingSources = sources.ToArray();
|
||||
var entries = new List<ShrinkLoaderEntry>();
|
||||
foreach (var source in sources)
|
||||
{
|
||||
@@ -184,79 +194,34 @@ namespace ShrinkModFramework
|
||||
throw new InvalidOperationException($"Duplicate mod source id: {source.Info.ModId}");
|
||||
}
|
||||
|
||||
foreach (var source in sources)
|
||||
{
|
||||
foreach (var dependency in source.Info.Dependencies)
|
||||
{
|
||||
if (!map.TryGetValue(dependency.ModId, out var target))
|
||||
{
|
||||
if (!dependency.Optional)
|
||||
throw new InvalidOperationException(
|
||||
$"Mod {source.Info.ModId} is missing required dependency {dependency.ModId}.");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(dependency.MinimumVersion) &&
|
||||
ShrinkVersionUtility.Compare(target.Info.Version, dependency.MinimumVersion) < 0)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Mod {source.Info.ModId} requires {dependency.ModId} >= {dependency.MinimumVersion}, " +
|
||||
$"but found {target.Info.Version}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var result = new List<ShrinkModComponentSource>();
|
||||
var visiting = new HashSet<string>(StringComparer.Ordinal);
|
||||
var visited = new HashSet<string>(StringComparer.Ordinal);
|
||||
foreach (var source in sources.OrderBy(item => item.Info.LoadOrder)
|
||||
.ThenBy(item => item.Info.ModId, StringComparer.Ordinal))
|
||||
{
|
||||
Visit(source, map, visiting, visited, result);
|
||||
}
|
||||
return result;
|
||||
return ShrinkModDependencyGraph.Order(sources.Select(source => source.Info))
|
||||
.Select(info => map[info.ModId]).ToList();
|
||||
}
|
||||
|
||||
private List<ShrinkModComponentSource> ReuseUnchangedSources(
|
||||
IEnumerable<ShrinkModComponentSource> desired)
|
||||
{
|
||||
var desiredArray = desired.ToArray();
|
||||
var currentById = _currentSources.ToDictionary(source => source.Info.ModId, StringComparer.Ordinal);
|
||||
var normalized = new List<ShrinkModComponentSource>();
|
||||
foreach (var source in desired)
|
||||
foreach (var source in desiredArray)
|
||||
{
|
||||
if (currentById.TryGetValue(source.Info.ModId, out var current) &&
|
||||
string.Equals(current.Revision, source.Revision, StringComparison.Ordinal))
|
||||
string.Equals(current.Revision, source.Revision, StringComparison.Ordinal) &&
|
||||
ShrinkModDependencyGraph.SameMetadata(current.Info, source.Info) &&
|
||||
ShrinkModDependencyGraph.Predecessors(source.Info.ModId, _currentSources.Select(item => item.Info)).OrderBy(id => id)
|
||||
.SequenceEqual(ShrinkModDependencyGraph.Predecessors(source.Info.ModId, desiredArray.Select(item => item.Info)).OrderBy(id => id)))
|
||||
{
|
||||
normalized.Add(current);
|
||||
}
|
||||
else
|
||||
{
|
||||
normalized.Add(source);
|
||||
normalized.Add(ReferenceEquals(source, current)
|
||||
? source.WithMetadata(source.Info, source.Revision) : source);
|
||||
}
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
private static void Visit(ShrinkModComponentSource source,
|
||||
IReadOnlyDictionary<string, ShrinkModComponentSource> map,
|
||||
ISet<string> visiting,
|
||||
ISet<string> visited,
|
||||
ICollection<ShrinkModComponentSource> result)
|
||||
{
|
||||
if (visited.Contains(source.Info.ModId))
|
||||
return;
|
||||
if (!visiting.Add(source.Info.ModId))
|
||||
throw new InvalidOperationException($"Circular mod dependency includes {source.Info.ModId}.");
|
||||
|
||||
foreach (var dependency in source.Info.Dependencies.OrderBy(item => item.ModId, StringComparer.Ordinal))
|
||||
{
|
||||
if (map.TryGetValue(dependency.ModId, out var target))
|
||||
Visit(target, map, visiting, visited, result);
|
||||
}
|
||||
|
||||
visiting.Remove(source.Info.ModId);
|
||||
visited.Add(source.Info.ModId);
|
||||
result.Add(source);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user