mirror of
https://github.com/soarqin/DSP_Mods.git
synced 2026-08-05 15:10:23 +08:00
refactor(UXAssist): harden ModFeatureRegistry
This commit is contained in:
@@ -1,10 +1,33 @@
|
|||||||
namespace UXAssist.Common.ModFeatures;
|
namespace UXAssist.Common.ModFeatures;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Interface implemented by instance mod features registered with <see cref="ModFeatureRegistry"/>.
|
||||||
|
/// All lifecycle methods are invoked by the registry in the order described below.
|
||||||
|
/// </summary>
|
||||||
public interface IModFeature
|
public interface IModFeature
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Called once when the mod is initialized.
|
||||||
|
/// </summary>
|
||||||
void Init();
|
void Init();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called once after initialization, when the mod should begin active behavior.
|
||||||
|
/// </summary>
|
||||||
void Start();
|
void Start();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called once when the mod is being shut down or re-initialized.
|
||||||
|
/// </summary>
|
||||||
void Uninit();
|
void Uninit();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called every frame for input handling. Should be lightweight.
|
||||||
|
/// </summary>
|
||||||
void OnInputUpdate();
|
void OnInputUpdate();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called every frame for general updates. Should be lightweight.
|
||||||
|
/// </summary>
|
||||||
void OnUpdate();
|
void OnUpdate();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,27 @@ using System;
|
|||||||
|
|
||||||
namespace UXAssist.Common.ModFeatures;
|
namespace UXAssist.Common.ModFeatures;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Marks a static class as a mod feature so that <see cref="ModFeatureRegistry"/> can discover it.
|
||||||
|
/// Lifecycle methods (<c>Init</c>, <c>Start</c>, <c>Uninit</c>, <c>OnInputUpdate</c>, <c>OnUpdate</c>) are optional.
|
||||||
|
/// </summary>
|
||||||
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
|
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
|
||||||
public sealed class ModFeatureAttribute : Attribute
|
public sealed class ModFeatureAttribute : Attribute
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Optional display name of the feature.
|
||||||
|
/// </summary>
|
||||||
public string Name { get; }
|
public string Name { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Execution order among discovered static features. Lower values run first.
|
||||||
|
/// </summary>
|
||||||
public int Order { get; set; }
|
public int Order { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="ModFeatureAttribute"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">Optional display name of the feature.</param>
|
||||||
public ModFeatureAttribute(string name = null)
|
public ModFeatureAttribute(string name = null)
|
||||||
{
|
{
|
||||||
Name = name;
|
Name = name;
|
||||||
|
|||||||
@@ -5,12 +5,51 @@ using System.Reflection;
|
|||||||
|
|
||||||
namespace UXAssist.Common.ModFeatures;
|
namespace UXAssist.Common.ModFeatures;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registry that discovers static mod features from assemblies and holds registered instance mod features.
|
||||||
|
/// Static lifecycle methods are optional; if a method is missing it is simply skipped.
|
||||||
|
/// </summary>
|
||||||
public static class ModFeatureRegistry
|
public static class ModFeatureRegistry
|
||||||
{
|
{
|
||||||
private static readonly List<Type> _staticFeatures = [];
|
private sealed class StaticFeature
|
||||||
|
{
|
||||||
|
public Type Type { get; }
|
||||||
|
public Action Init { get; }
|
||||||
|
public Action Start { get; }
|
||||||
|
public Action Uninit { get; }
|
||||||
|
public Action OnInputUpdate { get; }
|
||||||
|
public Action OnUpdate { get; }
|
||||||
|
|
||||||
|
public StaticFeature(Type type)
|
||||||
|
{
|
||||||
|
Type = type;
|
||||||
|
Init = GetDelegate(type, "Init");
|
||||||
|
Start = GetDelegate(type, "Start");
|
||||||
|
Uninit = GetDelegate(type, "Uninit");
|
||||||
|
OnInputUpdate = GetDelegate(type, "OnInputUpdate");
|
||||||
|
OnUpdate = GetDelegate(type, "OnUpdate");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Action GetDelegate(Type type, string methodName)
|
||||||
|
{
|
||||||
|
var method = type.GetMethod(methodName,
|
||||||
|
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static,
|
||||||
|
null, Type.EmptyTypes, null);
|
||||||
|
if (method == null) return null;
|
||||||
|
return (Action)Delegate.CreateDelegate(typeof(Action), method);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readonly List<StaticFeature> _staticFeatures = [];
|
||||||
private static readonly List<IModFeature> _instanceFeatures = [];
|
private static readonly List<IModFeature> _instanceFeatures = [];
|
||||||
|
private static readonly HashSet<Type> _registeredInstanceTypes = [];
|
||||||
private static readonly HashSet<Assembly> _discoveredAssemblies = [];
|
private static readonly HashSet<Assembly> _discoveredAssemblies = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Discovers static mod feature classes marked with <see cref="ModFeatureAttribute"/> in the given assembly.
|
||||||
|
/// Each assembly is only discovered once.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="assembly">The assembly to scan.</param>
|
||||||
public static void Discover(Assembly assembly)
|
public static void Discover(Assembly assembly)
|
||||||
{
|
{
|
||||||
if (!_discoveredAssemblies.Add(assembly)) return;
|
if (!_discoveredAssemblies.Add(assembly)) return;
|
||||||
@@ -21,57 +60,76 @@ public static class ModFeatureRegistry
|
|||||||
|
|
||||||
foreach (var type in staticTypes.OrderBy(GetOrder))
|
foreach (var type in staticTypes.OrderBy(GetOrder))
|
||||||
{
|
{
|
||||||
if (!_staticFeatures.Contains(type))
|
if (_staticFeatures.All(f => f.Type != type))
|
||||||
_staticFeatures.Add(type);
|
_staticFeatures.Add(new StaticFeature(type));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers a new instance mod feature if an instance of the same type is not already registered.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The mod feature type to register.</typeparam>
|
||||||
public static void Register<T>() where T : class, IModFeature, new()
|
public static void Register<T>() where T : class, IModFeature, new()
|
||||||
{
|
{
|
||||||
|
var type = typeof(T);
|
||||||
|
if (!_registeredInstanceTypes.Add(type)) return;
|
||||||
|
|
||||||
var instance = new T();
|
var instance = new T();
|
||||||
_instanceFeatures.Add(instance);
|
_instanceFeatures.Add(instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calls <see cref="IModFeature.Init"/> on all registered instance features
|
||||||
|
/// and invokes the cached static <c>Init</c> methods on all discovered static features.
|
||||||
|
/// </summary>
|
||||||
public static void InitAll()
|
public static void InitAll()
|
||||||
{
|
{
|
||||||
ForEachStatic("Init");
|
foreach (var f in _staticFeatures) f.Init?.Invoke();
|
||||||
foreach (var f in _instanceFeatures) f.Init();
|
foreach (var f in _instanceFeatures) f.Init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calls <see cref="IModFeature.Start"/> on all registered instance features
|
||||||
|
/// and invokes the cached static <c>Start</c> methods on all discovered static features.
|
||||||
|
/// </summary>
|
||||||
public static void StartAll()
|
public static void StartAll()
|
||||||
{
|
{
|
||||||
ForEachStatic("Start");
|
foreach (var f in _staticFeatures) f.Start?.Invoke();
|
||||||
foreach (var f in _instanceFeatures) f.Start();
|
foreach (var f in _instanceFeatures) f.Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calls <see cref="IModFeature.Uninit"/> on all registered instance features
|
||||||
|
/// and invokes the cached static <c>Uninit</c> methods on all discovered static features.
|
||||||
|
/// </summary>
|
||||||
public static void UninitAll()
|
public static void UninitAll()
|
||||||
{
|
{
|
||||||
ForEachStatic("Uninit");
|
foreach (var f in _staticFeatures) f.Uninit?.Invoke();
|
||||||
foreach (var f in _instanceFeatures) f.Uninit();
|
foreach (var f in _instanceFeatures) f.Uninit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calls <see cref="IModFeature.OnInputUpdate"/> on all registered instance features
|
||||||
|
/// and invokes the cached static <c>OnInputUpdate</c> delegates on all discovered static features.
|
||||||
|
/// This method is meant to be called every frame; no reflection is performed here.
|
||||||
|
/// </summary>
|
||||||
public static void OnInputUpdateAll()
|
public static void OnInputUpdateAll()
|
||||||
{
|
{
|
||||||
ForEachStatic("OnInputUpdate");
|
foreach (var f in _staticFeatures) f.OnInputUpdate?.Invoke();
|
||||||
foreach (var f in _instanceFeatures) f.OnInputUpdate();
|
foreach (var f in _instanceFeatures) f.OnInputUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calls <see cref="IModFeature.OnUpdate"/> on all registered instance features
|
||||||
|
/// and invokes the cached static <c>OnUpdate</c> delegates on all discovered static features.
|
||||||
|
/// This method is meant to be called every frame; no reflection is performed here.
|
||||||
|
/// </summary>
|
||||||
public static void OnUpdateAll()
|
public static void OnUpdateAll()
|
||||||
{
|
{
|
||||||
ForEachStatic("OnUpdate");
|
foreach (var f in _staticFeatures) f.OnUpdate?.Invoke();
|
||||||
foreach (var f in _instanceFeatures) f.OnUpdate();
|
foreach (var f in _instanceFeatures) f.OnUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ForEachStatic(string methodName)
|
|
||||||
{
|
|
||||||
foreach (var type in _staticFeatures)
|
|
||||||
{
|
|
||||||
var method = type.GetMethod(methodName,
|
|
||||||
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
|
|
||||||
method?.Invoke(null, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int GetOrder(Type type)
|
private static int GetOrder(Type type)
|
||||||
{
|
{
|
||||||
return type.GetCustomAttribute<ModFeatureAttribute>()?.Order ?? 0;
|
return type.GetCustomAttribute<ModFeatureAttribute>()?.Order ?? 0;
|
||||||
|
|||||||
+13
-1
@@ -8,16 +8,28 @@ namespace UXAssist.Common;
|
|||||||
|
|
||||||
public static class Util
|
public static class Util
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Returns all types from the assembly matching the predicate, tolerating partially loadable assemblies.
|
||||||
|
/// </summary>
|
||||||
public static Type[] GetTypesFiltered(Assembly assembly, Func<Type, bool> predicate)
|
public static Type[] GetTypesFiltered(Assembly assembly, Func<Type, bool> predicate)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
return [.. assembly.GetTypes().Where(predicate)];
|
return [.. assembly.GetTypes().Where(predicate)];
|
||||||
}
|
}
|
||||||
|
catch (ReflectionTypeLoadException ex)
|
||||||
|
{
|
||||||
|
return [.. ex.Types.Where(t => t != null).Where(predicate)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static Type[] GetTypesInNamespace(Assembly assembly, string nameSpace) => GetTypesFiltered(assembly, t => string.Equals(t.Namespace, nameSpace, StringComparison.Ordinal));
|
public static Type[] GetTypesInNamespace(Assembly assembly, string nameSpace) => GetTypesFiltered(assembly, t => string.Equals(t.Namespace, nameSpace, StringComparison.Ordinal));
|
||||||
|
|
||||||
public static Type[] GetTypesInNamespacePrefix(Assembly assembly, string prefix)
|
public static Type[] GetTypesInNamespacePrefix(Assembly assembly, string prefix)
|
||||||
{
|
{
|
||||||
return GetTypesFiltered(assembly, t => t.Namespace != null && t.Namespace.StartsWith(prefix, StringComparison.Ordinal));
|
return GetTypesFiltered(assembly, t =>
|
||||||
|
t.Namespace != null &&
|
||||||
|
(t.Namespace == prefix || t.Namespace.StartsWith(prefix + ".", StringComparison.Ordinal)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static byte[] LoadEmbeddedResource(string path, Assembly assembly = null)
|
public static byte[] LoadEmbeddedResource(string path, Assembly assembly = null)
|
||||||
|
|||||||
Reference in New Issue
Block a user