Browse Source

Add the ability for init injectors to themselves request injected parameters

pull/72/head
Anairkoen Schno 2 years ago
parent
commit
6d00fcd2b9
Signed by: DaNike GPG Key ID: BEFB74D5F3FC4387
2 changed files with 112 additions and 42 deletions
  1. +111
    -41
      IPA.Loader/Loader/PluginInitInjector.cs
  2. +1
    -1
      IPA.Loader/Loader/manifest.json

+ 111
- 41
IPA.Loader/Loader/PluginInitInjector.cs View File

@ -3,10 +3,8 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using IPA.Config;
using IPA.Logging;
using IPA.Utilities;
using System.Linq.Expressions;
using IPA.AntiMalware;
#if NET4
using Expression = System.Linq.Expressions.Expression;
@ -61,12 +59,53 @@ namespace IPA.Loader
/// <returns>the value to inject into that parameter.</returns>
public delegate object? InjectParameter(object? previous, ParameterInfo param, PluginMetadata meta);
/// <summary>
/// A provider for parameter injectors to request injected values themselves.
/// </summary>
/// <remarks>
/// Some injectors may look at attributes on the parameter to gain additional information about what it should provide.
/// If an injector wants to allow end users to affect the things it requests, it may pass the parameter it is currently
/// injecting for to this delegate along with a type override to select some other type.
/// </remarks>
/// <param name="forParam">the parameter that this is providing for.</param>
/// <param name="typeOverride">an optional override for the parameter type.</param>
/// <returns>the value that would otherwise be injected.</returns>
public delegate object? InjectedValueProvider(ParameterInfo forParam, Type? typeOverride = null);
/// <summary>
/// A typed injector for a plugin's Init method. When registered, called for all associated types. If it returns null, the default for the type will be used.
/// </summary>
/// <param name="previous">the previous return value of the function, or <see langword="null"/> if never called for plugin.</param>
/// <param name="param">the <see cref="ParameterInfo"/> of the parameter being injected.</param>
/// <param name="meta">the <see cref="PluginMetadata"/> for the plugin being loaded.</param>
/// <param name="provider">an <see cref="InjectedValueProvider"/> to allow the injector to request injected values.</param>
/// <returns>the value to inject into that parameter.</returns>
public delegate object? InjectParameterNested(object? previous, ParameterInfo param, PluginMetadata meta, InjectedValueProvider provider);
/// <summary>
/// Invokes the provider with <paramref name="param"/> and <typeparamref name="T"/> and casts the result to <typeparamref name="T"/>.
/// </summary>
/// <typeparam name="T">the type of object to be injected</typeparam>
/// <param name="provider">the provider to invoke.</param>
/// <param name="param">the parameter to provide for</param>
/// <returns>the value requested, or <see langword="null"/>.</returns>
public static T? Inject<T>(this InjectedValueProvider provider, ParameterInfo param)
=> (T?)provider?.Invoke(param, typeof(T));
/// <summary>
/// Adds an injector to be used when calling future plugins' Init methods.
/// </summary>
/// <param name="type">the type of the parameter.</param>
/// <param name="injector">the function to call for injection.</param>
public static void AddInjector(Type type, InjectParameter injector)
=> AddInjector(type, (pre, par, met, pro) => injector(pre, par, met));
/// <summary>
/// Adds an injector to be used when calling future plugins' Init methods.
/// </summary>
/// <param name="type">the type of the parameter.</param>
/// <param name="injector">the function to call for injection.</param>
public static void AddInjector(Type type, InjectParameterNested injector)
{
injectors.Add(new TypedInjector(type, injector));
}
@ -74,13 +113,13 @@ namespace IPA.Loader
private struct TypedInjector : IEquatable<TypedInjector>
{
public Type Type;
public InjectParameter Injector;
public InjectParameterNested Injector;
public TypedInjector(Type t, InjectParameter i)
public TypedInjector(Type t, InjectParameterNested i)
{ Type = t; Injector = i; }
public object? Inject(object? prev, ParameterInfo info, PluginMetadata meta)
=> Injector(prev, info, meta);
public object? Inject(object? prev, ParameterInfo info, PluginMetadata meta, InjectedValueProvider provider)
=> Injector(prev, info, meta, provider);
public bool Equals(TypedInjector other)
=> Type == other.Type && Injector == other.Injector;
@ -98,10 +137,10 @@ namespace IPA.Loader
private static readonly List<TypedInjector> injectors = new()
{
new TypedInjector(typeof(Logger), (prev, param, meta) => prev ?? new StandardLogger(meta.Name)),
new TypedInjector(typeof(PluginMetadata), (prev, param, meta) => prev ?? meta),
new TypedInjector(typeof(Config.Config), (prev, param, meta) => prev ?? Config.Config.GetConfigFor(meta.Name, param)),
new TypedInjector(typeof(IAntiMalware), (prev, param, meta) => prev ?? AntiMalwareEngine.Engine)
new TypedInjector(typeof(Logger), (prev, param, meta, _) => prev ?? new StandardLogger(meta.Name)),
new TypedInjector(typeof(PluginMetadata), (prev, param, meta, _) => prev ?? meta),
new TypedInjector(typeof(Config.Config), (prev, param, meta, _) => prev ?? Config.Config.GetConfigFor(meta.Name, param)),
new TypedInjector(typeof(IAntiMalware), (prev, param, meta, _) => prev ?? AntiMalwareEngine.Engine)
};
private static int? MatchPriority(Type target, Type source)
@ -138,52 +177,83 @@ namespace IPA.Loader
Expression.ArrayIndex(arr, Expression.Constant(i)), t))));
}
internal static object?[] Inject(ParameterInfo[] initParams, PluginMetadata meta, ref object? persist)
private static object? InjectForParameter(
Dictionary<TypedInjector, object?> previousValues,
PluginMetadata meta,
ParameterInfo param,
Type paramType,
InjectedValueProvider provider)
{
var initArgs = new List<object?>();
var value = paramType.GetDefault();
var toUse = injectors
.Select(i => (inject: i, priority: MatchPriority(paramType, i.Type))) // check match priority, combine it
.NonNull(t => t.priority) // filter null priorities
.Select(t => (t.inject, priority: t.priority!.Value)) // remove nullable
.OrderByDescending(t => t.priority) // sort by value
.Select(t => t.inject); // remove priority value
var previousValues = persist as Dictionary<TypedInjector, object?>;
if (previousValues == null)
// this tries injectors in order of closest match by type provided
foreach (var pair in toUse)
{
previousValues = new(injectors.Count);
persist = previousValues;
object? prev = null;
if (previousValues.ContainsKey(pair))
prev = previousValues[pair];
var val = pair.Inject(prev, param, meta, provider);
if (previousValues.ContainsKey(pair))
previousValues[pair] = val;
else
previousValues.Add(pair, val);
if (val == null) continue;
value = val;
break;
}
foreach (var param in initParams)
{
var paramType = param.ParameterType;
return value;
}
var value = paramType.GetDefault();
private class InjectedValueProviderWrapperImplementation
{
public Dictionary<TypedInjector, object?> PreviousValues { get; }
var toUse = injectors.Select(i => (inject: i, priority: MatchPriority(paramType, i.Type))) // check match priority, combine it
.NonNull(t => t.priority) // filter null priorities
.Select(t => (t.inject, priority: t.priority!.Value)) // remove nullable
.OrderByDescending(t => t.priority) // sort by value
.Select(t => t.inject); // remove priority value
public PluginMetadata Meta { get; }
// this tries injectors in order of closest match by type provided
foreach (var pair in toUse)
{
object? prev = null;
if (previousValues.ContainsKey(pair))
prev = previousValues[pair];
public InjectedValueProvider Provider { get; }
public InjectedValueProviderWrapperImplementation(PluginMetadata meta)
{
Meta = meta;
PreviousValues = new();
Provider = Inject;
}
var val = pair.Inject(prev, param, meta);
private object? Inject(ParameterInfo param, Type? typeOverride = null)
=> InjectForParameter(PreviousValues, Meta, param, typeOverride ?? param.ParameterType, Provider);
}
if (previousValues.ContainsKey(pair))
previousValues[pair] = val;
else
previousValues.Add(pair, val);
internal static object?[] Inject(ParameterInfo[] initParams, PluginMetadata meta, ref object? persist)
{
var initArgs = new List<object?>();
if (val == null) continue;
value = val;
break;
}
var impl = persist as InjectedValueProviderWrapperImplementation;
if (impl == null || impl.Meta != meta)
{
impl = new(meta);
persist = impl;
}
foreach (var param in initParams)
{
var paramType = param.ParameterType;
var value = InjectForParameter(impl.PreviousValues, meta, param, paramType, impl.Provider);
initArgs.Add(value);
}
//init.Invoke(instance, initArgs.ToArray());
return initArgs.ToArray();
}
}


+ 1
- 1
IPA.Loader/Loader/manifest.json View File

@ -8,7 +8,7 @@
"gameVersion": "1.16.1",
"id": "BSIPA",
"name": "Beat Saber IPA",
"version": "4.2.0-pre.4",
"version": "4.2.0-pre.5",
"icon": "IPA.icon_white.png",
"features": {
"IPA.DefineFeature": {


Loading…
Cancel
Save