- Added ISystem (automatically run services) service type.

This commit is contained in:
MapleWheels
2026-02-10 14:28:22 -05:00
parent 30149b504d
commit 6b9e48f96a
4 changed files with 74 additions and 31 deletions

View File

@@ -215,7 +215,7 @@ namespace Barotrauma
servicesProvider.RegisterServiceType<IPackageManagementServiceConfig, PackageManagementServiceConfig>(ServiceLifetime.Singleton);
// gen IL
servicesProvider.Compile();
servicesProvider.CompileAndRun();
return servicesProvider;
}

View File

@@ -1,5 +1,8 @@
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;
@@ -12,7 +15,14 @@ public class ServicesProvider : IServicesProvider
{
private ServiceContainer _serviceContainerInst;
private ServiceContainer ServiceContainer => _serviceContainerInst;
/// <summary>
/// Definition: [Key: InterfaceType, Value: ConcreteTypes]
/// </summary>
private readonly ConcurrentDictionary<Type, ConcurrentBag<Type>> _systemTypeDefs = new();
/// <summary>
/// Definition: [Key: ConcreteType, Value: TypeInstance]
/// </summary>
private readonly ConcurrentDictionary<Type, ISystem> _systemInstances = new();
private readonly ReaderWriterLockSlim _serviceLock = new();
public ServicesProvider()
@@ -27,6 +37,13 @@ public class ServicesProvider : IServicesProvider
public void RegisterServiceType<TSvcInterface, TService>(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<Type>());
}
if (lifetimeInstance is null)
{
switch (lifetime)
@@ -54,7 +71,6 @@ public class ServicesProvider : IServicesProvider
ServiceContainer.Register<TSvcInterface, TService>(lifetimeInstance);
else
ServiceContainer.Register<TSvcInterface, TService>();
OnServiceRegistered?.Invoke(typeof(TSvcInterface), typeof(TService));
}
finally
{
@@ -70,6 +86,13 @@ public class ServicesProvider : IServicesProvider
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<Type>());
}
if (lifetimeInstance is null)
{
switch (lifetime)
@@ -94,7 +117,6 @@ public class ServicesProvider : IServicesProvider
{
_serviceLock.EnterReadLock();
ServiceContainer.Register<TSvcInterface, TService>(name, lifetimeInstance);
OnServiceRegistered?.Invoke(typeof(TSvcInterface), typeof(TService));
}
finally
{
@@ -115,20 +137,26 @@ public class ServicesProvider : IServicesProvider
}
}
public void Compile()
public void CompileAndRun()
{
try
{
_serviceLock.EnterReadLock();
_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.ExitReadLock();
_serviceLock.ExitWriteLock();
}
}
public event Action<Type, Type> OnServiceRegistered;
public void InjectServices<T>(T inst) where T : class
{
@@ -209,19 +237,32 @@ public class ServicesProvider : IServicesProvider
}
}
[MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.NoInlining)]
[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 DisposeAllServices().");
$"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();
}

View File

@@ -3,6 +3,12 @@ using Microsoft.Toolkit.Diagnostics;
namespace Barotrauma.LuaCs;
/// <summary>
/// Represents a <see cref="IReusableService"/> that is automatically instantiated at startup for the lifetime of the
/// <see cref="IServiceProvider"/> instance.
/// </summary>
public interface ISystem : IReusableService { }
/// <summary>
/// Defines a service that can be reset to it's post-constructor state and reused without needing to be disposed.
/// Intended for persistent services.

View File

@@ -6,7 +6,8 @@ using LightInject;
namespace Barotrauma.LuaCs;
/// <summary>
/// Provides instancing and management of IServices.
/// Provides instancing and management of <see cref="IService"/>, <see cref="IReusableService"/>, and <see cref="ISystem"/>
/// instances.
/// </summary>
public interface IServicesProvider
{
@@ -15,28 +16,23 @@ public interface IServicesProvider
/// <summary>
/// Registers a type as a service for a given interface.
/// </summary>
/// <param name="lifetime"></param>
/// <param name="lifetimeInstance"></param>
/// <typeparam name="TSvcInterface"></typeparam>
/// <typeparam name="TService"></typeparam>
/// <remarks>NOTE: <see cref="ISystem"/> services are forced to <see cref="ServiceLifetime.Singleton"/></remarks>
/// <param name="lifetime">The <see cref="ServiceLifetime"/> of the service when requested.</param>
/// <param name="lifetimeInstance">Custom lifetime instance.</param>
/// <typeparam name="TSvcInterface">Service interface.</typeparam>
/// <typeparam name="TService">Implementing service type.</typeparam>
void RegisterServiceType<TSvcInterface, TService>(ServiceLifetime lifetime, ILifetime lifetimeInstance = null) where TSvcInterface : class, IService where TService : class, IService, TSvcInterface;
/// <summary>
/// Registers a type as a service for a given interface that can be requested by name.
/// </summary>
/// <param name="name"></param>
/// <param name="lifetime"></param>
/// <param name="lifetimeInstance"></param>
/// <typeparam name="TSvcInterface"></typeparam>
/// <typeparam name="TService"></typeparam>
/// <remarks>NOTE: <see cref="ISystem"/> services are forced to <see cref="ServiceLifetime.Singleton"/></remarks>
/// <param name="name">Name of the service for lookup.</param>
/// <param name="lifetime">The <see cref="ServiceLifetime"/> of the service when requested.</param>
/// <param name="lifetimeInstance">Custom lifetime instance.</param>
/// <typeparam name="TSvcInterface">Service interface.</typeparam>
/// <typeparam name="TService">Implementing service type.</typeparam>
void RegisterServiceType<TSvcInterface, TService>(string name, ServiceLifetime lifetime, ILifetime lifetimeInstance = null) where TSvcInterface : class, IService where TService : class, IService, TSvcInterface;
/// <summary>
/// Called whenever a new service type for a given interface is implemented.
/// Args[0]: Interface type
/// Args[1]: Implementing type
/// </summary>
event System.Action<Type, Type> OnServiceRegistered;
/// <summary>
/// Registers a factory for resolving the service type.
@@ -46,14 +42,14 @@ public interface IServicesProvider
void RegisterServiceResolver<TSvcInterface>(Func<ServiceContainer, TSvcInterface> factory) where TSvcInterface : class, IService;
/// <summary>
/// Runs compilation of registered services.
/// Compiles/Generates IL for registered services and instantiates all registered <see cref="ISystem"/> types.
/// </summary>
public void Compile();
public void CompileAndRun();
#endregion
#region Services_Instancing_Injection
/// <summary>
/// Injects services into the properties of already instanced objects.
/// </summary>