[Refactor-Minor]

- Refactored interface definition.
- Plugin Loading System Refactor (incomplete).
This commit is contained in:
MapleWheels
2024-12-20 16:17:14 -05:00
committed by Maplewheels
parent 4b2bac7cd8
commit d2b9ca4c1b
16 changed files with 563 additions and 1010 deletions
@@ -1,759 +0,0 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Loader;
using System.Threading;
using FluentResults;
using FluentResults.LuaCs;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
// ReSharper disable EventNeverSubscribedTo.Global
// ReSharper disable InconsistentNaming
namespace Barotrauma.LuaCs.Services;
/***
* Note: This class was written to be thread-safe in order to allow parallelization in loading in the future if the need
* becomes necessary as there is almost no serial performance overhead for adding threading protection.
*/
/// <summary>
/// Provides functionality for the loading, unloading and management of plugins implementing IAssemblyPlugin.
/// All plugins are loaded into their own AssemblyLoadContext along with their dependencies.
/// </summary>
[Obsolete]
public class AssemblyManager : IAssemblyManagementService, IPluginManagementService
{
#region ExternalAPI
public event Action<Assembly> OnAssemblyLoaded;
public event Action<Assembly> OnAssemblyUnloading;
public event Action<string, Exception> OnException;
public event Action<Guid> OnACLUnload;
public ImmutableList<WeakReference<MemoryFileAssemblyContextLoader>> StillUnloadingACLs
{
get
{
OpsLockUnloaded.EnterReadLock();
try
{
return UnloadingACLs.ToImmutableList();
}
finally
{
OpsLockUnloaded.ExitReadLock();
}
}
}
public bool IsCurrentlyUnloading
{
get
{
OpsLockUnloaded.EnterReadLock();
try
{
return UnloadingACLs.Any();
}
catch (Exception)
{
return false;
}
finally
{
OpsLockUnloaded.ExitReadLock();
}
}
}
public IEnumerable<Type> GetSubTypesInLoadedAssemblies<T>(bool rebuildList)
{
Type targetType = typeof(T);
string typeName = targetType.FullName ?? targetType.Name;
// rebuild
if (rebuildList)
RebuildTypesList();
// check cache
if (_subTypesLookupCache.TryGetValue(typeName, out var subTypeList))
{
return subTypeList;
}
// build from scratch
OpsLockLoaded.EnterReadLock();
try
{
// build list
var list1 = _defaultContextTypes
.Where(kvp1 => targetType.IsAssignableFrom(kvp1.Value) && !kvp1.Value.IsInterface)
.Concat(LoadedACLs
.SelectMany(kvp => kvp.Value.AssembliesTypes)
.Where(kvp2 => targetType.IsAssignableFrom(kvp2.Value) && !kvp2.Value.IsInterface))
.Select(kvp3 => kvp3.Value)
.ToImmutableList();
// only add if we find something
if (list1.Count > 0)
{
if (!_subTypesLookupCache.TryAdd(typeName, list1))
{
ModUtils.Logging.PrintError(
$"{nameof(AssemblyManager)}: Unable to add subtypes to cache of type {typeName}!");
}
}
else
{
ModUtils.Logging.PrintMessage(
$"{nameof(AssemblyManager)}: Warning: No types found during search for subtypes of {typeName}");
}
return list1;
}
catch (Exception e)
{
this.OnException?.Invoke($"{nameof(AssemblyManager)}::{nameof(GetSubTypesInLoadedAssemblies)}() | Error: {e.Message}", e);
return ImmutableList<Type>.Empty;
}
finally
{
OpsLockLoaded.ExitReadLock();
}
}
public bool TryGetSubTypesFromACL<T>(Guid id, out IEnumerable<Type> types)
{
Type targetType = typeof(T);
if (TryGetACL(id, out var acl))
{
types = acl.AssembliesTypes
.Where(kvp => targetType.IsAssignableFrom(kvp.Value) && !kvp.Value.IsInterface)
.Select(kvp => kvp.Value);
return true;
}
types = null;
return false;
}
public bool TryGetSubTypesFromACL(Guid id, out IEnumerable<Type> types)
{
if (TryGetACL(id, out var acl))
{
types = acl.AssembliesTypes.Select(kvp => kvp.Value);
return true;
}
types = null;
return false;
}
public IEnumerable<Type> GetTypesByName(string typeName)
{
List<Type> types = new();
if (typeName.IsNullOrWhiteSpace())
return types;
bool byRef = false;
if (typeName.StartsWith("out ") || typeName.StartsWith("ref "))
{
typeName = typeName.Remove(0, 4);
byRef = true;
}
TypesListHelper();
if (types.Count > 0)
return types;
// we couldn't find it, rebuild and try one more time
RebuildTypesList();
TypesListHelper();
if (types.Count > 0)
return types;
OpsLockLoaded.EnterReadLock();
try
{
// fallback to Type.GetType
Type t = Type.GetType(typeName, false, false);
if (t is not null)
{
types.Add(byRef ? t.MakeByRefType() : t);
return types;
}
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
try
{
t = assembly.GetType(typeName, false, false);
if (t is not null)
types.Add(byRef ? t.MakeByRefType() : t);
}
catch (Exception e)
{
this.OnException?.Invoke(
$"{nameof(AssemblyManager)}::{nameof(GetTypesByName)}() | Error: {e.Message}", e);
}
}
}
finally
{
OpsLockLoaded.ExitReadLock();
}
return types;
void TypesListHelper()
{
if (_defaultContextTypes.TryGetValue(typeName, out var type1))
{
if (type1 is not null)
types.Add(byRef ? type1.MakeByRefType() : type1);
}
OpsLockLoaded.EnterReadLock();
try
{
foreach (KeyValuePair<Guid,LoadedACL> loadedAcl in LoadedACLs)
{
var at = loadedAcl.Value.AssembliesTypes;
if (at.TryGetValue(typeName, out var type2))
{
if (type2 is not null)
types.Add(byRef ? type2.MakeByRefType() : type2);
}
}
}
finally
{
OpsLockLoaded.ExitReadLock();
}
}
}
public IEnumerable<Type> GetAllTypesInLoadedAssemblies()
{
OpsLockLoaded.EnterReadLock();
try
{
return _defaultContextTypes
.Select(kvp => kvp.Value)
.Concat(LoadedACLs
.SelectMany(kvp => kvp.Value?.AssembliesTypes.Select(kv => kv.Value)))
.ToImmutableList();
}
catch
{
return ImmutableList<Type>.Empty;
}
finally
{
OpsLockLoaded.ExitReadLock();
}
}
public IEnumerable<LoadedACL> GetAllLoadedACLs()
{
OpsLockLoaded.EnterReadLock();
try
{
if (!LoadedACLs.Any())
{
return ImmutableList<LoadedACL>.Empty;
}
return LoadedACLs.Select(kvp => kvp.Value).ToImmutableList();
}
catch
{
return ImmutableList<LoadedACL>.Empty;
}
finally
{
OpsLockLoaded.ExitReadLock();
}
}
public bool IsAssemblyLoadedGlobal(string friendlyName)
{
throw new NotImplementedException();
}
#endregion
#region InternalAPI
[MethodImpl(MethodImplOptions.Synchronized | MethodImplOptions.NoInlining)]
ImmutableList<LoadedACL> IAssemblyManagementService.UnsafeGetAllLoadedACLs()
{
if (LoadedACLs.IsEmpty)
return ImmutableList<LoadedACL>.Empty;
return LoadedACLs.Select(kvp => kvp.Value).ToImmutableList();
}
public event System.Func<LoadedACL, bool> IsReadyToUnloadACL;
public AssemblyLoadingSuccessState LoadAssemblyFromMemory([NotNull] string compiledAssemblyName,
[NotNull] IEnumerable<SyntaxTree> syntaxTree,
IEnumerable<MetadataReference> externalMetadataReferences,
[NotNull] CSharpCompilationOptions compilationOptions,
string friendlyName,
ref Guid id,
IEnumerable<Assembly> externFileAssemblyRefs = null)
{
// validation
if (compiledAssemblyName.IsNullOrWhiteSpace())
return AssemblyLoadingSuccessState.BadName;
if (syntaxTree is null)
return AssemblyLoadingSuccessState.InvalidAssembly;
if (!GetOrCreateACL(id, friendlyName, out var acl))
return AssemblyLoadingSuccessState.ACLLoadFailure;
id = acl.Id; // pass on true id returned
// this acl is already hosting an in-memory assembly
if (acl.Acl.CompiledAssembly is not null)
return AssemblyLoadingSuccessState.AlreadyLoaded;
// compile
AssemblyLoadingSuccessState state;
string messages;
try
{
state = acl.Acl.CompileAndLoadScriptAssembly(compiledAssemblyName, syntaxTree, externalMetadataReferences,
compilationOptions, out messages, externFileAssemblyRefs);
}
catch (Exception e)
{
ModUtils.Logging.PrintError($"{nameof(AssemblyManager)}::{nameof(LoadAssemblyFromMemory)}() | Failed to compile and load assemblies for [ {compiledAssemblyName} / {friendlyName} ]! Details: {e.Message} | {e.StackTrace}");
return AssemblyLoadingSuccessState.InvalidAssembly;
}
// get types
if (state is AssemblyLoadingSuccessState.Success)
{
_subTypesLookupCache.Clear();
acl.RebuildTypesList();
OnAssemblyLoaded?.Invoke(acl.Acl.CompiledAssembly);
}
else
{
ModUtils.Logging.PrintError($"Unable to compile assembly '{compiledAssemblyName}' due to errors: {messages}");
}
return state;
}
public bool SetACLToTemplateMode(Guid guid)
{
if (!TryGetACL(guid, out var acl))
return false;
acl.Acl.IsTemplateMode = true;
return true;
}
public AssemblyLoadingSuccessState LoadAssembliesFromLocations([NotNull] IEnumerable<string> filePaths,
string friendlyName, ref Guid id)
{
if (filePaths is null)
{
var exception = new ArgumentNullException(
$"{nameof(AssemblyManager)}::{nameof(LoadAssembliesFromLocations)}() | file paths supplied is null!");
this.OnException?.Invoke($"Error: {exception.Message}", exception);
throw exception;
}
ImmutableList<string> assemblyFilePaths = filePaths.ToImmutableList(); // copy the list before loading
if (!assemblyFilePaths.Any())
{
return AssemblyLoadingSuccessState.NoAssemblyFound;
}
if (GetOrCreateACL(id, friendlyName, out var loadedAcl))
{
var state = loadedAcl.Acl.LoadFromFiles(assemblyFilePaths);
// if failure, we dispose of the acl
if (state != AssemblyLoadingSuccessState.Success)
{
DisposeACL(loadedAcl.Id);
ModUtils.Logging.PrintError($"ACL {friendlyName} failed, unloading...");
return state;
}
// build types list
_subTypesLookupCache.Clear();
loadedAcl.RebuildTypesList();
id = loadedAcl.Id;
foreach (Assembly assembly in loadedAcl.Acl.Assemblies)
{
OnAssemblyLoaded?.Invoke(assembly);
}
return state;
}
return AssemblyLoadingSuccessState.ACLLoadFailure;
}
[MethodImpl(MethodImplOptions.NoInlining)]
public bool TryBeginDispose()
{
OpsLockLoaded.EnterWriteLock();
OpsLockUnloaded.EnterWriteLock();
try
{
_subTypesLookupCache.Clear();
_defaultContextTypes = _defaultContextTypes.Clear();
foreach (KeyValuePair<Guid, LoadedACL> loadedAcl in LoadedACLs)
{
if (loadedAcl.Value.Acl is not null)
{
if (IsReadyToUnloadACL is not null)
{
foreach (Delegate del in IsReadyToUnloadACL.GetInvocationList())
{
if (del is System.Func<LoadedACL, bool> { } func)
{
if (!func.Invoke(loadedAcl.Value))
return false; // Not ready, exit
}
}
}
foreach (Assembly assembly in loadedAcl.Value.Acl.Assemblies)
{
OnAssemblyUnloading?.Invoke(assembly);
}
UnloadingACLs.Add(new WeakReference<MemoryFileAssemblyContextLoader>(loadedAcl.Value.Acl, true));
loadedAcl.Value.ClearTypesList();
loadedAcl.Value.Acl.Unload();
loadedAcl.Value.ClearACLRef();
OnACLUnload?.Invoke(loadedAcl.Value.Id);
}
}
LoadedACLs.Clear();
return true;
}
catch(Exception e)
{
// should never happen
this.OnException?.Invoke($"{nameof(TryBeginDispose)}() | Error: {e.Message}", e);
return false;
}
finally
{
OpsLockUnloaded.ExitWriteLock();
OpsLockLoaded.ExitWriteLock();
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
public bool FinalizeDispose()
{
bool isUnloaded;
OpsLockUnloaded.EnterUpgradeableReadLock();
try
{
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced); // force the gc to collect unloaded acls.
List<WeakReference<MemoryFileAssemblyContextLoader>> toRemove = new();
foreach (WeakReference<MemoryFileAssemblyContextLoader> weakReference in UnloadingACLs)
{
if (!weakReference.TryGetTarget(out _))
{
toRemove.Add(weakReference);
}
}
if (toRemove.Any())
{
OpsLockUnloaded.EnterWriteLock();
try
{
foreach (WeakReference<MemoryFileAssemblyContextLoader> reference in toRemove)
{
UnloadingACLs.Remove(reference);
}
}
finally
{
OpsLockUnloaded.ExitWriteLock();
}
}
isUnloaded = !UnloadingACLs.Any();
}
finally
{
OpsLockUnloaded.ExitUpgradeableReadLock();
}
return isUnloaded;
}
[MethodImpl(MethodImplOptions.NoInlining)]
public bool TryGetACL(Guid id, out LoadedACL acl)
{
acl = null;
OpsLockLoaded.EnterReadLock();
try
{
if (id.Equals(Guid.Empty) || !LoadedACLs.ContainsKey(id))
return false;
acl = LoadedACLs[id];
return true;
}
finally
{
OpsLockLoaded.ExitReadLock();
}
}
/// <summary>
/// Gets or creates an AssemblyCtxLoader for the given ID. Creates if the ID is empty or no ACL can be found.
/// [IMPORTANT] After calling this method, the id you use should be taken from the acl container (acl.Id).
/// </summary>
/// <param name="id"></param>
/// <param name="friendlyName">A non-unique name for later reference. Optional.</param>
/// <param name="acl"></param>
/// <returns>Should only return false if an error occurs.</returns>
[MethodImpl(MethodImplOptions.NoInlining)]
private bool GetOrCreateACL(Guid id, string friendlyName, out LoadedACL acl)
{
OpsLockLoaded.EnterUpgradeableReadLock();
try
{
if (id.Equals(Guid.Empty) || !LoadedACLs.ContainsKey(id) || LoadedACLs[id] is null)
{
OpsLockLoaded.EnterWriteLock();
try
{
id = Guid.NewGuid();
acl = new LoadedACL(id, this, friendlyName);
LoadedACLs[id] = acl;
return true;
}
finally
{
OpsLockLoaded.ExitWriteLock();
}
}
else
{
acl = LoadedACLs[id];
return true;
}
}
catch(Exception e)
{
this.OnException?.Invoke($"{nameof(GetOrCreateACL)}Error: {e.Message}", e);
acl = null;
return false;
}
finally
{
OpsLockLoaded.ExitUpgradeableReadLock();
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
private bool DisposeACL(Guid id)
{
OpsLockLoaded.EnterWriteLock();
OpsLockUnloaded.EnterWriteLock();
try
{
if (LoadedACLs.ContainsKey(id) && LoadedACLs[id] == null)
{
if (!LoadedACLs.TryRemove(id, out _))
{
ModUtils.Logging.PrintWarning($"An ACL with the GUID {id.ToString()} was found as null. Unable to remove null ACL entry.");
}
}
if (id.Equals(Guid.Empty) || !LoadedACLs.ContainsKey(id))
{
return false; // nothing to dispose of
}
var acl = LoadedACLs[id];
foreach (Assembly assembly in acl.Acl.Assemblies)
{
OnAssemblyUnloading?.Invoke(assembly);
}
_subTypesLookupCache.Clear();
UnloadingACLs.Add(new WeakReference<MemoryFileAssemblyContextLoader>(acl.Acl, true));
acl.Acl.Unload();
acl.ClearACLRef();
OnACLUnload?.Invoke(acl.Id);
return true;
}
catch (Exception e)
{
this.OnException?.Invoke($"{nameof(DisposeACL)}() | Error: {e.Message}", e);
return false;
}
finally
{
OpsLockLoaded.ExitWriteLock();
OpsLockUnloaded.ExitWriteLock();
}
}
internal AssemblyManager()
{
RebuildTypesList();
}
/// <summary>
/// Rebuilds the list of types in the default assembly load context.
/// </summary>
private void RebuildTypesList()
{
try
{
_defaultContextTypes = AssemblyLoadContext.Default.Assemblies
.SelectMany(a => a.GetSafeTypes())
.ToImmutableDictionary(t => t.FullName ?? t.Name, t => t);
_subTypesLookupCache.Clear();
}
catch(ArgumentException ae)
{
this.OnException?.Invoke($"{nameof(RebuildTypesList)}() | Error: {ae.Message}", ae);
try
{
// some types must've had duplicate type names, build the list while filtering
Dictionary<string, Type> types = new();
foreach (var type in AssemblyLoadContext.Default.Assemblies.SelectMany(a => a.GetSafeTypes()))
{
try
{
types.TryAdd(type.FullName ?? type.Name, type);
}
catch
{
// ignore, null key exception
}
}
_defaultContextTypes = types.ToImmutableDictionary();
}
catch (Exception e)
{
this.OnException?.Invoke($"{nameof(RebuildTypesList)}() | Error: {e.Message}", e);
ModUtils.Logging.PrintError($"{nameof(AssemblyManager)}: Unable to create list of default assembly types! Default AssemblyLoadContext types searching not available.");
#if DEBUG
ModUtils.Logging.PrintError($"{nameof(AssemblyManager)}: Exception Details :{e.Message} | {e.InnerException}");
#endif
_defaultContextTypes = ImmutableDictionary<string, Type>.Empty;
}
}
}
#endregion
#region Data
private readonly ConcurrentDictionary<string, ImmutableList<Type>> _subTypesLookupCache = new();
private ImmutableDictionary<string, Type> _defaultContextTypes;
private readonly ConcurrentDictionary<Guid, LoadedACL> LoadedACLs = new();
private readonly List<WeakReference<MemoryFileAssemblyContextLoader>> UnloadingACLs= new();
private readonly ReaderWriterLockSlim OpsLockLoaded = new ();
private readonly ReaderWriterLockSlim OpsLockUnloaded = new ();
#endregion
#region TypeDefs
public sealed class LoadedACL
{
public readonly Guid Id;
private ImmutableDictionary<string, Type> _assembliesTypes = ImmutableDictionary<string, Type>.Empty;
public MemoryFileAssemblyContextLoader Acl { get; private set; }
internal LoadedACL(Guid id, AssemblyManager manager, string friendlyName)
{
this.Id = id;
this.Acl = new(manager)
{
FriendlyName = friendlyName
};
}
public ref readonly ImmutableDictionary<string, Type> AssembliesTypes => ref _assembliesTypes;
/// <summary>
/// Warning: For use by the Assembly Manager only! Do not call this method otherwise.
/// </summary>
internal void ClearACLRef()
{
Acl = null;
}
/// <summary>
/// Rebuild the list of types from assemblies loaded in the AsmCtxLoader.
/// </summary>
internal void RebuildTypesList()
{
if (this.Acl is null)
{
ModUtils.Logging.PrintWarning($"{nameof(RebuildTypesList)}() | ACL with GUID {Id.ToString()} is null, cannot rebuild.");
return;
}
ClearTypesList();
try
{
_assembliesTypes = this.Acl.Assemblies
.SelectMany(a => a.GetSafeTypes())
.ToImmutableDictionary(t => t.FullName ?? t.Name, t => t);
}
catch(ArgumentException)
{
// some types must've had duplicate type names, build the list while filtering
Dictionary<string, Type> types = new();
foreach (var type in this.Acl.Assemblies.SelectMany(a => a.GetSafeTypes()))
{
try
{
types.TryAdd(type.FullName ?? type.Name, type);
}
catch
{
// ignore, null key exception
}
}
_assembliesTypes = types.ToImmutableDictionary();
}
}
internal void ClearTypesList()
{
_assembliesTypes = ImmutableDictionary<string, Type>.Empty;
}
}
#endregion
public void Dispose()
{
TryBeginDispose();
}
public FluentResults.Result Reset()
{
return TryBeginDispose() ? FluentResults.Result.Ok()
: FluentResults.Result.Fail(new Error($"{nameof(AssemblyManager)}: failed to Reset service.")
.WithMetadata(MetadataType.ExceptionObject, this));
}
}
@@ -23,7 +23,7 @@ public partial class PackageService : IPackageService
// mod config / package scanners/parsers
private readonly Lazy<IModConfigParserService> _configParserService;
private readonly Lazy<IModConfigCreatorService> _configParserService;
private readonly Lazy<ILuaScriptService> _luaScriptService;
private readonly Lazy<ILocalizationService> _localizationService;
private readonly Lazy<IPluginService> _pluginService;
@@ -1,4 +1,14 @@
using System.Collections.Immutable;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Loader;
using System.Threading;
using Barotrauma.Extensions;
using Barotrauma.LuaCs.Data;
using FluentResults;
using Microsoft.CodeAnalysis;
@@ -7,5 +17,152 @@ namespace Barotrauma.LuaCs.Services;
public class PluginManagementService : IPluginManagementService, IAssemblyManagementService
{
public bool IsDisposed
{
get => ModUtils.Threading.GetBool(ref _isDisposed);
set => ModUtils.Threading.SetBool(ref _isDisposed, value);
}
private int _isDisposed;
private readonly ReaderWriterLockSlim _operationsLock = new(LockRecursionPolicy.SupportsRecursion);
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();
return _resourceData.ContainsKey(resource);
}
public Result<ImmutableArray<Type>> GetImplementingTypes<T>(string namespacePrefix = null, bool includeInterfaces = false,
bool includeAbstractTypes = false, bool includeDefaultContext = true)
{
((IService)this).CheckDisposed();
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)
{
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();
}
}
public Type GetType(string typeName)
{
((IService)this).CheckDisposed();
_operationsLock.EnterReadLock();
try
{
if (DefaultTypeCache.TryGetValue(typeName, out var type))
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();
}
}
public Result<ImmutableArray<IAssemblyResourceInfo>> LoadAssemblyResources(ImmutableArray<IAssemblyResourceInfo> resource)
{
throw new NotImplementedException();
}
public Result<Assembly> GetLoadedAssembly(string assemblyName, in Guid[] excludedContexts)
{
((IService)this).CheckDisposed();
_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 void Dispose()
{
// TODO release managed resources here
throw new NotImplementedException();
}
public FluentResults.Result Reset()
{
throw new NotImplementedException();
}
}
@@ -2,7 +2,7 @@
namespace Barotrauma.LuaCs.Services.Processing;
public interface IModConfigParserService : IReusableService
public interface IModConfigCreatorService : IService
{
FluentResults.Result<IModConfigInfo> BuildConfigForPackage(ContentPackage package);
FluentResults.Result<IModConfigInfo> BuildConfigFromManifest(string manifestPath);
@@ -18,28 +18,24 @@ public interface IPluginManagementService : IReusableService
/// <summary>
///
/// </summary>
/// <param name="package"></param>
/// <param name="namespacePrefix"></param>
/// <param name="includeInterfaces"></param>
/// <param name="includeAbstractTypes"></param>
/// <param name="includeDefaultContext"></param>
/// <param name="includeExplicitAssembliesOnly"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
FluentResults.Result<ImmutableArray<T>> GetTypes<T>(
ContentPackage package = null,
FluentResults.Result<ImmutableArray<Type>> GetImplementingTypes<T>(
string namespacePrefix = null,
bool includeInterfaces = false,
bool includeAbstractTypes = false,
bool includeDefaultContext = true,
bool includeExplicitAssembliesOnly = false);
bool includeDefaultContext = true);
/// <summary>
///
/// Tries to get the
/// </summary>
/// <param name="package"></param>
/// <param name="typeName"></param>
/// <returns></returns>
FluentResults.Result<ImmutableArray<IAssemblyResourceInfo>> GetCachedAssembliesForPackage(ContentPackage package);
Type GetType(string typeName);
/// <summary>
///
@@ -19,7 +19,7 @@ public interface IServicesProvider
/// <param name="lifetimeInstance"></param>
/// <typeparam name="TSvcInterface"></typeparam>
/// <typeparam name="TService"></typeparam>
void RegisterServiceType<TSvcInterface, TService>(ServiceLifetime lifetime, ILifetime lifetimeInstance = null) where TSvcInterface : class, IReusableService where TService : class, IReusableService, TSvcInterface;
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.
@@ -29,7 +29,7 @@ public interface IServicesProvider
/// <param name="lifetimeInstance"></param>
/// <typeparam name="TSvcInterface"></typeparam>
/// <typeparam name="TService"></typeparam>
void RegisterServiceType<TSvcInterface, TService>(string name, ServiceLifetime lifetime, ILifetime lifetimeInstance = null) where TSvcInterface : class, IReusableService where TService : class, IReusableService, TSvcInterface;
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.
@@ -58,27 +58,25 @@ public interface IServicesProvider
/// Tries to get a service for the given interface, returns success/failure.
/// </summary>
/// <param name="service"></param>
/// <param name="lifetime"></param>
/// <typeparam name="TSvcInterface"></typeparam>
/// <returns></returns>
bool TryGetService<TSvcInterface>(out TSvcInterface service) where TSvcInterface : class, IReusableService;
bool TryGetService<TSvcInterface>(out TSvcInterface service) where TSvcInterface : class, IService;
/// <summary>
/// Tries to get a service for the given name and interface, returns success/failure.
/// </summary>
/// <param name="name"></param>
/// <param name="service"></param>
/// <param name="lifetime"></param>
/// <typeparam name="TSvcInterface"></typeparam>
/// <returns></returns>
bool TryGetService<TSvcInterface>(string name, out TSvcInterface service) where TSvcInterface : class, IReusableService;
bool TryGetService<TSvcInterface>(string name, out TSvcInterface service) where TSvcInterface : class, IService;
/// <summary>
/// Called whenever a new service is created/instanced.
/// Args[0]: The interface type of the service.
/// Args[1]: The instance of the service.
/// </summary>
event System.Action<Type, IReusableService> OnServiceInstanced;
event System.Action<Type, IService> OnServiceInstanced;
#endregion
@@ -89,7 +87,7 @@ public interface IServicesProvider
/// </summary>
/// <typeparam name="TSvc"></typeparam>
/// <returns></returns>
ImmutableArray<TSvc> GetAllServices<TSvc>() where TSvc : class, IReusableService;
ImmutableArray<TSvc> GetAllServices<TSvc>() where TSvc : class, IService;
#endregion
@@ -7,8 +7,7 @@ namespace Barotrauma.LuaCs.Services;
public interface IStorageService : IService
{
#region LocalGameData
// -- local game folder storage
FluentResults.Result<XDocument> LoadLocalXml(ContentPackage package, string localFilePath);
FluentResults.Result<byte[]> LoadLocalBinary(ContentPackage package, string localFilePath);
FluentResults.Result<string> LoadLocalText(ContentPackage package, string localFilePath);
@@ -22,10 +21,8 @@ public interface IStorageService : IService
Task<FluentResults.Result> SaveLocalXmlAsync(ContentPackage package, string localFilePath, XDocument document);
Task<FluentResults.Result> SaveLocalBinaryAsync(ContentPackage package, string localFilePath, byte[] bytes);
Task<FluentResults.Result> SaveLocalTextAsync(ContentPackage package, string localFilePath, string text);
#endregion
#region ContentPackageData
// -- package directory
// singles
FluentResults.Result<XDocument> LoadPackageXml(ContentPackage package, string localFilePath);
FluentResults.Result<byte[]> LoadPackageBinary(ContentPackage package, string localFilePath);
@@ -45,9 +42,7 @@ public interface IStorageService : IService
Task<ImmutableArray<(string, FluentResults.Result<byte[]>)>> LoadPackageBinaryFilesAsync(ContentPackage package, ImmutableArray<string> localFilePaths);
Task<ImmutableArray<(string, FluentResults.Result<string>)>> LoadPackageTextFilesAsync(ContentPackage package, ImmutableArray<string> localFilePaths);
#endregion
#region AbsolutePaths
// -- absolute paths
FluentResults.Result<XDocument> TryLoadXml(string filePath, Encoding encoding = null);
FluentResults.Result<string> TryLoadText(string filePath, Encoding encoding = null);
FluentResults.Result<byte[]> TryLoadBinary(string filePath);
@@ -62,5 +57,4 @@ public interface IStorageService : IService
Task<FluentResults.Result> TrySaveXmlAsync(string filePath, XDocument document, Encoding encoding = null);
Task<FluentResults.Result> TrySaveTextAsync(string filePath, string text, Encoding encoding = null);
Task<FluentResults.Result> TrySaveBinaryAsync(string filePath, byte[] bytes);
#endregion
}