From db3e9a4171bc353f8e86987b29cc272a0fb68de4 Mon Sep 17 00:00:00 2001 From: Soreepeong Date: Wed, 24 Jul 2024 19:17:31 +0900 Subject: [PATCH] Add IDalamudPluginInterface.InjectAsync --- Dalamud/IoC/Internal/ServiceContainer.cs | 134 +++++++++------------- Dalamud/IoC/Internal/ServiceScope.cs | 10 +- Dalamud/Plugin/DalamudPluginInterface.cs | 37 ++++-- Dalamud/Plugin/IDalamudPluginInterface.cs | 8 ++ 4 files changed, 92 insertions(+), 97 deletions(-) diff --git a/Dalamud/IoC/Internal/ServiceContainer.cs b/Dalamud/IoC/Internal/ServiceContainer.cs index 06e2ff14d..39c2007f3 100644 --- a/Dalamud/IoC/Internal/ServiceContainer.cs +++ b/Dalamud/IoC/Internal/ServiceContainer.cs @@ -86,70 +86,55 @@ internal class ServiceContainer : IServiceProvider, IServiceType /// The created object. public async Task CreateAsync(Type objectType, object[] scopedObjects, IServiceScope? scope = null) { - var scopeImpl = scope as ServiceScopeImpl; + var errorStep = "constructor lookup"; - var ctor = this.FindApplicableCtor(objectType, scopedObjects); - if (ctor == null) + try { - throw new InvalidOperationException( - $"Failed to create {objectType.FullName ?? objectType.Name}; an eligible ctor with satisfiable services could not be found"); - } + var scopeImpl = scope as ServiceScopeImpl; - // validate dependency versions (if they exist) - var parameterTypes = ctor.GetParameters().Select(p => p.ParameterType).ToList(); + var ctor = this.FindApplicableCtor(objectType, scopedObjects) + ?? throw new InvalidOperationException("An eligible ctor with satisfiable services could not be found"); - var resolvedParams = - await Task.WhenAll( - parameterTypes - .Select(async type => + errorStep = "requested service resolution"; + var resolvedParams = + await Task.WhenAll( + ctor.GetParameters() + .Select(p => p.ParameterType) + .Select(type => this.GetService(type, scopeImpl, scopedObjects))); + + var instance = RuntimeHelpers.GetUninitializedObject(objectType); + + errorStep = "property injection"; + await this.InjectProperties(instance, scopedObjects, scope); + + errorStep = "ctor invocation"; + var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var thr = new Thread( + () => + { + try { - var service = await this.GetService(type, scopeImpl, scopedObjects); + ctor.Invoke(instance, resolvedParams); + } + catch (Exception e) + { + tcs.SetException(e); + return; + } - if (service == null) - { - Log.Error("Requested ctor service type {TypeName} was not available (null)", type.FullName!); - } + tcs.SetResult(); + }); - return service; - })); + thr.Start(); + await tcs.Task.ConfigureAwait(false); + thr.Join(); - var hasNull = resolvedParams.Any(p => p == null); - if (hasNull) - { - throw new InvalidOperationException( - $"Failed to create {objectType.FullName ?? objectType.Name}; a requested service type could not be satisfied"); + return instance; } - - var instance = RuntimeHelpers.GetUninitializedObject(objectType); - - if (!await this.InjectProperties(instance, scopedObjects, scope)) + catch (Exception e) { - throw new InvalidOperationException( - $"Failed to create {objectType.FullName ?? objectType.Name}; a requested property service type could not be satisfied"); + throw new AggregateException($"Failed to create {objectType.FullName ?? objectType.Name} ({errorStep})", e); } - - var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - var thr = new Thread( - () => - { - try - { - ctor.Invoke(instance, resolvedParams); - } - catch (Exception e) - { - tcs.SetException(e); - return; - } - - tcs.SetResult(); - }); - - thr.Start(); - await tcs.Task.ConfigureAwait(false); - thr.Join(); - - return instance; } /// @@ -159,28 +144,21 @@ internal class ServiceContainer : IServiceProvider, IServiceType /// The object instance. /// Scoped objects to be injected. /// The scope to be used to create scoped services. - /// Whether or not the injection was successful. - public async Task InjectProperties(object instance, object[] publicScopes, IServiceScope? scope = null) + /// A representing the operation. + public async ValueTask InjectProperties(object instance, object[] publicScopes, IServiceScope? scope = null) { var scopeImpl = scope as ServiceScopeImpl; var objectType = instance.GetType(); - var props = objectType.GetProperties(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | - BindingFlags.NonPublic).Where(x => x.GetCustomAttributes(typeof(PluginServiceAttribute)).Any()).ToArray(); + var props = + objectType + .GetProperties( + BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) + .Where(x => x.GetCustomAttributes(typeof(PluginServiceAttribute)).Any()) + .ToArray(); foreach (var prop in props) - { - var service = await this.GetService(prop.PropertyType, scopeImpl, publicScopes); - if (service == null) - { - Log.Error("Requested service type {TypeName} was not available (null)", prop.PropertyType.FullName!); - return false; - } - - prop.SetValue(instance, service); - } - - return true; + prop.SetValue(instance, await this.GetService(prop.PropertyType, scopeImpl, publicScopes)); } /// @@ -192,7 +170,7 @@ internal class ServiceContainer : IServiceProvider, IServiceType /// object? IServiceProvider.GetService(Type serviceType) => this.GetSingletonService(serviceType); - private async Task GetService(Type serviceType, ServiceScopeImpl? scope, object[] scopedObjects) + private async Task GetService(Type serviceType, ServiceScopeImpl? scope, object[] scopedObjects) { if (this.interfaceToTypeMap.TryGetValue(serviceType, out var implementingType)) serviceType = implementingType; @@ -201,8 +179,8 @@ internal class ServiceContainer : IServiceProvider, IServiceType { if (scope == null) { - Log.Error("Failed to create {TypeName}, is scoped but no scope provided", serviceType.FullName!); - return null; + throw new InvalidOperationException( + $"Failed to create {serviceType.FullName ?? serviceType.Name}, is scoped but no scope provided"); } return await scope.CreatePrivateScopedObject(serviceType, scopedObjects); @@ -210,18 +188,12 @@ internal class ServiceContainer : IServiceProvider, IServiceType var singletonService = await this.GetSingletonService(serviceType, false); if (singletonService != null) - { return singletonService; - } // resolve dependency from scoped objects - var scoped = scopedObjects.FirstOrDefault(o => o.GetType().IsAssignableTo(serviceType)); - if (scoped == default) - { - return null; - } - - return scoped; + return scopedObjects.FirstOrDefault(o => o.GetType().IsAssignableTo(serviceType)) + ?? throw new InvalidOperationException( + $"Requested type {serviceType.FullName ?? serviceType.Name} could not be found from {nameof(scopedObjects)}"); } private async Task GetSingletonService(Type serviceType, bool tryGetInterface = true) diff --git a/Dalamud/IoC/Internal/ServiceScope.cs b/Dalamud/IoC/Internal/ServiceScope.cs index 33a4d3f54..fb06ec75c 100644 --- a/Dalamud/IoC/Internal/ServiceScope.cs +++ b/Dalamud/IoC/Internal/ServiceScope.cs @@ -33,8 +33,8 @@ internal interface IServiceScope : IDisposable /// /// The object instance. /// Scoped objects to be injected. - /// Whether or not the injection was successful. - public Task InjectPropertiesAsync(object instance, params object[] scopedObjects); + /// A representing the status of the operation. + public ValueTask InjectPropertiesAsync(object instance, params object[] scopedObjects); } /// @@ -69,10 +69,8 @@ internal class ServiceScopeImpl : IServiceScope } /// - public Task InjectPropertiesAsync(object instance, params object[] scopedObjects) - { - return this.container.InjectProperties(instance, scopedObjects, this); - } + public ValueTask InjectPropertiesAsync(object instance, params object[] scopedObjects) => + this.container.InjectProperties(instance, scopedObjects, this); /// /// Create a service scoped to this scope, with private scoped objects. diff --git a/Dalamud/Plugin/DalamudPluginInterface.cs b/Dalamud/Plugin/DalamudPluginInterface.cs index 9fb73fbe1..f0882c6fe 100644 --- a/Dalamud/Plugin/DalamudPluginInterface.cs +++ b/Dalamud/Plugin/DalamudPluginInterface.cs @@ -468,7 +468,14 @@ internal sealed class DalamudPluginInterface : IDalamudPluginInterface, IDisposa t.Wait(); if (t.Exception is { } e) - Log.Error(e, "{who}: Failed to initialize {what}", this.plugin.Name, typeof(T).FullName ?? typeof(T).Name); + { + Log.Error( + e, + "{who}: Exception during {where}: {what}", + this.plugin.Name, + nameof(this.Create), + typeof(T).FullName ?? typeof(T).Name); + } return t.IsCompletedSuccessfully ? t.Result : null; } @@ -477,19 +484,29 @@ internal sealed class DalamudPluginInterface : IDalamudPluginInterface, IDisposa public async Task CreateAsync(params object[] scopedObjects) where T : class => (T)await this.plugin.ServiceScope!.CreateAsync(typeof(T), this.GetPublicIocScopes(scopedObjects)); - /// - /// Inject services into properties on the provided object instance. - /// - /// The instance to inject services into. - /// Objects to inject additionally. - /// Whether or not the injection succeeded. + /// public bool Inject(object instance, params object[] scopedObjects) { - return this.plugin.ServiceScope!.InjectPropertiesAsync( - instance, - this.GetPublicIocScopes(scopedObjects)).GetAwaiter().GetResult(); + var t = this.InjectAsync(instance, scopedObjects).AsTask(); + t.Wait(); + + if (t.Exception is { } e) + { + Log.Error( + e, + "{who}: Exception during {where}: {what}", + this.plugin.Name, + nameof(this.Inject), + instance.GetType().FullName ?? instance.GetType().Name); + } + + return t.IsCompletedSuccessfully; } + /// + public ValueTask InjectAsync(object instance, params object[] scopedObjects) => + this.plugin.ServiceScope!.InjectPropertiesAsync(instance, this.GetPublicIocScopes(scopedObjects)); + #endregion /// Unregister the plugin and dispose all references. diff --git a/Dalamud/Plugin/IDalamudPluginInterface.cs b/Dalamud/Plugin/IDalamudPluginInterface.cs index b5257c033..5205c3ed1 100644 --- a/Dalamud/Plugin/IDalamudPluginInterface.cs +++ b/Dalamud/Plugin/IDalamudPluginInterface.cs @@ -323,4 +323,12 @@ public interface IDalamudPluginInterface /// Objects to inject additionally. /// Whether or not the injection succeeded. bool Inject(object instance, params object[] scopedObjects); + + /// + /// Inject services into properties on the provided object instance. + /// + /// The instance to inject services into. + /// Objects to inject additionally. + /// A representing the status of the operation. + ValueTask InjectAsync(object instance, params object[] scopedObjects); }