Browse Source

Added the rest of the code to generate delegates for PluginExecutor

4.0.0-beta
Anairkoen Schno 4 years ago
parent
commit
154f22292e
1 changed files with 131 additions and 39 deletions
  1. +131
    -39
      IPA.Loader/Loader/PluginLoader.cs

+ 131
- 39
IPA.Loader/Loader/PluginLoader.cs View File

@ -135,6 +135,7 @@ namespace IPA.Loader
public PluginExecutor(PluginMetadata meta)
{
Metadata = meta;
PrepareDelegates();
}
@ -159,51 +160,142 @@ namespace IPA.Loader
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();
CreatePlugin = MakeCreateFunc(type, Metadata.Name);
LifecycleEnable = MakeLifecycleEnableFunc(type, Metadata.Name);
LifecycleDisable = MakeLifecycleDisableFunc(type, Metadata.Name);
}
private static Func<PluginMetadata, object> MakeCreateFunc(Type type, string name)
{ // 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 {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)
{ // 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]");
}
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.");
}
// TODO: how do I make this work for .NET 3? FEC.LightExpression but hacked to work on .NET 3?
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(Expression.Convert(objVar, typeof(object)))),
metaParam);
// TODO: since this new system will be doing a fuck load of compilation, maybe add FastExpressionCompiler
return createExpr.Compile();
}
private static Action<object> MakeLifecycleEnableFunc(Type type, string name)
{
var enableMethods = type.GetMethods(BindingFlags.Public | BindingFlags.Instance)
.Select(m => (m, attrs: m.GetCustomAttributes(typeof(IEdgeLifecycleAttribute), false)))
.Select(t => (t.m, attrs: t.attrs.Cast<IEdgeLifecycleAttribute>()))
.Where(t => t.attrs.Any(a => a.Type == EdgeLifecycleType.Enable))
.Select(t => t.m).ToArray();
if (enableMethods.Length == 0)
{
Logger.loader.Notice($"Plugin {name} has no methods marked [OnStart] or [OnEnable]. Is this intentional?");
return o => { };
}
foreach (var m in enableMethods)
{
if (m.GetParameters().Length > 0)
throw new InvalidOperationException($"Method {m} on {type.FullName} is marked [OnStart] or [OnEnable] and has parameters.");
if (m.ReturnType != typeof(void))
Logger.loader.Warn($"Method {m} on {type.FullName} is marked [OnStart] or [OnEnable] and returns a value. It will be ignored.");
}
var objParam = Expression.Parameter(typeof(object));
var instVar = Expression.Variable(type);
var createExpr = Expression.Lambda<Action<object>>(
Expression.Block(
enableMethods
.Select(m => Expression.Call(instVar, m))
.Prepend<Expression>(Expression.Assign(instVar, Expression.Convert(objParam, type)))),
objParam);
return createExpr.Compile();
}
private static Func<object, Task> MakeLifecycleDisableFunc(Type type, string name)
{
var disableMethods = type.GetMethods(BindingFlags.Public | BindingFlags.Instance)
.Select(m => (m, attrs: m.GetCustomAttributes(typeof(IEdgeLifecycleAttribute), false)))
.Select(t => (t.m, attrs: t.attrs.Cast<IEdgeLifecycleAttribute>()))
.Where(t => t.attrs.Any(a => a.Type == EdgeLifecycleType.Disable))
.Select(t => t.m).ToArray();
if (disableMethods.Length == 0)
{
Logger.loader.Notice($"Plugin {name} has no methods marked [OnExit] or [OnDisable]. Is this intentional?");
return o => Task.CompletedTask;
}
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 taskMethods = new List<MethodInfo>();
var nonTaskMethods = new List<MethodInfo>();
foreach (var m in disableMethods)
{
if (m.GetParameters().Length > 0)
throw new InvalidOperationException($"Method {m} on {type.FullName} is marked [OnExit] or [OnDisable] and has parameters.");
if (m.ReturnType != typeof(void))
{
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.");
if (typeof(Task).IsAssignableFrom(m.ReturnType))
{
taskMethods.Add(m);
continue;
}
else
Logger.loader.Warn($"Method {m} on {type.FullName} is marked [OnExit] or [OnDisable] and returns a non-Task value. It will be ignored.");
}
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();
nonTaskMethods.Add(m);
}
Expression<Func<Task>> completedTaskDel = () => Task.CompletedTask;
var getCompletedTask = completedTaskDel.Body;
var taskWhenAll = typeof(Task).GetMethod(nameof(Task.WhenAll), BindingFlags.Public | BindingFlags.Static);
var objParam = Expression.Parameter(typeof(object));
var instVar = Expression.Variable(type);
var createExpr = Expression.Lambda<Func<object, Task>>(
Expression.Block(
nonTaskMethods
.Select(m => Expression.Call(instVar, m))
.Prepend<Expression>(Expression.Assign(instVar, Expression.Convert(objParam, type)))
.Append(
taskMethods.Count == 0
? getCompletedTask
: Expression.Call(taskWhenAll,
Expression.NewArrayInit(typeof(Task),
taskMethods.Select(m =>
Expression.Convert(Expression.Call(instVar, m), typeof(Task))))))),
objParam);
return createExpr.Compile();
}
}


Loading…
Cancel
Save