Files
Nitrox/NitroxModel/Core/NitroxServiceLocator.cs
2025-07-06 00:23:46 +02:00

160 lines
6.3 KiB
C#

using System;
using Autofac;
using Autofac.Builder;
using NitroxModel.DataStructures.Util;
namespace NitroxModel.Core;
/// <summary>
/// Dependency Injection (DI) class for resolving types as defined in the DI registrar, implementing <see cref="IAutoFacRegistrar" />.
/// </summary>
public static class NitroxServiceLocator
{
private static IContainer DependencyContainer { get; set; }
private static ILifetimeScope CurrentLifetimeScope { get; set; }
public static event EventHandler LifetimeScopeEnded;
public static void InitializeDependencyContainer(params IAutoFacRegistrar[] registrars)
{
ContainerBuilder builder = new();
foreach (IAutoFacRegistrar registrar in registrars)
{
registrar.RegisterDependencies(builder);
}
// IgnoreStartableComponents - Prevents "phantom" executions of the Start() method
// on a MonoBehaviour because someone accidentally did something funky with a DI registration.
DependencyContainer = builder.Build(ContainerBuildOptions.IgnoreStartableComponents);
}
/// <summary>
/// Starts a new life time scope. A single instance per registered service will be returned while this scope is active.
/// Services can scoped to this life time using <see cref="IRegistrationBuilder{TLimit,TActivatorData,TRegistrationStyle}.InstancePerLifetimeScope" />.
/// </summary>
/// <remarks>
/// A life time scope should be created when the game leaves the main menu and loads a level with multiplayer.
/// It should end when the game process unloads the level (e.g. player returns to the main menu).
/// </remarks>
public static void BeginNewLifetimeScope()
{
if (DependencyContainer == null)
{
throw new InvalidOperationException("You must install an Autofac container before initializing a new lifetime scope.");
}
CurrentLifetimeScope?.Dispose();
CurrentLifetimeScope = DependencyContainer.BeginLifetimeScope();
}
/// <summary>
/// Ends the life time scoped services that were registered using <see cref="IRegistrationBuilder{TLimit,TActivatorData,TRegistrationStyle}.InstancePerLifetimeScope" />.
/// </summary>
public static void EndCurrentLifetimeScope()
{
CurrentLifetimeScope?.Dispose();
OnLifetimeScopeEnded();
}
/// <summary>
/// Only locates the service in the container, pre-lifetime scope.
/// </summary>
public static T LocateServicePreLifetime<T>()
{
return DependencyContainer.Resolve<T>();
}
/// <summary>
/// Retrieves a service which was registered into the DI container. Creates a new instance if required.<br />
/// </summary>
/// <remarks>
/// This method should not be used if the constructor is available for defining a parameter where its type is the service to inject.
/// For Unity monobehaviours the constructor is used by Unity and cannot be used to inject services. In this case, use this method.
/// </remarks>
public static T LocateService<T>()
where T : class
{
CheckServiceResolutionViability();
return CurrentLifetimeScope.Resolve<T>();
}
/// <summary>
/// Non-generic alternative to <see cref="LocateService{T}" />.
/// </summary>
public static object LocateService(Type serviceType)
{
CheckServiceResolutionViability();
return CurrentLifetimeScope.Resolve(serviceType);
}
/// <summary>
/// Tries to locate the service if it exists. Can return an <see cref="Optional{T}" /> without a value.
/// </summary>
/// <typeparam name="T">Type of service to try to locate.</typeparam>
/// <returns>Optional that might or might not hold the service instance.</returns>
public static Optional<T> LocateOptionalService<T>() where T : class
{
CheckServiceResolutionViability();
return Optional.OfNullable(CurrentLifetimeScope.ResolveOptional<T>());
}
/// <summary>
/// Tries to locate the service if it exists. Can return an <see cref="Optional{T}" /> without a value.
/// </summary>
/// <param name="serviceType">Type of service to try to locate.</param>
/// <returns>Optional that might or might not hold the service instance.</returns>
public static Optional<object> LocateOptionalService(Type serviceType)
{
CheckServiceResolutionViability();
return Optional.OfNullable(CurrentLifetimeScope.ResolveOptional(serviceType));
}
/// <summary>
/// Throws if a service is asked for but without a proper life time scope.
/// </summary>
private static void CheckServiceResolutionViability()
{
if (DependencyContainer == null)
{
throw new InvalidOperationException("You must install an Autofac container before resolving dependencies.");
}
if (CurrentLifetimeScope == null)
{
throw new InvalidOperationException("You must begin a new lifetime scope before resolving dependencies.");
}
}
private static void OnLifetimeScopeEnded() => LifetimeScopeEnded?.Invoke(null, EventArgs.Empty);
/// <summary>
/// Generic static class to cache type with very fast lookups. Only use for singleton types.
/// </summary>
/// <typeparam name="T">Type in the cache, should be singleton.</typeparam>
public static class Cache<T> where T : class
{
private static T value;
public static T Value => value ??= LocateServiceAndRegister();
public static T ValuePreLifetime => value ??= LocateServicePreLifetimeAndRegister();
private static T LocateServiceAndRegister()
{
LifetimeScopeEnded += Invalidate;
return LocateService<T>();
}
private static T LocateServicePreLifetimeAndRegister()
{
LifetimeScopeEnded += Invalidate;
return LocateServicePreLifetime<T>();
}
/// <summary>
/// Invalidates the cache for type <see cref="T" />. The next <see cref="Value" /> access will request from <see cref="NitroxServiceLocator" /> again.
/// </summary>
private static void Invalidate(object _, EventArgs __)
{
value = null;
LifetimeScopeEnded -= Invalidate;
}
}
}