Refactor `ServiceInjector`

This commit is contained in:
YoshiRulz 2023-04-23 16:29:19 +10:00
parent c5d6a66e01
commit 70bd081a93
No known key found for this signature in database
GPG Key ID: C4DE31C245353FB7
4 changed files with 60 additions and 19 deletions

View File

@ -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;
}));
}

View File

@ -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<LuaLibraryAttribute>(inherit: false)?.Released is not false)
{
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
// 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();
}
}

View File

@ -123,7 +123,7 @@ namespace BizHawk.Client.EmuHawk
if (!(CreateInstance<T>(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

View File

@ -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
/// </summary>
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>
/// Feeds the target its required services.
/// </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>
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();
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.
/// </summary>
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)]