diff --git a/Dalamud/IoC/Internal/ServiceContainer.cs b/Dalamud/IoC/Internal/ServiceContainer.cs
index 7a0b4347d..a8eacb02d 100644
--- a/Dalamud/IoC/Internal/ServiceContainer.cs
+++ b/Dalamud/IoC/Internal/ServiceContainer.cs
@@ -84,72 +84,57 @@ internal class ServiceContainer : IServiceProvider, IServiceType
/// Scoped objects to be included in the constructor.
/// The scope to be used to create scoped services.
/// The created object.
- public async Task CreateAsync(Type objectType, object[] scopedObjects, IServiceScope? scope = null)
+ 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
{
- Log.Error("Failed to create {TypeName}, an eligible ctor with satisfiable services could not be found", objectType.FullName!);
- return null;
- }
+ 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)
- {
- Log.Error("Failed to create {TypeName}, a requested service type could not be satisfied", objectType.FullName!);
- return null;
+ return instance;
}
-
- var instance = RuntimeHelpers.GetUninitializedObject(objectType);
-
- if (!await this.InjectProperties(instance, scopedObjects, scope))
+ catch (Exception e)
{
- Log.Error("Failed to create {TypeName}, a requested property service type could not be satisfied", objectType.FullName!);
- return null;
+ 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 Task 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 c21e73f34..4fc299f6e 100644
--- a/Dalamud/IoC/Internal/ServiceScope.cs
+++ b/Dalamud/IoC/Internal/ServiceScope.cs
@@ -1,7 +1,10 @@
-using System.Collections.Generic;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
+using Serilog;
+
namespace Dalamud.IoC.Internal;
///
@@ -14,7 +17,7 @@ internal interface IServiceScope : IDisposable
/// but not directly to created objects.
///
/// The scopes to add.
- public void RegisterPrivateScopes(params object[] scopes);
+ void RegisterPrivateScopes(params object[] scopes);
///
/// Create an object.
@@ -22,7 +25,7 @@ internal interface IServiceScope : IDisposable
/// The type of object to create.
/// Scoped objects to be included in the constructor.
/// The created object.
- public Task CreateAsync(Type objectType, params object[] scopedObjects);
+ Task CreateAsync(Type objectType, params object[] scopedObjects);
///
/// Inject interfaces into public or static properties on the provided object.
@@ -30,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.
+ Task InjectPropertiesAsync(object instance, params object[] scopedObjects);
}
///
@@ -41,35 +44,24 @@ internal class ServiceScopeImpl : IServiceScope
{
private readonly ServiceContainer container;
- private readonly List privateScopedObjects = new();
- private readonly List scopeCreatedObjects = new();
+ private readonly List privateScopedObjects = [];
+ private readonly ConcurrentDictionary> scopeCreatedObjects = new();
- ///
- /// Initializes a new instance of the class.
- ///
+ /// Initializes a new instance of the class.
/// The container this scope will use to create services.
- public ServiceScopeImpl(ServiceContainer container)
- {
- this.container = container;
- }
+ public ServiceScopeImpl(ServiceContainer container) => this.container = container;
///
- public void RegisterPrivateScopes(params object[] scopes)
- {
+ public void RegisterPrivateScopes(params object[] scopes) =>
this.privateScopedObjects.AddRange(scopes);
- }
///
- public Task CreateAsync(Type objectType, params object[] scopedObjects)
- {
- return this.container.CreateAsync(objectType, scopedObjects, this);
- }
+ public Task CreateAsync(Type objectType, params object[] scopedObjects) =>
+ this.container.CreateAsync(objectType, scopedObjects, this);
///
- public Task InjectPropertiesAsync(object instance, params object[] scopedObjects)
- {
- return this.container.InjectProperties(instance, scopedObjects, this);
- }
+ public Task InjectPropertiesAsync(object instance, params object[] scopedObjects) =>
+ this.container.InjectProperties(instance, scopedObjects, this);
///
/// Create a service scoped to this scope, with private scoped objects.
@@ -77,34 +69,39 @@ internal class ServiceScopeImpl : IServiceScope
/// The type of object to create.
/// Additional scoped objects.
/// The created object, or null.
- public async Task CreatePrivateScopedObject(Type objectType, params object[] scopedObjects)
- {
- var instance = this.scopeCreatedObjects.FirstOrDefault(x => x.GetType() == objectType);
- if (instance != null)
- return instance;
-
- instance =
- await this.container.CreateAsync(objectType, scopedObjects.Concat(this.privateScopedObjects).ToArray());
- if (instance != null)
- this.scopeCreatedObjects.Add(instance);
-
- return instance;
- }
+ public Task CreatePrivateScopedObject(Type objectType, params object[] scopedObjects) =>
+ this.scopeCreatedObjects.GetOrAdd(
+ objectType,
+ static (objectType, p) => p.Scope.container.CreateAsync(
+ objectType,
+ p.Objects.Concat(p.Scope.privateScopedObjects).ToArray()),
+ (Scope: this, Objects: scopedObjects));
///
public void Dispose()
{
- foreach (var createdObject in this.scopeCreatedObjects)
+ foreach (var objectTask in this.scopeCreatedObjects)
{
- switch (createdObject)
- {
- case IInternalDisposableService d:
- d.DisposeService();
- break;
- case IDisposable d:
- d.Dispose();
- break;
- }
+ objectTask.Value.ContinueWith(
+ static r =>
+ {
+ if (!r.IsCompletedSuccessfully)
+ {
+ if (r.Exception is { } e)
+ Log.Error(e, "{what}: Failed to load.", nameof(ServiceScopeImpl));
+ return;
+ }
+
+ switch (r.Result)
+ {
+ case IInternalDisposableService d:
+ d.DisposeService();
+ break;
+ case IDisposable d:
+ d.Dispose();
+ break;
+ }
+ });
}
}
}
diff --git a/Dalamud/Plugin/DalamudPluginInterface.cs b/Dalamud/Plugin/DalamudPluginInterface.cs
index bb92b6b0c..ecd2e0799 100644
--- a/Dalamud/Plugin/DalamudPluginInterface.cs
+++ b/Dalamud/Plugin/DalamudPluginInterface.cs
@@ -5,6 +5,7 @@ using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
+using System.Threading.Tasks;
using Dalamud.Configuration;
using Dalamud.Configuration.Internal;
@@ -26,6 +27,8 @@ using Dalamud.Plugin.Ipc;
using Dalamud.Plugin.Ipc.Exceptions;
using Dalamud.Plugin.Ipc.Internal;
+using Serilog;
+
namespace Dalamud.Plugin;
///
@@ -458,34 +461,52 @@ internal sealed class DalamudPluginInterface : IDalamudPluginInterface, IDisposa
#region Dependency Injection
- ///
- /// Create a new object of the provided type using its default constructor, then inject objects and properties.
- ///
- /// Objects to inject additionally.
- /// The type to create.
- /// The created and initialized type.
+ ///
public T? Create(params object[] scopedObjects) where T : class
{
- var svcContainer = Service.Get();
+ var t = this.CreateAsync(scopedObjects);
+ t.Wait();
- return (T)this.plugin.ServiceScope!.CreateAsync(
- typeof(T),
- this.GetPublicIocScopes(scopedObjects)).GetAwaiter().GetResult();
+ if (t.Exception is { } e)
+ {
+ 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;
}
- ///
- /// 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 async Task CreateAsync(params object[] scopedObjects) where T : class =>
+ (T)await this.plugin.ServiceScope!.CreateAsync(typeof(T), this.GetPublicIocScopes(scopedObjects));
+
+ ///
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);
+ 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 Task 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 6393dc5ab..100d4570e 100644
--- a/Dalamud/Plugin/IDalamudPluginInterface.cs
+++ b/Dalamud/Plugin/IDalamudPluginInterface.cs
@@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
+using System.Threading.Tasks;
using Dalamud.Configuration;
using Dalamud.Game.Text;
@@ -304,14 +305,30 @@ public interface IDalamudPluginInterface
///
/// Objects to inject additionally.
/// The type to create.
- /// The created and initialized type.
+ /// The created and initialized type, or null on failure.
T? Create(params object[] scopedObjects) where T : class;
+ ///
+ /// Create a new object of the provided type using its default constructor, then inject objects and properties.
+ ///
+ /// Objects to inject additionally.
+ /// The type to create.
+ /// A task representing the created and initialized type.
+ Task CreateAsync(params object[] scopedObjects) where T : class;
+
///
/// 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.
+ /// Whether 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.
+ Task InjectAsync(object instance, params object[] scopedObjects);
}
diff --git a/Dalamud/Plugin/Internal/Types/LocalPlugin.cs b/Dalamud/Plugin/Internal/Types/LocalPlugin.cs
index f7bb3495c..00fa9d243 100644
--- a/Dalamud/Plugin/Internal/Types/LocalPlugin.cs
+++ b/Dalamud/Plugin/Internal/Types/LocalPlugin.cs
@@ -417,24 +417,16 @@ internal class LocalPlugin : IDisposable
try
{
- if (this.manifest.LoadSync && this.manifest.LoadRequiredState is 0 or 1)
- {
- var newInstance = await framework.RunOnFrameworkThread(
- () => this.ServiceScope.CreateAsync(
- this.pluginType!,
- this.DalamudInterface!)).ConfigureAwait(false);
-
- this.instance = newInstance as IDalamudPlugin;
- }
- else
- {
- this.instance =
- await this.ServiceScope.CreateAsync(this.pluginType!, this.DalamudInterface!) as IDalamudPlugin;
- }
+ var forceFrameworkThread = this.manifest.LoadSync && this.manifest.LoadRequiredState is 0 or 1;
+ var newInstanceTask = forceFrameworkThread ? framework.RunOnFrameworkThread(Create) : Create();
+ this.instance = await newInstanceTask.ConfigureAwait(false);
+
+ async Task Create() =>
+ (IDalamudPlugin)await this.ServiceScope!.CreateAsync(this.pluginType!, this.DalamudInterface!);
}
catch (Exception ex)
{
- Log.Error(ex, "Exception in plugin constructor");
+ Log.Error(ex, "Exception during plugin initialization");
this.instance = null;
}