From 08221f4382ffe645c7faf62de97d428d19fbf86d Mon Sep 17 00:00:00 2001 From: Anairkoen Schno Date: Sat, 4 Jan 2020 04:26:46 -0600 Subject: [PATCH] Added new PluginExecutor type for new attribute-based system --- IPA.Loader/Loader/PluginInitInjector.cs | 20 ++- IPA.Loader/Loader/PluginLoader.cs | 94 ++++++++-- IPA.Loader/Utilities/EnumerableExtensions.cs | 180 +++++++++++-------- 3 files changed, 202 insertions(+), 92 deletions(-) diff --git a/IPA.Loader/Loader/PluginInitInjector.cs b/IPA.Loader/Loader/PluginInitInjector.cs index 384a2ac3..d8d5d629 100644 --- a/IPA.Loader/Loader/PluginInitInjector.cs +++ b/IPA.Loader/Loader/PluginInitInjector.cs @@ -5,6 +5,7 @@ using System.Reflection; using IPA.Config; using IPA.Logging; using IPA.Utilities; +using System.Linq.Expressions; #if NET3 using Net3_Proxy; #endif @@ -94,13 +95,21 @@ namespace IPA.Loader } } - internal static void Inject(MethodInfo init, PluginLoader.PluginInfo info) + private static readonly MethodInfo InjectMethod = typeof(PluginInitInjector).GetMethod(nameof(Inject), BindingFlags.NonPublic | BindingFlags.Static); + internal static Expression InjectedCallExpr(ParameterInfo[] initParams, Expression meta, Func, Expression> exprGen) { - var instance = info.Plugin; - var meta = info.Metadata; + var arr = Expression.Variable(typeof(object[])); + return Expression.Block( + Expression.Assign(arr, Expression.Call(InjectMethod, Expression.Constant(initParams), meta)), + exprGen(initParams + .Select(p => p.ParameterType) + .Select((t, i) => Expression.Convert( + Expression.ArrayIndex(arr, Expression.Constant(i)), t)))); + } + internal static object[] Inject(ParameterInfo[] initParams, PluginLoader.PluginMetadata meta) + { var initArgs = new List(); - var initParams = init.GetParameters(); var previousValues = new Dictionary(injectors.Count); @@ -138,7 +147,8 @@ namespace IPA.Loader initArgs.Add(value); } - init.Invoke(instance, initArgs.ToArray()); + //init.Invoke(instance, initArgs.ToArray()); + return initArgs.ToArray(); } } } diff --git a/IPA.Loader/Loader/PluginLoader.cs b/IPA.Loader/Loader/PluginLoader.cs index b1b6ca11..96355e3d 100644 --- a/IPA.Loader/Loader/PluginLoader.cs +++ b/IPA.Loader/Loader/PluginLoader.cs @@ -13,6 +13,7 @@ using System.Text.RegularExpressions; using System.Threading.Tasks; using Version = SemVer.Version; using SemVer; +using System.Linq.Expressions; #if NET4 using Task = System.Threading.Tasks.Task; using TaskEx = System.Threading.Tasks.Task; @@ -128,9 +129,88 @@ namespace IPA.Loader public override string ToString() => $"{Name}({Id}@{Version})({PluginType?.FullName}) from '{Utils.GetRelativePath(File?.FullName, BeatSaber.InstallPath)}'"; } + internal class PluginExecutor + { + public PluginMetadata Metadata { get; } + public PluginExecutor(PluginMetadata meta) + { + Metadata = meta; + } + + + private object pluginObject = null; + private Func CreatePlugin { get; set; } + private Action LifecycleEnable { get; set; } + // disable may be async (#24) + private Func LifecycleDisable { get; set; } + + public void Create() + { + if (pluginObject != null) return; + pluginObject = CreatePlugin(Metadata); + } + + public void Enable() => LifecycleEnable(pluginObject); + public Task Disable() => LifecycleDisable(pluginObject); + + + private void PrepareDelegates() + { // TODO: use custom exception types or something + Load(Metadata); + var type = Metadata.Assembly.GetType(Metadata.PluginType.FullName); + + { // TODO: what do i want the visibiliy of Init methods to be? + var ctors = type.GetConstructors(BindingFlags.Public | BindingFlags.Instance) + .Select(c => (c, attr: c.GetCustomAttribute())) + .NonNull(t => t.attr) + .OrderByDescending(t => t.c.GetParameters().Length) + .Select(t => t.c).ToArray(); + if (ctors.Length > 1) + Logger.loader.Warn($"Plugin {Metadata.Name} has multiple [Init] constructors. Picking the one with the most parameters."); + + bool usingDefaultCtor = false; + var ctor = ctors.FirstOrDefault(); + if (ctor == null) + { // this is a normal case + usingDefaultCtor = true; + ctor = type.GetConstructor(Type.EmptyTypes); + if (ctor == null) + throw new InvalidOperationException($"{type.FullName} does not expose a public default constructor and has no constructors marked [Init]"); + } + + var initMethods = type.GetMethods(BindingFlags.Public | BindingFlags.Instance) + .Select(m => (m, attr:m.GetCustomAttribute())) + .NonNull(t => t.attr).Select(t => t.m).ToArray(); + // verify that they don't have lifecycle attributes on them + foreach (var method in initMethods) + { + var attrs = method.GetCustomAttributes(typeof(IEdgeLifecycleAttribute), false); + if (attrs.Length != 0) + throw new InvalidOperationException($"Method {method} on {type.FullName} has both an [Init] attribute and a lifecycle attribute."); + } + + var metaParam = Expression.Parameter(typeof(PluginMetadata)); + var objVar = Expression.Variable(type); + var createExpr = Expression.Lambda>( + Expression.Block( + initMethods + .Select(m => PluginInitInjector.InjectedCallExpr(m.GetParameters(), metaParam, es => Expression.Call(objVar, m, es))) + .Prepend(Expression.Assign(objVar, + usingDefaultCtor + ? Expression.New(ctor) + : PluginInitInjector.InjectedCallExpr(ctor.GetParameters(), metaParam, es => Expression.New(ctor, es)))) + .Append(objVar)), + metaParam); + // TODO: since this new system will be doing a fuck load of compilation, maybe add FastExpressionCompiler + CreatePlugin = createExpr.Compile(); + } + } + } + /// /// A container object for all the data relating to a plugin. /// + [Obsolete("No longer useful as a construct")] public class PluginInfo { internal IPlugin Plugin { get; set; } @@ -788,7 +868,8 @@ namespace IPA.Loader return null; } - PluginInitInjector.Inject(init, info); + var args = PluginInitInjector.Inject(init.GetParameters(), meta); + init.Invoke(info.Plugin, args); } foreach (var feature in meta.Features) @@ -800,17 +881,6 @@ namespace IPA.Loader { Logger.loader.Critical($"Feature errored in {nameof(Feature.AfterInit)}: {e}"); } - - /*try // TODO: move this out to after all plugins have been inited - { - instance.OnEnable(); - } - catch (Exception e) - { - Logger.loader.Error($"Error occurred trying to enable {meta.Name}"); - Logger.loader.Error(e); - return null; // is enable failure a full load failure? - }*/ } catch (AmbiguousMatchException) { diff --git a/IPA.Loader/Utilities/EnumerableExtensions.cs b/IPA.Loader/Utilities/EnumerableExtensions.cs index f9220e89..2a3d14e6 100644 --- a/IPA.Loader/Utilities/EnumerableExtensions.cs +++ b/IPA.Loader/Utilities/EnumerableExtensions.cs @@ -1,75 +1,105 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace IPA.Utilities -{ - /// - /// Extensions for that don't currently exist in System.Linq. - /// - public static class EnumerableExtensions - { - /// - /// Adds a value to the beginning of the sequence. - /// - /// the type of the elements of - /// a sequence of values - /// the value to prepend to - /// a new sequence beginning with - public static IEnumerable Prepend(this IEnumerable seq, T prep) - => new PrependEnumerable(seq, prep); - - private sealed class PrependEnumerable : IEnumerable - { - private readonly IEnumerable rest; - private readonly T first; - - public PrependEnumerable(IEnumerable rest, T first) - { - this.rest = rest; - this.first = first; - } - - public IEnumerator GetEnumerator() - { - yield return first; - foreach (var v in rest) yield return v; - } - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - } - - /// - /// LINQ extension method that filters elements out of an enumeration. - /// - /// the type of the enumeration - /// the enumeration to filter - /// a filtered enumerable - public static IEnumerable NonNull(this IEnumerable self) where T : class - => self.Where(o => o != null); - - /// - /// LINQ extension method that filters elements out of an enumeration based on a converter. - /// - /// the type of the enumeration - /// the type to compare to null - /// the enumeration to filter - /// the predicate to select for filtering - /// a filtered enumerable - public static IEnumerable NonNull(this IEnumerable self, Func pred) where T : class where U : class - => self.Where(o => pred(o) != null); - - /// - /// LINQ extension method that filters elements from an enumeration of nullable types. - /// - /// the underlying type of the nullable enumeration - /// the enumeration to filter - /// a filtered enumerable - public static IEnumerable NonNull(this IEnumerable self) where T : struct - => self.Where(o => o != null).Select(o => o.Value); - - } -} +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace IPA.Utilities +{ + /// + /// Extensions for that don't currently exist in System.Linq. + /// + public static class EnumerableExtensions + { + /// + /// Adds a value to the beginning of the sequence. + /// + /// the type of the elements of + /// a sequence of values + /// the value to prepend to + /// a new sequence beginning with + public static IEnumerable Prepend(this IEnumerable seq, T prep) + => new PrependEnumerable(seq, prep); + + private sealed class PrependEnumerable : IEnumerable + { + private readonly IEnumerable rest; + private readonly T first; + + public PrependEnumerable(IEnumerable rest, T first) + { + this.rest = rest; + this.first = first; + } + + public IEnumerator GetEnumerator() + { // TODO: a custom impl that is less garbage + yield return first; + foreach (var v in rest) yield return v; + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } + + /// + /// Adds a value to the end of the sequence. + /// + /// the type of the elements of + /// a sequence of values + /// the value to append to + /// a new sequence ending with + public static IEnumerable Append(this IEnumerable seq, T app) + => new AppendEnumerable(seq, app); + + private sealed class AppendEnumerable : IEnumerable + { + private readonly IEnumerable rest; + private readonly T last; + + public AppendEnumerable(IEnumerable rest, T last) + { + this.rest = rest; + this.last = last; + } + + public IEnumerator GetEnumerator() + { // TODO: a custom impl that is less garbage + foreach (var v in rest) yield return v; + yield return last; + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } + + /// + /// LINQ extension method that filters elements out of an enumeration. + /// + /// the type of the enumeration + /// the enumeration to filter + /// a filtered enumerable + public static IEnumerable NonNull(this IEnumerable self) where T : class + => self.Where(o => o != null); + + /// + /// LINQ extension method that filters elements out of an enumeration based on a converter. + /// + /// the type of the enumeration + /// the type to compare to null + /// the enumeration to filter + /// the predicate to select for filtering + /// a filtered enumerable + public static IEnumerable NonNull(this IEnumerable self, Func pred) where U : class + => self.Where(o => pred(o) != null); + + /// + /// LINQ extension method that filters elements from an enumeration of nullable types. + /// + /// the underlying type of the nullable enumeration + /// the enumeration to filter + /// a filtered enumerable + public static IEnumerable NonNull(this IEnumerable self) where T : struct + => self.Where(o => o != null).Select(o => o.Value); + + } +}