mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-30 04:13:40 +01:00
IoC: Allow private scoped objects to resolve singleton services
This commit is contained in:
parent
c82bb8191d
commit
69d8968dca
9 changed files with 134 additions and 78 deletions
|
|
@ -26,9 +26,9 @@ internal class ServicesWidget : IDataWindowWidget
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public string[]? CommandShortcuts { get; init; } = { "services" };
|
public string[]? CommandShortcuts { get; init; } = { "services" };
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public string DisplayName { get; init; } = "Service Container";
|
public string DisplayName { get; init; } = "Service Container";
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public bool Ready { get; set; }
|
public bool Ready { get; set; }
|
||||||
|
|
@ -48,7 +48,7 @@ internal class ServicesWidget : IDataWindowWidget
|
||||||
{
|
{
|
||||||
if (ImGui.Button("Clear selection"))
|
if (ImGui.Button("Clear selection"))
|
||||||
this.selectedNodes.Clear();
|
this.selectedNodes.Clear();
|
||||||
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
switch (this.includeUnloadDependencies)
|
switch (this.includeUnloadDependencies)
|
||||||
{
|
{
|
||||||
|
|
@ -90,12 +90,12 @@ internal class ServicesWidget : IDataWindowWidget
|
||||||
var dl = ImGui.GetWindowDrawList();
|
var dl = ImGui.GetWindowDrawList();
|
||||||
var mouse = ImGui.GetMousePos();
|
var mouse = ImGui.GetMousePos();
|
||||||
var maxRowWidth = 0f;
|
var maxRowWidth = 0f;
|
||||||
|
|
||||||
// 1. Layout
|
// 1. Layout
|
||||||
for (var level = 0; level < this.dependencyNodes.Count; level++)
|
for (var level = 0; level < this.dependencyNodes.Count; level++)
|
||||||
{
|
{
|
||||||
var levelNodes = this.dependencyNodes[level];
|
var levelNodes = this.dependencyNodes[level];
|
||||||
|
|
||||||
var rowWidth = 0f;
|
var rowWidth = 0f;
|
||||||
foreach (var node in levelNodes)
|
foreach (var node in levelNodes)
|
||||||
rowWidth += node.DisplayedNameSize.X + cellPad.X + margin.X;
|
rowWidth += node.DisplayedNameSize.X + cellPad.X + margin.X;
|
||||||
|
|
@ -139,7 +139,7 @@ internal class ServicesWidget : IDataWindowWidget
|
||||||
{
|
{
|
||||||
var rect = this.nodeRects[node];
|
var rect = this.nodeRects[node];
|
||||||
var point1 = new Vector2((rect.X + rect.Z) / 2, rect.Y);
|
var point1 = new Vector2((rect.X + rect.Z) / 2, rect.Y);
|
||||||
|
|
||||||
foreach (var parent in node.InvalidParents)
|
foreach (var parent in node.InvalidParents)
|
||||||
{
|
{
|
||||||
rect = this.nodeRects[parent];
|
rect = this.nodeRects[parent];
|
||||||
|
|
@ -149,7 +149,7 @@ internal class ServicesWidget : IDataWindowWidget
|
||||||
|
|
||||||
dl.AddLine(point1, point2, lineInvalidColor, 2f * ImGuiHelpers.GlobalScale);
|
dl.AddLine(point1, point2, lineInvalidColor, 2f * ImGuiHelpers.GlobalScale);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var parent in node.Parents)
|
foreach (var parent in node.Parents)
|
||||||
{
|
{
|
||||||
rect = this.nodeRects[parent];
|
rect = this.nodeRects[parent];
|
||||||
|
|
@ -170,7 +170,7 @@ internal class ServicesWidget : IDataWindowWidget
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Draw boxes
|
// 3. Draw boxes
|
||||||
foreach (var levelNodes in this.dependencyNodes)
|
foreach (var levelNodes in this.dependencyNodes)
|
||||||
{
|
{
|
||||||
|
|
@ -231,36 +231,49 @@ internal class ServicesWidget : IDataWindowWidget
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.SetCursorPos(default);
|
ImGui.SetCursorPos(default);
|
||||||
ImGui.Dummy(new(maxRowWidth, this.dependencyNodes.Count * rowHeight));
|
ImGui.Dummy(new(maxRowWidth, this.dependencyNodes.Count * rowHeight));
|
||||||
ImGui.EndChild();
|
ImGui.EndChild();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui.CollapsingHeader("Plugin-facing Services"))
|
if (ImGui.CollapsingHeader("Singleton Services"))
|
||||||
{
|
{
|
||||||
foreach (var instance in container.Instances)
|
foreach (var instance in container.Instances)
|
||||||
{
|
{
|
||||||
var hasInterface = container.InterfaceToTypeMap.Values.Any(x => x == instance.Key);
|
|
||||||
var isPublic = instance.Key.IsPublic;
|
var isPublic = instance.Key.IsPublic;
|
||||||
|
|
||||||
ImGui.BulletText($"{instance.Key.FullName} ({instance.Key.GetServiceKind()})");
|
ImGui.BulletText($"{instance.Key.FullName} ({instance.Key.GetServiceKind()})");
|
||||||
|
|
||||||
using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudRed, !hasInterface))
|
|
||||||
{
|
|
||||||
ImGui.Text(
|
|
||||||
hasInterface
|
|
||||||
? $"\t => Provided via interface: {container.InterfaceToTypeMap.First(x => x.Value == instance.Key).Key.FullName}"
|
|
||||||
: "\t => NO INTERFACE!!!");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isPublic)
|
if (isPublic)
|
||||||
{
|
{
|
||||||
using var color = ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudRed);
|
using var color = ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudRed);
|
||||||
ImGui.Text("\t => PUBLIC!!!");
|
ImGui.Text("\t => PUBLIC!!!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switch (instance.Value.Visibility)
|
||||||
|
{
|
||||||
|
case ObjectInstanceVisibility.Internal:
|
||||||
|
ImGui.Text("\t => Internally resolved");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ObjectInstanceVisibility.ExposedToPlugins:
|
||||||
|
var hasInterface = container.InterfaceToTypeMap.Values.Any(x => x == instance.Key);
|
||||||
|
using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudRed, !hasInterface))
|
||||||
|
{
|
||||||
|
ImGui.Text("\t => Exposed to plugins!");
|
||||||
|
ImGui.Text(
|
||||||
|
hasInterface
|
||||||
|
? $"\t => Provided via interface: {container.InterfaceToTypeMap.First(x => x.Value == instance.Key).Key.FullName}"
|
||||||
|
: "\t => NO INTERFACE!!!");
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
|
||||||
ImGuiHelpers.ScaledDummy(2);
|
ImGuiHelpers.ScaledDummy(2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -301,7 +314,7 @@ internal class ServicesWidget : IDataWindowWidget
|
||||||
public string DisplayedName { get; }
|
public string DisplayedName { get; }
|
||||||
|
|
||||||
public string TypeSuffix { get; }
|
public string TypeSuffix { get; }
|
||||||
|
|
||||||
public uint TypeSuffixColor { get; }
|
public uint TypeSuffixColor { get; }
|
||||||
|
|
||||||
public Vector2 DisplayedNameSize =>
|
public Vector2 DisplayedNameSize =>
|
||||||
|
|
@ -319,7 +332,7 @@ internal class ServicesWidget : IDataWindowWidget
|
||||||
|
|
||||||
public IEnumerable<ServiceDependencyNode> Relatives =>
|
public IEnumerable<ServiceDependencyNode> Relatives =>
|
||||||
this.parents.Concat(this.children).Concat(this.invalidParents);
|
this.parents.Concat(this.children).Concat(this.invalidParents);
|
||||||
|
|
||||||
public int Level { get; private set; }
|
public int Level { get; private set; }
|
||||||
|
|
||||||
public static List<ServiceDependencyNode> CreateTree(bool includeUnloadDependencies)
|
public static List<ServiceDependencyNode> CreateTree(bool includeUnloadDependencies)
|
||||||
|
|
|
||||||
|
|
@ -13,9 +13,11 @@ internal class ObjectInstance
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="instanceTask">Weak reference to the underlying instance.</param>
|
/// <param name="instanceTask">Weak reference to the underlying instance.</param>
|
||||||
/// <param name="type">Type of the underlying instance.</param>
|
/// <param name="type">Type of the underlying instance.</param>
|
||||||
public ObjectInstance(Task<WeakReference> instanceTask, Type type)
|
/// <param name="visibility">The visibility of this instance.</param>
|
||||||
|
public ObjectInstance(Task<WeakReference> instanceTask, Type type, ObjectInstanceVisibility visibility)
|
||||||
{
|
{
|
||||||
this.InstanceTask = instanceTask;
|
this.InstanceTask = instanceTask;
|
||||||
|
this.Visibility = visibility;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -23,4 +25,9 @@ internal class ObjectInstance
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>The underlying instance.</returns>
|
/// <returns>The underlying instance.</returns>
|
||||||
public Task<WeakReference> InstanceTask { get; }
|
public Task<WeakReference> InstanceTask { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the visibility of the object instance.
|
||||||
|
/// </summary>
|
||||||
|
public ObjectInstanceVisibility Visibility { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
17
Dalamud/IoC/Internal/ObjectInstanceVisibility.cs
Normal file
17
Dalamud/IoC/Internal/ObjectInstanceVisibility.cs
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
namespace Dalamud.IoC.Internal;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enum that declares the visibility of an object instance in the service container.
|
||||||
|
/// </summary>
|
||||||
|
internal enum ObjectInstanceVisibility
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The object instance is only visible to other internal services.
|
||||||
|
/// </summary>
|
||||||
|
Internal,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The object instance is visible to all services and plugins.
|
||||||
|
/// </summary>
|
||||||
|
ExposedToPlugins,
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.Design;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
@ -12,7 +13,7 @@ namespace Dalamud.IoC.Internal;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A simple singleton-only IOC container that provides (optional) version-based dependency resolution.
|
/// A simple singleton-only IOC container that provides (optional) version-based dependency resolution.
|
||||||
///
|
///
|
||||||
/// This is only used to resolve dependencies for plugins.
|
/// This is only used to resolve dependencies for plugins.
|
||||||
/// Dalamud services are constructed via Service{T}.ConstructObject at the moment.
|
/// Dalamud services are constructed via Service{T}.ConstructObject at the moment.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -29,13 +30,18 @@ internal class ServiceContainer : IServiceProvider, IServiceType
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ServiceContainer()
|
public ServiceContainer()
|
||||||
{
|
{
|
||||||
|
// Register the service container itself as a singleton.
|
||||||
|
// For all other services, this is done through the static constructor of Service{T}.
|
||||||
|
this.instances.Add(
|
||||||
|
typeof(IServiceContainer),
|
||||||
|
new(new Task<WeakReference>(() => new WeakReference(this), TaskCreationOptions.RunContinuationsAsynchronously), typeof(ServiceContainer), ObjectInstanceVisibility.Internal));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a dictionary of all registered instances.
|
/// Gets a dictionary of all registered instances.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IReadOnlyDictionary<Type, ObjectInstance> Instances => this.instances;
|
public IReadOnlyDictionary<Type, ObjectInstance> Instances => this.instances;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a dictionary mapping interfaces to their implementations.
|
/// Gets a dictionary mapping interfaces to their implementations.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -45,15 +51,13 @@ internal class ServiceContainer : IServiceProvider, IServiceType
|
||||||
/// Register a singleton object of any type into the current IOC container.
|
/// Register a singleton object of any type into the current IOC container.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="instance">The existing instance to register in the container.</param>
|
/// <param name="instance">The existing instance to register in the container.</param>
|
||||||
|
/// <param name="visibility">The visibility of this singleton.</param>
|
||||||
/// <typeparam name="T">The type to register.</typeparam>
|
/// <typeparam name="T">The type to register.</typeparam>
|
||||||
public void RegisterSingleton<T>(Task<T> instance)
|
public void RegisterSingleton<T>(Task<T> instance, ObjectInstanceVisibility visibility)
|
||||||
{
|
{
|
||||||
if (instance == null)
|
ArgumentNullException.ThrowIfNull(instance);
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(instance));
|
|
||||||
}
|
|
||||||
|
|
||||||
this.instances[typeof(T)] = new(instance.ContinueWith(x => new WeakReference(x.Result)), typeof(T));
|
this.instances[typeof(T)] = new(instance.ContinueWith(x => new WeakReference(x.Result)), typeof(T), visibility);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -69,7 +73,7 @@ internal class ServiceContainer : IServiceProvider, IServiceType
|
||||||
foreach (var resolvableType in resolveViaTypes)
|
foreach (var resolvableType in resolveViaTypes)
|
||||||
{
|
{
|
||||||
Log.Verbose("=> {InterfaceName} provides for {TName}", resolvableType.FullName ?? "???", type.FullName ?? "???");
|
Log.Verbose("=> {InterfaceName} provides for {TName}", resolvableType.FullName ?? "???", type.FullName ?? "???");
|
||||||
|
|
||||||
Debug.Assert(!this.interfaceToTypeMap.ContainsKey(resolvableType), "A service already implements this interface, this is not allowed");
|
Debug.Assert(!this.interfaceToTypeMap.ContainsKey(resolvableType), "A service already implements this interface, this is not allowed");
|
||||||
Debug.Assert(type.IsAssignableTo(resolvableType), "Service does not inherit from indicated ResolveVia type");
|
Debug.Assert(type.IsAssignableTo(resolvableType), "Service does not inherit from indicated ResolveVia type");
|
||||||
|
|
||||||
|
|
@ -81,10 +85,11 @@ internal class ServiceContainer : IServiceProvider, IServiceType
|
||||||
/// Create an object.
|
/// Create an object.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="objectType">The type of object to create.</param>
|
/// <param name="objectType">The type of object to create.</param>
|
||||||
|
/// <param name="allowedVisibility">Defines which services are allowed to be directly resolved into this type.</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>
|
||||||
/// <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, ObjectInstanceVisibility allowedVisibility, object[] scopedObjects, IServiceScope? scope = null)
|
||||||
{
|
{
|
||||||
var errorStep = "constructor lookup";
|
var errorStep = "constructor lookup";
|
||||||
|
|
||||||
|
|
@ -174,7 +179,7 @@ internal class ServiceContainer : IServiceProvider, IServiceType
|
||||||
{
|
{
|
||||||
if (this.interfaceToTypeMap.TryGetValue(serviceType, out var implementingType))
|
if (this.interfaceToTypeMap.TryGetValue(serviceType, out var implementingType))
|
||||||
serviceType = implementingType;
|
serviceType = implementingType;
|
||||||
|
|
||||||
if (serviceType.GetCustomAttribute<ServiceManager.ScopedServiceAttribute>() != null)
|
if (serviceType.GetCustomAttribute<ServiceManager.ScopedServiceAttribute>() != null)
|
||||||
{
|
{
|
||||||
if (scope == null)
|
if (scope == null)
|
||||||
|
|
@ -211,7 +216,7 @@ internal class ServiceContainer : IServiceProvider, IServiceType
|
||||||
private ConstructorInfo? FindApplicableCtor(Type type, object[] scopedObjects)
|
private ConstructorInfo? FindApplicableCtor(Type type, object[] scopedObjects)
|
||||||
{
|
{
|
||||||
// get a list of all the available types: scoped and singleton
|
// get a list of all the available types: scoped and singleton
|
||||||
var types = scopedObjects
|
var allValidServiceTypes = scopedObjects
|
||||||
.Select(o => o.GetType())
|
.Select(o => o.GetType())
|
||||||
.Union(this.instances.Keys)
|
.Union(this.instances.Keys)
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
@ -224,7 +229,7 @@ internal class ServiceContainer : IServiceProvider, IServiceType
|
||||||
var ctors = type.GetConstructors(ctorFlags);
|
var ctors = type.GetConstructors(ctorFlags);
|
||||||
foreach (var ctor in ctors)
|
foreach (var ctor in ctors)
|
||||||
{
|
{
|
||||||
if (this.ValidateCtor(ctor, types))
|
if (this.ValidateCtor(ctor, allValidServiceTypes))
|
||||||
{
|
{
|
||||||
return ctor;
|
return ctor;
|
||||||
}
|
}
|
||||||
|
|
@ -233,28 +238,30 @@ internal class ServiceContainer : IServiceProvider, IServiceType
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool ValidateCtor(ConstructorInfo ctor, Type[] types)
|
private bool ValidateCtor(ConstructorInfo ctor, Type[] validTypes)
|
||||||
{
|
{
|
||||||
bool IsTypeValid(Type type)
|
bool IsTypeValid(Type type)
|
||||||
{
|
{
|
||||||
var contains = types.Any(x => x.IsAssignableTo(type));
|
var contains = validTypes.Any(x => x.IsAssignableTo(type));
|
||||||
|
|
||||||
// Scoped services are created on-demand
|
// Scoped services are created on-demand
|
||||||
return contains || type.GetCustomAttribute<ServiceManager.ScopedServiceAttribute>() != null;
|
return contains || type.GetCustomAttribute<ServiceManager.ScopedServiceAttribute>() != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var parameters = ctor.GetParameters();
|
var parameters = ctor.GetParameters();
|
||||||
foreach (var parameter in parameters)
|
foreach (var parameter in parameters)
|
||||||
{
|
{
|
||||||
var valid = IsTypeValid(parameter.ParameterType);
|
var valid = IsTypeValid(parameter.ParameterType);
|
||||||
|
|
||||||
// If this service is provided by an interface
|
// If this service is provided by an interface
|
||||||
if (!valid && this.interfaceToTypeMap.TryGetValue(parameter.ParameterType, out var implementationType))
|
if (!valid && this.interfaceToTypeMap.TryGetValue(parameter.ParameterType, out var implementationType))
|
||||||
valid = IsTypeValid(implementationType);
|
valid = IsTypeValid(implementationType);
|
||||||
|
|
||||||
if (!valid)
|
if (!valid)
|
||||||
{
|
{
|
||||||
Log.Error("Failed to validate {TypeName}, unable to find any services that satisfy the type", parameter.ParameterType.FullName!);
|
Log.Error("Ctor from {DeclaringType}: Failed to validate {TypeName}, unable to find any services that satisfy the type",
|
||||||
|
ctor.DeclaringType?.FullName ?? ctor.DeclaringType?.Name ?? "null",
|
||||||
|
parameter.ParameterType.FullName!);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,9 +25,10 @@ internal interface IServiceScope : IAsyncDisposable
|
||||||
/// Create an object.
|
/// Create an object.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="objectType">The type of object to create.</param>
|
/// <param name="objectType">The type of object to create.</param>
|
||||||
|
/// <param name="allowedVisibility">Defines which services are allowed to be directly resolved into this type.</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>
|
||||||
Task<object> CreateAsync(Type objectType, params object[] scopedObjects);
|
Task<object> CreateAsync(Type objectType, ObjectInstanceVisibility allowedVisibility, 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.
|
||||||
|
|
@ -72,13 +73,13 @@ internal class ServiceScopeImpl : IServiceScope
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public Task<object> CreateAsync(Type objectType, params object[] scopedObjects)
|
public Task<object> CreateAsync(Type objectType, ObjectInstanceVisibility allowedVisibility, params object[] scopedObjects)
|
||||||
{
|
{
|
||||||
this.disposeLock.EnterReadLock();
|
this.disposeLock.EnterReadLock();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
ObjectDisposedException.ThrowIf(this.disposed, this);
|
ObjectDisposedException.ThrowIf(this.disposed, this);
|
||||||
return this.container.CreateAsync(objectType, scopedObjects, this);
|
return this.container.CreateAsync(objectType, allowedVisibility, scopedObjects, this);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
|
@ -117,7 +118,9 @@ internal class ServiceScopeImpl : IServiceScope
|
||||||
objectType,
|
objectType,
|
||||||
static (objectType, p) => p.Scope.container.CreateAsync(
|
static (objectType, p) => p.Scope.container.CreateAsync(
|
||||||
objectType,
|
objectType,
|
||||||
p.Objects.Concat(p.Scope.privateScopedObjects).ToArray()),
|
ObjectInstanceVisibility.Internal, // We are allowed to resolve internal services here since this is a private scoped object.
|
||||||
|
p.Objects.Concat(p.Scope.privateScopedObjects).ToArray(),
|
||||||
|
p.Scope),
|
||||||
(Scope: this, Objects: scopedObjects));
|
(Scope: this, Objects: scopedObjects));
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ using Dalamud.Interface;
|
||||||
using Dalamud.Interface.Internal;
|
using Dalamud.Interface.Internal;
|
||||||
using Dalamud.Interface.Internal.Windows.PluginInstaller;
|
using Dalamud.Interface.Internal.Windows.PluginInstaller;
|
||||||
using Dalamud.Interface.Internal.Windows.Settings;
|
using Dalamud.Interface.Internal.Windows.Settings;
|
||||||
|
using Dalamud.IoC.Internal;
|
||||||
using Dalamud.Plugin.Internal;
|
using Dalamud.Plugin.Internal;
|
||||||
using Dalamud.Plugin.Internal.AutoUpdate;
|
using Dalamud.Plugin.Internal.AutoUpdate;
|
||||||
using Dalamud.Plugin.Internal.Types;
|
using Dalamud.Plugin.Internal.Types;
|
||||||
|
|
@ -482,7 +483,7 @@ internal sealed class DalamudPluginInterface : IDalamudPluginInterface, IDisposa
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public async Task<T> CreateAsync<T>(params object[] scopedObjects) where T : class =>
|
public async Task<T> CreateAsync<T>(params object[] scopedObjects) where T : class =>
|
||||||
(T)await this.plugin.ServiceScope!.CreateAsync(typeof(T), this.GetPublicIocScopes(scopedObjects));
|
(T)await this.plugin.ServiceScope!.CreateAsync(typeof(T), ObjectInstanceVisibility.ExposedToPlugins, this.GetPublicIocScopes(scopedObjects));
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public bool Inject(object instance, params object[] scopedObjects)
|
public bool Inject(object instance, params object[] scopedObjects)
|
||||||
|
|
|
||||||
|
|
@ -577,7 +577,7 @@ internal class LocalPlugin : IAsyncDisposable
|
||||||
var newInstanceTask = forceFrameworkThread ? framework.RunOnFrameworkThread(Create) : Create();
|
var newInstanceTask = forceFrameworkThread ? framework.RunOnFrameworkThread(Create) : Create();
|
||||||
return await newInstanceTask.ConfigureAwait(false);
|
return await newInstanceTask.ConfigureAwait(false);
|
||||||
|
|
||||||
async Task<IDalamudPlugin> Create() => (IDalamudPlugin)await scope.CreateAsync(type, dalamudInterface);
|
async Task<IDalamudPlugin> Create() => (IDalamudPlugin)await scope.CreateAsync(type, ObjectInstanceVisibility.ExposedToPlugins, dalamudInterface);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void SetupLoaderConfig(LoaderConfig config)
|
private static void SetupLoaderConfig(LoaderConfig config)
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ internal static class ServiceManager
|
||||||
/// <param name="justification">The justification for using this feature.</param>
|
/// <param name="justification">The justification for using this feature.</param>
|
||||||
[InjectableType]
|
[InjectableType]
|
||||||
public delegate void RegisterUnloadAfterDelegate(IEnumerable<Type> unloadAfter, string justification);
|
public delegate void RegisterUnloadAfterDelegate(IEnumerable<Type> unloadAfter, string justification);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Kinds of services.
|
/// Kinds of services.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -83,27 +83,27 @@ internal static class ServiceManager
|
||||||
/// Not a service.
|
/// Not a service.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
None = 0,
|
None = 0,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Service that is loaded manually.
|
/// Service that is loaded manually.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
ProvidedService = 1 << 0,
|
ProvidedService = 1 << 0,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Service that is loaded asynchronously while the game starts.
|
/// Service that is loaded asynchronously while the game starts.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
EarlyLoadedService = 1 << 1,
|
EarlyLoadedService = 1 << 1,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Service that is loaded before the game starts.
|
/// Service that is loaded before the game starts.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
BlockingEarlyLoadedService = 1 << 2,
|
BlockingEarlyLoadedService = 1 << 2,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Service that is only instantiable via scopes.
|
/// Service that is only instantiable via scopes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
ScopedService = 1 << 3,
|
ScopedService = 1 << 3,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Service that is loaded automatically when the game starts, synchronously or asynchronously.
|
/// Service that is loaded automatically when the game starts, synchronously or asynchronously.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -114,7 +114,7 @@ internal static class ServiceManager
|
||||||
/// Gets task that gets completed when all blocking early loading services are done loading.
|
/// Gets task that gets completed when all blocking early loading services are done loading.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static Task BlockingResolved { get; } = BlockingServicesLoadedTaskCompletionSource.Task;
|
public static Task BlockingResolved { get; } = BlockingServicesLoadedTaskCompletionSource.Task;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a cancellation token that will be cancelled once Dalamud needs to unload, be it due to a failure state
|
/// Gets a cancellation token that will be cancelled once Dalamud needs to unload, be it due to a failure state
|
||||||
/// during initialization or during regular operation.
|
/// during initialization or during regular operation.
|
||||||
|
|
@ -139,10 +139,13 @@ internal static class ServiceManager
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
lock (LoadedServices)
|
lock (LoadedServices)
|
||||||
{
|
{
|
||||||
|
// ServiceContainer MUST be first. The static ctor of Service<T> will call Service<ServiceContainer>.Get()
|
||||||
|
// which causes a deadlock otherwise.
|
||||||
|
ProvideService(new ServiceContainer());
|
||||||
|
|
||||||
ProvideService(dalamud);
|
ProvideService(dalamud);
|
||||||
ProvideService(fs);
|
ProvideService(fs);
|
||||||
ProvideService(configuration);
|
ProvideService(configuration);
|
||||||
ProvideService(new ServiceContainer());
|
|
||||||
ProvideService(scanner);
|
ProvideService(scanner);
|
||||||
ProvideService(localization);
|
ProvideService(localization);
|
||||||
}
|
}
|
||||||
|
|
@ -193,7 +196,7 @@ internal static class ServiceManager
|
||||||
var getAsyncTaskMap = new Dictionary<Type, Task>();
|
var getAsyncTaskMap = new Dictionary<Type, Task>();
|
||||||
|
|
||||||
var serviceContainer = Service<ServiceContainer>.Get();
|
var serviceContainer = Service<ServiceContainer>.Get();
|
||||||
|
|
||||||
foreach (var serviceType in GetConcreteServiceTypes())
|
foreach (var serviceType in GetConcreteServiceTypes())
|
||||||
{
|
{
|
||||||
var serviceKind = serviceType.GetServiceKind();
|
var serviceKind = serviceType.GetServiceKind();
|
||||||
|
|
@ -202,13 +205,13 @@ internal static class ServiceManager
|
||||||
|
|
||||||
// Let IoC know about the interfaces this service implements
|
// Let IoC know about the interfaces this service implements
|
||||||
serviceContainer.RegisterInterfaces(serviceType);
|
serviceContainer.RegisterInterfaces(serviceType);
|
||||||
|
|
||||||
// Scoped service do not go through Service<T> and are never early loaded
|
// Scoped service do not go through Service<T> and are never early loaded
|
||||||
if (serviceKind.HasFlag(ServiceKind.ScopedService))
|
if (serviceKind.HasFlag(ServiceKind.ScopedService))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
var genericWrappedServiceType = typeof(Service<>).MakeGenericType(serviceType);
|
var genericWrappedServiceType = typeof(Service<>).MakeGenericType(serviceType);
|
||||||
|
|
||||||
var getTask = (Task)genericWrappedServiceType
|
var getTask = (Task)genericWrappedServiceType
|
||||||
.InvokeMember(
|
.InvokeMember(
|
||||||
nameof(Service<IServiceType>.GetAsync),
|
nameof(Service<IServiceType>.GetAsync),
|
||||||
|
|
@ -290,7 +293,7 @@ internal static class ServiceManager
|
||||||
var tasks = tasksEnumerable.AsReadOnlyCollection();
|
var tasks = tasksEnumerable.AsReadOnlyCollection();
|
||||||
if (tasks.Count == 0)
|
if (tasks.Count == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Time we wait until showing the loading dialog
|
// Time we wait until showing the loading dialog
|
||||||
const int loadingDialogTimeout = 10000;
|
const int loadingDialogTimeout = 10000;
|
||||||
|
|
||||||
|
|
@ -330,7 +333,7 @@ internal static class ServiceManager
|
||||||
hasDeps = false;
|
hasDeps = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hasDeps)
|
if (!hasDeps)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
|
@ -437,7 +440,7 @@ internal static class ServiceManager
|
||||||
public static void UnloadAllServices()
|
public static void UnloadAllServices()
|
||||||
{
|
{
|
||||||
UnloadCancellationTokenSource.Cancel();
|
UnloadCancellationTokenSource.Cancel();
|
||||||
|
|
||||||
var framework = Service<Framework>.GetNullable(Service<Framework>.ExceptionPropagationMode.None);
|
var framework = Service<Framework>.GetNullable(Service<Framework>.ExceptionPropagationMode.None);
|
||||||
if (framework is { IsInFrameworkUpdateThread: false, IsFrameworkUnloading: false })
|
if (framework is { IsInFrameworkUpdateThread: false, IsFrameworkUnloading: false })
|
||||||
{
|
{
|
||||||
|
|
@ -450,14 +453,14 @@ internal static class ServiceManager
|
||||||
var dependencyServicesMap = new Dictionary<Type, IReadOnlyCollection<Type>>();
|
var dependencyServicesMap = new Dictionary<Type, IReadOnlyCollection<Type>>();
|
||||||
var allToUnload = new HashSet<Type>();
|
var allToUnload = new HashSet<Type>();
|
||||||
var unloadOrder = new List<Type>();
|
var unloadOrder = new List<Type>();
|
||||||
|
|
||||||
Log.Information("==== COLLECTING SERVICES TO UNLOAD ====");
|
Log.Information("==== COLLECTING SERVICES TO UNLOAD ====");
|
||||||
|
|
||||||
foreach (var serviceType in GetConcreteServiceTypes())
|
foreach (var serviceType in GetConcreteServiceTypes())
|
||||||
{
|
{
|
||||||
if (!serviceType.IsAssignableTo(typeof(IServiceType)))
|
if (!serviceType.IsAssignableTo(typeof(IServiceType)))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// Scoped services shall never be unloaded here.
|
// Scoped services shall never be unloaded here.
|
||||||
// Their lifetime must be managed by the IServiceScope that owns them. If it leaks, it's their fault.
|
// Their lifetime must be managed by the IServiceScope that owns them. If it leaks, it's their fault.
|
||||||
if (serviceType.GetServiceKind() == ServiceKind.ScopedService)
|
if (serviceType.GetServiceKind() == ServiceKind.ScopedService)
|
||||||
|
|
@ -485,12 +488,12 @@ internal static class ServiceManager
|
||||||
unloadOrder.Add(serviceType);
|
unloadOrder.Add(serviceType);
|
||||||
Log.Information("Queue for unload {Type}", serviceType.FullName!);
|
Log.Information("Queue for unload {Type}", serviceType.FullName!);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var serviceType in allToUnload)
|
foreach (var serviceType in allToUnload)
|
||||||
{
|
{
|
||||||
UnloadService(serviceType);
|
UnloadService(serviceType);
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.Information("==== UNLOADING ALL SERVICES ====");
|
Log.Information("==== UNLOADING ALL SERVICES ====");
|
||||||
|
|
||||||
unloadOrder.Reverse();
|
unloadOrder.Reverse();
|
||||||
|
|
@ -507,7 +510,7 @@ internal static class ServiceManager
|
||||||
null,
|
null,
|
||||||
null);
|
null);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
lock (LoadedServices)
|
lock (LoadedServices)
|
||||||
{
|
{
|
||||||
|
|
@ -536,17 +539,17 @@ internal static class ServiceManager
|
||||||
var attr = type.GetCustomAttribute<ServiceAttribute>(true)?.GetType();
|
var attr = type.GetCustomAttribute<ServiceAttribute>(true)?.GetType();
|
||||||
if (attr == null)
|
if (attr == null)
|
||||||
return ServiceKind.None;
|
return ServiceKind.None;
|
||||||
|
|
||||||
Debug.Assert(
|
Debug.Assert(
|
||||||
type.IsAssignableTo(typeof(IServiceType)),
|
type.IsAssignableTo(typeof(IServiceType)),
|
||||||
"Service did not inherit from IServiceType");
|
"Service did not inherit from IServiceType");
|
||||||
|
|
||||||
if (attr.IsAssignableTo(typeof(BlockingEarlyLoadedServiceAttribute)))
|
if (attr.IsAssignableTo(typeof(BlockingEarlyLoadedServiceAttribute)))
|
||||||
return ServiceKind.BlockingEarlyLoadedService;
|
return ServiceKind.BlockingEarlyLoadedService;
|
||||||
|
|
||||||
if (attr.IsAssignableTo(typeof(EarlyLoadedServiceAttribute)))
|
if (attr.IsAssignableTo(typeof(EarlyLoadedServiceAttribute)))
|
||||||
return ServiceKind.EarlyLoadedService;
|
return ServiceKind.EarlyLoadedService;
|
||||||
|
|
||||||
if (attr.IsAssignableTo(typeof(ScopedServiceAttribute)))
|
if (attr.IsAssignableTo(typeof(ScopedServiceAttribute)))
|
||||||
return ServiceKind.ScopedService;
|
return ServiceKind.ScopedService;
|
||||||
|
|
||||||
|
|
@ -572,7 +575,7 @@ internal static class ServiceManager
|
||||||
var isAnyDisposable =
|
var isAnyDisposable =
|
||||||
isServiceDisposable
|
isServiceDisposable
|
||||||
|| serviceType.IsAssignableTo(typeof(IDisposable))
|
|| serviceType.IsAssignableTo(typeof(IDisposable))
|
||||||
|| serviceType.IsAssignableTo(typeof(IAsyncDisposable));
|
|| serviceType.IsAssignableTo(typeof(IAsyncDisposable));
|
||||||
if (isAnyDisposable && !isServiceDisposable)
|
if (isAnyDisposable && !isServiceDisposable)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException(
|
throw new InvalidOperationException(
|
||||||
|
|
|
||||||
|
|
@ -42,8 +42,13 @@ internal static class Service<T> where T : IServiceType
|
||||||
else
|
else
|
||||||
ServiceManager.Log.Debug("Service<{0}>: Static ctor called", type.Name);
|
ServiceManager.Log.Debug("Service<{0}>: Static ctor called", type.Name);
|
||||||
|
|
||||||
if (exposeToPlugins)
|
// We can't use the service container to register itself. It does so in its constructor.
|
||||||
Service<ServiceContainer>.Get().RegisterSingleton(instanceTcs.Task);
|
if (typeof(T) != typeof(ServiceContainer))
|
||||||
|
{
|
||||||
|
Service<ServiceContainer>.Get().RegisterSingleton(
|
||||||
|
instanceTcs.Task,
|
||||||
|
exposeToPlugins ? ObjectInstanceVisibility.ExposedToPlugins : ObjectInstanceVisibility.Internal);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -163,7 +168,7 @@ internal static class Service<T> where T : IServiceType
|
||||||
return dependencyServices;
|
return dependencyServices;
|
||||||
|
|
||||||
var res = new List<Type>();
|
var res = new List<Type>();
|
||||||
|
|
||||||
ServiceManager.Log.Verbose("Service<{0}>: Getting dependencies", typeof(T).Name);
|
ServiceManager.Log.Verbose("Service<{0}>: Getting dependencies", typeof(T).Name);
|
||||||
|
|
||||||
var ctor = GetServiceConstructor();
|
var ctor = GetServiceConstructor();
|
||||||
|
|
@ -174,12 +179,12 @@ internal static class Service<T> where T : IServiceType
|
||||||
.Select(x => x.ParameterType)
|
.Select(x => x.ParameterType)
|
||||||
.Where(x => x.GetServiceKind() != ServiceManager.ServiceKind.None));
|
.Where(x => x.GetServiceKind() != ServiceManager.ServiceKind.None));
|
||||||
}
|
}
|
||||||
|
|
||||||
res.AddRange(typeof(T)
|
res.AddRange(typeof(T)
|
||||||
.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
|
.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
|
||||||
.Where(x => x.GetCustomAttribute<ServiceManager.ServiceDependency>(true) != null)
|
.Where(x => x.GetCustomAttribute<ServiceManager.ServiceDependency>(true) != null)
|
||||||
.Select(x => x.FieldType));
|
.Select(x => x.FieldType));
|
||||||
|
|
||||||
res.AddRange(typeof(T)
|
res.AddRange(typeof(T)
|
||||||
.GetCustomAttributes()
|
.GetCustomAttributes()
|
||||||
.OfType<InherentDependencyAttribute>()
|
.OfType<InherentDependencyAttribute>()
|
||||||
|
|
@ -351,7 +356,7 @@ internal static class Service<T> where T : IServiceType
|
||||||
var ctor = GetServiceConstructor();
|
var ctor = GetServiceConstructor();
|
||||||
if (ctor == null)
|
if (ctor == null)
|
||||||
throw new Exception($"Service \"{typeof(T).FullName}\" had no applicable constructor");
|
throw new Exception($"Service \"{typeof(T).FullName}\" had no applicable constructor");
|
||||||
|
|
||||||
var args = await ResolveInjectedParameters(ctor.GetParameters(), additionalProvidedTypedObjects)
|
var args = await ResolveInjectedParameters(ctor.GetParameters(), additionalProvidedTypedObjects)
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
using (Timings.Start($"{typeof(T).Name} Construct"))
|
using (Timings.Start($"{typeof(T).Name} Construct"))
|
||||||
|
|
@ -387,7 +392,7 @@ internal static class Service<T> where T : IServiceType
|
||||||
argTask = Task.FromResult(additionalProvidedTypedObjects.Single(x => x.GetType() == argType));
|
argTask = Task.FromResult(additionalProvidedTypedObjects.Single(x => x.GetType() == argType));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
argTask = (Task<object>)typeof(Service<>)
|
argTask = (Task<object>)typeof(Service<>)
|
||||||
.MakeGenericType(argType)
|
.MakeGenericType(argType)
|
||||||
.InvokeMember(
|
.InvokeMember(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue