diff --git a/Source/Engine/Engine/NativeInterop.Unmanaged.cs b/Source/Engine/Engine/NativeInterop.Unmanaged.cs index 57454ad1e..d2a8ca55e 100644 --- a/Source/Engine/Engine/NativeInterop.Unmanaged.cs +++ b/Source/Engine/Engine/NativeInterop.Unmanaged.cs @@ -903,11 +903,25 @@ namespace FlaxEngine.Interop AssemblyLocations.Remove(assembly.FullName); - // Clear all caches which might hold references to closing assembly + // Unload native library handles associated for this assembly + string nativeLibraryName = assemblyOwnedNativeLibraries.GetValueOrDefault(assembly); + if (nativeLibraryName != null && loadedNativeLibraries.TryGetValue(nativeLibraryName, out IntPtr nativeLibrary)) + { + NativeLibrary.Free(nativeLibrary); + loadedNativeLibraries.Remove(nativeLibraryName); + } + if (nativeLibraryName != null) + nativeLibraryPaths.Remove(nativeLibraryName); + } + + [UnmanagedCallersOnly] + internal static void ReloadScriptingAssemblyLoadContext() + { +#if FLAX_EDITOR + // Clear all caches which might hold references to assemblies in collectible ALC typeCache.Clear(); // Release all references in collectible ALC -#if FLAX_EDITOR cachedDelegatesCollectible.Clear(); foreach (var pair in typeHandleCacheCollectible) pair.Value.Free(); @@ -918,23 +932,13 @@ namespace FlaxEngine.Interop foreach (var handle in fieldHandleCacheCollectible) handle.Free(); fieldHandleCacheCollectible.Clear(); -#endif + _typeSizeCache.Clear(); foreach (var pair in classAttributesCacheCollectible) pair.Value.Free(); classAttributesCacheCollectible.Clear(); - // Unload native library handles associated for this assembly - string nativeLibraryName = assemblyOwnedNativeLibraries.GetValueOrDefault(assembly); - if (nativeLibraryName != null && loadedNativeLibraries.TryGetValue(nativeLibraryName, out IntPtr nativeLibrary)) - { - NativeLibrary.Free(nativeLibrary); - loadedNativeLibraries.Remove(nativeLibraryName); - } - if (nativeLibraryName != null) - nativeLibraryPaths.Remove(nativeLibraryName); - // Unload the ALC bool unloading = true; scriptingAssemblyLoadContext.Unloading += (alc) => { unloading = false; }; @@ -945,6 +949,7 @@ namespace FlaxEngine.Interop InitScriptingAssemblyLoadContext(); DelegateHelpers.InitMethods(); +#endif } [UnmanagedCallersOnly] diff --git a/Source/Engine/Scripting/ManagedCLR/MCore.h b/Source/Engine/Scripting/ManagedCLR/MCore.h index 38b58403d..e1de3c207 100644 --- a/Source/Engine/Scripting/ManagedCLR/MCore.h +++ b/Source/Engine/Scripting/ManagedCLR/MCore.h @@ -47,7 +47,7 @@ public: #if USE_EDITOR // Called by Scripting in a middle of hot-reload (after unloading modules but before loading them again). - static void OnMidHotReload(); + static void ReloadScriptingAssemblyLoadContext(); #endif public: diff --git a/Source/Engine/Scripting/Runtime/DotNet.cpp b/Source/Engine/Scripting/Runtime/DotNet.cpp index 8d75d7a95..36f1f16af 100644 --- a/Source/Engine/Scripting/Runtime/DotNet.cpp +++ b/Source/Engine/Scripting/Runtime/DotNet.cpp @@ -160,6 +160,7 @@ DECLARE_ENUM_OPERATORS(MTypeAttributes); DECLARE_ENUM_OPERATORS(MMethodAttributes); DECLARE_ENUM_OPERATORS(MFieldAttributes); +// Multiple AppDomains are superseded by AssemblyLoadContext in .NET extern MDomain* MRootDomain; extern MDomain* MActiveDomain; extern Array> MDomains; @@ -301,11 +302,14 @@ void MCore::UnloadEngine() #if USE_EDITOR -void MCore::OnMidHotReload() +void MCore::ReloadScriptingAssemblyLoadContext() { // Clear any cached class attributes (see https://github.com/FlaxEngine/FlaxEngine/issues/1108) for (auto e : CachedClassHandles) e.Value->_attributes.Clear(); + + static void* ReloadScriptingAssemblyLoadContextPtr = GetStaticMethodPointer(TEXT("ReloadScriptingAssemblyLoadContext")); + CallStaticMethod(ReloadScriptingAssemblyLoadContextPtr); } #endif @@ -723,16 +727,12 @@ bool MAssembly::LoadImage(const String& assemblyPath, const StringView& nativePa bool MAssembly::UnloadImage(bool isReloading) { - if (_handle) + if (_handle && isReloading) { - // TODO: closing assembly on reload only is copy-paste from mono, do we need do this on .NET too? - if (isReloading) - { - LOG(Info, "Unloading managed assembly \'{0}\' (is reloading)", String(_name)); + LOG(Info, "Unloading managed assembly \'{0}\' (is reloading)", String(_name)); - static void* CloseAssemblyPtr = GetStaticMethodPointer(TEXT("CloseAssembly")); - CallStaticMethod(CloseAssemblyPtr, _handle); - } + static void* CloseAssemblyPtr = GetStaticMethodPointer(TEXT("CloseAssembly")); + CallStaticMethod(CloseAssemblyPtr, _handle); CachedAssemblyHandles.Remove(_handle); _handle = nullptr; diff --git a/Source/Engine/Scripting/Runtime/Mono.cpp b/Source/Engine/Scripting/Runtime/Mono.cpp index ca4ef367b..0a60db42f 100644 --- a/Source/Engine/Scripting/Runtime/Mono.cpp +++ b/Source/Engine/Scripting/Runtime/Mono.cpp @@ -716,7 +716,7 @@ void MCore::UnloadEngine() #if USE_EDITOR -void MCore::OnMidHotReload() +void MCore::ReloadScriptingAssemblyLoadContext() { } diff --git a/Source/Engine/Scripting/Runtime/None.cpp b/Source/Engine/Scripting/Runtime/None.cpp index c8f8926e5..ef9c118cf 100644 --- a/Source/Engine/Scripting/Runtime/None.cpp +++ b/Source/Engine/Scripting/Runtime/None.cpp @@ -61,7 +61,7 @@ void MCore::UnloadEngine() #if USE_EDITOR -void MCore::OnMidHotReload() +void MCore::ReloadScriptingAssemblyLoadContext() { } diff --git a/Source/Engine/Scripting/Scripting.cpp b/Source/Engine/Scripting/Scripting.cpp index bbfa802f1..6210063b8 100644 --- a/Source/Engine/Scripting/Scripting.cpp +++ b/Source/Engine/Scripting/Scripting.cpp @@ -706,7 +706,9 @@ void Scripting::Reload(bool canTriggerSceneReload) modules.Clear(); _nonNativeModules.ClearDelete(); _hasGameModulesLoaded = false; - MCore::OnMidHotReload(); + + // Release and create a new assembly load context for user assemblies + MCore::ReloadScriptingAssemblyLoadContext(); // Give GC a try to cleanup old user objects and the other mess MCore::GC::Collect();