From d28cfdb4e89714b29cc8d25b84914593b7b7caff Mon Sep 17 00:00:00 2001 From: Anairkoen Schno Date: Mon, 2 Dec 2019 18:48:11 -0600 Subject: [PATCH] Updated Injector to inject based on closest match, instead of first assignable --- IPA.Loader/Loader/PluginInitInjector.cs | 74 +++++++++++++++++++++---- 1 file changed, 64 insertions(+), 10 deletions(-) diff --git a/IPA.Loader/Loader/PluginInitInjector.cs b/IPA.Loader/Loader/PluginInitInjector.cs index 552f815b..c3dda102 100644 --- a/IPA.Loader/Loader/PluginInitInjector.cs +++ b/IPA.Loader/Loader/PluginInitInjector.cs @@ -33,17 +33,42 @@ namespace IPA.Loader /// the function to call for injection. public static void AddInjector(Type type, InjectParameter injector) { - injectors.Add(Tuple.Create(type, injector)); + injectors.Add(new TypedInjector(type, injector)); } - private static readonly List> injectors = new List> + private struct TypedInjector : IEquatable { - new Tuple(typeof(Logger), (prev, param, meta) => prev ?? new StandardLogger(meta.Name)), + public Type Type; + public InjectParameter Injector; + + public TypedInjector(Type t, InjectParameter i) + { Type = t; Injector = i; } + + public object Inject(object prev, ParameterInfo info, PluginLoader.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 injectors = new List + { + new TypedInjector(typeof(Logger), (prev, param, meta) => prev ?? new StandardLogger(meta.Name)), #pragma warning disable CS0618 // Type or member is obsolete - new Tuple(typeof(IModPrefs), (prev, param, meta) => prev ?? new ModPrefs(meta)), + new TypedInjector(typeof(IModPrefs), (prev, param, meta) => prev ?? new ModPrefs(meta)), #pragma warning restore CS0618 // Type or member is obsolete - new Tuple(typeof(PluginLoader.PluginMetadata), (prev, param, meta) => prev ?? meta), - new Tuple(typeof(IConfigProvider), (prev, param, meta) => + new TypedInjector(typeof(PluginLoader.PluginMetadata), (prev, param, meta) => prev ?? meta), + new TypedInjector(typeof(IConfigProvider), (prev, param, meta) => { if (prev != null) return prev; var cfgProvider = Config.Config.GetProviderFor(meta.Name, param); @@ -52,6 +77,28 @@ namespace IPA.Loader }) }; + 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; + } + } + } + internal static void Inject(MethodInfo init, PluginLoader.PluginInfo info) { var instance = info.Plugin; @@ -60,21 +107,28 @@ namespace IPA.Loader var initArgs = new List(); var initParams = init.GetParameters(); - var previousValues = new Dictionary, object>(injectors.Count); + var previousValues = new Dictionary(injectors.Count); foreach (var param in initParams) { var paramType = param.ParameterType; var value = paramType.GetDefault(); - // TODO: make this work on closest match - foreach (var pair in injectors.Where(t => paramType.IsAssignableFrom(t.Item1))) + + 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.Item2?.Invoke(prev, param, meta); + var val = pair.Inject(prev, param, meta); if (previousValues.ContainsKey(pair)) previousValues[pair] = val;