mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-12 10:17:22 +01:00
IOC: scoped/on-demand services (#1120)
This commit is contained in:
parent
ca8a05b672
commit
daa9f72218
8 changed files with 309 additions and 62 deletions
|
|
@ -54,7 +54,7 @@ namespace Dalamud.CorePlugin
|
||||||
/// Initializes a new instance of the <see cref="PluginImpl"/> class.
|
/// Initializes a new instance of the <see cref="PluginImpl"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="pluginInterface">Dalamud plugin interface.</param>
|
/// <param name="pluginInterface">Dalamud plugin interface.</param>
|
||||||
public PluginImpl(DalamudPluginInterface pluginInterface)
|
public PluginImpl(DalamudPluginInterface pluginInterface, PluginLog log)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
@ -68,7 +68,7 @@ namespace Dalamud.CorePlugin
|
||||||
|
|
||||||
Service<CommandManager>.Get().AddHandler("/coreplug", new(this.OnCommand) { HelpMessage = $"Access the {this.Name} plugin." });
|
Service<CommandManager>.Get().AddHandler("/coreplug", new(this.OnCommand) { HelpMessage = $"Access the {this.Name} plugin." });
|
||||||
|
|
||||||
PluginLog.Information("CorePlugin ctor!");
|
log.Information("CorePlugin ctor!");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -2252,7 +2252,8 @@ internal class PluginInstallerWindow : Window, IDisposable
|
||||||
disabled = disabled || (plugin.IsOrphaned && !plugin.IsLoaded);
|
disabled = disabled || (plugin.IsOrphaned && !plugin.IsLoaded);
|
||||||
|
|
||||||
// Disable everything if the plugin failed to load
|
// Disable everything if the plugin failed to load
|
||||||
disabled = disabled || plugin.State == PluginState.LoadError || plugin.State == PluginState.DependencyResolutionFailed;
|
// Now handled by the first case below
|
||||||
|
// disabled = disabled || plugin.State == PluginState.LoadError || plugin.State == PluginState.DependencyResolutionFailed;
|
||||||
|
|
||||||
// Disable everything if we're working
|
// Disable everything if we're working
|
||||||
disabled = disabled || plugin.State == PluginState.Loading || plugin.State == PluginState.Unloading;
|
disabled = disabled || plugin.State == PluginState.Loading || plugin.State == PluginState.Unloading;
|
||||||
|
|
@ -2263,7 +2264,7 @@ internal class PluginInstallerWindow : Window, IDisposable
|
||||||
|
|
||||||
StyleModelV1.DalamudStandard.Push();
|
StyleModelV1.DalamudStandard.Push();
|
||||||
|
|
||||||
if (plugin.State == PluginState.UnloadError && !plugin.IsDev)
|
if (plugin.State is PluginState.UnloadError or PluginState.LoadError or PluginState.DependencyResolutionFailed && !plugin.IsDev)
|
||||||
{
|
{
|
||||||
ImGuiComponents.DisabledButton(FontAwesomeIcon.Frown);
|
ImGuiComponents.DisabledButton(FontAwesomeIcon.Frown);
|
||||||
|
|
||||||
|
|
@ -3064,7 +3065,7 @@ internal class PluginInstallerWindow : Window, IDisposable
|
||||||
|
|
||||||
public static string PluginButtonToolTip_UpdateSingle(string version) => Loc.Localize("InstallerUpdateSingle", "Update to {0}").Format(version);
|
public static string PluginButtonToolTip_UpdateSingle(string version) => Loc.Localize("InstallerUpdateSingle", "Update to {0}").Format(version);
|
||||||
|
|
||||||
public static string PluginButtonToolTip_UnloadFailed => Loc.Localize("InstallerUnloadFailedTooltip", "Plugin unload failed, please restart your game and try again.");
|
public static string PluginButtonToolTip_UnloadFailed => Loc.Localize("InstallerLoadUnloadFailedTooltip", "Plugin load/unload failed, please restart your game and try again.");
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ using System.Runtime.Serialization;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
using Dalamud.Logging.Internal;
|
using Dalamud.Logging.Internal;
|
||||||
|
using Dalamud.Plugin.Internal.Types;
|
||||||
|
|
||||||
namespace Dalamud.IoC.Internal;
|
namespace Dalamud.IoC.Internal;
|
||||||
|
|
||||||
|
|
@ -45,9 +46,12 @@ internal class ServiceContainer : IServiceProvider, IServiceType
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <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>
|
||||||
|
/// <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, params object[] scopedObjects)
|
public async Task<object?> CreateAsync(Type objectType, object[] scopedObjects, IServiceScope? scope = null)
|
||||||
{
|
{
|
||||||
|
var scopeImpl = scope as ServiceScopeImpl;
|
||||||
|
|
||||||
var ctor = this.FindApplicableCtor(objectType, scopedObjects);
|
var ctor = this.FindApplicableCtor(objectType, scopedObjects);
|
||||||
if (ctor == null)
|
if (ctor == null)
|
||||||
{
|
{
|
||||||
|
|
@ -76,11 +80,22 @@ internal class ServiceContainer : IServiceProvider, IServiceType
|
||||||
parameters
|
parameters
|
||||||
.Select(async p =>
|
.Select(async p =>
|
||||||
{
|
{
|
||||||
|
if (p.parameterType.GetCustomAttribute<ServiceManager.ScopedService>() != null)
|
||||||
|
{
|
||||||
|
if (scopeImpl == null)
|
||||||
|
{
|
||||||
|
Log.Error("Failed to create {TypeName}, depends on scoped service but no scope", objectType.FullName!);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await scopeImpl.CreatePrivateScopedObject(p.parameterType, scopedObjects);
|
||||||
|
}
|
||||||
|
|
||||||
var service = await this.GetService(p.parameterType, scopedObjects);
|
var service = await this.GetService(p.parameterType, scopedObjects);
|
||||||
|
|
||||||
if (service == null)
|
if (service == null)
|
||||||
{
|
{
|
||||||
Log.Error("Requested service type {TypeName} was not available (null)", p.parameterType.FullName!);
|
Log.Error("Requested ctor service type {TypeName} was not available (null)", p.parameterType.FullName!);
|
||||||
}
|
}
|
||||||
|
|
||||||
return service;
|
return service;
|
||||||
|
|
@ -95,7 +110,7 @@ internal class ServiceContainer : IServiceProvider, IServiceType
|
||||||
|
|
||||||
var instance = FormatterServices.GetUninitializedObject(objectType);
|
var instance = FormatterServices.GetUninitializedObject(objectType);
|
||||||
|
|
||||||
if (!await this.InjectProperties(instance, scopedObjects))
|
if (!await this.InjectProperties(instance, scopedObjects, scope))
|
||||||
{
|
{
|
||||||
Log.Error("Failed to create {TypeName}, a requested property service type could not be satisfied", objectType.FullName!);
|
Log.Error("Failed to create {TypeName}, a requested property service type could not be satisfied", objectType.FullName!);
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -112,10 +127,12 @@ internal class ServiceContainer : IServiceProvider, IServiceType
|
||||||
/// The properties can be marked with the <see cref="RequiredVersionAttribute"/> to lock down versions.
|
/// The properties can be marked with the <see cref="RequiredVersionAttribute"/> to lock down versions.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="instance">The object instance.</param>
|
/// <param name="instance">The object instance.</param>
|
||||||
/// <param name="scopedObjects">Scoped objects.</param>
|
/// <param name="publicScopes">Scoped objects to be injected.</param>
|
||||||
|
/// <param name="scope">The scope to be used to create scoped services.</param>
|
||||||
/// <returns>Whether or not the injection was successful.</returns>
|
/// <returns>Whether or not the injection was successful.</returns>
|
||||||
public async Task<bool> InjectProperties(object instance, params object[] scopedObjects)
|
public async Task<bool> InjectProperties(object instance, object[] publicScopes, IServiceScope? scope = null)
|
||||||
{
|
{
|
||||||
|
var scopeImpl = scope as ServiceScopeImpl;
|
||||||
var objectType = instance.GetType();
|
var objectType = instance.GetType();
|
||||||
|
|
||||||
var props = objectType.GetProperties(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public |
|
var props = objectType.GetProperties(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public |
|
||||||
|
|
@ -136,7 +153,21 @@ internal class ServiceContainer : IServiceProvider, IServiceType
|
||||||
|
|
||||||
foreach (var prop in props)
|
foreach (var prop in props)
|
||||||
{
|
{
|
||||||
var service = await this.GetService(prop.propertyInfo.PropertyType, scopedObjects);
|
object service = null;
|
||||||
|
|
||||||
|
if (prop.propertyInfo.PropertyType.GetCustomAttribute<ServiceManager.ScopedService>() != null)
|
||||||
|
{
|
||||||
|
if (scopeImpl == null)
|
||||||
|
{
|
||||||
|
Log.Error("Failed to create {TypeName}, depends on scoped service but no scope", objectType.FullName!);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
service = await scopeImpl.CreatePrivateScopedObject(prop.propertyInfo.PropertyType, publicScopes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
service ??= await this.GetService(prop.propertyInfo.PropertyType, publicScopes);
|
||||||
|
|
||||||
if (service == null)
|
if (service == null)
|
||||||
{
|
{
|
||||||
|
|
@ -150,6 +181,12 @@ internal class ServiceContainer : IServiceProvider, IServiceType
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get a service scope, enabling the creation of objects with scoped services.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>An implementation of a service scope.</returns>
|
||||||
|
public IServiceScope GetScope() => new ServiceScopeImpl(this);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
object? IServiceProvider.GetService(Type serviceType) => this.GetService(serviceType);
|
object? IServiceProvider.GetService(Type serviceType) => this.GetService(serviceType);
|
||||||
|
|
||||||
|
|
@ -185,7 +222,7 @@ internal class ServiceContainer : IServiceProvider, IServiceType
|
||||||
}
|
}
|
||||||
|
|
||||||
// resolve dependency from scoped objects
|
// resolve dependency from scoped objects
|
||||||
var scoped = scopedObjects.FirstOrDefault(o => o.GetType() == serviceType);
|
var scoped = scopedObjects.FirstOrDefault(o => o.GetType().IsAssignableTo(serviceType));
|
||||||
if (scoped == default)
|
if (scoped == default)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -211,7 +248,12 @@ internal class ServiceContainer : IServiceProvider, IServiceType
|
||||||
.Union(this.instances.Keys)
|
.Union(this.instances.Keys)
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
var ctors = type.GetConstructors(BindingFlags.Public | BindingFlags.Instance);
|
// Allow resolving non-public ctors for Dalamud types
|
||||||
|
var ctorFlags = BindingFlags.Public | BindingFlags.Instance;
|
||||||
|
if (type.Assembly == Assembly.GetExecutingAssembly())
|
||||||
|
ctorFlags |= BindingFlags.NonPublic;
|
||||||
|
|
||||||
|
var ctors = type.GetConstructors(ctorFlags);
|
||||||
foreach (var ctor in ctors)
|
foreach (var ctor in ctors)
|
||||||
{
|
{
|
||||||
if (this.ValidateCtor(ctor, types))
|
if (this.ValidateCtor(ctor, types))
|
||||||
|
|
@ -228,8 +270,10 @@ internal class ServiceContainer : IServiceProvider, IServiceType
|
||||||
var parameters = ctor.GetParameters();
|
var parameters = ctor.GetParameters();
|
||||||
foreach (var parameter in parameters)
|
foreach (var parameter in parameters)
|
||||||
{
|
{
|
||||||
var contains = types.Contains(parameter.ParameterType);
|
var contains = types.Any(x => x.IsAssignableTo(parameter.ParameterType));
|
||||||
if (!contains)
|
|
||||||
|
// Scoped services are created on-demand
|
||||||
|
if (!contains && parameter.ParameterType.GetCustomAttribute<ServiceManager.ScopedService>() == null)
|
||||||
{
|
{
|
||||||
Log.Error("Failed to validate {TypeName}, unable to find any services that satisfy the type", parameter.ParameterType.FullName!);
|
Log.Error("Failed to validate {TypeName}, unable to find any services that satisfy the type", parameter.ParameterType.FullName!);
|
||||||
return false;
|
return false;
|
||||||
|
|
|
||||||
101
Dalamud/IoC/Internal/ServiceScope.cs
Normal file
101
Dalamud/IoC/Internal/ServiceScope.cs
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Dalamud.IoC.Internal;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Container enabling the creation of scoped services.
|
||||||
|
/// </summary>
|
||||||
|
internal interface IServiceScope : IDisposable
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Register objects that may be injected to scoped services,
|
||||||
|
/// but not directly to created objects.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="scopes">The scopes to add.</param>
|
||||||
|
public void RegisterPrivateScopes(params object[] scopes);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create an object.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="objectType">The type of object to create.</param>
|
||||||
|
/// <param name="scopedObjects">Scoped objects to be included in the constructor.</param>
|
||||||
|
/// <returns>The created object.</returns>
|
||||||
|
public Task<object?> CreateAsync(Type objectType, params object[] scopedObjects);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Inject <see cref="PluginInterfaceAttribute" /> interfaces into public or static properties on the provided object.
|
||||||
|
/// The properties have to be marked with the <see cref="PluginServiceAttribute" />.
|
||||||
|
/// The properties can be marked with the <see cref="RequiredVersionAttribute" /> to lock down versions.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="instance">The object instance.</param>
|
||||||
|
/// <param name="scopedObjects">Scoped objects to be injected.</param>
|
||||||
|
/// <returns>Whether or not the injection was successful.</returns>
|
||||||
|
public Task<bool> InjectPropertiesAsync(object instance, params object[] scopedObjects);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Implementation of a service scope.
|
||||||
|
/// </summary>
|
||||||
|
internal class ServiceScopeImpl : IServiceScope
|
||||||
|
{
|
||||||
|
private readonly ServiceContainer container;
|
||||||
|
|
||||||
|
private readonly List<object> privateScopedObjects = new();
|
||||||
|
private readonly List<object> scopeCreatedObjects = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="ServiceScopeImpl" /> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="container">The container this scope will use to create services.</param>
|
||||||
|
public ServiceScopeImpl(ServiceContainer container)
|
||||||
|
{
|
||||||
|
this.container = container;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void RegisterPrivateScopes(params object[] scopes)
|
||||||
|
{
|
||||||
|
this.privateScopedObjects.AddRange(scopes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Task<object?> CreateAsync(Type objectType, params object[] scopedObjects)
|
||||||
|
{
|
||||||
|
return this.container.CreateAsync(objectType, scopedObjects, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Task<bool> InjectPropertiesAsync(object instance, params object[] scopedObjects)
|
||||||
|
{
|
||||||
|
return this.container.InjectProperties(instance, scopedObjects, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a service scoped to this scope, with private scoped objects.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="objectType">The type of object to create.</param>
|
||||||
|
/// <param name="scopedObjects">Additional scoped objects.</param>
|
||||||
|
/// <returns>The created object, or null.</returns>
|
||||||
|
public async Task<object?> 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
foreach (var createdObject in this.scopeCreatedObjects.OfType<IDisposable>()) createdObject.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
||||||
|
using Dalamud.IoC;
|
||||||
|
using Dalamud.IoC.Internal;
|
||||||
|
using Dalamud.Plugin.Internal.Types;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using Serilog.Events;
|
using Serilog.Events;
|
||||||
|
|
||||||
|
|
@ -9,9 +12,29 @@ namespace Dalamud.Logging;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class offering various static methods to allow for logging in plugins.
|
/// Class offering various static methods to allow for logging in plugins.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class PluginLog
|
[PluginInterface]
|
||||||
|
[InterfaceVersion("1.0")]
|
||||||
|
[ServiceManager.ScopedService]
|
||||||
|
public class PluginLog : IServiceType, IDisposable
|
||||||
{
|
{
|
||||||
#region "Log" prefixed Serilog style methods
|
private readonly LocalPlugin plugin;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="PluginLog"/> class.
|
||||||
|
/// Do not use this ctor, inject PluginLog instead.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="plugin">The plugin this service is scoped for.</param>
|
||||||
|
internal PluginLog(LocalPlugin plugin)
|
||||||
|
{
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a prefix appended to log messages.
|
||||||
|
/// </summary>
|
||||||
|
public string? LogPrefix { get; set; } = null;
|
||||||
|
|
||||||
|
#region Legacy static "Log" prefixed Serilog style methods
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Log a templated message to the in-game debug log.
|
/// Log a templated message to the in-game debug log.
|
||||||
|
|
@ -134,7 +157,7 @@ public static class PluginLog
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Serilog style methods
|
#region Legacy static Serilog style methods
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Log a templated verbose message to the in-game debug log.
|
/// Log a templated verbose message to the in-game debug log.
|
||||||
|
|
@ -254,6 +277,25 @@ public static class PluginLog
|
||||||
public static void LogRaw(LogEventLevel level, Exception? exception, string messageTemplate, params object[] values)
|
public static void LogRaw(LogEventLevel level, Exception? exception, string messageTemplate, params object[] values)
|
||||||
=> WriteLog(Assembly.GetCallingAssembly().GetName().Name, level, messageTemplate, exception, values);
|
=> WriteLog(Assembly.GetCallingAssembly().GetName().Name, level, messageTemplate, exception, values);
|
||||||
|
|
||||||
|
#region New instanced methods
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Log some information.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">The message.</param>
|
||||||
|
internal void Information(string message)
|
||||||
|
{
|
||||||
|
Serilog.Log.Information($"[{this.plugin.InternalName}] {this.LogPrefix} {message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
void IDisposable.Dispose()
|
||||||
|
{
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
|
||||||
private static ILogger GetPluginLogger(string? pluginName)
|
private static ILogger GetPluginLogger(string? pluginName)
|
||||||
{
|
{
|
||||||
return Serilog.Log.ForContext("SourceContext", pluginName ?? string.Empty);
|
return Serilog.Log.ForContext("SourceContext", pluginName ?? string.Empty);
|
||||||
|
|
@ -272,3 +314,24 @@ public static class PluginLog
|
||||||
values);
|
values);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Class offering logging services, for a specific type.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type to log for.</typeparam>
|
||||||
|
[PluginInterface]
|
||||||
|
[InterfaceVersion("1.0")]
|
||||||
|
[ServiceManager.ScopedService]
|
||||||
|
public class PluginLog<T> : PluginLog
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="PluginLog{T}"/> class.
|
||||||
|
/// Do not use this ctor, inject PluginLog instead.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="plugin">The plugin this service is scoped for.</param>
|
||||||
|
internal PluginLog(LocalPlugin plugin)
|
||||||
|
: base(plugin)
|
||||||
|
{
|
||||||
|
this.LogPrefix = typeof(T).Name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,33 +33,30 @@ namespace Dalamud.Plugin;
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class DalamudPluginInterface : IDisposable
|
public sealed class DalamudPluginInterface : IDisposable
|
||||||
{
|
{
|
||||||
private readonly string pluginName;
|
private readonly LocalPlugin plugin;
|
||||||
private readonly PluginConfigurations configs;
|
private readonly PluginConfigurations configs;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="DalamudPluginInterface"/> class.
|
/// Initializes a new instance of the <see cref="DalamudPluginInterface"/> class.
|
||||||
/// Set up the interface and populate all fields needed.
|
/// Set up the interface and populate all fields needed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="pluginName">The internal name of the plugin.</param>
|
/// <param name="plugin">The plugin this interface belongs to.</param>
|
||||||
/// <param name="assemblyLocation">Location of the assembly.</param>
|
|
||||||
/// <param name="reason">The reason the plugin was loaded.</param>
|
/// <param name="reason">The reason the plugin was loaded.</param>
|
||||||
/// <param name="isDev">A value indicating whether this is a dev plugin.</param>
|
internal DalamudPluginInterface(
|
||||||
/// <param name="manifest">The local manifest for this plugin.</param>
|
LocalPlugin plugin,
|
||||||
internal DalamudPluginInterface(string pluginName, FileInfo assemblyLocation, PluginLoadReason reason, bool isDev, LocalPluginManifest manifest)
|
PluginLoadReason reason)
|
||||||
{
|
{
|
||||||
|
this.plugin = plugin;
|
||||||
var configuration = Service<DalamudConfiguration>.Get();
|
var configuration = Service<DalamudConfiguration>.Get();
|
||||||
var dataManager = Service<DataManager>.Get();
|
var dataManager = Service<DataManager>.Get();
|
||||||
var localization = Service<Localization>.Get();
|
var localization = Service<Localization>.Get();
|
||||||
|
|
||||||
this.UiBuilder = new UiBuilder(pluginName);
|
this.UiBuilder = new UiBuilder(plugin.Name);
|
||||||
|
|
||||||
this.pluginName = pluginName;
|
|
||||||
this.AssemblyLocation = assemblyLocation;
|
|
||||||
this.configs = Service<PluginManager>.Get().PluginConfigs;
|
this.configs = Service<PluginManager>.Get().PluginConfigs;
|
||||||
this.Reason = reason;
|
this.Reason = reason;
|
||||||
this.IsDev = isDev;
|
this.SourceRepository = this.IsDev ? LocalPluginManifest.FlagDevPlugin : plugin.Manifest.InstalledFromUrl;
|
||||||
this.SourceRepository = isDev ? LocalPluginManifest.FlagDevPlugin : manifest.InstalledFromUrl;
|
this.IsTesting = plugin.IsTesting;
|
||||||
this.IsTesting = manifest.Testing;
|
|
||||||
|
|
||||||
this.LoadTime = DateTime.Now;
|
this.LoadTime = DateTime.Now;
|
||||||
this.LoadTimeUTC = DateTime.UtcNow;
|
this.LoadTimeUTC = DateTime.UtcNow;
|
||||||
|
|
@ -128,12 +125,12 @@ public sealed class DalamudPluginInterface : IDisposable
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the current internal plugin name.
|
/// Gets the current internal plugin name.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string InternalName => this.pluginName;
|
public string InternalName => this.plugin.InternalName;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether this is a dev plugin.
|
/// Gets a value indicating whether this is a dev plugin.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsDev { get; }
|
public bool IsDev => this.plugin.IsDev;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether this is a testing release of a plugin.
|
/// Gets a value indicating whether this is a testing release of a plugin.
|
||||||
|
|
@ -166,7 +163,7 @@ public sealed class DalamudPluginInterface : IDisposable
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the location of your plugin assembly.
|
/// Gets the location of your plugin assembly.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public FileInfo AssemblyLocation { get; }
|
public FileInfo AssemblyLocation => this.plugin.DllFile;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the directory your plugin configurations are stored in.
|
/// Gets the directory your plugin configurations are stored in.
|
||||||
|
|
@ -176,7 +173,7 @@ public sealed class DalamudPluginInterface : IDisposable
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the config file of your plugin.
|
/// Gets the config file of your plugin.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public FileInfo ConfigFile => this.configs.GetConfigFile(this.pluginName);
|
public FileInfo ConfigFile => this.configs.GetConfigFile(this.plugin.InternalName);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the <see cref="UiBuilder"/> instance which allows you to draw UI into the game via ImGui draw calls.
|
/// Gets the <see cref="UiBuilder"/> instance which allows you to draw UI into the game via ImGui draw calls.
|
||||||
|
|
@ -238,7 +235,7 @@ public sealed class DalamudPluginInterface : IDisposable
|
||||||
}
|
}
|
||||||
|
|
||||||
dalamudInterface.OpenPluginInstallerPluginInstalled();
|
dalamudInterface.OpenPluginInstallerPluginInstalled();
|
||||||
dalamudInterface.SetPluginInstallerSearchText(this.pluginName);
|
dalamudInterface.SetPluginInstallerSearchText(this.plugin.InternalName);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -357,7 +354,7 @@ public sealed class DalamudPluginInterface : IDisposable
|
||||||
if (currentConfig == null)
|
if (currentConfig == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
this.configs.Save(currentConfig, this.pluginName);
|
this.configs.Save(currentConfig, this.plugin.InternalName);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -379,30 +376,32 @@ public sealed class DalamudPluginInterface : IDisposable
|
||||||
{
|
{
|
||||||
var mi = this.configs.GetType().GetMethod("LoadForType");
|
var mi = this.configs.GetType().GetMethod("LoadForType");
|
||||||
var fn = mi.MakeGenericMethod(type);
|
var fn = mi.MakeGenericMethod(type);
|
||||||
return (IPluginConfiguration)fn.Invoke(this.configs, new object[] { this.pluginName });
|
return (IPluginConfiguration)fn.Invoke(this.configs, new object[] { this.plugin.InternalName });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// this shouldn't be a thing, I think, but just in case
|
// this shouldn't be a thing, I think, but just in case
|
||||||
return this.configs.Load(this.pluginName);
|
return this.configs.Load(this.plugin.InternalName);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get the config directory.
|
/// Get the config directory.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>directory with path of AppData/XIVLauncher/pluginConfig/PluginInternalName.</returns>
|
/// <returns>directory with path of AppData/XIVLauncher/pluginConfig/PluginInternalName.</returns>
|
||||||
public string GetPluginConfigDirectory() => this.configs.GetDirectory(this.pluginName);
|
public string GetPluginConfigDirectory() => this.configs.GetDirectory(this.plugin.InternalName);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get the loc directory.
|
/// Get the loc directory.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>directory with path of AppData/XIVLauncher/pluginConfig/PluginInternalName/loc.</returns>
|
/// <returns>directory with path of AppData/XIVLauncher/pluginConfig/PluginInternalName/loc.</returns>
|
||||||
public string GetPluginLocDirectory() => this.configs.GetDirectory(Path.Combine(this.pluginName, "loc"));
|
public string GetPluginLocDirectory() => this.configs.GetDirectory(Path.Combine(this.plugin.InternalName, "loc"));
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Chat Links
|
#region Chat Links
|
||||||
|
|
||||||
|
// TODO API9: Move to chatgui, don't allow passing own commandId
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Register a chat link handler.
|
/// Register a chat link handler.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -411,7 +410,7 @@ public sealed class DalamudPluginInterface : IDisposable
|
||||||
/// <returns>Returns an SeString payload for the link.</returns>
|
/// <returns>Returns an SeString payload for the link.</returns>
|
||||||
public DalamudLinkPayload AddChatLinkHandler(uint commandId, Action<uint, SeString> commandAction)
|
public DalamudLinkPayload AddChatLinkHandler(uint commandId, Action<uint, SeString> commandAction)
|
||||||
{
|
{
|
||||||
return Service<ChatGui>.Get().AddChatLinkHandler(this.pluginName, commandId, commandAction);
|
return Service<ChatGui>.Get().AddChatLinkHandler(this.plugin.InternalName, commandId, commandAction);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -420,7 +419,7 @@ public sealed class DalamudPluginInterface : IDisposable
|
||||||
/// <param name="commandId">The ID of the command.</param>
|
/// <param name="commandId">The ID of the command.</param>
|
||||||
public void RemoveChatLinkHandler(uint commandId)
|
public void RemoveChatLinkHandler(uint commandId)
|
||||||
{
|
{
|
||||||
Service<ChatGui>.Get().RemoveChatLinkHandler(this.pluginName, commandId);
|
Service<ChatGui>.Get().RemoveChatLinkHandler(this.plugin.InternalName, commandId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -428,7 +427,7 @@ public sealed class DalamudPluginInterface : IDisposable
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void RemoveChatLinkHandler()
|
public void RemoveChatLinkHandler()
|
||||||
{
|
{
|
||||||
Service<ChatGui>.Get().RemoveChatLinkHandler(this.pluginName);
|
Service<ChatGui>.Get().RemoveChatLinkHandler(this.plugin.InternalName);
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|
@ -444,11 +443,9 @@ public sealed class DalamudPluginInterface : IDisposable
|
||||||
{
|
{
|
||||||
var svcContainer = Service<IoC.Internal.ServiceContainer>.Get();
|
var svcContainer = Service<IoC.Internal.ServiceContainer>.Get();
|
||||||
|
|
||||||
var realScopedObjects = new object[scopedObjects.Length + 1];
|
return (T)this.plugin.ServiceScope!.CreateAsync(
|
||||||
realScopedObjects[0] = this;
|
typeof(T),
|
||||||
Array.Copy(scopedObjects, 0, realScopedObjects, 1, scopedObjects.Length);
|
this.GetPublicIocScopes(scopedObjects)).GetAwaiter().GetResult();
|
||||||
|
|
||||||
return (T)svcContainer.CreateAsync(typeof(T), realScopedObjects).GetAwaiter().GetResult();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -459,13 +456,9 @@ public sealed class DalamudPluginInterface : IDisposable
|
||||||
/// <returns>Whether or not the injection succeeded.</returns>
|
/// <returns>Whether or not the injection succeeded.</returns>
|
||||||
public bool Inject(object instance, params object[] scopedObjects)
|
public bool Inject(object instance, params object[] scopedObjects)
|
||||||
{
|
{
|
||||||
var svcContainer = Service<IoC.Internal.ServiceContainer>.Get();
|
return this.plugin.ServiceScope!.InjectPropertiesAsync(
|
||||||
|
instance,
|
||||||
var realScopedObjects = new object[scopedObjects.Length + 1];
|
this.GetPublicIocScopes(scopedObjects)).GetAwaiter().GetResult();
|
||||||
realScopedObjects[0] = this;
|
|
||||||
Array.Copy(scopedObjects, 0, realScopedObjects, 1, scopedObjects.Length);
|
|
||||||
|
|
||||||
return svcContainer.InjectProperties(instance, realScopedObjects).GetAwaiter().GetResult();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
@ -476,7 +469,7 @@ public sealed class DalamudPluginInterface : IDisposable
|
||||||
void IDisposable.Dispose()
|
void IDisposable.Dispose()
|
||||||
{
|
{
|
||||||
this.UiBuilder.ExplicitDispose();
|
this.UiBuilder.ExplicitDispose();
|
||||||
Service<ChatGui>.Get().RemoveChatLinkHandler(this.pluginName);
|
Service<ChatGui>.Get().RemoveChatLinkHandler(this.plugin.InternalName);
|
||||||
Service<Localization>.Get().LocalizationChanged -= this.OnLocalizationChanged;
|
Service<Localization>.Get().LocalizationChanged -= this.OnLocalizationChanged;
|
||||||
Service<DalamudConfiguration>.Get().DalamudConfigurationSaved -= this.OnDalamudConfigurationSaved;
|
Service<DalamudConfiguration>.Get().DalamudConfigurationSaved -= this.OnDalamudConfigurationSaved;
|
||||||
}
|
}
|
||||||
|
|
@ -510,4 +503,9 @@ public sealed class DalamudPluginInterface : IDisposable
|
||||||
{
|
{
|
||||||
this.GeneralChatType = dalamudConfiguration.GeneralChatType;
|
this.GeneralChatType = dalamudConfiguration.GeneralChatType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private object[] GetPublicIocScopes(IEnumerable<object> scopedObjects)
|
||||||
|
{
|
||||||
|
return scopedObjects.Append(this).ToArray();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ using Dalamud.Game.Gui.Dtr;
|
||||||
using Dalamud.Interface.GameFonts;
|
using Dalamud.Interface.GameFonts;
|
||||||
using Dalamud.Interface.Internal;
|
using Dalamud.Interface.Internal;
|
||||||
using Dalamud.IoC.Internal;
|
using Dalamud.IoC.Internal;
|
||||||
|
using Dalamud.Logging;
|
||||||
using Dalamud.Logging.Internal;
|
using Dalamud.Logging.Internal;
|
||||||
using Dalamud.Plugin.Internal.Exceptions;
|
using Dalamud.Plugin.Internal.Exceptions;
|
||||||
using Dalamud.Plugin.Internal.Loader;
|
using Dalamud.Plugin.Internal.Loader;
|
||||||
|
|
@ -180,10 +181,15 @@ internal class LocalPlugin : IDisposable
|
||||||
public AssemblyName? AssemblyName { get; private set; }
|
public AssemblyName? AssemblyName { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the plugin name, directly from the plugin or if it is not loaded from the manifest.
|
/// Gets the plugin name from the manifest.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Name => this.Manifest.Name;
|
public string Name => this.Manifest.Name;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the plugin internal name from the manifest.
|
||||||
|
/// </summary>
|
||||||
|
public string InternalName => this.Manifest.Name;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets an optional reason, if the plugin is banned.
|
/// Gets an optional reason, if the plugin is banned.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -238,6 +244,11 @@ internal class LocalPlugin : IDisposable
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsDev => this is LocalDevPlugin;
|
public bool IsDev => this is LocalDevPlugin;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the service scope for this plugin.
|
||||||
|
/// </summary>
|
||||||
|
public IServiceScope? ServiceScope { get; private set; }
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
|
|
@ -259,6 +270,9 @@ internal class LocalPlugin : IDisposable
|
||||||
this.DalamudInterface?.ExplicitDispose();
|
this.DalamudInterface?.ExplicitDispose();
|
||||||
this.DalamudInterface = null;
|
this.DalamudInterface = null;
|
||||||
|
|
||||||
|
this.ServiceScope?.Dispose();
|
||||||
|
this.ServiceScope = null;
|
||||||
|
|
||||||
this.pluginType = null;
|
this.pluginType = null;
|
||||||
this.pluginAssembly = null;
|
this.pluginAssembly = null;
|
||||||
|
|
||||||
|
|
@ -410,17 +424,20 @@ internal class LocalPlugin : IDisposable
|
||||||
PluginManager.PluginLocations[this.pluginType.Assembly.FullName] = new PluginPatchData(this.DllFile);
|
PluginManager.PluginLocations[this.pluginType.Assembly.FullName] = new PluginPatchData(this.DllFile);
|
||||||
|
|
||||||
this.DalamudInterface =
|
this.DalamudInterface =
|
||||||
new DalamudPluginInterface(this.pluginAssembly.GetName().Name!, this.DllFile, reason, this.IsDev, this.Manifest);
|
new DalamudPluginInterface(this, reason);
|
||||||
|
|
||||||
|
this.ServiceScope = ioc.GetScope();
|
||||||
|
this.ServiceScope.RegisterPrivateScopes(this); // Add this LocalPlugin as a private scope, so services can get it
|
||||||
|
|
||||||
if (this.Manifest.LoadSync && this.Manifest.LoadRequiredState is 0 or 1)
|
if (this.Manifest.LoadSync && this.Manifest.LoadRequiredState is 0 or 1)
|
||||||
{
|
{
|
||||||
this.instance = await framework.RunOnFrameworkThread(
|
this.instance = await framework.RunOnFrameworkThread(
|
||||||
() => ioc.CreateAsync(this.pluginType!, this.DalamudInterface!)) as IDalamudPlugin;
|
() => this.ServiceScope.CreateAsync(this.pluginType!, this.DalamudInterface!)) as IDalamudPlugin;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
this.instance =
|
this.instance =
|
||||||
await ioc.CreateAsync(this.pluginType!, this.DalamudInterface!) as IDalamudPlugin;
|
await this.ServiceScope.CreateAsync(this.pluginType!, this.DalamudInterface!) as IDalamudPlugin;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.instance == null)
|
if (this.instance == null)
|
||||||
|
|
@ -466,6 +483,7 @@ internal class LocalPlugin : IDisposable
|
||||||
{
|
{
|
||||||
var configuration = Service<DalamudConfiguration>.Get();
|
var configuration = Service<DalamudConfiguration>.Get();
|
||||||
var framework = Service<Framework>.GetNullable();
|
var framework = Service<Framework>.GetNullable();
|
||||||
|
var ioc = await Service<ServiceContainer>.GetAsync();
|
||||||
|
|
||||||
await this.pluginLoadStateLock.WaitAsync();
|
await this.pluginLoadStateLock.WaitAsync();
|
||||||
try
|
try
|
||||||
|
|
@ -504,6 +522,9 @@ internal class LocalPlugin : IDisposable
|
||||||
this.DalamudInterface?.ExplicitDispose();
|
this.DalamudInterface?.ExplicitDispose();
|
||||||
this.DalamudInterface = null;
|
this.DalamudInterface = null;
|
||||||
|
|
||||||
|
this.ServiceScope?.Dispose();
|
||||||
|
this.ServiceScope = null;
|
||||||
|
|
||||||
this.pluginType = null;
|
this.pluginType = null;
|
||||||
this.pluginAssembly = null;
|
this.pluginAssembly = null;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,11 @@ internal static class ServiceManager
|
||||||
/// </summary>
|
/// </summary>
|
||||||
BlockingEarlyLoadedService = 1 << 2,
|
BlockingEarlyLoadedService = 1 << 2,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Service that is only instantiable via scopes.
|
||||||
|
/// </summary>
|
||||||
|
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>
|
||||||
|
|
@ -133,10 +138,12 @@ internal static class ServiceManager
|
||||||
foreach (var serviceType in Assembly.GetExecutingAssembly().GetTypes())
|
foreach (var serviceType in Assembly.GetExecutingAssembly().GetTypes())
|
||||||
{
|
{
|
||||||
var serviceKind = serviceType.GetServiceKind();
|
var serviceKind = serviceType.GetServiceKind();
|
||||||
if (serviceKind == ServiceKind.None)
|
if (serviceKind is ServiceKind.None or ServiceKind.ScopedService)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
Debug.Assert(!serviceKind.HasFlag(ServiceKind.ManualService), "Regular services should never end up here");
|
Debug.Assert(
|
||||||
|
!serviceKind.HasFlag(ServiceKind.ManualService) && !serviceKind.HasFlag(ServiceKind.ScopedService),
|
||||||
|
"Regular and scoped services should never be loaded early");
|
||||||
|
|
||||||
var genericWrappedServiceType = typeof(Service<>).MakeGenericType(serviceType);
|
var genericWrappedServiceType = typeof(Service<>).MakeGenericType(serviceType);
|
||||||
|
|
||||||
|
|
@ -390,6 +397,9 @@ internal static class ServiceManager
|
||||||
if (attr.IsAssignableTo(typeof(EarlyLoadedService)))
|
if (attr.IsAssignableTo(typeof(EarlyLoadedService)))
|
||||||
return ServiceKind.EarlyLoadedService;
|
return ServiceKind.EarlyLoadedService;
|
||||||
|
|
||||||
|
if (attr.IsAssignableTo(typeof(ScopedService)))
|
||||||
|
return ServiceKind.ScopedService;
|
||||||
|
|
||||||
return ServiceKind.ManualService;
|
return ServiceKind.ManualService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -435,6 +445,15 @@ internal static class ServiceManager
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates that the class is a service that will be created specifically for a
|
||||||
|
/// service scope, and that it cannot be created outside of a scope.
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Class)]
|
||||||
|
public class ScopedService : Service
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Indicates that the method should be called when the services given in the constructor are ready.
|
/// Indicates that the method should be called when the services given in the constructor are ready.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue