From 70bd081a930d232fcfd6732549eb7488bb67f5fc Mon Sep 17 00:00:00 2001 From: YoshiRulz Date: Sun, 23 Apr 2023 16:29:19 +1000 Subject: [PATCH] Refactor `ServiceInjector` --- src/BizHawk.Client.EmuHawk/Api/ApiManager.cs | 3 +- .../tools/Lua/LuaLibraries.cs | 5 +- .../tools/ToolManager.cs | 2 +- .../ServiceInjector.cs | 69 +++++++++++++++---- 4 files changed, 60 insertions(+), 19 deletions(-) diff --git a/src/BizHawk.Client.EmuHawk/Api/ApiManager.cs b/src/BizHawk.Client.EmuHawk/Api/ApiManager.cs index c0c0733431..f96dd9348c 100644 --- a/src/BizHawk.Client.EmuHawk/Api/ApiManager.cs +++ b/src/BizHawk.Client.EmuHawk/Api/ApiManager.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Reflection; @@ -62,7 +63,7 @@ namespace BizHawk.Client.EmuHawk tuple => { var instance = tuple.Ctor.Invoke(tuple.CtorTypes.Select(t => avail[t]).ToArray()); - ServiceInjector.UpdateServices(serviceProvider, instance); + Debug.Assert(ServiceInjector.UpdateServices(serviceProvider, instance, mayCache: true)); return (IExternalApi) instance; })); } diff --git a/src/BizHawk.Client.EmuHawk/tools/Lua/LuaLibraries.cs b/src/BizHawk.Client.EmuHawk/tools/Lua/LuaLibraries.cs index 2eb27c78ea..300521d3f4 100644 --- a/src/BizHawk.Client.EmuHawk/tools/Lua/LuaLibraries.cs +++ b/src/BizHawk.Client.EmuHawk/tools/Lua/LuaLibraries.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; @@ -65,7 +66,7 @@ namespace BizHawk.Client.EmuHawk || lib.GetCustomAttribute(inherit: false)?.Released is not false) { var instance = (LuaLibraryBase)Activator.CreateInstance(lib, this, _apiContainer, (Action)LogToLuaConsole); - ServiceInjector.UpdateServices(serviceProvider, instance); + Debug.Assert(ServiceInjector.UpdateServices(serviceProvider, instance, mayCache: true)); // TODO: make EmuHawk libraries have a base class with common properties such as this // and inject them here @@ -181,7 +182,7 @@ namespace BizHawk.Client.EmuHawk foreach (var lib in Libraries.Values) { lib.APIs = _apiContainer; - ServiceInjector.UpdateServices(newServiceProvider, lib); + Debug.Assert(ServiceInjector.UpdateServices(newServiceProvider, lib, mayCache: true)); lib.Restarted(); } } diff --git a/src/BizHawk.Client.EmuHawk/tools/ToolManager.cs b/src/BizHawk.Client.EmuHawk/tools/ToolManager.cs index cbd977ebc5..1ebbbd0679 100644 --- a/src/BizHawk.Client.EmuHawk/tools/ToolManager.cs +++ b/src/BizHawk.Client.EmuHawk/tools/ToolManager.cs @@ -123,7 +123,7 @@ namespace BizHawk.Client.EmuHawk if (!(CreateInstance(toolPath) is T newTool)) return null; if (newTool is Form form) form.Owner = _owner; - if (!ServiceInjector.UpdateServices(_emulator.ServiceProvider, newTool)) return null; + if (!ServiceInjector.UpdateServices(_emulator.ServiceProvider, newTool)) return null; //TODO pass `true` for `mayCache` when from EmuHawk assembly SetBaseProperties(newTool); var toolTypeName = typeof(T).FullName!; // auto settings diff --git a/src/BizHawk.Emulation.Common/ServiceInjector.cs b/src/BizHawk.Emulation.Common/ServiceInjector.cs index 1b9fe3a6c7..7ac06df321 100644 --- a/src/BizHawk.Emulation.Common/ServiceInjector.cs +++ b/src/BizHawk.Emulation.Common/ServiceInjector.cs @@ -1,5 +1,7 @@ using System; +using System.Collections.Generic; using System.Linq; +using System.Reflection; using BizHawk.Common.ReflectionExtensions; @@ -10,30 +12,71 @@ namespace BizHawk.Emulation.Common /// public static class ServiceInjector { + private readonly struct ServicePropInfo + { + public readonly Type PropType; + + public readonly MethodInfo Setter; + + public ServicePropInfo(Type propType, MethodInfo setter) + { + PropType = propType; + Setter = setter; + } + } + + private static readonly Dictionary Req, List Opt)> _cache = new(); + + private static (List Req, List Opt) GetServicePropsFor( + this Type @class, + bool mayCache) + { + if (_cache.TryGetValue(@class, out var pair)) return pair; + pair = (new(), new()); + foreach (var pi in @class.GetProperties(ReflectionExtensions.DI_TARGET_PROPS)) + { + //TODO enumerate attrs only once + if (pi.GetCustomAttributes(typeof(RequiredServiceAttribute), inherit: false).Length is not 0) + { + pair.Req.Add(new(pi.PropertyType, pi.GetSetMethod(nonPublic: true))); + } + else if (pi.GetCustomAttributes(typeof(OptionalServiceAttribute), inherit: false).Length is not 0) + { + pair.Opt.Add(new(pi.PropertyType, pi.GetSetMethod(nonPublic: true))); + } + } + if (mayCache) _cache[@class] = pair; + return pair; + } + /// /// Feeds the target its required services. /// + /// + /// if the properties of may be written to cache, + /// i.e. if it's known to come from a first-party assembly and not an ext. tool.
+ /// Cache will still be read from regardless. + /// /// false if update failed - public static bool UpdateServices(IEmulatorServiceProvider source, object target) + /// don't think having a genericised overload would be helpful, but TODO pass in type to save target.GetType() call + public static bool UpdateServices(IEmulatorServiceProvider source, object target, bool mayCache = false) { Type targetType = target.GetType(); object?[] tmp = new object?[1]; - - foreach (var propInfo in targetType.GetPropertiesWithAttrib(typeof(RequiredServiceAttribute))) + var (req, opt) = GetServicePropsFor(targetType, mayCache: mayCache); + foreach (var info in req) { - tmp[0] = source.GetService(propInfo.PropertyType); + tmp[0] = source.GetService(info.PropType); if (tmp[0] == null) { return false; } - - propInfo.GetSetMethod(true).Invoke(target, tmp); + info.Setter.Invoke(target, tmp); } - - foreach (var propInfo in targetType.GetPropertiesWithAttrib(typeof(OptionalServiceAttribute))) + foreach (var info in opt) { - tmp[0] = source.GetService(propInfo.PropertyType); - propInfo.GetSetMethod(true).Invoke(target, tmp); + tmp[0] = source.GetService(info.PropType); + info.Setter.Invoke(target, tmp); } return true; @@ -44,11 +87,7 @@ namespace BizHawk.Emulation.Common /// and the services provided by the emulator core. /// public static bool IsAvailable(IEmulatorServiceProvider source, Type targetType) - { - return targetType.GetPropertiesWithAttrib(typeof(RequiredServiceAttribute)) - .Select(pi => pi.PropertyType) - .All(source.HasService); - } + => GetServicePropsFor(targetType, mayCache: false).Req.TrueForAll(info => source.HasService(info.PropType)); } [AttributeUsage(AttributeTargets.Property)]