using System; using Autofac; using Autofac.Builder; using NitroxModel.DataStructures.Util; namespace NitroxModel.Core; /// /// Dependency Injection (DI) class for resolving types as defined in the DI registrar, implementing . /// 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); } /// /// 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 . /// /// /// 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). /// 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(); } /// /// Ends the life time scoped services that were registered using . /// public static void EndCurrentLifetimeScope() { CurrentLifetimeScope?.Dispose(); OnLifetimeScopeEnded(); } /// /// Only locates the service in the container, pre-lifetime scope. /// public static T LocateServicePreLifetime() { return DependencyContainer.Resolve(); } /// /// Retrieves a service which was registered into the DI container. Creates a new instance if required.
///
/// /// 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. /// public static T LocateService() where T : class { CheckServiceResolutionViability(); return CurrentLifetimeScope.Resolve(); } /// /// Non-generic alternative to . /// public static object LocateService(Type serviceType) { CheckServiceResolutionViability(); return CurrentLifetimeScope.Resolve(serviceType); } /// /// Tries to locate the service if it exists. Can return an without a value. /// /// Type of service to try to locate. /// Optional that might or might not hold the service instance. public static Optional LocateOptionalService() where T : class { CheckServiceResolutionViability(); return Optional.OfNullable(CurrentLifetimeScope.ResolveOptional()); } /// /// Tries to locate the service if it exists. Can return an without a value. /// /// Type of service to try to locate. /// Optional that might or might not hold the service instance. public static Optional LocateOptionalService(Type serviceType) { CheckServiceResolutionViability(); return Optional.OfNullable(CurrentLifetimeScope.ResolveOptional(serviceType)); } /// /// Throws if a service is asked for but without a proper life time scope. /// 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); /// /// Generic static class to cache type with very fast lookups. Only use for singleton types. /// /// Type in the cache, should be singleton. public static class Cache 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(); } private static T LocateServicePreLifetimeAndRegister() { LifetimeScopeEnded += Invalidate; return LocateServicePreLifetime(); } /// /// Invalidates the cache for type . The next access will request from again. /// private static void Invalidate(object _, EventArgs __) { value = null; LifetimeScopeEnded -= Invalidate; } } }