2024-07-25 22:59:11 +02:00
|
|
|
|
using Nerfed.Runtime;
|
|
|
|
|
using System.Diagnostics;
|
|
|
|
|
using System.Reflection;
|
|
|
|
|
using System.Runtime.CompilerServices;
|
|
|
|
|
|
|
|
|
|
namespace Nerfed.Editor.Project;
|
|
|
|
|
|
|
|
|
|
// https://github.com/godotengine/godot/blob/master/modules/mono/glue/GodotSharp/GodotPlugins/Main.c
|
|
|
|
|
// https://gitlab.com/robertk92/assemblyreloadtest/-/blob/main/AppContextTest/Program.cs
|
|
|
|
|
|
|
|
|
|
internal static class EditorAssemblyLoader
|
|
|
|
|
{
|
2024-09-08 20:58:13 +02:00
|
|
|
|
internal sealed class EditorAssemblyLoadContextWrapper
|
2024-07-25 22:59:11 +02:00
|
|
|
|
{
|
|
|
|
|
private EditorAssemblyLoadContext assemblyLoadContext;
|
|
|
|
|
private readonly WeakReference weakReference;
|
|
|
|
|
|
|
|
|
|
private EditorAssemblyLoadContextWrapper(EditorAssemblyLoadContext assemblyLoadContext, WeakReference weakReference)
|
|
|
|
|
{
|
|
|
|
|
this.assemblyLoadContext = assemblyLoadContext;
|
|
|
|
|
this.weakReference = weakReference;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public bool IsCollectible
|
|
|
|
|
{
|
|
|
|
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
|
|
|
|
// If assemblyLoadContext is null we already started unloading, so it was collectible.
|
|
|
|
|
get => assemblyLoadContext?.IsCollectible ?? true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public bool IsAlive
|
|
|
|
|
{
|
|
|
|
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
|
|
|
|
get => weakReference.IsAlive;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
|
|
|
|
public static (Assembly, EditorAssemblyLoadContextWrapper) CreateAndLoad(AssemblyName assemblyName)
|
|
|
|
|
{
|
|
|
|
|
EditorAssemblyLoadContext context = new EditorAssemblyLoadContext();
|
|
|
|
|
WeakReference reference = new WeakReference(context, trackResurrection: true);
|
|
|
|
|
EditorAssemblyLoadContextWrapper wrapper = new EditorAssemblyLoadContextWrapper(context, reference);
|
|
|
|
|
Assembly assembly = context.LoadFromAssemblyName(assemblyName);
|
|
|
|
|
return (assembly, wrapper);
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-08 20:58:13 +02:00
|
|
|
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
|
|
|
|
public static (Assembly, EditorAssemblyLoadContextWrapper) CreateAndLoad(string assemblyPath)
|
|
|
|
|
{
|
|
|
|
|
EditorAssemblyLoadContext context = new EditorAssemblyLoadContext();
|
|
|
|
|
WeakReference reference = new WeakReference(context, trackResurrection: true);
|
|
|
|
|
EditorAssemblyLoadContextWrapper wrapper = new EditorAssemblyLoadContextWrapper(context, reference);
|
|
|
|
|
Assembly assembly = context.LoadFromAssemblyPath(assemblyPath);
|
|
|
|
|
return (assembly, wrapper);
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-25 22:59:11 +02:00
|
|
|
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
|
|
|
|
internal void Unload()
|
|
|
|
|
{
|
|
|
|
|
assemblyLoadContext?.Unload();
|
|
|
|
|
assemblyLoadContext = null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-08 20:58:13 +02:00
|
|
|
|
internal static (Assembly, EditorAssemblyLoadContextWrapper) Load(string assemblyFilePath)
|
2024-07-25 22:59:11 +02:00
|
|
|
|
{
|
|
|
|
|
string assemblyFileName = Path.GetFileNameWithoutExtension(assemblyFilePath);
|
|
|
|
|
|
|
|
|
|
AssemblyName assemblyName = new AssemblyName(assemblyFileName);
|
|
|
|
|
|
|
|
|
|
return EditorAssemblyLoadContextWrapper.CreateAndLoad(assemblyName);
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-08 20:58:13 +02:00
|
|
|
|
internal static (Assembly, EditorAssemblyLoadContextWrapper) LoadFromPath(string assemblyFilePath)
|
|
|
|
|
{
|
|
|
|
|
return EditorAssemblyLoadContextWrapper.CreateAndLoad(assemblyFilePath);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal static bool Unload(EditorAssemblyLoadContextWrapper assemblyLoadContextWrapper)
|
2024-07-25 22:59:11 +02:00
|
|
|
|
{
|
|
|
|
|
if (assemblyLoadContextWrapper == null)
|
|
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!assemblyLoadContextWrapper.IsCollectible)
|
|
|
|
|
{
|
|
|
|
|
Log.Error($"{assemblyLoadContextWrapper} is not collectable!");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
assemblyLoadContextWrapper.Unload();
|
|
|
|
|
|
|
|
|
|
GC.Collect();
|
|
|
|
|
GC.WaitForPendingFinalizers();
|
|
|
|
|
|
|
|
|
|
TimeSpan timeout = TimeSpan.FromSeconds(30);
|
|
|
|
|
Stopwatch stopwatch = Stopwatch.StartNew();
|
|
|
|
|
|
|
|
|
|
while (assemblyLoadContextWrapper.IsAlive)
|
|
|
|
|
{
|
|
|
|
|
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
|
|
|
|
|
GC.WaitForPendingFinalizers();
|
|
|
|
|
|
|
|
|
|
if (!assemblyLoadContextWrapper.IsAlive)
|
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (stopwatch.Elapsed.TotalSeconds % 10 == 0)
|
|
|
|
|
{
|
|
|
|
|
Log.Info("Tring to unload assembly...");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (stopwatch.Elapsed >= timeout)
|
|
|
|
|
{
|
|
|
|
|
Log.Error("Failed to unload assembly!");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|