From 4148b2729f29f997e87ae03eec7d5e5ff7cb163a Mon Sep 17 00:00:00 2001 From: Meivyn <793322+Meivyn@users.noreply.github.com> Date: Mon, 8 Jan 2024 20:53:22 -0500 Subject: [PATCH] Implement async `OnEnable`/`OnDisable` --- .../Loader/Composite/CompositeBSPlugin.cs | 15 ++++-- IPA.Loader/Loader/PluginExecutor.cs | 50 +++++++++++++------ 2 files changed, 47 insertions(+), 18 deletions(-) diff --git a/IPA.Loader/Loader/Composite/CompositeBSPlugin.cs b/IPA.Loader/Loader/Composite/CompositeBSPlugin.cs index 883ad7c4..b5a03829 100644 --- a/IPA.Loader/Loader/Composite/CompositeBSPlugin.cs +++ b/IPA.Loader/Loader/Composite/CompositeBSPlugin.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; using UnityEngine.SceneManagement; using Logger = IPA.Logging.Logger; @@ -10,9 +12,9 @@ namespace IPA.Loader.Composite { private readonly IEnumerable plugins; - private delegate void CompositeCall(PluginExecutor plugin); - - public CompositeBSPlugin(IEnumerable plugins) + private delegate Task CompositeCall(PluginExecutor plugin); + + public CompositeBSPlugin(IEnumerable plugins) { this.plugins = plugins; } @@ -23,7 +25,12 @@ namespace IPA.Loader.Composite try { if (plugin != null) - callback(plugin); + { + callback(plugin).ContinueWith(t => + { + Logger.Default.Error($"{plugin.Metadata.Name} {method}: {t.Exception!.InnerException}"); + }, CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.Default); + } } catch (Exception ex) { diff --git a/IPA.Loader/Loader/PluginExecutor.cs b/IPA.Loader/Loader/PluginExecutor.cs index 307d4557..976b4281 100644 --- a/IPA.Loader/Loader/PluginExecutor.cs +++ b/IPA.Loader/Loader/PluginExecutor.cs @@ -38,7 +38,7 @@ namespace IPA.Loader if (specialType != Special.None) { CreatePlugin = m => null; - LifecycleEnable = o => { }; + LifecycleEnable = o => TaskEx.WhenAll(); LifecycleDisable = o => TaskEx.WhenAll(); } else @@ -48,8 +48,7 @@ namespace IPA.Loader public object Instance { get; private set; } = null; private Func CreatePlugin { get; set; } - private Action LifecycleEnable { get; set; } - // disable may be async (#24) + private Func LifecycleEnable { get; set; } private Func LifecycleDisable { get; set; } public void Create() @@ -58,7 +57,7 @@ namespace IPA.Loader Instance = CreatePlugin(Metadata); } - public void Enable() => LifecycleEnable(Instance); + public Task Enable() => LifecycleEnable(Instance); public Task Disable() => LifecycleDisable(Instance); @@ -120,7 +119,7 @@ namespace IPA.Loader return createExpr.Compile(); } // TODO: make enable and disable able to take a bool indicating which it is - private static Action MakeLifecycleEnableFunc(Type type, string name) + private static Func MakeLifecycleEnableFunc(Type type, string name) { var noEnableDisable = type.GetCustomAttribute() is not null; var enableMethods = type.GetMethods(BindingFlags.Public | BindingFlags.Instance) @@ -132,24 +131,47 @@ namespace IPA.Loader { if (!noEnableDisable) Logger.Loader.Notice($"Plugin {name} has no methods marked [OnStart] or [OnEnable]. Is this intentional?"); - return o => { }; + return o => TaskEx.WhenAll(); } + var taskMethods = new List(); + var nonTaskMethods = new List(); 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."); + { + if (typeof(Task).IsAssignableFrom(m.ReturnType)) + { + taskMethods.Add(m); + continue; + } + + Logger.Loader.Warn($"Method {m} on {type.FullName} is marked [OnStart] or [OnEnable] and returns a non-Task value. It will be ignored."); + } + + nonTaskMethods.Add(m); } + Expression> completedTaskDel = () => TaskEx.WhenAll(); + var getCompletedTask = completedTaskDel.Body; + var taskWhenAll = typeof(TaskEx).GetMethod(nameof(TaskEx.WhenAll), new[] { typeof(Task[]) }); + var objParam = Expression.Parameter(typeof(object), "obj"); var instVar = ExpressionEx.Variable(type, "inst"); - var createExpr = Expression.Lambda>( + var createExpr = Expression.Lambda>( ExpressionEx.Block(new[] { instVar }, - enableMethods + nonTaskMethods .Select(m => (Expression)Expression.Call(instVar, m)) - .Prepend(ExpressionEx.Assign(instVar, Expression.Convert(objParam, type)))), + .Prepend(ExpressionEx.Assign(instVar, Expression.Convert(objParam, type))) + .Append( + taskMethods.Count == 0 + ? getCompletedTask + : Expression.Call(taskWhenAll, + Expression.NewArrayInit(typeof(Task), + taskMethods.Select(m => + (Expression)Expression.Convert(Expression.Call(instVar, m), typeof(Task))))))), objParam); return createExpr.Compile(); } @@ -181,8 +203,8 @@ namespace IPA.Loader 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."); + + Logger.Loader.Warn($"Method {m} on {type.FullName} is marked [OnExit] or [OnDisable] and returns a non-Task value. It will be ignored."); } nonTaskMethods.Add(m); @@ -200,11 +222,11 @@ namespace IPA.Loader .Select(m => (Expression)Expression.Call(instVar, m)) .Prepend(ExpressionEx.Assign(instVar, Expression.Convert(objParam, type))) .Append( - taskMethods.Count == 0 + taskMethods.Count == 0 ? getCompletedTask : Expression.Call(taskWhenAll, Expression.NewArrayInit(typeof(Task), - taskMethods.Select(m => + taskMethods.Select(m => (Expression)Expression.Convert(Expression.Call(instVar, m), typeof(Task))))))), objParam); return createExpr.Compile();