mirror of
https://github.com/goatcorp/Dalamud.git
synced 2026-01-01 13:23:40 +01:00
chore: convert Dalamud to file-scoped namespaces
This commit is contained in:
parent
b093323acc
commit
987ff8dc8f
368 changed files with 55081 additions and 55450 deletions
|
|
@ -9,253 +9,252 @@ using Dalamud.IoC.Internal;
|
|||
using Dalamud.Utility.Timing;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Dalamud
|
||||
namespace Dalamud;
|
||||
|
||||
/// <summary>
|
||||
/// Basic service locator.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Only used internally within Dalamud, if plugins need access to things it should be _only_ via DI.
|
||||
/// </remarks>
|
||||
/// <typeparam name="T">The class you want to store in the service locator.</typeparam>
|
||||
internal static class Service<T> where T : IServiceType
|
||||
{
|
||||
/// <summary>
|
||||
/// Basic service locator.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Only used internally within Dalamud, if plugins need access to things it should be _only_ via DI.
|
||||
/// </remarks>
|
||||
/// <typeparam name="T">The class you want to store in the service locator.</typeparam>
|
||||
internal static class Service<T> where T : IServiceType
|
||||
private static TaskCompletionSource<T> instanceTcs = new();
|
||||
|
||||
static Service()
|
||||
{
|
||||
private static TaskCompletionSource<T> instanceTcs = new();
|
||||
var exposeToPlugins = typeof(T).GetCustomAttribute<PluginInterfaceAttribute>() != null;
|
||||
if (exposeToPlugins)
|
||||
ServiceManager.Log.Debug("Service<{0}>: Static ctor called; will be exposed to plugins", typeof(T).Name);
|
||||
else
|
||||
ServiceManager.Log.Debug("Service<{0}>: Static ctor called", typeof(T).Name);
|
||||
|
||||
static Service()
|
||||
{
|
||||
var exposeToPlugins = typeof(T).GetCustomAttribute<PluginInterfaceAttribute>() != null;
|
||||
if (exposeToPlugins)
|
||||
ServiceManager.Log.Debug("Service<{0}>: Static ctor called; will be exposed to plugins", typeof(T).Name);
|
||||
else
|
||||
ServiceManager.Log.Debug("Service<{0}>: Static ctor called", typeof(T).Name);
|
||||
if (exposeToPlugins)
|
||||
Service<ServiceContainer>.Get().RegisterSingleton(instanceTcs.Task);
|
||||
}
|
||||
|
||||
if (exposeToPlugins)
|
||||
Service<ServiceContainer>.Get().RegisterSingleton(instanceTcs.Task);
|
||||
}
|
||||
/// <summary>
|
||||
/// Specifies how to handle the cases of failed services when calling <see cref="Service{T}.GetNullable"/>.
|
||||
/// </summary>
|
||||
public enum ExceptionPropagationMode
|
||||
{
|
||||
/// <summary>
|
||||
/// Propagate all exceptions.
|
||||
/// </summary>
|
||||
PropagateAll,
|
||||
|
||||
/// <summary>
|
||||
/// Specifies how to handle the cases of failed services when calling <see cref="Service{T}.GetNullable"/>.
|
||||
/// Propagate all exceptions, except for <see cref="UnloadedException"/>.
|
||||
/// </summary>
|
||||
public enum ExceptionPropagationMode
|
||||
{
|
||||
/// <summary>
|
||||
/// Propagate all exceptions.
|
||||
/// </summary>
|
||||
PropagateAll,
|
||||
|
||||
/// <summary>
|
||||
/// Propagate all exceptions, except for <see cref="UnloadedException"/>.
|
||||
/// </summary>
|
||||
PropagateNonUnloaded,
|
||||
|
||||
/// <summary>
|
||||
/// Treat all exceptions as null.
|
||||
/// </summary>
|
||||
None,
|
||||
}
|
||||
PropagateNonUnloaded,
|
||||
|
||||
/// <summary>
|
||||
/// Sets the type in the service locator to the given object.
|
||||
/// Treat all exceptions as null.
|
||||
/// </summary>
|
||||
/// <param name="obj">Object to set.</param>
|
||||
public static void Provide(T obj)
|
||||
{
|
||||
instanceTcs.SetResult(obj);
|
||||
ServiceManager.Log.Debug("Service<{0}>: Provided", typeof(T).Name);
|
||||
}
|
||||
None,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the service load state to failure.
|
||||
/// </summary>
|
||||
/// <param name="exception">The exception.</param>
|
||||
public static void ProvideException(Exception exception)
|
||||
{
|
||||
ServiceManager.Log.Error(exception, "Service<{0}>: Error", typeof(T).Name);
|
||||
instanceTcs.SetException(exception);
|
||||
}
|
||||
/// <summary>
|
||||
/// Sets the type in the service locator to the given object.
|
||||
/// </summary>
|
||||
/// <param name="obj">Object to set.</param>
|
||||
public static void Provide(T obj)
|
||||
{
|
||||
instanceTcs.SetResult(obj);
|
||||
ServiceManager.Log.Debug("Service<{0}>: Provided", typeof(T).Name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pull the instance out of the service locator, waiting if necessary.
|
||||
/// </summary>
|
||||
/// <returns>The object.</returns>
|
||||
public static T Get()
|
||||
{
|
||||
if (!instanceTcs.Task.IsCompleted)
|
||||
instanceTcs.Task.Wait();
|
||||
/// <summary>
|
||||
/// Sets the service load state to failure.
|
||||
/// </summary>
|
||||
/// <param name="exception">The exception.</param>
|
||||
public static void ProvideException(Exception exception)
|
||||
{
|
||||
ServiceManager.Log.Error(exception, "Service<{0}>: Error", typeof(T).Name);
|
||||
instanceTcs.SetException(exception);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pull the instance out of the service locator, waiting if necessary.
|
||||
/// </summary>
|
||||
/// <returns>The object.</returns>
|
||||
public static T Get()
|
||||
{
|
||||
if (!instanceTcs.Task.IsCompleted)
|
||||
instanceTcs.Task.Wait();
|
||||
return instanceTcs.Task.Result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pull the instance out of the service locator, waiting if necessary.
|
||||
/// </summary>
|
||||
/// <returns>The object.</returns>
|
||||
[UsedImplicitly]
|
||||
public static Task<T> GetAsync() => instanceTcs.Task;
|
||||
|
||||
/// <summary>
|
||||
/// Attempt to pull the instance out of the service locator.
|
||||
/// </summary>
|
||||
/// <param name="propagateException">Specifies which exceptions to propagate.</param>
|
||||
/// <returns>The object if registered, null otherwise.</returns>
|
||||
public static T? GetNullable(ExceptionPropagationMode propagateException = ExceptionPropagationMode.PropagateNonUnloaded)
|
||||
{
|
||||
if (instanceTcs.Task.IsCompletedSuccessfully)
|
||||
return instanceTcs.Task.Result;
|
||||
if (instanceTcs.Task.IsFaulted && propagateException != ExceptionPropagationMode.None)
|
||||
{
|
||||
if (propagateException == ExceptionPropagationMode.PropagateNonUnloaded
|
||||
&& instanceTcs.Task.Exception!.InnerExceptions.FirstOrDefault() is UnloadedException)
|
||||
return default;
|
||||
throw instanceTcs.Task.Exception!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pull the instance out of the service locator, waiting if necessary.
|
||||
/// </summary>
|
||||
/// <returns>The object.</returns>
|
||||
[UsedImplicitly]
|
||||
public static Task<T> GetAsync() => instanceTcs.Task;
|
||||
return default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempt to pull the instance out of the service locator.
|
||||
/// </summary>
|
||||
/// <param name="propagateException">Specifies which exceptions to propagate.</param>
|
||||
/// <returns>The object if registered, null otherwise.</returns>
|
||||
public static T? GetNullable(ExceptionPropagationMode propagateException = ExceptionPropagationMode.PropagateNonUnloaded)
|
||||
/// <summary>
|
||||
/// Gets an enumerable containing Service<T>s that are required for this Service to initialize without blocking.
|
||||
/// </summary>
|
||||
/// <returns>List of dependency services.</returns>
|
||||
[UsedImplicitly]
|
||||
public static List<Type> GetDependencyServices()
|
||||
{
|
||||
var res = new List<Type>();
|
||||
res.AddRange(GetServiceConstructor()
|
||||
.GetParameters()
|
||||
.Select(x => x.ParameterType));
|
||||
res.AddRange(typeof(T)
|
||||
.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
|
||||
.Select(x => x.FieldType)
|
||||
.Where(x => x.GetCustomAttribute<ServiceManager.ServiceDependency>(true) != null));
|
||||
return res
|
||||
.Distinct()
|
||||
.Select(x => typeof(Service<>).MakeGenericType(x))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
[UsedImplicitly]
|
||||
private static Task<T> StartLoader()
|
||||
{
|
||||
if (instanceTcs.Task.IsCompleted)
|
||||
throw new InvalidOperationException($"{typeof(T).Name} is already loaded or disposed.");
|
||||
|
||||
var attr = typeof(T).GetCustomAttribute<ServiceManager.Service>(true)?.GetType();
|
||||
if (attr?.IsAssignableTo(typeof(ServiceManager.EarlyLoadedService)) != true)
|
||||
throw new InvalidOperationException($"{typeof(T).Name} is not an EarlyLoadedService");
|
||||
|
||||
return Task.Run(Timings.AttachTimingHandle(async () =>
|
||||
{
|
||||
if (instanceTcs.Task.IsCompletedSuccessfully)
|
||||
return instanceTcs.Task.Result;
|
||||
if (instanceTcs.Task.IsFaulted && propagateException != ExceptionPropagationMode.None)
|
||||
ServiceManager.Log.Debug("Service<{0}>: Begin construction", typeof(T).Name);
|
||||
try
|
||||
{
|
||||
if (propagateException == ExceptionPropagationMode.PropagateNonUnloaded
|
||||
&& instanceTcs.Task.Exception!.InnerExceptions.FirstOrDefault() is UnloadedException)
|
||||
return default;
|
||||
throw instanceTcs.Task.Exception!;
|
||||
}
|
||||
var instance = await ConstructObject();
|
||||
instanceTcs.SetResult(instance);
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an enumerable containing Service<T>s that are required for this Service to initialize without blocking.
|
||||
/// </summary>
|
||||
/// <returns>List of dependency services.</returns>
|
||||
[UsedImplicitly]
|
||||
public static List<Type> GetDependencyServices()
|
||||
{
|
||||
var res = new List<Type>();
|
||||
res.AddRange(GetServiceConstructor()
|
||||
.GetParameters()
|
||||
.Select(x => x.ParameterType));
|
||||
res.AddRange(typeof(T)
|
||||
.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
|
||||
.Select(x => x.FieldType)
|
||||
.Where(x => x.GetCustomAttribute<ServiceManager.ServiceDependency>(true) != null));
|
||||
return res
|
||||
.Distinct()
|
||||
.Select(x => typeof(Service<>).MakeGenericType(x))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
[UsedImplicitly]
|
||||
private static Task<T> StartLoader()
|
||||
{
|
||||
if (instanceTcs.Task.IsCompleted)
|
||||
throw new InvalidOperationException($"{typeof(T).Name} is already loaded or disposed.");
|
||||
|
||||
var attr = typeof(T).GetCustomAttribute<ServiceManager.Service>(true)?.GetType();
|
||||
if (attr?.IsAssignableTo(typeof(ServiceManager.EarlyLoadedService)) != true)
|
||||
throw new InvalidOperationException($"{typeof(T).Name} is not an EarlyLoadedService");
|
||||
|
||||
return Task.Run(Timings.AttachTimingHandle(async () =>
|
||||
{
|
||||
ServiceManager.Log.Debug("Service<{0}>: Begin construction", typeof(T).Name);
|
||||
try
|
||||
foreach (var method in typeof(T).GetMethods(
|
||||
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
|
||||
{
|
||||
var instance = await ConstructObject();
|
||||
instanceTcs.SetResult(instance);
|
||||
if (method.GetCustomAttribute<ServiceManager.CallWhenServicesReady>(true) == null)
|
||||
continue;
|
||||
|
||||
foreach (var method in typeof(T).GetMethods(
|
||||
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
|
||||
{
|
||||
if (method.GetCustomAttribute<ServiceManager.CallWhenServicesReady>(true) == null)
|
||||
continue;
|
||||
|
||||
ServiceManager.Log.Debug("Service<{0}>: Calling {1}", typeof(T).Name, method.Name);
|
||||
var args = await Task.WhenAll(method.GetParameters().Select(
|
||||
x => ResolveServiceFromTypeAsync(x.ParameterType)));
|
||||
method.Invoke(instance, args);
|
||||
}
|
||||
|
||||
ServiceManager.Log.Debug("Service<{0}>: Construction complete", typeof(T).Name);
|
||||
return instance;
|
||||
ServiceManager.Log.Debug("Service<{0}>: Calling {1}", typeof(T).Name, method.Name);
|
||||
var args = await Task.WhenAll(method.GetParameters().Select(
|
||||
x => ResolveServiceFromTypeAsync(x.ParameterType)));
|
||||
method.Invoke(instance, args);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ServiceManager.Log.Error(e, "Service<{0}>: Construction failure", typeof(T).Name);
|
||||
instanceTcs.SetException(e);
|
||||
throw;
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
[UsedImplicitly]
|
||||
private static void Unset()
|
||||
{
|
||||
if (!instanceTcs.Task.IsCompletedSuccessfully)
|
||||
return;
|
||||
|
||||
var instance = instanceTcs.Task.Result;
|
||||
if (instance is IDisposable disposable)
|
||||
{
|
||||
ServiceManager.Log.Debug("Service<{0}>: Disposing", typeof(T).Name);
|
||||
try
|
||||
{
|
||||
disposable.Dispose();
|
||||
ServiceManager.Log.Debug("Service<{0}>: Disposed", typeof(T).Name);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ServiceManager.Log.Warning(e, "Service<{0}>: Dispose failure", typeof(T).Name);
|
||||
}
|
||||
ServiceManager.Log.Debug("Service<{0}>: Construction complete", typeof(T).Name);
|
||||
return instance;
|
||||
}
|
||||
else
|
||||
catch (Exception e)
|
||||
{
|
||||
ServiceManager.Log.Debug("Service<{0}>: Unset", typeof(T).Name);
|
||||
ServiceManager.Log.Error(e, "Service<{0}>: Construction failure", typeof(T).Name);
|
||||
instanceTcs.SetException(e);
|
||||
throw;
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
instanceTcs = new TaskCompletionSource<T>();
|
||||
instanceTcs.SetException(new UnloadedException());
|
||||
}
|
||||
[UsedImplicitly]
|
||||
private static void Unset()
|
||||
{
|
||||
if (!instanceTcs.Task.IsCompletedSuccessfully)
|
||||
return;
|
||||
|
||||
private static async Task<object?> ResolveServiceFromTypeAsync(Type type)
|
||||
var instance = instanceTcs.Task.Result;
|
||||
if (instance is IDisposable disposable)
|
||||
{
|
||||
var task = (Task)typeof(Service<>)
|
||||
.MakeGenericType(type)
|
||||
.InvokeMember(
|
||||
"GetAsync",
|
||||
BindingFlags.InvokeMethod |
|
||||
BindingFlags.Static |
|
||||
BindingFlags.Public,
|
||||
null,
|
||||
null,
|
||||
null)!;
|
||||
await task;
|
||||
return typeof(Task<>).MakeGenericType(type)
|
||||
.GetProperty("Result", BindingFlags.Instance | BindingFlags.Public)!
|
||||
.GetValue(task);
|
||||
}
|
||||
|
||||
private static ConstructorInfo GetServiceConstructor()
|
||||
{
|
||||
const BindingFlags ctorBindingFlags =
|
||||
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic |
|
||||
BindingFlags.CreateInstance | BindingFlags.OptionalParamBinding;
|
||||
return typeof(T)
|
||||
.GetConstructors(ctorBindingFlags)
|
||||
.Single(x => x.GetCustomAttributes(typeof(ServiceManager.ServiceConstructor), true).Any());
|
||||
}
|
||||
|
||||
private static async Task<T> ConstructObject()
|
||||
{
|
||||
var ctor = GetServiceConstructor();
|
||||
var args = await Task.WhenAll(
|
||||
ctor.GetParameters().Select(x => ResolveServiceFromTypeAsync(x.ParameterType)));
|
||||
using (Timings.Start($"{typeof(T).Name} Construct"))
|
||||
ServiceManager.Log.Debug("Service<{0}>: Disposing", typeof(T).Name);
|
||||
try
|
||||
{
|
||||
return (T)ctor.Invoke(args)!;
|
||||
disposable.Dispose();
|
||||
ServiceManager.Log.Debug("Service<{0}>: Disposed", typeof(T).Name);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ServiceManager.Log.Warning(e, "Service<{0}>: Dispose failure", typeof(T).Name);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ServiceManager.Log.Debug("Service<{0}>: Unset", typeof(T).Name);
|
||||
}
|
||||
|
||||
instanceTcs = new TaskCompletionSource<T>();
|
||||
instanceTcs.SetException(new UnloadedException());
|
||||
}
|
||||
|
||||
private static async Task<object?> ResolveServiceFromTypeAsync(Type type)
|
||||
{
|
||||
var task = (Task)typeof(Service<>)
|
||||
.MakeGenericType(type)
|
||||
.InvokeMember(
|
||||
"GetAsync",
|
||||
BindingFlags.InvokeMethod |
|
||||
BindingFlags.Static |
|
||||
BindingFlags.Public,
|
||||
null,
|
||||
null,
|
||||
null)!;
|
||||
await task;
|
||||
return typeof(Task<>).MakeGenericType(type)
|
||||
.GetProperty("Result", BindingFlags.Instance | BindingFlags.Public)!
|
||||
.GetValue(task);
|
||||
}
|
||||
|
||||
private static ConstructorInfo GetServiceConstructor()
|
||||
{
|
||||
const BindingFlags ctorBindingFlags =
|
||||
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic |
|
||||
BindingFlags.CreateInstance | BindingFlags.OptionalParamBinding;
|
||||
return typeof(T)
|
||||
.GetConstructors(ctorBindingFlags)
|
||||
.Single(x => x.GetCustomAttributes(typeof(ServiceManager.ServiceConstructor), true).Any());
|
||||
}
|
||||
|
||||
private static async Task<T> ConstructObject()
|
||||
{
|
||||
var ctor = GetServiceConstructor();
|
||||
var args = await Task.WhenAll(
|
||||
ctor.GetParameters().Select(x => ResolveServiceFromTypeAsync(x.ParameterType)));
|
||||
using (Timings.Start($"{typeof(T).Name} Construct"))
|
||||
{
|
||||
return (T)ctor.Invoke(args)!;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exception thrown when service is attempted to be retrieved when it's unloaded.
|
||||
/// </summary>
|
||||
public class UnloadedException : InvalidOperationException
|
||||
{
|
||||
/// <summary>
|
||||
/// Exception thrown when service is attempted to be retrieved when it's unloaded.
|
||||
/// Initializes a new instance of the <see cref="UnloadedException"/> class.
|
||||
/// </summary>
|
||||
public class UnloadedException : InvalidOperationException
|
||||
public UnloadedException()
|
||||
: base("Service is unloaded.")
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="UnloadedException"/> class.
|
||||
/// </summary>
|
||||
public UnloadedException()
|
||||
: base("Service is unloaded.")
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue