2 using System.Collections.Concurrent;
3 using System.Collections.Generic;
4 using System.Collections.Immutable;
5 using System.Diagnostics.CodeAnalysis;
7 using System.Reflection;
8 using System.Runtime.CompilerServices;
9 using System.Runtime.Loader;
10 using System.Threading;
11 using Microsoft.CodeAnalysis;
12 using Microsoft.CodeAnalysis.CSharp;
62 OpsLockUnloaded.EnterReadLock();
65 return UnloadingACLs.ToImmutableList();
69 OpsLockUnloaded.ExitReadLock();
83 OpsLockUnloaded.EnterReadLock();
86 return UnloadingACLs.Any();
94 OpsLockUnloaded.ExitReadLock();
115 Type targetType = typeof(T);
116 string typeName = targetType.FullName ?? targetType.Name;
123 if (_subTypesLookupCache.TryGetValue(typeName, out var subTypeList))
129 OpsLockLoaded.EnterReadLock();
133 var list1 = _defaultContextTypes
134 .Where(kvp1 => targetType.IsAssignableFrom(kvp1.Value) && !kvp1.Value.IsInterface)
136 .SelectMany(kvp => kvp.Value.AssembliesTypes)
137 .Where(kvp2 => targetType.IsAssignableFrom(kvp2.Value) && !kvp2.Value.IsInterface))
138 .Select(kvp3 => kvp3.Value)
144 if (!_subTypesLookupCache.TryAdd(typeName, list1))
146 ModUtils.Logging.PrintError(
147 $
"{nameof(AssemblyManager)}: Unable to add subtypes to cache of type {typeName}!");
152 ModUtils.Logging.PrintMessage(
153 $
"{nameof(AssemblyManager)}: Warning: No types found during search for subtypes of {typeName}");
160 this.OnException?.Invoke($
"{nameof(AssemblyManager)}::{nameof(GetSubTypesInLoadedAssemblies)}() | Error: {e.Message}", e);
161 return ImmutableList<Type>.Empty;
165 OpsLockLoaded.ExitReadLock();
178 Type targetType = typeof(T);
182 types = acl.AssembliesTypes
183 .Where(kvp => targetType.IsAssignableFrom(kvp.Value) && !kvp.Value.IsInterface)
184 .Select(kvp => kvp.Value);
202 types = acl.AssembliesTypes.Select(kvp => kvp.Value);
219 List<Type> types =
new();
220 if (typeName.IsNullOrWhiteSpace())
224 if (typeName.StartsWith(
"out ") || typeName.StartsWith(
"ref "))
226 typeName = typeName.Remove(0, 4);
242 OpsLockLoaded.EnterReadLock();
246 Type t = Type.GetType(typeName,
false,
false);
249 types.Add(byRef ? t.MakeByRefType() : t);
253 foreach (var assembly
in AppDomain.CurrentDomain.GetAssemblies())
257 t = assembly.GetType(typeName,
false,
false);
259 types.Add(byRef ? t.MakeByRefType() : t);
263 this.OnException?.Invoke(
264 $
"{nameof(AssemblyManager)}::{nameof(GetTypesByName)}() | Error: {e.Message}", e);
270 OpsLockLoaded.ExitReadLock();
275 void TypesListHelper()
277 if (_defaultContextTypes.TryGetValue(typeName, out var type1))
279 if (type1 is not
null)
280 types.Add(byRef ? type1.MakeByRefType() : type1);
283 OpsLockLoaded.EnterReadLock();
286 foreach (KeyValuePair<Guid,LoadedACL> loadedAcl
in LoadedACLs)
288 var at = loadedAcl.Value.AssembliesTypes;
289 if (at.TryGetValue(typeName, out var type2))
291 if (type2 is not
null)
292 types.Add(byRef ? type2.MakeByRefType() : type2);
298 OpsLockLoaded.ExitReadLock();
310 OpsLockLoaded.EnterReadLock();
313 return _defaultContextTypes
314 .Select(kvp => kvp.Value)
316 .SelectMany(kvp => kvp.Value?.AssembliesTypes.Select(kv => kv.Value)))
321 return ImmutableList<Type>.Empty;
325 OpsLockLoaded.ExitReadLock();
337 OpsLockLoaded.EnterReadLock();
340 if (!LoadedACLs.Any())
342 return ImmutableList<LoadedACL>.Empty;
345 return LoadedACLs.Select(kvp => kvp.Value).ToImmutableList();
349 return ImmutableList<LoadedACL>.Empty;
353 OpsLockLoaded.ExitReadLock();
366 [MethodImpl(MethodImplOptions.Synchronized | MethodImplOptions.NoInlining)]
367 internal ImmutableList<LoadedACL> UnsafeGetAllLoadedACLs()
369 if (LoadedACLs.IsEmpty)
370 return ImmutableList<LoadedACL>.Empty;
371 return LoadedACLs.Select(kvp => kvp.Value).ToImmutableList();
392 [NotNull] IEnumerable<SyntaxTree> syntaxTree,
393 IEnumerable<MetadataReference> externalMetadataReferences,
394 [NotNull] CSharpCompilationOptions compilationOptions,
397 IEnumerable<Assembly> externFileAssemblyRefs =
null)
400 if (compiledAssemblyName.IsNullOrWhiteSpace())
401 return AssemblyLoadingSuccessState.BadName;
403 if (syntaxTree is
null)
404 return AssemblyLoadingSuccessState.InvalidAssembly;
406 if (!GetOrCreateACL(
id, friendlyName, out var acl))
407 return AssemblyLoadingSuccessState.ACLLoadFailure;
412 if (acl.Acl.CompiledAssembly is not
null)
413 return AssemblyLoadingSuccessState.AlreadyLoaded;
416 AssemblyLoadingSuccessState state;
420 state = acl.Acl.CompileAndLoadScriptAssembly(compiledAssemblyName, syntaxTree, externalMetadataReferences,
421 compilationOptions, out messages, externFileAssemblyRefs);
425 ModUtils.Logging.PrintError($
"{nameof(AssemblyManager)}::{nameof(LoadAssemblyFromMemory)}() | Failed to compile and load assemblies for [ {compiledAssemblyName} / {friendlyName} ]! Details: {e.Message} | {e.StackTrace}");
426 return AssemblyLoadingSuccessState.InvalidAssembly;
430 if (state is AssemblyLoadingSuccessState.Success)
432 _subTypesLookupCache.Clear();
433 acl.RebuildTypesList();
438 ModUtils.Logging.PrintError($
"Unable to compile assembly '{compiledAssemblyName}' due to errors: {messages}");
455 acl.Acl.IsTemplateMode =
true;
469 string friendlyName, ref Guid
id)
472 if (filePaths is
null)
474 var exception =
new ArgumentNullException(
475 $
"{nameof(AssemblyManager)}::{nameof(LoadAssembliesFromLocations)}() | file paths supplied is null!");
476 this.OnException?.Invoke($
"Error: {exception.Message}", exception);
480 ImmutableList<string> assemblyFilePaths = filePaths.ToImmutableList();
482 if (!assemblyFilePaths.Any())
484 return AssemblyLoadingSuccessState.NoAssemblyFound;
487 if (GetOrCreateACL(
id, friendlyName, out var loadedAcl))
489 var state = loadedAcl.Acl.LoadFromFiles(assemblyFilePaths);
491 if (state != AssemblyLoadingSuccessState.Success)
493 DisposeACL(loadedAcl.Id);
494 ModUtils.Logging.PrintError($
"ACL {friendlyName} failed, unloading...");
498 _subTypesLookupCache.Clear();
499 loadedAcl.RebuildTypesList();
501 foreach (Assembly assembly
in loadedAcl.Acl.Assemblies)
508 return AssemblyLoadingSuccessState.ACLLoadFailure;
512 [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.Synchronized)]
515 OpsLockLoaded.EnterWriteLock();
516 OpsLockUnloaded.EnterWriteLock();
519 _subTypesLookupCache.Clear();
520 _defaultContextTypes = _defaultContextTypes.Clear();
522 foreach (KeyValuePair<Guid, LoadedACL> loadedAcl
in LoadedACLs)
524 if (loadedAcl.Value.Acl is not
null)
530 if (del is System.Func<
LoadedACL,
bool> { } func)
532 if (!func.Invoke(loadedAcl.Value))
538 foreach (Assembly assembly
in loadedAcl.Value.Acl.Assemblies)
543 UnloadingACLs.Add(
new WeakReference<MemoryFileAssemblyContextLoader>(loadedAcl.Value.Acl,
true));
544 loadedAcl.Value.ClearTypesList();
545 loadedAcl.Value.Acl.Unload();
546 loadedAcl.Value.ClearACLRef();
557 this.OnException?.Invoke($
"{nameof(TryBeginDispose)}() | Error: {e.Message}", e);
562 OpsLockUnloaded.ExitWriteLock();
563 OpsLockLoaded.ExitWriteLock();
568 [MethodImpl(MethodImplOptions.NoInlining)]
572 OpsLockUnloaded.EnterUpgradeableReadLock();
575 GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
576 List<WeakReference<MemoryFileAssemblyContextLoader>> toRemove =
new();
577 foreach (WeakReference<MemoryFileAssemblyContextLoader> weakReference
in UnloadingACLs)
579 if (!weakReference.TryGetTarget(out _))
581 toRemove.Add(weakReference);
587 OpsLockUnloaded.EnterWriteLock();
590 foreach (WeakReference<MemoryFileAssemblyContextLoader> reference
in toRemove)
592 UnloadingACLs.Remove(reference);
597 OpsLockUnloaded.ExitWriteLock();
600 isUnloaded = !UnloadingACLs.Any();
604 OpsLockUnloaded.ExitUpgradeableReadLock();
618 [MethodImpl(MethodImplOptions.NoInlining)]
622 OpsLockLoaded.EnterReadLock();
625 if (
id.Equals(Guid.Empty) || !LoadedACLs.ContainsKey(
id))
627 acl = LoadedACLs[id];
632 OpsLockLoaded.ExitReadLock();
645 [MethodImpl(MethodImplOptions.NoInlining)]
646 private bool GetOrCreateACL(Guid
id,
string friendlyName, out LoadedACL acl)
648 OpsLockLoaded.EnterUpgradeableReadLock();
651 if (
id.Equals(Guid.Empty) || !LoadedACLs.ContainsKey(
id) || LoadedACLs[
id] is
null)
653 OpsLockLoaded.EnterWriteLock();
657 acl =
new LoadedACL(
id,
this, friendlyName);
658 LoadedACLs[id] = acl;
663 OpsLockLoaded.ExitWriteLock();
668 acl = LoadedACLs[id];
675 this.OnException?.Invoke($
"{nameof(GetOrCreateACL)}Error: {e.Message}", e);
681 OpsLockLoaded.ExitUpgradeableReadLock();
686 [MethodImpl(MethodImplOptions.NoInlining)]
687 private bool DisposeACL(Guid
id)
689 OpsLockLoaded.EnterWriteLock();
690 OpsLockUnloaded.EnterWriteLock();
693 if (LoadedACLs.ContainsKey(
id) && LoadedACLs[
id] ==
null)
695 if (!LoadedACLs.TryRemove(
id, out _))
697 ModUtils.Logging.PrintWarning($
"An ACL with the GUID {id.ToString()} was found as null. Unable to remove null ACL entry.");
701 if (
id.Equals(Guid.Empty) || !LoadedACLs.ContainsKey(
id))
706 var acl = LoadedACLs[id];
708 foreach (Assembly assembly
in acl.Acl.Assemblies)
713 _subTypesLookupCache.Clear();
714 UnloadingACLs.Add(
new WeakReference<MemoryFileAssemblyContextLoader>(acl.Acl,
true));
723 this.OnException?.Invoke($
"{nameof(DisposeACL)}() | Error: {e.Message}", e);
728 OpsLockLoaded.ExitWriteLock();
729 OpsLockUnloaded.ExitWriteLock();
741 private void RebuildTypesList()
745 _defaultContextTypes = AssemblyLoadContext.Default.Assemblies
746 .SelectMany(a => a.GetSafeTypes())
747 .ToImmutableDictionary(t => t.FullName ?? t.Name, t => t);
748 _subTypesLookupCache.Clear();
750 catch(ArgumentException ae)
752 this.OnException?.Invoke($
"{nameof(RebuildTypesList)}() | Error: {ae.Message}", ae);
756 Dictionary<string, Type> types =
new();
757 foreach (var type
in AssemblyLoadContext.Default.Assemblies.SelectMany(a => a.GetSafeTypes()))
761 types.TryAdd(type.FullName ?? type.Name, type);
769 _defaultContextTypes = types.ToImmutableDictionary();
773 this.OnException?.Invoke($
"{nameof(RebuildTypesList)}() | Error: {e.Message}", e);
774 ModUtils.Logging.PrintError($
"{nameof(AssemblyManager)}: Unable to create list of default assembly types! Default AssemblyLoadContext types searching not available.");
776 ModUtils.Logging.PrintError($
"{nameof(AssemblyManager)}: Exception Details :{e.Message} | {e.InnerException}");
778 _defaultContextTypes = ImmutableDictionary<string, Type>.Empty;
787 private readonly ConcurrentDictionary<string, ImmutableList<Type>> _subTypesLookupCache =
new();
788 private ImmutableDictionary<string, Type> _defaultContextTypes;
789 private readonly ConcurrentDictionary<Guid, LoadedACL> LoadedACLs =
new();
790 private readonly List<WeakReference<MemoryFileAssemblyContextLoader>> UnloadingACLs=
new();
791 private readonly ReaderWriterLockSlim OpsLockLoaded =
new ();
792 private readonly ReaderWriterLockSlim OpsLockUnloaded =
new ();
801 public readonly Guid
Id;
802 private ImmutableDictionary<string, Type> _assembliesTypes = ImmutableDictionary<string, Type>.Empty;
808 this.
Acl =
new(manager)
810 FriendlyName = friendlyName
813 public ref readonly ImmutableDictionary<string, Type>
AssembliesTypes => ref _assembliesTypes;
818 internal void ClearACLRef()
826 internal void RebuildTypesList()
828 if (this.
Acl is
null)
830 ModUtils.Logging.PrintWarning($
"{nameof(RebuildTypesList)}() | ACL with GUID {Id.ToString()} is null, cannot rebuild.");
837 _assembliesTypes = this.
Acl.Assemblies
838 .SelectMany(a => a.GetSafeTypes())
839 .ToImmutableDictionary(t => t.FullName ?? t.Name, t => t);
841 catch(ArgumentException)
844 Dictionary<string, Type> types =
new();
845 foreach (var type
in this.
Acl.Assemblies.SelectMany(a => a.GetSafeTypes()))
849 types.TryAdd(type.FullName ?? type.Name, type);
857 _assembliesTypes = types.ToImmutableDictionary();
861 internal void ClearTypesList()
863 _assembliesTypes = ImmutableDictionary<string, Type>.Empty;
870 public static class AssemblyExtensions
877 public static IEnumerable<Type> GetSafeTypes(
this Assembly assembly)
883 return assembly.GetTypes();
885 catch (ReflectionTypeLoadException re)
889 return re.Types.Where(x => x !=
null)!;
891 catch (InvalidOperationException)
893 return new List<Type>();
898 return new List<Type>();
MemoryFileAssemblyContextLoader Acl
ref readonly ImmutableDictionary< string, Type > AssembliesTypes
Provides functionality for the loading, unloading and management of plugins implementing IAssemblyPlu...
bool TryGetACL(Guid id, out LoadedACL acl)
Tries to retrieve the LoadedACL with the given ID or null if none is found. WARNING: External referen...
AssemblyLoadingSuccessState LoadAssembliesFromLocations([NotNull] IEnumerable< string > filePaths, string friendlyName, ref Guid id)
Tries to load all assemblies at the supplied file paths list into the ACl with the given Guid....
IEnumerable< Type > GetTypesByName(string typeName)
Allows iteration over all types, including interfaces, in all loaded assemblies in the AsmMgr who's n...
Action< string, Exception > OnException
Called whenever an exception is thrown. First arg is a formatted message, Second arg is the Exception...
Action< Assembly > OnAssemblyLoaded
Called when an assembly is loaded.
ImmutableList< WeakReference< MemoryFileAssemblyContextLoader > > StillUnloadingACLs
[DEBUG ONLY] Returns a list of the current unloading ACLs.
IEnumerable< LoadedACL > GetAllLoadedACLs()
Returns a list of all loaded ACLs. WARNING: References to these ACLs outside of the AssemblyManager s...
System.Func< LoadedACL, bool > IsReadyToUnloadACL
Used by content package and plugin management to stop unloading of a given ACL until all plugins have...
IEnumerable< Type > GetSubTypesInLoadedAssemblies< T >()
IEnumerable< Type > GetAllTypesInLoadedAssemblies()
Allows iteration over all types (including interfaces) in all loaded assemblies managed by the AsmMgr...
bool IsCurrentlyUnloading
Checks if there are any AssemblyLoadContexts still in the process of unloading.
bool TryGetSubTypesFromACL< T >(Guid id, out IEnumerable< Type > types)
Tries to get types assignable to type from the ACL given the Guid.
bool SetACLToTemplateMode(Guid guid)
Switches the ACL with the given Guid to Template Mode, which disables assembly name resolution for an...
Action< Guid > OnACLUnload
For unloading issue debugging. Called whenever MemoryFileAssemblyContextLoader [load context] is unlo...
AssemblyLoadingSuccessState LoadAssemblyFromMemory([NotNull] string compiledAssemblyName, [NotNull] IEnumerable< SyntaxTree > syntaxTree, IEnumerable< MetadataReference > externalMetadataReferences, [NotNull] CSharpCompilationOptions compilationOptions, string friendlyName, ref Guid id, IEnumerable< Assembly > externFileAssemblyRefs=null)
Compiles an assembly from supplied references and syntax trees into the specified AssemblyContextLoad...
Action< Assembly > OnAssemblyUnloading
Called when an assembly is marked for unloading, before unloading begins. You should use this to clea...
bool TryGetSubTypesFromACL(Guid id, out IEnumerable< Type > types)
Tries to get types from the ACL given the Guid.
AssemblyLoadContext to compile from syntax trees in memory and to load from disk/file....