using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Dalamud.IoC;
using Dalamud.IoC.Internal;
using Dalamud.Utility.Timing;
using JetBrains.Annotations;
namespace Dalamud;
///
/// Basic service locator.
///
///
/// Only used internally within Dalamud, if plugins need access to things it should be _only_ via DI.
///
/// The class you want to store in the service locator.
internal static class Service where T : IServiceType
{
private static TaskCompletionSource instanceTcs = new();
static Service()
{
var exposeToPlugins = typeof(T).GetCustomAttribute() != 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.Get().RegisterSingleton(instanceTcs.Task);
}
///
/// Specifies how to handle the cases of failed services when calling .
///
public enum ExceptionPropagationMode
{
///
/// Propagate all exceptions.
///
PropagateAll,
///
/// Propagate all exceptions, except for .
///
PropagateNonUnloaded,
///
/// Treat all exceptions as null.
///
None,
}
///
/// Sets the type in the service locator to the given object.
///
/// Object to set.
public static void Provide(T obj)
{
instanceTcs.SetResult(obj);
ServiceManager.Log.Debug("Service<{0}>: Provided", typeof(T).Name);
}
///
/// Sets the service load state to failure.
///
/// The exception.
public static void ProvideException(Exception exception)
{
ServiceManager.Log.Error(exception, "Service<{0}>: Error", typeof(T).Name);
instanceTcs.SetException(exception);
}
///
/// Pull the instance out of the service locator, waiting if necessary.
///
/// The object.
public static T Get()
{
if (!instanceTcs.Task.IsCompleted)
instanceTcs.Task.Wait();
return instanceTcs.Task.Result;
}
///
/// Pull the instance out of the service locator, waiting if necessary.
///
/// The object.
[UsedImplicitly]
public static Task GetAsync() => instanceTcs.Task;
///
/// Attempt to pull the instance out of the service locator.
///
/// Specifies which exceptions to propagate.
/// The object if registered, null otherwise.
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!;
}
return default;
}
///
/// Gets an enumerable containing Service<T>s that are required for this Service to initialize without blocking.
///
/// List of dependency services.
[UsedImplicitly]
public static List GetDependencyServices()
{
var res = new List();
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(true) != null));
res.AddRange(typeof(T)
.GetCustomAttributes()
.OfType()
.Select(x => x.GetType().GetGenericArguments().First()));
return res
.Distinct()
.Select(x => typeof(Service<>).MakeGenericType(x))
.ToList();
}
[UsedImplicitly]
private static Task StartLoader()
{
if (instanceTcs.Task.IsCompleted)
throw new InvalidOperationException($"{typeof(T).Name} is already loaded or disposed.");
var attr = typeof(T).GetCustomAttribute(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
{
var instance = await ConstructObject();
instanceTcs.SetResult(instance);
foreach (var method in typeof(T).GetMethods(
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
{
if (method.GetCustomAttribute(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;
}
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);
}
}
else
{
ServiceManager.Log.Debug("Service<{0}>: Unset", typeof(T).Name);
}
instanceTcs = new TaskCompletionSource();
instanceTcs.SetException(new UnloadedException());
}
private static async Task