Browse Source

Added new PluginExecutor type for new attribute-based system

pull/46/head
Anairkoen Schno 4 years ago
parent
commit
4343431eed
3 changed files with 202 additions and 92 deletions
  1. +15
    -5
      IPA.Loader/Loader/PluginInitInjector.cs
  2. +82
    -12
      IPA.Loader/Loader/PluginLoader.cs
  3. +105
    -75
      IPA.Loader/Utilities/EnumerableExtensions.cs

+ 15
- 5
IPA.Loader/Loader/PluginInitInjector.cs View File

@ -5,6 +5,7 @@ using System.Reflection;
using IPA.Config; using IPA.Config;
using IPA.Logging; using IPA.Logging;
using IPA.Utilities; using IPA.Utilities;
using System.Linq.Expressions;
#if NET3 #if NET3
using Net3_Proxy; using Net3_Proxy;
#endif #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<IEnumerable<Expression>, 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<object>(); var initArgs = new List<object>();
var initParams = init.GetParameters();
var previousValues = new Dictionary<TypedInjector, object>(injectors.Count); var previousValues = new Dictionary<TypedInjector, object>(injectors.Count);
@ -138,7 +147,8 @@ namespace IPA.Loader
initArgs.Add(value); initArgs.Add(value);
} }
init.Invoke(instance, initArgs.ToArray());
//init.Invoke(instance, initArgs.ToArray());
return initArgs.ToArray();
} }
} }
} }

+ 82
- 12
IPA.Loader/Loader/PluginLoader.cs View File

@ -13,6 +13,7 @@ using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using Version = SemVer.Version; using Version = SemVer.Version;
using SemVer; using SemVer;
using System.Linq.Expressions;
#if NET4 #if NET4
using Task = System.Threading.Tasks.Task; using Task = System.Threading.Tasks.Task;
using TaskEx = 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)}'"; 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<PluginMetadata, object> CreatePlugin { get; set; }
private Action<object> LifecycleEnable { get; set; }
// disable may be async (#24)
private Func<object, Task> 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<InitAttribute>()))
.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<InitAttribute>()))
.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<Func<PluginMetadata, object>>(
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();
}
}
}
/// <summary> /// <summary>
/// A container object for all the data relating to a plugin. /// A container object for all the data relating to a plugin.
/// </summary> /// </summary>
[Obsolete("No longer useful as a construct")]
public class PluginInfo public class PluginInfo
{ {
internal IPlugin Plugin { get; set; } internal IPlugin Plugin { get; set; }
@ -788,7 +868,8 @@ namespace IPA.Loader
return null; return null;
} }
PluginInitInjector.Inject(init, info);
var args = PluginInitInjector.Inject(init.GetParameters(), meta);
init.Invoke(info.Plugin, args);
} }
foreach (var feature in meta.Features) foreach (var feature in meta.Features)
@ -800,17 +881,6 @@ namespace IPA.Loader
{ {
Logger.loader.Critical($"Feature errored in {nameof(Feature.AfterInit)}: {e}"); 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) catch (AmbiguousMatchException)
{ {


+ 105
- 75
IPA.Loader/Utilities/EnumerableExtensions.cs View File

@ -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
{
/// <summary>
/// Extensions for <see cref="IEnumerable{T}"/> that don't currently exist in <c>System.Linq</c>.
/// </summary>
public static class EnumerableExtensions
{
/// <summary>
/// Adds a value to the beginning of the sequence.
/// </summary>
/// <typeparam name="T">the type of the elements of <paramref name="seq"/></typeparam>
/// <param name="seq">a sequence of values</param>
/// <param name="prep">the value to prepend to <paramref name="seq"/></param>
/// <returns>a new sequence beginning with <paramref name="prep"/></returns>
public static IEnumerable<T> Prepend<T>(this IEnumerable<T> seq, T prep)
=> new PrependEnumerable<T>(seq, prep);
private sealed class PrependEnumerable<T> : IEnumerable<T>
{
private readonly IEnumerable<T> rest;
private readonly T first;
public PrependEnumerable(IEnumerable<T> rest, T first)
{
this.rest = rest;
this.first = first;
}
public IEnumerator<T> GetEnumerator()
{
yield return first;
foreach (var v in rest) yield return v;
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
/// <summary>
/// LINQ extension method that filters <see langword="null"/> elements out of an enumeration.
/// </summary>
/// <typeparam name="T">the type of the enumeration</typeparam>
/// <param name="self">the enumeration to filter</param>
/// <returns>a filtered enumerable</returns>
public static IEnumerable<T> NonNull<T>(this IEnumerable<T> self) where T : class
=> self.Where(o => o != null);
/// <summary>
/// LINQ extension method that filters <see langword="null"/> elements out of an enumeration based on a converter.
/// </summary>
/// <typeparam name="T">the type of the enumeration</typeparam>
/// <typeparam name="U">the type to compare to null</typeparam>
/// <param name="self">the enumeration to filter</param>
/// <param name="pred">the predicate to select for filtering</param>
/// <returns>a filtered enumerable</returns>
public static IEnumerable<T> NonNull<T, U>(this IEnumerable<T> self, Func<T, U> pred) where T : class where U : class
=> self.Where(o => pred(o) != null);
/// <summary>
/// LINQ extension method that filters <see langword="null"/> elements from an enumeration of nullable types.
/// </summary>
/// <typeparam name="T">the underlying type of the nullable enumeration</typeparam>
/// <param name="self">the enumeration to filter</param>
/// <returns>a filtered enumerable</returns>
public static IEnumerable<T> NonNull<T>(this IEnumerable<T?> 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
{
/// <summary>
/// Extensions for <see cref="IEnumerable{T}"/> that don't currently exist in <c>System.Linq</c>.
/// </summary>
public static class EnumerableExtensions
{
/// <summary>
/// Adds a value to the beginning of the sequence.
/// </summary>
/// <typeparam name="T">the type of the elements of <paramref name="seq"/></typeparam>
/// <param name="seq">a sequence of values</param>
/// <param name="prep">the value to prepend to <paramref name="seq"/></param>
/// <returns>a new sequence beginning with <paramref name="prep"/></returns>
public static IEnumerable<T> Prepend<T>(this IEnumerable<T> seq, T prep)
=> new PrependEnumerable<T>(seq, prep);
private sealed class PrependEnumerable<T> : IEnumerable<T>
{
private readonly IEnumerable<T> rest;
private readonly T first;
public PrependEnumerable(IEnumerable<T> rest, T first)
{
this.rest = rest;
this.first = first;
}
public IEnumerator<T> GetEnumerator()
{ // TODO: a custom impl that is less garbage
yield return first;
foreach (var v in rest) yield return v;
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
/// <summary>
/// Adds a value to the end of the sequence.
/// </summary>
/// <typeparam name="T">the type of the elements of <paramref name="seq"/></typeparam>
/// <param name="seq">a sequence of values</param>
/// <param name="app">the value to append to <paramref name="seq"/></param>
/// <returns>a new sequence ending with <paramref name="app"/></returns>
public static IEnumerable<T> Append<T>(this IEnumerable<T> seq, T app)
=> new AppendEnumerable<T>(seq, app);
private sealed class AppendEnumerable<T> : IEnumerable<T>
{
private readonly IEnumerable<T> rest;
private readonly T last;
public AppendEnumerable(IEnumerable<T> rest, T last)
{
this.rest = rest;
this.last = last;
}
public IEnumerator<T> GetEnumerator()
{ // TODO: a custom impl that is less garbage
foreach (var v in rest) yield return v;
yield return last;
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
/// <summary>
/// LINQ extension method that filters <see langword="null"/> elements out of an enumeration.
/// </summary>
/// <typeparam name="T">the type of the enumeration</typeparam>
/// <param name="self">the enumeration to filter</param>
/// <returns>a filtered enumerable</returns>
public static IEnumerable<T> NonNull<T>(this IEnumerable<T> self) where T : class
=> self.Where(o => o != null);
/// <summary>
/// LINQ extension method that filters <see langword="null"/> elements out of an enumeration based on a converter.
/// </summary>
/// <typeparam name="T">the type of the enumeration</typeparam>
/// <typeparam name="U">the type to compare to null</typeparam>
/// <param name="self">the enumeration to filter</param>
/// <param name="pred">the predicate to select for filtering</param>
/// <returns>a filtered enumerable</returns>
public static IEnumerable<T> NonNull<T, U>(this IEnumerable<T> self, Func<T, U> pred) where U : class
=> self.Where(o => pred(o) != null);
/// <summary>
/// LINQ extension method that filters <see langword="null"/> elements from an enumeration of nullable types.
/// </summary>
/// <typeparam name="T">the underlying type of the nullable enumeration</typeparam>
/// <param name="self">the enumeration to filter</param>
/// <returns>a filtered enumerable</returns>
public static IEnumerable<T> NonNull<T>(this IEnumerable<T?> self) where T : struct
=> self.Where(o => o != null).Select(o => o.Value);
}
}

Loading…
Cancel
Save