Remove nullability from IServiceScope.CreateAsync

This commit is contained in:
Soreepeong 2024-07-24 18:55:27 +09:00
parent 32b24b3b5a
commit 4b98f4e60a
5 changed files with 42 additions and 37 deletions

View file

@ -84,15 +84,15 @@ internal class ServiceContainer : IServiceProvider, IServiceType
/// <param name="scopedObjects">Scoped objects to be included in the constructor.</param> /// <param name="scopedObjects">Scoped objects to be included in the constructor.</param>
/// <param name="scope">The scope to be used to create scoped services.</param> /// <param name="scope">The scope to be used to create scoped services.</param>
/// <returns>The created object.</returns> /// <returns>The created object.</returns>
public async Task<object?> CreateAsync(Type objectType, object[] scopedObjects, IServiceScope? scope = null) public async Task<object> CreateAsync(Type objectType, object[] scopedObjects, IServiceScope? scope = null)
{ {
var scopeImpl = scope as ServiceScopeImpl; var scopeImpl = scope as ServiceScopeImpl;
var ctor = this.FindApplicableCtor(objectType, scopedObjects); var ctor = this.FindApplicableCtor(objectType, scopedObjects);
if (ctor == null) if (ctor == null)
{ {
Log.Error("Failed to create {TypeName}, an eligible ctor with satisfiable services could not be found", objectType.FullName!); throw new InvalidOperationException(
return null; $"Failed to create {objectType.FullName ?? objectType.Name}; an eligible ctor with satisfiable services could not be found");
} }
// validate dependency versions (if they exist) // validate dependency versions (if they exist)
@ -116,16 +116,16 @@ internal class ServiceContainer : IServiceProvider, IServiceType
var hasNull = resolvedParams.Any(p => p == null); var hasNull = resolvedParams.Any(p => p == null);
if (hasNull) if (hasNull)
{ {
Log.Error("Failed to create {TypeName}, a requested service type could not be satisfied", objectType.FullName!); throw new InvalidOperationException(
return null; $"Failed to create {objectType.FullName ?? objectType.Name}; a requested service type could not be satisfied");
} }
var instance = RuntimeHelpers.GetUninitializedObject(objectType); var instance = RuntimeHelpers.GetUninitializedObject(objectType);
if (!await this.InjectProperties(instance, scopedObjects, scope)) if (!await this.InjectProperties(instance, scopedObjects, scope))
{ {
Log.Error("Failed to create {TypeName}, a requested property service type could not be satisfied", objectType.FullName!); throw new InvalidOperationException(
return null; $"Failed to create {objectType.FullName ?? objectType.Name}; a requested property service type could not be satisfied");
} }
var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);

View file

@ -25,7 +25,7 @@ internal interface IServiceScope : IDisposable
/// <param name="objectType">The type of object to create.</param> /// <param name="objectType">The type of object to create.</param>
/// <param name="scopedObjects">Scoped objects to be included in the constructor.</param> /// <param name="scopedObjects">Scoped objects to be included in the constructor.</param>
/// <returns>The created object.</returns> /// <returns>The created object.</returns>
public Task<object?> CreateAsync(Type objectType, params object[] scopedObjects); public Task<object> CreateAsync(Type objectType, params object[] scopedObjects);
/// <summary> /// <summary>
/// Inject <see cref="PluginInterfaceAttribute" /> interfaces into public or static properties on the provided object. /// Inject <see cref="PluginInterfaceAttribute" /> interfaces into public or static properties on the provided object.
@ -45,7 +45,7 @@ internal class ServiceScopeImpl : IServiceScope
private readonly ServiceContainer container; private readonly ServiceContainer container;
private readonly List<object> privateScopedObjects = []; private readonly List<object> privateScopedObjects = [];
private readonly ConcurrentDictionary<Type, Task<object?>> scopeCreatedObjects = new(); private readonly ConcurrentDictionary<Type, Task<object>> scopeCreatedObjects = new();
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ServiceScopeImpl" /> class. /// Initializes a new instance of the <see cref="ServiceScopeImpl" /> class.
@ -63,7 +63,7 @@ internal class ServiceScopeImpl : IServiceScope
} }
/// <inheritdoc /> /// <inheritdoc />
public Task<object?> CreateAsync(Type objectType, params object[] scopedObjects) public Task<object> CreateAsync(Type objectType, params object[] scopedObjects)
{ {
return this.container.CreateAsync(objectType, scopedObjects, this); return this.container.CreateAsync(objectType, scopedObjects, this);
} }
@ -80,7 +80,7 @@ internal class ServiceScopeImpl : IServiceScope
/// <param name="objectType">The type of object to create.</param> /// <param name="objectType">The type of object to create.</param>
/// <param name="scopedObjects">Additional scoped objects.</param> /// <param name="scopedObjects">Additional scoped objects.</param>
/// <returns>The created object, or null.</returns> /// <returns>The created object, or null.</returns>
public Task<object?> CreatePrivateScopedObject(Type objectType, params object[] scopedObjects) => public Task<object> CreatePrivateScopedObject(Type objectType, params object[] scopedObjects) =>
this.scopeCreatedObjects.GetOrAdd( this.scopeCreatedObjects.GetOrAdd(
objectType, objectType,
static (objectType, p) => p.Scope.container.CreateAsync( static (objectType, p) => p.Scope.container.CreateAsync(

View file

@ -5,6 +5,7 @@ using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Threading.Tasks;
using Dalamud.Configuration; using Dalamud.Configuration;
using Dalamud.Configuration.Internal; using Dalamud.Configuration.Internal;
@ -26,6 +27,8 @@ using Dalamud.Plugin.Ipc;
using Dalamud.Plugin.Ipc.Exceptions; using Dalamud.Plugin.Ipc.Exceptions;
using Dalamud.Plugin.Ipc.Internal; using Dalamud.Plugin.Ipc.Internal;
using Serilog;
namespace Dalamud.Plugin; namespace Dalamud.Plugin;
/// <summary> /// <summary>
@ -458,21 +461,22 @@ internal sealed class DalamudPluginInterface : IDalamudPluginInterface, IDisposa
#region Dependency Injection #region Dependency Injection
/// <summary> /// <inheritdoc/>
/// Create a new object of the provided type using its default constructor, then inject objects and properties.
/// </summary>
/// <param name="scopedObjects">Objects to inject additionally.</param>
/// <typeparam name="T">The type to create.</typeparam>
/// <returns>The created and initialized type.</returns>
public T? Create<T>(params object[] scopedObjects) where T : class public T? Create<T>(params object[] scopedObjects) where T : class
{ {
var svcContainer = Service<IoC.Internal.ServiceContainer>.Get(); var t = this.CreateAsync<T>(scopedObjects);
t.Wait();
return (T)this.plugin.ServiceScope!.CreateAsync( if (t.Exception is { } e)
typeof(T), Log.Error(e, "{who}: Failed to initialize {what}", this.plugin.Name, typeof(T).FullName ?? typeof(T).Name);
this.GetPublicIocScopes(scopedObjects)).GetAwaiter().GetResult();
return t.IsCompletedSuccessfully ? t.Result : null;
} }
/// <inheritdoc/>
public async Task<T> CreateAsync<T>(params object[] scopedObjects) where T : class =>
(T)await this.plugin.ServiceScope!.CreateAsync(typeof(T), this.GetPublicIocScopes(scopedObjects));
/// <summary> /// <summary>
/// Inject services into properties on the provided object instance. /// Inject services into properties on the provided object instance.
/// </summary> /// </summary>

View file

@ -1,6 +1,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using System.Threading.Tasks;
using Dalamud.Configuration; using Dalamud.Configuration;
using Dalamud.Game.Text; using Dalamud.Game.Text;
@ -304,9 +305,17 @@ public interface IDalamudPluginInterface
/// </summary> /// </summary>
/// <param name="scopedObjects">Objects to inject additionally.</param> /// <param name="scopedObjects">Objects to inject additionally.</param>
/// <typeparam name="T">The type to create.</typeparam> /// <typeparam name="T">The type to create.</typeparam>
/// <returns>The created and initialized type.</returns> /// <returns>The created and initialized type, or <c>null</c> on failure.</returns>
T? Create<T>(params object[] scopedObjects) where T : class; T? Create<T>(params object[] scopedObjects) where T : class;
/// <summary>
/// Create a new object of the provided type using its default constructor, then inject objects and properties.
/// </summary>
/// <param name="scopedObjects">Objects to inject additionally.</param>
/// <typeparam name="T">The type to create.</typeparam>
/// <returns>A task representing the created and initialized type.</returns>
Task<T> CreateAsync<T>(params object[] scopedObjects) where T : class;
/// <summary> /// <summary>
/// Inject services into properties on the provided object instance. /// Inject services into properties on the provided object instance.
/// </summary> /// </summary>

View file

@ -417,24 +417,16 @@ internal class LocalPlugin : IDisposable
try try
{ {
if (this.manifest.LoadSync && this.manifest.LoadRequiredState is 0 or 1) var forceFrameworkThread = this.manifest.LoadSync && this.manifest.LoadRequiredState is 0 or 1;
{ var newInstanceTask = forceFrameworkThread ? framework.RunOnFrameworkThread(Create) : Create();
var newInstance = await framework.RunOnFrameworkThread( this.instance = await newInstanceTask.ConfigureAwait(false);
() => this.ServiceScope.CreateAsync(
this.pluginType!, async Task<IDalamudPlugin> Create() =>
this.DalamudInterface!)).ConfigureAwait(false); (IDalamudPlugin)await this.ServiceScope!.CreateAsync(this.pluginType!, this.DalamudInterface!);
this.instance = newInstance as IDalamudPlugin;
}
else
{
this.instance =
await this.ServiceScope.CreateAsync(this.pluginType!, this.DalamudInterface!) as IDalamudPlugin;
}
} }
catch (Exception ex) catch (Exception ex)
{ {
Log.Error(ex, "Exception in plugin constructor"); Log.Error(ex, "Exception during plugin initialization");
this.instance = null; this.instance = null;
} }