Refactor `ServiceInjector`
This commit is contained in:
parent
c5d6a66e01
commit
70bd081a93
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
||||||
|
@ -62,7 +63,7 @@ namespace BizHawk.Client.EmuHawk
|
||||||
tuple =>
|
tuple =>
|
||||||
{
|
{
|
||||||
var instance = tuple.Ctor.Invoke(tuple.CtorTypes.Select(t => avail[t]).ToArray());
|
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;
|
return (IExternalApi) instance;
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
@ -65,7 +66,7 @@ namespace BizHawk.Client.EmuHawk
|
||||||
|| lib.GetCustomAttribute<LuaLibraryAttribute>(inherit: false)?.Released is not false)
|
|| lib.GetCustomAttribute<LuaLibraryAttribute>(inherit: false)?.Released is not false)
|
||||||
{
|
{
|
||||||
var instance = (LuaLibraryBase)Activator.CreateInstance(lib, this, _apiContainer, (Action<string>)LogToLuaConsole);
|
var instance = (LuaLibraryBase)Activator.CreateInstance(lib, this, _apiContainer, (Action<string>)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
|
// TODO: make EmuHawk libraries have a base class with common properties such as this
|
||||||
// and inject them here
|
// and inject them here
|
||||||
|
@ -181,7 +182,7 @@ namespace BizHawk.Client.EmuHawk
|
||||||
foreach (var lib in Libraries.Values)
|
foreach (var lib in Libraries.Values)
|
||||||
{
|
{
|
||||||
lib.APIs = _apiContainer;
|
lib.APIs = _apiContainer;
|
||||||
ServiceInjector.UpdateServices(newServiceProvider, lib);
|
Debug.Assert(ServiceInjector.UpdateServices(newServiceProvider, lib, mayCache: true));
|
||||||
lib.Restarted();
|
lib.Restarted();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -123,7 +123,7 @@ namespace BizHawk.Client.EmuHawk
|
||||||
if (!(CreateInstance<T>(toolPath) is T newTool)) return null;
|
if (!(CreateInstance<T>(toolPath) is T newTool)) return null;
|
||||||
|
|
||||||
if (newTool is Form form) form.Owner = _owner;
|
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);
|
SetBaseProperties(newTool);
|
||||||
var toolTypeName = typeof(T).FullName!;
|
var toolTypeName = typeof(T).FullName!;
|
||||||
// auto settings
|
// auto settings
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
using BizHawk.Common.ReflectionExtensions;
|
using BizHawk.Common.ReflectionExtensions;
|
||||||
|
|
||||||
|
@ -10,30 +12,71 @@ namespace BizHawk.Emulation.Common
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class ServiceInjector
|
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<Type, (List<ServicePropInfo> Req, List<ServicePropInfo> Opt)> _cache = new();
|
||||||
|
|
||||||
|
private static (List<ServicePropInfo> Req, List<ServicePropInfo> 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;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Feeds the target its required services.
|
/// Feeds the target its required services.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="mayCache">
|
||||||
|
/// <see langword="true"/> if the properties of <paramref name="target"/> may be written to cache,
|
||||||
|
/// i.e. if it's known to come from a first-party assembly and not an ext. tool.<br/>
|
||||||
|
/// Cache will still be read from regardless.
|
||||||
|
/// </param>
|
||||||
/// <returns>false if update failed</returns>
|
/// <returns>false if update failed</returns>
|
||||||
public static bool UpdateServices(IEmulatorServiceProvider source, object target)
|
/// <remarks>don't think having a genericised overload would be helpful, but TODO pass in type to save <c>target.GetType()</c> call</remarks>
|
||||||
|
public static bool UpdateServices(IEmulatorServiceProvider source, object target, bool mayCache = false)
|
||||||
{
|
{
|
||||||
Type targetType = target.GetType();
|
Type targetType = target.GetType();
|
||||||
object?[] tmp = new object?[1];
|
object?[] tmp = new object?[1];
|
||||||
|
var (req, opt) = GetServicePropsFor(targetType, mayCache: mayCache);
|
||||||
foreach (var propInfo in targetType.GetPropertiesWithAttrib(typeof(RequiredServiceAttribute)))
|
foreach (var info in req)
|
||||||
{
|
{
|
||||||
tmp[0] = source.GetService(propInfo.PropertyType);
|
tmp[0] = source.GetService(info.PropType);
|
||||||
if (tmp[0] == null)
|
if (tmp[0] == null)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
info.Setter.Invoke(target, tmp);
|
||||||
propInfo.GetSetMethod(true).Invoke(target, tmp);
|
|
||||||
}
|
}
|
||||||
|
foreach (var info in opt)
|
||||||
foreach (var propInfo in targetType.GetPropertiesWithAttrib(typeof(OptionalServiceAttribute)))
|
|
||||||
{
|
{
|
||||||
tmp[0] = source.GetService(propInfo.PropertyType);
|
tmp[0] = source.GetService(info.PropType);
|
||||||
propInfo.GetSetMethod(true).Invoke(target, tmp);
|
info.Setter.Invoke(target, tmp);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -44,11 +87,7 @@ namespace BizHawk.Emulation.Common
|
||||||
/// and the services provided by the emulator core.
|
/// and the services provided by the emulator core.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static bool IsAvailable(IEmulatorServiceProvider source, Type targetType)
|
public static bool IsAvailable(IEmulatorServiceProvider source, Type targetType)
|
||||||
{
|
=> GetServicePropsFor(targetType, mayCache: false).Req.TrueForAll(info => source.HasService(info.PropType));
|
||||||
return targetType.GetPropertiesWithAttrib(typeof(RequiredServiceAttribute))
|
|
||||||
.Select(pi => pi.PropertyType)
|
|
||||||
.All(source.HasService);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[AttributeUsage(AttributeTargets.Property)]
|
[AttributeUsage(AttributeTargets.Property)]
|
||||||
|
|
Loading…
Reference in New Issue