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 Barotrauma.LuaCs.Events; using FluentResults; using Microsoft.CodeAnalysis; using OneOf; namespace Barotrauma.LuaCs.Services; public class PluginManagementService : IPluginManagementService, IAssemblyManagementService { private readonly Func _assemblyLoaderServiceFactory; private readonly ConcurrentDictionary ResourceInfos, IAssemblyLoaderService Loader)> _packageAssemblyResources; private readonly ConcurrentDictionary> _pluginInstances; private readonly Lazy _eventService; private readonly ConditionalWeakTable _unloadingAssemblyLoaders; private readonly ConditionalWeakTable> _assemblyTypesCache; public PluginManagementService( Func assemblyLoaderServiceFactory, Lazy 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(sub => sub.OnAssemblyLoaded(args.LoadedAssembly)); var lookupDict = new ConcurrentDictionary(); 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 { get => ModUtils.Threading.GetBool(ref _isDisposed); private set => ModUtils.Threading.SetBool(ref _isDisposed, value); } public void Dispose() { throw new NotImplementedException(); } public FluentResults.Result Reset() { if (IsDisposed) return FluentResults.Result.Fail($"{nameof(PluginManagementService)} is disposed!"); throw new NotImplementedException(); } public Result> GetImplementingTypes(bool includeInterfaces = false, bool includeAbstractTypes = false, bool includeDefaultContext = true) { var builder = ImmutableArray.CreateBuilder(); if (this._packageAssemblyResources.Any()) { 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, bool isByRefType = false, bool includeInterfaces = false, bool includeDefaultContext = true) { if (includeDefaultContext) { var type = Type.GetType(typeName, false); } // TODO: implement by-ref type resolution throw new NotImplementedException(); } public Result> LoadAssemblyResources(ImmutableArray resource) { throw new NotImplementedException(); } public IReadOnlyList> ActivateTypeInstances(ImmutableArray types, bool serviceInjection = true, bool hostInstanceReference = false) where T : IDisposable { throw new NotImplementedException(); } public FluentResults.Result UnloadManagedAssemblies() { throw new NotImplementedException(); } public Result GetLoadedAssembly(OneOf assemblyName, in Guid[] excludedContexts) { throw new NotImplementedException(); } public ImmutableArray GetDefaultMetadataReferences(bool includeDefaultContext = true) { throw new NotImplementedException(); } public ImmutableArray AssemblyLoaderServices { get; } }