using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Threading; using LightInject; namespace Barotrauma.LuaCs; public class ServicesProvider : IServicesProvider { private ServiceContainer _serviceContainerInst; private ServiceContainer ServiceContainer => _serviceContainerInst; /// /// Definition: [Key: InterfaceType, Value: ConcreteTypes] /// private readonly ConcurrentDictionary> _systemTypeDefs = new(); /// /// Definition: [Key: ConcreteType, Value: TypeInstance] /// private readonly ConcurrentDictionary _systemInstances = new(); private readonly ReaderWriterLockSlim _serviceLock = new(); public ServicesProvider() { _serviceContainerInst = new ServiceContainer(new ContainerOptions() { EnablePropertyInjection = false }); //_serviceContainerInst.Register((f) => this); } public void RegisterServiceType(ServiceLifetime lifetime, ILifetime lifetimeInstance = null) where TSvcInterface : class, IService where TService : class, IService, TSvcInterface { // ISystem services must run as a lifetime singleton if (typeof(TSvcInterface).IsAssignableTo(typeof(ISystem))) { lifetimeInstance = new PerContainerLifetime(); _systemTypeDefs.GetOrAdd(typeof(TSvcInterface), (type) => new ConcurrentBag()); } if (lifetimeInstance is null) { switch (lifetime) { case ServiceLifetime.Singleton: lifetimeInstance = new PerContainerLifetime(); break; case ServiceLifetime.PerThread: lifetimeInstance = new PerThreadLifetime(); break; // treat these as transient case ServiceLifetime.Transient: case ServiceLifetime.Invalid: case ServiceLifetime.Custom: default: lifetimeInstance = null; break; } } try { _serviceLock.EnterReadLock(); if (lifetimeInstance is not null) ServiceContainer.Register(lifetimeInstance); else ServiceContainer.Register(); } finally { _serviceLock.ExitReadLock(); } } public void RegisterServiceType(string name, ServiceLifetime lifetime, ILifetime lifetimeInstance = null) where TSvcInterface : class, IService where TService : class, IService, TSvcInterface { if (name.IsNullOrWhiteSpace()) { throw new ArgumentNullException($"Tried to register a service of type {typeof(TService).Name} but the name provided is null or empty." ); } // ISystem services must run as a lifetime singleton if (typeof(TSvcInterface).IsAssignableTo(typeof(ISystem))) { lifetimeInstance = new PerContainerLifetime(); _systemTypeDefs.GetOrAdd(typeof(TSvcInterface), (type) => new ConcurrentBag()); } if (lifetimeInstance is null) { switch (lifetime) { case ServiceLifetime.Singleton: lifetimeInstance = new PerContainerLifetime(); break; case ServiceLifetime.PerThread: lifetimeInstance = new PerThreadLifetime(); break; // treat these as transient case ServiceLifetime.Transient: case ServiceLifetime.Invalid: case ServiceLifetime.Custom: // lifetime should not be null here default: lifetimeInstance = new PerRequestLifeTime(); break; } } try { _serviceLock.EnterReadLock(); ServiceContainer.Register(name, lifetimeInstance); } finally { _serviceLock.ExitReadLock(); } } public void RegisterServiceResolver(Func factory) where TSvcInterface : class, IService { try { _serviceLock.EnterReadLock(); ServiceContainer.Register(f => factory(ServiceContainer)); } finally { _serviceLock.ExitReadLock(); } } public void CompileAndRun() { try { _serviceLock.EnterWriteLock(); ServiceContainer?.Compile(); foreach (var typeDef in _systemTypeDefs.Values.SelectMany(type => type)) { if (_systemInstances.ContainsKey(typeDef)) { continue; } _systemInstances[typeDef] = (ISystem)ServiceContainer?.TryGetInstance(typeDef); } } finally { _serviceLock.ExitWriteLock(); } } public void InjectServices(T inst) where T : class { try { _serviceLock.EnterReadLock(); ServiceContainer.InjectProperties(inst); } finally { _serviceLock.ExitReadLock(); } } public bool TryGetService(out TSvcInterface service) where TSvcInterface : class, IService { try { _serviceLock.EnterReadLock(); service = ServiceContainer.TryGetInstance(); return service is not null; } catch { service = null; return false; } finally { _serviceLock.ExitReadLock(); } } public TSvcInterface GetService() where TSvcInterface : class, IService { try { _serviceLock.EnterReadLock(); return ServiceContainer.GetInstance(); } finally { _serviceLock.ExitReadLock(); } } public bool TryGetService(string name, out TSvcInterface service) where TSvcInterface : class, IService { try { _serviceLock.EnterReadLock(); service = ServiceContainer.TryGetInstance(name); return service is not null; } catch { service = null; return false; } finally { _serviceLock.ExitReadLock(); } } public event Action OnServiceInstanced; public ImmutableArray GetAllServices() where TSvc : class, IService { try { _serviceLock.EnterReadLock(); return ServiceContainer.GetAllInstances().ToImmutableArray(); } finally { _serviceLock.ExitReadLock(); } } [MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.Synchronized)] public void DisposeAndReset() { // Plugins should never be allowed to execute this. if (Assembly.GetCallingAssembly() != Assembly.GetExecutingAssembly()) { throw new MethodAccessException( $"Assembly {Assembly.GetCallingAssembly().FullName} attempted to call {nameof(DisposeAndReset)}()."); } try { _serviceLock.EnterWriteLock(); foreach (var system in _systemInstances.Values) { try { system.Dispose(); } catch (Exception e) { // ignored, no logging services available. } } _systemInstances.Clear(); _systemTypeDefs.Clear(); _serviceContainerInst?.Dispose(); _serviceContainerInst = new ServiceContainer(); } finally { _serviceLock.ExitWriteLock(); } } } public class PerThreadLifetime : ILifetime { private readonly ThreadLocal _instance = new(); public object GetInstance(Func createInstance, Scope scope) { if (_instance.Value is null) { var inst = createInstance.Invoke(); // IDisposable dispatch if (inst is IDisposable disposable) { if (scope is null) { throw new InvalidOperationException("Attempt disposable object without a valid scope."); } scope.TrackInstance(disposable); } _instance.Value = inst; } return _instance.Value; } }