Files
ShrinkModFramework/Runtime/Context/ShrinkModComponentDiscovery.cs
T
cneicy d15bf00512
Publish UPM package / publish (push) Successful in 3s
Publish NuGet packages / publish (push) Successful in 2m18s
feat: 公开宿主组合加载 API、Forge 风格依赖版本区间与加载顺序、程序集解析策略
- ShrinkModComponentDiscovery.Discover 公开:只发现、不执行生命周期
- ShrinkModLoader.LoadComposed / ShrinkModCordisRuntime.ApplyComposedAsync:宿主提供元数据组合加载,支持纯数据包与 IShrinkModWrapper 公开真实实例
- 公开 TryAttachEventBusInstance,随生命周期逆操作释放
- 依赖新增 VersionRange(Maven/Forge 区间语法)与 Ordering(Before/After)
- AssemblyResolvePolicy 可限制跨模组程序集解析
2026-11-10 07:39:22 +08:00

146 lines
6.2 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEngine;
namespace ShrinkModFramework
{
/// <summary>
/// 把工程内程序集和外部 DLL 的当前 revision 投影为延迟组件源。
/// 同一路径的旧外部程序集无法从 Mono AppDomain 卸载,但不会再进入候选集合。
/// </summary>
public static class ShrinkModComponentDiscovery
{
public static IReadOnlyList<ShrinkModComponentSource> Discover(
ShrinkModFrameworkSettings settings, IEnumerable<string> authorizedDllPaths) =>
Discover(settings, settings != null && settings.verboseLogging, authorizedDllPaths);
/// <summary>只发现,不构造实例或提交 current revision。WithMetadata 可保留发现快照。</summary>
public static IReadOnlyList<ShrinkModComponentSource> Discover(
ShrinkModFrameworkSettings settings, bool verboseLogging, IEnumerable<string> authorizedDllPaths)
{
if (authorizedDllPaths == null) throw new ArgumentNullException(nameof(authorizedDllPaths));
var previous = ShrinkExternalModAssemblyLoader.CaptureState();
try
{
var sources = DiscoverCurrent(settings, verboseLogging, authorizedDllPaths);
var state = ShrinkExternalModAssemblyLoader.CaptureState();
foreach (var source in sources) source.DiscoveryState = state;
return sources;
}
finally { ShrinkExternalModAssemblyLoader.RestoreState(previous); }
}
internal static IReadOnlyList<ShrinkModComponentSource> DiscoverCurrent(
ShrinkModFrameworkSettings settings,
bool verboseLogging,
IEnumerable<string> authorizedDllPaths)
{
ShrinkExternalModAssemblyLoader.ScanExternalAssemblyRevisions(
settings, verboseLogging, authorizedDllPaths);
var results = new List<ShrinkModComponentSource>();
var prefixes = settings?.assemblyNamePrefixes;
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
if (ShrinkExternalModAssemblyLoader.IsExternalAssembly(assembly) &&
!ShrinkExternalModAssemblyLoader.TryGetCurrentRevision(assembly, out _))
{
continue;
}
if (!ShouldScanAssembly(assembly, prefixes))
continue;
var assemblyRevision = GetAssemblyRevision(assembly);
foreach (var type in GetTypesSafely(assembly))
{
if (type == null || type.IsAbstract || type.IsInterface)
continue;
var modAttribute = type.GetCustomAttribute<ShrinkModAttribute>(false);
if (modAttribute == null)
continue;
if (!typeof(IShrinkMod).IsAssignableFrom(type))
{
Debug.LogWarning(
$"[ShrinkModFramework] 类型 {type.FullName} 标记了 ShrinkMod,但没有实现 IShrinkMod,已跳过。");
continue;
}
if (type.GetConstructor(Type.EmptyTypes) == null)
throw new InvalidOperationException($"模组 {type.FullName} 缺少无参构造函数,无法实例化。");
var dependencies = type
.GetCustomAttributes<ShrinkModDependencyAttribute>(false)
.Select(attribute => new ShrinkModDependency(
attribute.ModId, attribute.MinimumVersion, attribute.Optional, attribute.Ordering, attribute.VersionRange))
.ToArray();
var info = new ShrinkModInfo(
modAttribute.ModId,
modAttribute.DisplayName,
modAttribute.Version,
modAttribute.LoadOrder,
type,
modAttribute.AutoApplyHarmonyPatches,
dependencies);
var revision = $"{assemblyRevision}:{type.FullName}";
results.Add(new ShrinkModComponentSource(info, revision,
() => (IShrinkMod)Activator.CreateInstance(type)));
}
}
return results;
}
private static string GetAssemblyRevision(Assembly assembly)
{
if (ShrinkExternalModAssemblyLoader.TryGetCurrentRevision(assembly, out var externalRevision))
return externalRevision;
try
{
return assembly.ManifestModule.ModuleVersionId.ToString("N");
}
catch
{
return assembly.FullName ?? assembly.GetName().Name ?? "unknown";
}
}
private static bool ShouldScanAssembly(Assembly assembly, string[] prefixes)
{
var name = assembly.GetName().Name;
if (string.IsNullOrEmpty(name))
return false;
if (name.StartsWith("Unity", StringComparison.Ordinal) ||
name.StartsWith("System", StringComparison.Ordinal) ||
name.StartsWith("mscorlib", StringComparison.Ordinal) ||
name.StartsWith("netstandard", StringComparison.Ordinal))
{
return false;
}
if (prefixes == null || prefixes.Length == 0)
return true;
return prefixes.Any(prefix => !string.IsNullOrWhiteSpace(prefix) &&
name.StartsWith(prefix, StringComparison.OrdinalIgnoreCase));
}
private static IEnumerable<Type> GetTypesSafely(Assembly assembly)
{
try
{
return assembly.GetTypes();
}
catch (ReflectionTypeLoadException exception)
{
return exception.Types.Where(type => type != null);
}
catch
{
return Array.Empty<Type>();
}
}
}
}