using System; using System.Collections.Generic; using System.Linq; using System.Reflection; namespace UXAssist.Common.ModFeatures; /// /// 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. /// public static class ModFeatureRegistry { 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 _staticFeatures = []; private static readonly List _instanceFeatures = []; private static readonly HashSet _registeredInstanceTypes = []; private static readonly HashSet _discoveredAssemblies = []; /// /// Discovers static mod feature classes marked with in the given assembly. /// Each assembly is only discovered once. /// /// The assembly to scan. public static void Discover(Assembly assembly) { if (!_discoveredAssemblies.Add(assembly)) return; var staticTypes = Util.GetTypesFiltered(assembly, t => t.IsClass && !t.IsInterface && Attribute.IsDefined(t, typeof(ModFeatureAttribute)) && !typeof(IModFeature).IsAssignableFrom(t)); foreach (var type in staticTypes.OrderBy(GetOrder)) { if (_staticFeatures.All(f => f.Type != type)) _staticFeatures.Add(new StaticFeature(type)); } } /// /// Registers a new instance mod feature if an instance of the same type is not already registered. /// /// The mod feature type to register. public static void Register() where T : class, IModFeature, new() { var type = typeof(T); if (!_registeredInstanceTypes.Add(type)) return; var instance = new T(); _instanceFeatures.Add(instance); } /// /// Calls on all registered instance features /// and invokes the cached static Init methods on all discovered static features. /// public static void InitAll() { foreach (var f in _staticFeatures) f.Init?.Invoke(); foreach (var f in _instanceFeatures) f.Init(); } /// /// Calls on all registered instance features /// and invokes the cached static Start methods on all discovered static features. /// public static void StartAll() { foreach (var f in _staticFeatures) f.Start?.Invoke(); foreach (var f in _instanceFeatures) f.Start(); } /// /// Calls on all registered instance features /// and invokes the cached static Uninit methods on all discovered static features. /// public static void UninitAll() { foreach (var f in _staticFeatures) f.Uninit?.Invoke(); foreach (var f in _instanceFeatures) f.Uninit(); } /// /// Calls on all registered instance features /// and invokes the cached static OnInputUpdate delegates on all discovered static features. /// This method is meant to be called every frame; no reflection is performed here. /// public static void OnInputUpdateAll() { foreach (var f in _staticFeatures) f.OnInputUpdate?.Invoke(); foreach (var f in _instanceFeatures) f.OnInputUpdate(); } /// /// Calls on all registered instance features /// and invokes the cached static OnUpdate delegates on all discovered static features. /// This method is meant to be called every frame; no reflection is performed here. /// public static void OnUpdateAll() { foreach (var f in _staticFeatures) f.OnUpdate?.Invoke(); foreach (var f in _instanceFeatures) f.OnUpdate(); } private static int GetOrder(Type type) { return type.GetCustomAttribute()?.Order ?? 0; } }