[In-Progress] Plugin system rewrite.

Game starts up, runs until unimplemented functions are reached without errors.
This commit is contained in:
Maplewheels
2025-12-28 07:23:58 -05:00
parent cce5bf26c8
commit 26b657a96f
8 changed files with 177 additions and 183 deletions
@@ -7,6 +7,7 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Security.AccessControl; using System.Security.AccessControl;
using Barotrauma.LuaCs.Services;
using Barotrauma.Networking; using Barotrauma.Networking;
using FluentResults; using FluentResults;
@@ -14,7 +15,8 @@ namespace Barotrauma.LuaCs.Data;
// --- Storage Service // --- Storage Service
public interface IStorageServiceConfig // TODO: Configs should not be services, add new registration path for them.
public interface IStorageServiceConfig : IService
{ {
string LocalModsDirectory { get; } string LocalModsDirectory { get; }
string WorkshopModsDirectory { get; } string WorkshopModsDirectory { get; }
@@ -176,10 +178,17 @@ public record StorageServiceConfig : IStorageServiceConfig, IStorageServiceConfi
return result.WithSuccess($"Whitelist updated."); return result.WithSuccess($"Whitelist updated.");
} }
public void Dispose()
{
// cannot be disposed.
}
public bool IsDisposed => false;
} }
// --- Config Service // --- Config Service
public interface IConfigServiceConfig public interface IConfigServiceConfig : IService
{ {
string LocalConfigPathPartial { get; } string LocalConfigPathPartial { get; }
string FileNamePattern { get; } string FileNamePattern { get; }
@@ -189,11 +198,16 @@ public record ConfigServiceConfig : IConfigServiceConfig
{ {
public string LocalConfigPathPartial => $"/Config/{FileNamePattern}.xml"; public string LocalConfigPathPartial => $"/Config/{FileNamePattern}.xml";
public string FileNamePattern => "<ConfigName>"; public string FileNamePattern => "<ConfigName>";
public void Dispose()
{
// ignored
}
public bool IsDisposed => false;
} }
// --- Lua Scripts Service // --- Lua Scripts Service
public interface ILuaScriptServicesConfig public interface ILuaScriptServicesConfig : IService
{ {
bool SafeLuaIOEnabled { get; } bool SafeLuaIOEnabled { get; }
bool UseCaching { get; } bool UseCaching { get; }
@@ -203,4 +217,10 @@ public record LuaScriptServicesConfig : ILuaScriptServicesConfig
{ {
public bool SafeLuaIOEnabled => true; public bool SafeLuaIOEnabled => true;
public bool UseCaching => true; public bool UseCaching => true;
public void Dispose()
{
// ignored
}
public bool IsDisposed => false;
} }
@@ -12,6 +12,7 @@ using Barotrauma.LuaCs.Events;
using Barotrauma.LuaCs.Services; using Barotrauma.LuaCs.Services;
using Barotrauma.LuaCs.Services.Compatibility; using Barotrauma.LuaCs.Services.Compatibility;
using Barotrauma.LuaCs.Services.Processing; using Barotrauma.LuaCs.Services.Processing;
using Barotrauma.LuaCs.Services.Safe;
using Barotrauma.Networking; using Barotrauma.Networking;
using FluentResults; using FluentResults;
using ImpromptuInterface; using ImpromptuInterface;
@@ -47,6 +48,7 @@ namespace Barotrauma
_servicesProvider.RegisterServiceType<IPackageManagementService, PackageManagementService>(ServiceLifetime.Singleton); _servicesProvider.RegisterServiceType<IPackageManagementService, PackageManagementService>(ServiceLifetime.Singleton);
_servicesProvider.RegisterServiceType<IPluginManagementService, PluginManagementService>(ServiceLifetime.Singleton); _servicesProvider.RegisterServiceType<IPluginManagementService, PluginManagementService>(ServiceLifetime.Singleton);
_servicesProvider.RegisterServiceType<ILuaScriptManagementService, LuaScriptManagementService>(ServiceLifetime.Singleton); _servicesProvider.RegisterServiceType<ILuaScriptManagementService, LuaScriptManagementService>(ServiceLifetime.Singleton);
_servicesProvider.RegisterServiceType<ILuaScriptLoader, LuaScriptLoader>(ServiceLifetime.Transient);
_servicesProvider.RegisterServiceType<LuaGame, LuaGame>(ServiceLifetime.Singleton); _servicesProvider.RegisterServiceType<LuaGame, LuaGame>(ServiceLifetime.Singleton);
// TODO: IConfigService // TODO: IConfigService
@@ -64,6 +66,12 @@ namespace Barotrauma
_servicesProvider.RegisterServiceType<IConverterServiceAsync<ContentPackage, IModConfigInfo>, ModConfigService>(ServiceLifetime.Transient); _servicesProvider.RegisterServiceType<IConverterServiceAsync<ContentPackage, IModConfigInfo>, ModConfigService>(ServiceLifetime.Transient);
_servicesProvider.RegisterServiceType<IConfigIOService, ConfigIOService>(ServiceLifetime.Transient); _servicesProvider.RegisterServiceType<IConfigIOService, ConfigIOService>(ServiceLifetime.Transient);
// service config data
_servicesProvider.RegisterServiceType<IStorageServiceConfig, StorageServiceConfig>(ServiceLifetime.Singleton);
_servicesProvider.RegisterServiceType<ILuaScriptServicesConfig, LuaScriptServicesConfig>(ServiceLifetime.Singleton);
_servicesProvider.RegisterServiceType<IConfigServiceConfig, ConfigServiceConfig>(ServiceLifetime.Singleton);
// gen IL
_servicesProvider.Compile(); _servicesProvider.Compile();
} }
@@ -702,7 +710,7 @@ namespace Barotrauma
{ {
EventService.ClearAllSubscribers(); EventService.ClearAllSubscribers();
LuaScriptManagementService.UnloadActiveScripts(); LuaScriptManagementService.UnloadActiveScripts();
PluginManagementService.UnloadAllAssemblyResources(); PluginManagementService.UnloadManagedAssemblies();
SubscribeToLuaCsEvents(); SubscribeToLuaCsEvents();
if (IsCodeRunning) if (IsCodeRunning)
@@ -10,96 +10,116 @@ using System.Runtime.Loader;
using System.Threading; using System.Threading;
using Barotrauma.Extensions; using Barotrauma.Extensions;
using Barotrauma.LuaCs.Data; using Barotrauma.LuaCs.Data;
using Barotrauma.LuaCs.Events;
using FluentResults; using FluentResults;
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis;
using OneOf;
namespace Barotrauma.LuaCs.Services; namespace Barotrauma.LuaCs.Services;
public class PluginManagementService : IPluginManagementService, IAssemblyManagementService public class PluginManagementService : IPluginManagementService, IAssemblyManagementService
{ {
private readonly Func<IAssemblyLoaderService.LoaderInitData, IAssemblyLoaderService> _assemblyLoaderServiceFactory;
private readonly ConcurrentDictionary<ContentPackage, (List<IAssemblyResourceInfo> ResourceInfos, IAssemblyLoaderService Loader)> _packageAssemblyResources;
private readonly ConcurrentDictionary<ContentPackage, List<IDisposable>> _pluginInstances;
private readonly Lazy<IEventService> _eventService;
private readonly ConditionalWeakTable<IAssemblyLoaderService, ContentPackage> _unloadingAssemblyLoaders;
private readonly ConditionalWeakTable<Assembly, ConcurrentDictionary<string, Type>> _assemblyTypesCache;
public PluginManagementService(
Func<IAssemblyLoaderService.LoaderInitData, IAssemblyLoaderService> assemblyLoaderServiceFactory,
Lazy<IEventService> eventService)
{
_assemblyLoaderServiceFactory = assemblyLoaderServiceFactory;
_eventService = eventService;
AppDomain.CurrentDomain.AssemblyLoad += OnAssemblyLoadedGlobal;
}
private void OnAssemblyLoadedGlobal(object sender, AssemblyLoadEventArgs args)
{
// cache types by name
try
{
var context = AssemblyLoadContext.GetLoadContext(args.LoadedAssembly);
if (context is not IAssemblyLoaderService loaderService)
return;
_eventService.Value.PublishEvent<IEventAssemblyLoaded>(sub => sub.OnAssemblyLoaded(args.LoadedAssembly));
var lookupDict = new ConcurrentDictionary<string, Type>();
foreach (var type in args.LoadedAssembly.GetSafeTypes())
{
lookupDict[type.FullName ?? type.Name] = type;
}
_assemblyTypesCache.AddOrUpdate(args.LoadedAssembly, lookupDict);
}
catch (Exception e)
{
// ignored
return;
}
}
private int _isDisposed = 0;
public bool IsDisposed public bool IsDisposed
{ {
get => ModUtils.Threading.GetBool(ref _isDisposed); get => ModUtils.Threading.GetBool(ref _isDisposed);
set => ModUtils.Threading.SetBool(ref _isDisposed, value); private set => ModUtils.Threading.SetBool(ref _isDisposed, value);
} }
private int _isDisposed;
private readonly ReaderWriterLockSlim _operationsLock = new(LockRecursionPolicy.SupportsRecursion); public void Dispose()
private readonly ConcurrentDictionary<Guid, IAssemblyLoaderService> _assemblyServices = new();
private readonly ConcurrentDictionary<IAssemblyResourceInfo, Guid> _resourceData = new();
private readonly Lazy<IEventService> _eventService;
private readonly Func<IAssemblyLoaderService> _assemblyServiceFactory;
private ImmutableDictionary<string, Type> _cachedTypes = null;
private ImmutableDictionary<string, Type> DefaultTypeCache => _cachedTypes ??= AssemblyLoadContext.Default.Assemblies
.SelectMany(ass => ass.GetSafeTypes()).ToImmutableDictionary(type => type.FullName, type => type);
public bool IsResourceLoaded<T>(T resource) where T : IAssemblyResourceInfo
{ {
((IService)this).CheckDisposed(); throw new NotImplementedException();
return _resourceData.ContainsKey(resource);
} }
public Result<ImmutableArray<Type>> GetImplementingTypes<T>(string namespacePrefix = null, bool includeInterfaces = false, public FluentResults.Result Reset()
{
if (IsDisposed)
return FluentResults.Result.Fail($"{nameof(PluginManagementService)} is disposed!");
throw new NotImplementedException();
}
public Result<ImmutableArray<Type>> GetImplementingTypes<T>(bool includeInterfaces = false,
bool includeAbstractTypes = false, bool includeDefaultContext = true) bool includeAbstractTypes = false, bool includeDefaultContext = true)
{ {
((IService)this).CheckDisposed(); var builder = ImmutableArray.CreateBuilder<Type>();
var types = ImmutableArray.CreateBuilder<Type>();
_operationsLock.EnterReadLock();
try
{
if (AssemblyLoaderServices.Any())
{
types.AddRange(AssemblyLoaderServices
.SelectMany(als => als.UnsafeGetTypesInAssemblies())
.Where(t => t is not null)
.Where(type => typeof(T).IsAssignableFrom(type))
.Where(type => includeInterfaces || !type.IsInterface)
.Where(type => includeAbstractTypes || !type.IsAbstract)
.Where(type => namespacePrefix is not null && type.FullName is not null && type.FullName.StartsWith(namespacePrefix)));
}
if (includeDefaultContext) if (this._packageAssemblyResources.Any())
{
types.AddRange(AssemblyLoadContext.Default.Assemblies
.SelectMany(ass => ass.GetSafeTypes())
.Where(t => t is not null)
.Where(type => typeof(T).IsAssignableFrom(type))
.Where(type => includeInterfaces || !type.IsInterface)
.Where(type => includeAbstractTypes || !type.IsAbstract)
.Where(type => namespacePrefix is not null && type.FullName is not null && type.FullName.StartsWith(namespacePrefix)));
}
return types.MoveToImmutable();
}
finally
{ {
_operationsLock.ExitReadLock(); foreach (var resource in this._packageAssemblyResources
.Where(res => !res.Value.Loader.IsReferenceOnlyMode))
{
builder.AddRange(resource.Value.Loader.Assemblies
.SelectMany(assembly => assembly.GetSafeTypes())
.Where(type => type.IsAssignableTo(typeof(T)))
.Where(type => includeInterfaces || !type.IsInterface)
.Where(type => includeAbstractTypes || !type.IsAbstract));
}
} }
if (includeDefaultContext)
{
builder.AddRange(AssemblyLoadContext.Default.Assemblies
.SelectMany(assembly => assembly.GetSafeTypes())
.Where(type => type.IsAssignableTo(typeof(T)))
.Where(type => includeInterfaces || !type.IsInterface)
.Where(type => includeAbstractTypes || !type.IsAbstract));
}
return builder.Count == 0
? FluentResults.Result.Fail($"Failed to find any types that implement {typeof(T).Name})")
: FluentResults.Result.Ok(builder.ToImmutable());
} }
public Type GetType(string typeName) public Type GetType(string typeName, bool isByRefType = false, bool includeInterfaces = false,
bool includeDefaultContext = true)
{ {
((IService)this).CheckDisposed(); if (includeDefaultContext)
_operationsLock.EnterReadLock();
try
{ {
if (DefaultTypeCache.TryGetValue(typeName, out var type)) var type = Type.GetType(typeName, false);
return type;
if (AssemblyLoaderServices.None())
return null;
foreach (var loaderService in AssemblyLoaderServices)
{
if (loaderService.GetTypeInAssemblies(typeName) is { IsSuccess: true, Value: not null } ret)
return ret.Value;
}
return null;
}
finally
{
_operationsLock.ExitReadLock();
} }
// TODO: implement by-ref type resolution
throw new NotImplementedException();
} }
public Result<ImmutableArray<IAssemblyResourceInfo>> LoadAssemblyResources(ImmutableArray<IAssemblyResourceInfo> resource) public Result<ImmutableArray<IAssemblyResourceInfo>> LoadAssemblyResources(ImmutableArray<IAssemblyResourceInfo> resource)
@@ -113,72 +133,20 @@ public class PluginManagementService : IPluginManagementService, IAssemblyManage
throw new NotImplementedException(); throw new NotImplementedException();
} }
public FluentResults.Result UnloadHostedReferences() public FluentResults.Result UnloadManagedAssemblies()
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
public FluentResults.Result UnloadAllAssemblyResources() public Result<Assembly> GetLoadedAssembly(OneOf<AssemblyName, string> assemblyName, in Guid[] excludedContexts)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
public Result<Assembly> GetLoadedAssembly(string assemblyName, in Guid[] excludedContexts) public ImmutableArray<MetadataReference> GetDefaultMetadataReferences(bool includeDefaultContext = true)
{ {
((IService)this).CheckDisposed(); throw new NotImplementedException();
_operationsLock.EnterReadLock();
try
{
foreach (var (guid, context) in _assemblyServices)
{
if (excludedContexts.Length > 0 && excludedContexts.Contains(guid))
continue;
if (context.GetAssemblyByName(assemblyName) is { IsSuccess: true, Value: not null } ret)
return ret.Value;
}
return FluentResults.Result.Fail($"Could not find assembly {assemblyName}");
}
finally
{
_operationsLock.ExitReadLock();
}
}
public Result<Assembly> GetLoadedAssembly(AssemblyName assemblyName, in Guid[] excludedContexts)
=> GetLoadedAssembly(assemblyName.FullName, excludedContexts);
public ImmutableArray<MetadataReference> GetDefaultMetadataReferences() =>
Basic.Reference.Assemblies.Net60.References.All.Select(Unsafe.As<MetadataReference>).ToImmutableArray();
public ImmutableArray<MetadataReference> GetAddInContextsMetadataReferences()
{
((IService)this).CheckDisposed();
_operationsLock.EnterReadLock();
try
{
if (_assemblyServices.IsEmpty)
return ImmutableArray<MetadataReference>.Empty;
var builder = ImmutableArray.CreateBuilder<MetadataReference>();
foreach (var context in _assemblyServices.Values)
builder.AddRange(context.AssemblyReferences);
return builder.ToImmutable();
}
finally
{
_operationsLock.ExitReadLock();
}
} }
public ImmutableArray<IAssemblyLoaderService> AssemblyLoaderServices { get; } public ImmutableArray<IAssemblyLoaderService> AssemblyLoaderServices { get; }
public void Dispose()
{
// TODO release managed resources here
throw new NotImplementedException();
}
public FluentResults.Result Reset()
{
throw new NotImplementedException();
}
} }
@@ -6,41 +6,33 @@ using System.Reflection;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp;
using OneOf;
// ReSharper disable InconsistentNaming // ReSharper disable InconsistentNaming
namespace Barotrauma.LuaCs.Services; namespace Barotrauma.LuaCs.Services;
public interface IAssemblyManagementService : IReusableService public interface IAssemblyManagementService : IReusableService
{ {
/// <summary>
/// Searches for an assembly given it's fully qualified name, while excluding the contexts with the given Guids, if supplied.
/// </summary>
/// <param name="assemblyName">The fully-qualified assembly name.</param>
/// <param name="excludedContexts">Guids of excluded contexts.</param>
/// <returns><b>On Success:</b> The assembly. <br/><b>On Failure:</b> nothing.</returns>
FluentResults.Result<Assembly> GetLoadedAssembly(string assemblyName, in Guid[] excludedContexts);
/// <summary> /// <summary>
/// Searches for an assembly given it's fully qualified name, while excluding the contexts with the given Guids, if supplied. /// Searches for an assembly given it's fully qualified name, while excluding the contexts with the given Guids, if supplied.
/// </summary> /// </summary>
/// <param name="assemblyName">The assembly info.</param> /// <param name="assemblyName">The assembly info.</param>
/// <param name="excludedContexts">Guids of excluded contexts.</param> /// <param name="excludedContexts">Guids of excluded contexts.</param>
/// <returns><b>On Success:</b> The assembly. <br/><b>On Failure:</b> nothing.</returns> /// <returns><b>On Success:</b> The assembly. <br/><b>On Failure:</b> nothing.</returns>
FluentResults.Result<Assembly> GetLoadedAssembly(AssemblyName assemblyName, in Guid[] excludedContexts); FluentResults.Result<Assembly> GetLoadedAssembly(OneOf<AssemblyName, string> assemblyName, in Guid[] excludedContexts);
/// <summary> /// <summary>
/// Gets the assembly <see cref="MetadataReference"/> collection for the BCL and base game assemblies. /// Gets all <see cref="MetadataReference"/> for all service-managed assemblies.
/// </summary> /// </summary>
/// <returns><see cref="MetadataReference"/> collection, if any are found. Returns an empty collection otherwise.</returns> /// <returns><see cref="MetadataReference"/> collection for all service-managed, and default if selected, assemblies, if any are found. Returns an empty collection otherwise.</returns>
ImmutableArray<MetadataReference> GetDefaultMetadataReferences(); ImmutableArray<MetadataReference> GetDefaultMetadataReferences(bool includeDefaultContext = true);
/// <summary> /// <summary>
/// Gets the assembly <see cref="MetadataReference"/> collection for all add-in assemblies loaded. /// Returns all active, managed assembly loaders.
/// </summary>
/// <returns><see cref="MetadataReference"/> collection, if any are found. Returns an empty collection otherwise.</returns>
ImmutableArray<MetadataReference> GetAddInContextsMetadataReferences();
/// <summary>
///
/// </summary> /// </summary>
ImmutableArray<IAssemblyLoaderService> AssemblyLoaderServices { get; } ImmutableArray<IAssemblyLoaderService> AssemblyLoaderServices { get; }
} }
@@ -10,33 +10,27 @@ namespace Barotrauma.LuaCs.Services;
public interface IPluginManagementService : IReusableService public interface IPluginManagementService : IReusableService
{ {
/// <summary> /// <summary>
/// Checks if the supplied resource is currently loaded. /// Gets all types in searched <see cref="IAssemblyLoaderService"/> that implement the type supplied.
/// </summary> /// </summary>
/// <param name="resource">The resource to check.</param>
/// <returns></returns>
bool IsResourceLoaded<T>(T resource) where T : IAssemblyResourceInfo;
/// <summary>
///
/// </summary>
/// <param name="namespacePrefix"></param>
/// <param name="includeInterfaces"></param> /// <param name="includeInterfaces"></param>
/// <param name="includeAbstractTypes"></param> /// <param name="includeAbstractTypes"></param>
/// <param name="includeDefaultContext"></param> /// <param name="includeDefaultContext"></param>
/// <typeparam name="T"></typeparam> /// <typeparam name="T"></typeparam>
/// <returns></returns> /// <returns></returns>
FluentResults.Result<ImmutableArray<Type>> GetImplementingTypes<T>( FluentResults.Result<ImmutableArray<Type>> GetImplementingTypes<T>(
string namespacePrefix = null,
bool includeInterfaces = false, bool includeInterfaces = false,
bool includeAbstractTypes = false, bool includeAbstractTypes = false,
bool includeDefaultContext = true); bool includeDefaultContext = true);
/// <summary> /// <summary>
/// Tries to get the Type given the fully qualified name. /// Tries to find the type given the fully qualified name and filters.
/// </summary> /// </summary>
/// <param name="typeName"></param> /// <param name="typeName"></param>
/// <param name="isByRefType"></param>
/// <param name="includeInterfaces"></param>
/// <param name="includeDefaultContext"></param>
/// <returns></returns> /// <returns></returns>
Type GetType(string typeName); Type GetType(string typeName, bool isByRefType = false, bool includeInterfaces = false, bool includeDefaultContext = true);
/// <summary> /// <summary>
/// Loads the provided assembly resources in the order of their dependencies and intra-mod priority load order. /// Loads the provided assembly resources in the order of their dependencies and intra-mod priority load order.
@@ -56,11 +50,9 @@ public interface IPluginManagementService : IReusableService
IReadOnlyList<FluentResults.Result<(Type, T)>> ActivateTypeInstances<T>(ImmutableArray<Type> types, bool serviceInjection = true, IReadOnlyList<FluentResults.Result<(Type, T)>> ActivateTypeInstances<T>(ImmutableArray<Type> types, bool serviceInjection = true,
bool hostInstanceReference = false) where T : IDisposable; bool hostInstanceReference = false) where T : IDisposable;
FluentResults.Result UnloadHostedReferences();
/// <summary> /// <summary>
/// Tries to gracefully unload all hosted plugin references /// Unloads all managed <see cref="IAssemblyPlugin"/>, <see cref="Assembly"/>, and <see cref="IAssemblyLoaderService"/>s.
/// </summary> /// </summary>
/// <returns></returns> /// <returns>Success of the operation. <br/><b>Note: does not guarantee .NET runtime assembly unloading success.<b/></returns>
FluentResults.Result UnloadAllAssemblyResources(); FluentResults.Result UnloadManagedAssemblies();
} }
@@ -25,6 +25,7 @@ namespace Barotrauma.LuaCs.Services;
public sealed class AssemblyLoader : AssemblyLoadContext, IAssemblyLoaderService public sealed class AssemblyLoader : AssemblyLoadContext, IAssemblyLoaderService
{ {
public Guid Id { get; init; } public Guid Id { get; init; }
public ContentPackage OwnerPackage { get; private set; }
public bool IsReferenceOnlyMode { get; init; } public bool IsReferenceOnlyMode { get; init; }
public bool IsDisposed public bool IsDisposed
{ {
@@ -55,8 +56,8 @@ public sealed class AssemblyLoader : AssemblyLoadContext, IAssemblyLoaderService
//internal //internal
private readonly IAssemblyManagementService _assemblyManagementService; private readonly IAssemblyManagementService _assemblyManagementService;
private readonly Action<AssemblyLoader> _onUnload; private readonly Action<IAssemblyLoaderService> _onUnload;
private readonly Func<AssemblyLoadContext, AssemblyName, Assembly> _onResolvingManaged; private readonly Func<IAssemblyLoaderService, AssemblyName, Assembly> _onResolvingManaged;
private readonly Func<Assembly, string, IntPtr> _onResolvingUnmanagedDll; private readonly Func<Assembly, string, IntPtr> _onResolvingUnmanagedDll;
private readonly ConcurrentDictionary<string, AssemblyDependencyResolver> _dependencyResolvers = new(); private readonly ConcurrentDictionary<string, AssemblyDependencyResolver> _dependencyResolvers = new();
private readonly ConcurrentDictionary<AssemblyOrStringKey, AssemblyData> _loadedAssemblyData = new(); private readonly ConcurrentDictionary<AssemblyOrStringKey, AssemblyData> _loadedAssemblyData = new();
@@ -64,16 +65,16 @@ public sealed class AssemblyLoader : AssemblyLoadContext, IAssemblyLoaderService
private readonly ThreadLocal<bool> _isResolving = new(static()=>false); // cyclic resolution exit private readonly ThreadLocal<bool> _isResolving = new(static()=>false); // cyclic resolution exit
private readonly ThreadLocal<bool> _isResolvingNative = new(static () => false); private readonly ThreadLocal<bool> _isResolvingNative = new(static () => false);
public AssemblyLoader( public AssemblyLoader(IAssemblyLoaderService.LoaderInitData initData)
IAssemblyManagementService assemblyManagementService, : base(isCollectible: true, name: initData.Name)
Guid id, string name,
bool isReferenceOnlyMode,
Action<AssemblyLoader> onUnload = null)
: base(isCollectible: true, name: name)
{ {
_assemblyManagementService = assemblyManagementService; _assemblyManagementService = initData.AssemblyManagementService;
Id = id; Id = initData.InstanceId;
IsReferenceOnlyMode = isReferenceOnlyMode; IsReferenceOnlyMode = initData.IsReferenceMode;
this._onUnload = initData.OnUnload;
this._onResolvingManaged = initData.OnResolvingManaged;
this._onResolvingUnmanagedDll = initData.OnResolvingUnmanagedDll;
this.OwnerPackage = initData.OwnerPackage;
base.Unloading += OnUnload; base.Unloading += OnUnload;
base.Resolving += OnResolvingManagedAssembly; base.Resolving += OnResolvingManagedAssembly;
base.ResolvingUnmanagedDll += OnResolvingUnmanagedDll; base.ResolvingUnmanagedDll += OnResolvingUnmanagedDll;
@@ -139,6 +140,9 @@ public sealed class AssemblyLoader : AssemblyLoadContext, IAssemblyLoaderService
if (_isResolving.Value) if (_isResolving.Value)
return null; return null;
if (assemblyLoadContext != this)
return null;
AreOperationRunning = true; AreOperationRunning = true;
_isResolving.Value = true; _isResolving.Value = true;
try try
@@ -166,7 +170,7 @@ public sealed class AssemblyLoader : AssemblyLoadContext, IAssemblyLoaderService
{ {
try try
{ {
return _onResolvingManaged(assemblyLoadContext, assemblyName); return _onResolvingManaged(this, assemblyName);
} }
catch catch
{ {
@@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Reflection; using System.Reflection;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
@@ -13,17 +14,26 @@ namespace Barotrauma.LuaCs;
public interface IAssemblyLoaderService : IService public interface IAssemblyLoaderService : IService
{ {
/// <summary> /// <summary>
/// Assembly loader factory for DI registration. /// Constructor record for instancing.
/// </summary> /// </summary>
/// <param name="assemblyManagementService">The assembly hosting management service.</param> /// <param name="AssemblyManagementService"></param>
/// <param name="eventService">The event service for publishing.</param> /// <param name="InstanceId"></param>
/// <param name="id">The referencing ID. Intended to be used to distinguish between instances.</param> /// <param name="Name"></param>
/// <param name="name">The name of the friendly name instance, used for error messages.</param> /// <param name="IsReferenceMode">Assemblies and Types in this context are for <see cref="MetadataReference"/> only.
/// <param name="isReferenceOnlyMode">Loaded assemblies are not intended for execution, just MetadataReferences.</param> /// Execution of assembly data is forbidden.</param>
delegate IAssemblyLoaderService AssemblyLoaderDelegate( /// <param name="OwnerPackage"></param>
IAssemblyManagementService assemblyManagementService, /// <param name="OnUnload"></param>
IEventService eventService, Guid id, string name, /// <param name="OnResolvingManaged"></param>
bool isReferenceOnlyMode, Action<AssemblyLoader> onUnload); /// <param name="OnResolvingUnmanagedDll"></param>
public record LoaderInitData(
[Required][NotNull] IAssemblyManagementService AssemblyManagementService,
[Required] Guid InstanceId,
[Required][NotNull] string Name,
[Required] bool IsReferenceMode,
ContentPackage OwnerPackage,
Action<IAssemblyLoaderService> OnUnload,
Func<IAssemblyLoaderService, AssemblyName, Assembly> OnResolvingManaged,
Func<Assembly, string, IntPtr> OnResolvingUnmanagedDll);
/// <summary> /// <summary>
/// ID for this instance. /// ID for this instance.