|
|
- 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;
- #if NET4
- using Expression = System.Linq.Expressions.Expression;
- using ExpressionEx = System.Linq.Expressions.Expression;
- #endif
- #if NET3
- using Net3_Proxy;
- #endif
-
- namespace IPA.Loader
- {
- /// <summary>
- /// The type that handles value injecting into a plugin's initialization methods.
- /// </summary>
- /// <remarks>
- /// The default injectors and what they provide are shown in this table.
- /// <list type="table">
- /// <listheader>
- /// <term>Parameter Type</term>
- /// <description>Injected Value</description>
- /// </listheader>
- /// <item>
- /// <term><see cref="Logger"/></term>
- /// <description>A <see cref="StandardLogger"/> specialized for the plugin being injected</description>
- /// </item>
- /// <item>
- /// <term><see cref="PluginMetadata"/></term>
- /// <description>The <see cref="PluginMetadata"/> of the plugin being injected</description>
- /// </item>
- /// <item>
- /// <term><see cref="Config.Config"/></term>
- /// <description>
- /// <para>A <see cref="Config.Config"/> object for the plugin being injected.</para>
- /// <para>
- /// These parameters may have <see cref="Config.Config.NameAttribute"/> and <see cref="Config.Config.PreferAttribute"/> to control
- /// how it is constructed.
- /// </para>
- /// </description>
- /// </item>
- /// </list>
- /// For all of the default injectors, only one of each will be generated, and any later parameters will recieve the same value as the first one.
- /// </remarks>
- public static class PluginInitInjector
- {
-
- /// <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>
- /// <returns>the value to inject into that parameter.</returns>
- public delegate object InjectParameter(object previous, ParameterInfo param, PluginMetadata meta);
-
- /// <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)
- {
- injectors.Add(new TypedInjector(type, injector));
- }
-
- private struct TypedInjector : IEquatable<TypedInjector>
- {
- public Type Type;
- public InjectParameter Injector;
-
- public TypedInjector(Type t, InjectParameter i)
- { Type = t; Injector = i; }
-
- public object Inject(object prev, ParameterInfo info, PluginMetadata meta)
- => Injector(prev, info, meta);
-
- public bool Equals(TypedInjector other)
- => Type == other.Type && Injector == other.Injector;
-
- public override bool Equals(object obj)
- => obj is TypedInjector i && Equals(i);
-
-
- public override int GetHashCode()
- => Type.GetHashCode() ^ Injector.GetHashCode();
-
- public static bool operator ==(TypedInjector a, TypedInjector b) => a.Equals(b);
- public static bool operator !=(TypedInjector a, TypedInjector b) => !a.Equals(b);
- }
-
- private static readonly List<TypedInjector> injectors = new List<TypedInjector>
- {
- 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) =>
- {
- if (prev != null) return prev;
- return Config.Config.GetConfigFor(meta.Name, param);
- })
- };
-
- private static int? MatchPriority(Type target, Type source)
- {
- if (target == source) return int.MaxValue;
- if (!target.IsAssignableFrom(source)) return null;
- if (!target.IsInterface && !source.IsSubclassOf(target)) return int.MinValue;
-
- int value = 0;
- while (true)
- {
- if (source == null) return value;
- if (target.IsInterface && source.GetInterfaces().Contains(target))
- return value;
- else if (target == source)
- return value;
- else
- {
- value--; // lower priority
- source = source.BaseType;
- }
- }
- }
-
- private static readonly MethodInfo InjectMethod = typeof(PluginInitInjector).GetMethod(nameof(Inject), BindingFlags.NonPublic | BindingFlags.Static);
- internal static Expression InjectedCallExpr(ParameterInfo[] initParams, Expression meta, Expression persistVar, Func<IEnumerable<Expression>, Expression> exprGen)
- {
- var arr = ExpressionEx.Variable(typeof(object[]), "initArr");
- return ExpressionEx.Block(new[] { arr },
- ExpressionEx.Assign(arr, Expression.Call(InjectMethod, Expression.Constant(initParams), meta, persistVar)),
- exprGen(initParams
- .Select(p => p.ParameterType)
- .Select((t, i) => (Expression)Expression.Convert(
- Expression.ArrayIndex(arr, Expression.Constant(i)), t))));
- }
-
- internal static object[] Inject(ParameterInfo[] initParams, PluginMetadata meta, ref object persist)
- {
- var initArgs = new List<object>();
-
- var previousValues = persist as Dictionary<TypedInjector, object>;
- if (previousValues == null)
- {
- previousValues = new Dictionary<TypedInjector, object>(injectors.Count);
- persist = previousValues;
- }
-
- foreach (var param in initParams)
- {
- var paramType = param.ParameterType;
-
- var value = paramType.GetDefault();
-
- var toUse = injectors.Select(i => (inject: i, priority: MatchPriority(paramType, i.Type))) // check match priority, combine it
- .Where(t => t.priority != null) // 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
-
- // 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];
-
- var val = pair.Inject(prev, param, meta);
-
- if (previousValues.ContainsKey(pair))
- previousValues[pair] = val;
- else
- previousValues.Add(pair, val);
-
- if (val == null) continue;
- value = val;
- break;
- }
-
- initArgs.Add(value);
- }
-
- //init.Invoke(instance, initArgs.ToArray());
- return initArgs.ToArray();
- }
- }
- }
|