From b095acd4a5f64bda5a6d5e74443da8c36ff28875 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Mon, 22 Apr 2024 00:11:24 +0300 Subject: [PATCH 01/13] Fix scripting AssemblyLoadContext not getting unloaded --- Source/Engine/Engine/NativeInterop.Managed.cs | 28 +++-- .../Engine/Engine/NativeInterop.Unmanaged.cs | 107 ++++++++++++++++-- Source/Engine/Engine/NativeInterop.cs | 42 ++----- Source/Engine/Scripting/ManagedCLR/MCore.h | 11 +- Source/Engine/Scripting/Runtime/DotNet.cpp | 12 +- Source/Engine/Scripting/Runtime/Mono.cpp | 6 +- Source/Engine/Scripting/Runtime/None.cpp | 6 +- Source/Engine/Scripting/Scripting.cpp | 5 +- 8 files changed, 159 insertions(+), 58 deletions(-) diff --git a/Source/Engine/Engine/NativeInterop.Managed.cs b/Source/Engine/Engine/NativeInterop.Managed.cs index c7507c778..148efb007 100644 --- a/Source/Engine/Engine/NativeInterop.Managed.cs +++ b/Source/Engine/Engine/NativeInterop.Managed.cs @@ -176,6 +176,7 @@ namespace FlaxEngine.Interop _managedHandle.Free(); _unmanagedData = IntPtr.Zero; } + _arrayType = _elementType = null; ManagedArrayPool.Put(this); } @@ -442,22 +443,25 @@ namespace FlaxEngine.Interop /// /// Tries to free all references to old weak handles so GC can collect them. /// - internal static void TryCollectWeakHandles() + internal static void TryCollectWeakHandles(bool force = false) { - if (weakHandleAccumulator < nextWeakPoolCollection) - return; + if (!force) + { + if (weakHandleAccumulator < nextWeakPoolCollection) + return; - nextWeakPoolCollection = weakHandleAccumulator + 1000; + nextWeakPoolCollection = weakHandleAccumulator + 1000; - // Try to swap pools after garbage collection or whenever the pool gets too large - var gc0CollectionCount = GC.CollectionCount(0); - if (gc0CollectionCount < nextWeakPoolGCCollection && weakPool.Count < WeakPoolCollectionSizeThreshold) - return; - nextWeakPoolGCCollection = gc0CollectionCount + 1; + // Try to swap pools after garbage collection or whenever the pool gets too large + var gc0CollectionCount = GC.CollectionCount(0); + if (gc0CollectionCount < nextWeakPoolGCCollection && weakPool.Count < WeakPoolCollectionSizeThreshold) + return; + nextWeakPoolGCCollection = gc0CollectionCount + 1; - // Prevent huge allocations from swapping the pools in the middle of the operation - if (System.Diagnostics.Stopwatch.GetElapsedTime(lastWeakPoolCollectionTime).TotalMilliseconds < WeakPoolCollectionTimeThreshold) - return; + // Prevent huge allocations from swapping the pools in the middle of the operation + if (System.Diagnostics.Stopwatch.GetElapsedTime(lastWeakPoolCollectionTime).TotalMilliseconds < WeakPoolCollectionTimeThreshold) + return; + } lastWeakPoolCollectionTime = System.Diagnostics.Stopwatch.GetTimestamp(); // Swap the pools and release the oldest pool for GC diff --git a/Source/Engine/Engine/NativeInterop.Unmanaged.cs b/Source/Engine/Engine/NativeInterop.Unmanaged.cs index 71caee1e9..618dd8564 100644 --- a/Source/Engine/Engine/NativeInterop.Unmanaged.cs +++ b/Source/Engine/Engine/NativeInterop.Unmanaged.cs @@ -1054,7 +1054,49 @@ namespace FlaxEngine.Interop } [UnmanagedCallersOnly] - internal static void ReloadScriptingAssemblyLoadContext() + internal static void CreateScriptingAssemblyLoadContext() + { +#if FLAX_EDITOR + if (scriptingAssemblyLoadContext != null) + { + // Wait for previous ALC to finish unloading, track it without holding strong references to it + GCHandle weakRef = GCHandle.Alloc(scriptingAssemblyLoadContext, GCHandleType.WeakTrackResurrection); + scriptingAssemblyLoadContext = null; +#if true + // In case the ALC doesn't unload properly: https://learn.microsoft.com/en-us/dotnet/standard/assembly/unloadability#debug-unloading-issues + while (true) +#else + for (int attempts = 5; attempts > 0; attempts--) +#endif + { + GC.Collect(); + GC.WaitForPendingFinalizers(); + + if (!IsHandleAlive(weakRef)) + break; + System.Threading.Thread.Sleep(1); + } + if (IsHandleAlive(weakRef)) + Debug.LogWarning("Scripting AssemblyLoadContext was not unloaded."); + weakRef.Free(); + + static bool IsHandleAlive(GCHandle weakRef) + { + // Checking the target in scope somehow holds a reference to it...? + return weakRef.Target != null; + } + } + + scriptingAssemblyLoadContext = new AssemblyLoadContext("Flax", isCollectible: true); + scriptingAssemblyLoadContext.Resolving += OnScriptingAssemblyLoadContextResolving; +#else + scriptingAssemblyLoadContext = new AssemblyLoadContext("Flax", isCollectible: false); +#endif + DelegateHelpers.InitMethods(); + } + + [UnmanagedCallersOnly] + internal static void UnloadScriptingAssemblyLoadContext() { #if FLAX_EDITOR // Clear all caches which might hold references to assemblies in collectible ALC @@ -1072,24 +1114,73 @@ namespace FlaxEngine.Interop handle.Free(); propertyHandleCacheCollectible.Clear(); + foreach (var key in assemblyHandles.Keys.Where(x => x.IsCollectible)) + assemblyHandles.Remove(key); + foreach (var key in assemblyOwnedNativeLibraries.Keys.Where(x => x.IsCollectible)) + assemblyOwnedNativeLibraries.Remove(key); + _typeSizeCache.Clear(); foreach (var pair in classAttributesCacheCollectible) pair.Value.Free(); classAttributesCacheCollectible.Clear(); + ArrayFactory.marshalledTypes.Clear(); + ArrayFactory.arrayTypes.Clear(); + ArrayFactory.createArrayDelegates.Clear(); + FlaxEngine.Json.JsonSerializer.ResetCache(); + DelegateHelpers.Release(); + + // Ensure both pools are empty + ManagedHandle.ManagedHandlePool.TryCollectWeakHandles(true); + ManagedHandle.ManagedHandlePool.TryCollectWeakHandles(true); + + GC.Collect(); + GC.WaitForPendingFinalizers(); + + { + // HACK: Workaround for TypeDescriptor holding references to collectible types (https://github.com/dotnet/runtime/issues/30656) + + Type TypeDescriptionProviderType = typeof(System.ComponentModel.TypeDescriptionProvider); + MethodInfo clearCacheMethod = TypeDescriptionProviderType?.Assembly.GetType("System.ComponentModel.ReflectionCachesUpdateHandler")?.GetMethod("ClearCache"); + if (clearCacheMethod != null) + clearCacheMethod.Invoke(null, new object[] { null }); + else + { + MethodInfo beforeUpdateMethod = TypeDescriptionProviderType?.Assembly.GetType("System.ComponentModel.ReflectionCachesUpdateHandler")?.GetMethod("BeforeUpdate"); + if (beforeUpdateMethod != null) + beforeUpdateMethod.Invoke(null, new object[] { null }); + } + + Type TypeDescriptorType = typeof(System.ComponentModel.TypeDescriptor); + + object s_internalSyncObject = TypeDescriptorType?.GetField("s_internalSyncObject", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic)?.GetValue(null); + System.Collections.Hashtable s_defaultProviders = (System.Collections.Hashtable)TypeDescriptorType?.GetField("s_defaultProviders", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic)?.GetValue(null); + if (s_internalSyncObject != null && s_defaultProviders != null) + { + lock (s_internalSyncObject) + s_defaultProviders.Clear(); + } + + object s_providerTable = TypeDescriptorType?.GetField("s_providerTable", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic)?.GetValue(null); + System.Collections.Hashtable s_providerTypeTable = (System.Collections.Hashtable)TypeDescriptorType?.GetField("s_providerTypeTable", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic)?.GetValue(null); + if (s_providerTable != null && s_providerTypeTable != null) + { + lock (s_providerTable) + s_providerTypeTable.Clear(); + TypeDescriptorType.GetField("s_providerTable", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic) + ?.FieldType.GetMethods(BindingFlags.Instance | BindingFlags.Public).FirstOrDefault(x => x.Name == "Clear") + ?.Invoke(s_providerTable, new object[] { }); + } + } // Unload the ALC - bool unloading = true; - scriptingAssemblyLoadContext.Unloading += (alc) => { unloading = false; }; scriptingAssemblyLoadContext.Unload(); + scriptingAssemblyLoadContext.Resolving -= OnScriptingAssemblyLoadContextResolving; - while (unloading) - System.Threading.Thread.Sleep(1); - - InitScriptingAssemblyLoadContext(); - DelegateHelpers.InitMethods(); + GC.Collect(); + GC.WaitForPendingFinalizers(); #endif } diff --git a/Source/Engine/Engine/NativeInterop.cs b/Source/Engine/Engine/NativeInterop.cs index 35fe70221..7a9a8e5ff 100644 --- a/Source/Engine/Engine/NativeInterop.cs +++ b/Source/Engine/Engine/NativeInterop.cs @@ -73,19 +73,6 @@ namespace FlaxEngine.Interop return nativeLibrary; } - private static void InitScriptingAssemblyLoadContext() - { -#if FLAX_EDITOR - var isCollectible = true; -#else - var isCollectible = false; -#endif - scriptingAssemblyLoadContext = new AssemblyLoadContext("Flax", isCollectible); -#if FLAX_EDITOR - scriptingAssemblyLoadContext.Resolving += OnScriptingAssemblyLoadContextResolving; -#endif - } - [UnmanagedCallersOnly] internal static unsafe void Init() { @@ -97,8 +84,6 @@ namespace FlaxEngine.Interop System.Threading.Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture; System.Threading.Thread.CurrentThread.CurrentUICulture = CultureInfo.InvariantCulture; - InitScriptingAssemblyLoadContext(); - DelegateHelpers.InitMethods(); } #if FLAX_EDITOR @@ -1475,11 +1460,11 @@ namespace FlaxEngine.Interop internal static class ArrayFactory { - private delegate Array CreateArrayDelegate(long size); + internal delegate Array CreateArrayDelegate(long size); - private static ConcurrentDictionary marshalledTypes = new ConcurrentDictionary(1, 3); - private static ConcurrentDictionary arrayTypes = new ConcurrentDictionary(1, 3); - private static ConcurrentDictionary createArrayDelegates = new ConcurrentDictionary(1, 3); + internal static ConcurrentDictionary marshalledTypes = new ConcurrentDictionary(1, 3); + internal static ConcurrentDictionary arrayTypes = new ConcurrentDictionary(1, 3); + internal static ConcurrentDictionary createArrayDelegates = new ConcurrentDictionary(1, 3); internal static Type GetMarshalledType(Type elementType) { @@ -1645,17 +1630,6 @@ namespace FlaxEngine.Interop return RegisterType(type, true).typeHolder; } - internal static (TypeHolder typeHolder, ManagedHandle handle) GetTypeHolderAndManagedHandle(Type type) - { - if (managedTypes.TryGetValue(type, out (TypeHolder typeHolder, ManagedHandle handle) tuple)) - return tuple; -#if FLAX_EDITOR - if (managedTypesCollectible.TryGetValue(type, out tuple)) - return tuple; -#endif - return RegisterType(type, true); - } - /// /// Returns a static ManagedHandle to TypeHolder for given Type, and caches it if needed. /// @@ -1781,6 +1755,14 @@ namespace FlaxEngine.Interop #endif } + internal static void Release() + { + MakeNewCustomDelegateFunc = null; +#if FLAX_EDITOR + MakeNewCustomDelegateFuncCollectible = null; +#endif + } + internal static Type MakeNewCustomDelegate(Type[] parameters) { #if FLAX_EDITOR diff --git a/Source/Engine/Scripting/ManagedCLR/MCore.h b/Source/Engine/Scripting/ManagedCLR/MCore.h index 35227df56..d975f5af7 100644 --- a/Source/Engine/Scripting/ManagedCLR/MCore.h +++ b/Source/Engine/Scripting/ManagedCLR/MCore.h @@ -45,9 +45,16 @@ public: /// static void UnloadEngine(); + /// + /// Creates the assembly load context for assemblies used by Scripting. + /// + static void CreateScriptingAssemblyLoadContext(); + #if USE_EDITOR - // Called by Scripting in a middle of hot-reload (after unloading modules but before loading them again). - static void ReloadScriptingAssemblyLoadContext(); + /// + /// Called by Scripting in a middle of hot-reload (after unloading modules but before loading them again). + /// + static void UnloadScriptingAssemblyLoadContext(); #endif public: diff --git a/Source/Engine/Scripting/Runtime/DotNet.cpp b/Source/Engine/Scripting/Runtime/DotNet.cpp index ddafab614..29ccea8ae 100644 --- a/Source/Engine/Scripting/Runtime/DotNet.cpp +++ b/Source/Engine/Scripting/Runtime/DotNet.cpp @@ -330,9 +330,15 @@ void MCore::UnloadEngine() ShutdownHostfxr(); } +void MCore::CreateScriptingAssemblyLoadContext() +{ + static void* CreateScriptingAssemblyLoadContextPtr = GetStaticMethodPointer(TEXT("CreateScriptingAssemblyLoadContext")); + CallStaticMethod(CreateScriptingAssemblyLoadContextPtr); +} + #if USE_EDITOR -void MCore::ReloadScriptingAssemblyLoadContext() +void MCore::UnloadScriptingAssemblyLoadContext() { // Clear any cached class attributes (see https://github.com/FlaxEngine/FlaxEngine/issues/1108) for (auto e : CachedClassHandles) @@ -377,8 +383,8 @@ void MCore::ReloadScriptingAssemblyLoadContext() } } - static void* ReloadScriptingAssemblyLoadContextPtr = GetStaticMethodPointer(TEXT("ReloadScriptingAssemblyLoadContext")); - CallStaticMethod(ReloadScriptingAssemblyLoadContextPtr); + static void* UnloadScriptingAssemblyLoadContextPtr = GetStaticMethodPointer(TEXT("UnloadScriptingAssemblyLoadContext")); + CallStaticMethod(UnloadScriptingAssemblyLoadContextPtr); } #endif diff --git a/Source/Engine/Scripting/Runtime/Mono.cpp b/Source/Engine/Scripting/Runtime/Mono.cpp index 351e1569b..f4da8135c 100644 --- a/Source/Engine/Scripting/Runtime/Mono.cpp +++ b/Source/Engine/Scripting/Runtime/Mono.cpp @@ -715,9 +715,13 @@ void MCore::UnloadEngine() #endif } +void MCore::CreateScriptingAssemblyLoadContext() +{ +} + #if USE_EDITOR -void MCore::ReloadScriptingAssemblyLoadContext() +void MCore::UnloadScriptingAssemblyLoadContext() { } diff --git a/Source/Engine/Scripting/Runtime/None.cpp b/Source/Engine/Scripting/Runtime/None.cpp index 8731ed00d..98bf1f012 100644 --- a/Source/Engine/Scripting/Runtime/None.cpp +++ b/Source/Engine/Scripting/Runtime/None.cpp @@ -58,9 +58,13 @@ void MCore::UnloadEngine() MRootDomain = nullptr; } +void MCore::CreateScriptingAssemblyLoadContext() +{ +} + #if USE_EDITOR -void MCore::ReloadScriptingAssemblyLoadContext() +void MCore::UnloadScriptingAssemblyLoadContext() { } diff --git a/Source/Engine/Scripting/Scripting.cpp b/Source/Engine/Scripting/Scripting.cpp index 4731a088d..75d68394d 100644 --- a/Source/Engine/Scripting/Scripting.cpp +++ b/Source/Engine/Scripting/Scripting.cpp @@ -182,6 +182,8 @@ bool ScriptingService::Init() return true; } + MCore::CreateScriptingAssemblyLoadContext(); + // Cache root domain _rootDomain = MCore::GetRootDomain(); @@ -710,7 +712,8 @@ void Scripting::Reload(bool canTriggerSceneReload) _hasGameModulesLoaded = false; // Release and create a new assembly load context for user assemblies - MCore::ReloadScriptingAssemblyLoadContext(); + MCore::UnloadScriptingAssemblyLoadContext(); + MCore::CreateScriptingAssemblyLoadContext(); // Give GC a try to cleanup old user objects and the other mess MCore::GC::Collect(); From 691b9458eca14da5cdaeccf9cb71afdcff8956f9 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Mon, 22 Apr 2024 23:33:27 +0300 Subject: [PATCH 02/13] Fix wrong prefab event unregistration --- Source/Editor/CustomEditors/Dedicated/ActorEditor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Editor/CustomEditors/Dedicated/ActorEditor.cs b/Source/Editor/CustomEditors/Dedicated/ActorEditor.cs index d1cdd8780..488d180cf 100644 --- a/Source/Editor/CustomEditors/Dedicated/ActorEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/ActorEditor.cs @@ -206,7 +206,7 @@ namespace FlaxEditor.CustomEditors.Dedicated if (_linkedPrefabId != Guid.Empty) { _linkedPrefabId = Guid.Empty; - Editor.Instance.Prefabs.PrefabApplied -= OnPrefabApplying; + Editor.Instance.Prefabs.PrefabApplying -= OnPrefabApplying; Editor.Instance.Prefabs.PrefabApplied -= OnPrefabApplied; } } From 9f8faf4f17a082f18ace587ab3fcc4884cbdefa0 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Mon, 22 Apr 2024 23:55:39 +0300 Subject: [PATCH 03/13] Clear references holding on to types in game assemblies --- Source/Editor/CustomEditors/CustomEditorPresenter.cs | 9 +++++++++ Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs | 1 + Source/Editor/CustomEditors/Editors/GenericEditor.cs | 8 ++++++++ Source/Editor/SceneGraph/SceneGraphNode.cs | 1 + Source/Engine/UI/GUI/Tooltip.cs | 1 + 5 files changed, 20 insertions(+) diff --git a/Source/Editor/CustomEditors/CustomEditorPresenter.cs b/Source/Editor/CustomEditors/CustomEditorPresenter.cs index 703c3f51b..270d71057 100644 --- a/Source/Editor/CustomEditors/CustomEditorPresenter.cs +++ b/Source/Editor/CustomEditors/CustomEditorPresenter.cs @@ -195,6 +195,15 @@ namespace FlaxEditor.CustomEditors Presenter.AfterLayout?.Invoke(layout); } + /// + protected override void Deinitialize() + { + Editor = null; + _overrideEditor = null; + + base.Deinitialize(); + } + /// protected override void OnModified() { diff --git a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs index 583241915..b0239dc8f 100644 --- a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs @@ -1057,6 +1057,7 @@ namespace FlaxEditor.CustomEditors.Dedicated protected override void Deinitialize() { _scriptToggles = null; + _scripts.Clear(); base.Deinitialize(); } diff --git a/Source/Editor/CustomEditors/Editors/GenericEditor.cs b/Source/Editor/CustomEditors/Editors/GenericEditor.cs index bfcd8d046..6943f042d 100644 --- a/Source/Editor/CustomEditors/Editors/GenericEditor.cs +++ b/Source/Editor/CustomEditors/Editors/GenericEditor.cs @@ -819,6 +819,14 @@ namespace FlaxEditor.CustomEditors.Editors OnGroupsEnd(); } + protected override void Deinitialize() + { + _visibleIfCaches = null; + _visibleIfPropertiesListsCache = null; + + base.Deinitialize(); + } + /// public override void Refresh() { diff --git a/Source/Editor/SceneGraph/SceneGraphNode.cs b/Source/Editor/SceneGraph/SceneGraphNode.cs index 5becb69dc..37afc896e 100644 --- a/Source/Editor/SceneGraph/SceneGraphNode.cs +++ b/Source/Editor/SceneGraph/SceneGraphNode.cs @@ -469,6 +469,7 @@ namespace FlaxEditor.SceneGraph { ChildNodes[i].OnDispose(); } + ChildNodes.Clear(); SceneGraphFactory.Nodes.Remove(ID); } diff --git a/Source/Engine/UI/GUI/Tooltip.cs b/Source/Engine/UI/GUI/Tooltip.cs index 40aae0067..8c8e05b43 100644 --- a/Source/Engine/UI/GUI/Tooltip.cs +++ b/Source/Engine/UI/GUI/Tooltip.cs @@ -120,6 +120,7 @@ namespace FlaxEngine.GUI // Unlink IsLayoutLocked = true; Parent = null; + _showTarget = null; // Close window if (_window) From 98bbcbd44269b0f2cef55f4c7e6b7f6c14bef948 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Mon, 22 Apr 2024 23:58:27 +0300 Subject: [PATCH 04/13] Close and restore Prefab windows during scripting reload --- Source/Editor/Modules/WindowsModule.cs | 154 +++++++++++++++--- Source/Editor/Windows/Assets/PrefabWindow.cs | 20 +-- .../Engine/Engine/NativeInterop.Unmanaged.cs | 6 - 3 files changed, 138 insertions(+), 42 deletions(-) diff --git a/Source/Editor/Modules/WindowsModule.cs b/Source/Editor/Modules/WindowsModule.cs index fa7a04d9b..ba29bea4f 100644 --- a/Source/Editor/Modules/WindowsModule.cs +++ b/Source/Editor/Modules/WindowsModule.cs @@ -5,10 +5,12 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Reflection; +using System.Runtime.InteropServices; using System.Text; using System.Xml; using FlaxEditor.Content; using FlaxEditor.GUI.Dialogs; +using FlaxEditor.GUI.Docking; using FlaxEditor.Windows; using FlaxEditor.Windows.Assets; using FlaxEditor.Windows.Profiler; @@ -39,6 +41,7 @@ namespace FlaxEditor.Modules public DockState DockState; public DockPanel DockedTo; + public int DockedTabIndex; public float? SplitterValue = null; public bool SelectOnShow = false; @@ -48,6 +51,9 @@ namespace FlaxEditor.Modules public Float2 FloatSize; public Float2 FloatPosition; + public AssetItem Item; + public AssetItem Item2; + // Constructor, to allow for default values public WindowRestoreData() { @@ -807,6 +813,56 @@ namespace FlaxEditor.Modules Editor.StateMachine.StateChanged += OnEditorStateChanged; } + internal void AddToRestore(PrefabWindow win) + { + var type = win.GetType(); + var winData = new WindowRestoreData(); + var panel = win.ParentDockPanel; + + // Ensure that this window is only selected following recompilation + // if it was the active tab in its dock panel. Otherwise, there is a + // risk of interrupting the user's workflow by potentially selecting + // background tabs. + var window = win.RootWindow?.Window; + winData.SelectOnShow = panel.SelectedTab == win; + winData.DockedTabIndex = 0; + if (panel is FloatWindowDockPanel && window != null && panel.TabsCount == 1) + { + winData.DockState = DockState.Float; + winData.FloatPosition = window.Position; + winData.FloatSize = window.ClientSize; + winData.Maximize = window.IsMaximized; + winData.Minimize = window.IsMinimized; + winData.DockedTo = panel; + } + else + { + for (int i = 0; i < panel.Tabs.Count; i++) + { + if (panel.Tabs[i] == win) + { + winData.DockedTabIndex = i; + break; + } + } + if (panel.TabsCount > 1) + { + winData.DockState = DockState.DockFill; + winData.DockedTo = panel; + } + else + { + winData.DockState = panel.TryGetDockState(out var splitterValue); + winData.DockedTo = panel.ParentDockPanel; + winData.SplitterValue = splitterValue; + } + } + winData.AssemblyName = type.Assembly.GetName().Name; + winData.TypeName = type.FullName; + winData.Item = win.Item; + _restoreWindows.Add(winData); + } + internal void AddToRestore(CustomEditorWindow win) { var type = win.GetType(); @@ -839,7 +895,8 @@ namespace FlaxEditor.Modules { winData.DockState = DockState.DockFill; winData.DockedTo = panel; - }else + } + else { winData.DockState = panel.TryGetDockState(out var splitterValue); winData.DockedTo = panel.ParentDockPanel; @@ -853,36 +910,89 @@ namespace FlaxEditor.Modules private void OnScriptsReloadEnd() { - for (int i = 0; i < _restoreWindows.Count; i++) + // Go in reverse order to create floating Prefab windows first before docked windows + for (int i = _restoreWindows.Count - 1; i >= 0; i--) { var winData = _restoreWindows[i]; try { var assembly = Utils.GetAssemblyByName(winData.AssemblyName); - if (assembly != null) + if (assembly == null) + continue; + + var type = assembly.GetType(winData.TypeName); + if (type == null) + continue; + + if (type == typeof(PrefabWindow)) { - var type = assembly.GetType(winData.TypeName); - if (type != null) + var win = new PrefabWindow(Editor.Instance, winData.Item); + win.Show(winData.DockState, winData.DockState != DockState.Float ? winData.DockedTo : null, winData.SelectOnShow, winData.SplitterValue); + if (winData.DockState == DockState.Float) { - var win = (CustomEditorWindow)Activator.CreateInstance(type); - win.Show(winData.DockState, winData.DockedTo, winData.SelectOnShow, winData.SplitterValue); - if (winData.DockState == DockState.Float) + var window = win.RootWindow.Window; + window.Position = winData.FloatPosition; + if (winData.Maximize) { - var window = win.Window.RootWindow.Window; - window.Position = winData.FloatPosition; - if (winData.Maximize) - { - window.Maximize(); - } - else if (winData.Minimize) - { - window.Minimize(); - } - else - { - window.ClientSize = winData.FloatSize; - } + window.Maximize(); + } + else if (winData.Minimize) + { + window.Minimize(); + } + else + { + window.ClientSize = winData.FloatSize; + } + + // Update panel reference in other windows docked to this panel + foreach (ref var otherData in CollectionsMarshal.AsSpan(_restoreWindows)) + { + if (otherData.DockedTo == winData.DockedTo) + otherData.DockedTo = win.ParentDockPanel; + } + } + var panel = win.ParentDockPanel; + int currentTabIndex = 0; + for (int pi = 0; pi < panel.TabsCount; pi++) + { + if (panel.Tabs[pi] == win) + { + currentTabIndex = pi; + break; + } + } + while (currentTabIndex > winData.DockedTabIndex) + { + win.ParentDockPanel.MoveTabLeft(currentTabIndex); + currentTabIndex--; + } + while (currentTabIndex < winData.DockedTabIndex) + { + win.ParentDockPanel.MoveTabRight(currentTabIndex); + currentTabIndex++; + } + } + else + { + var win = (CustomEditorWindow)Activator.CreateInstance(type); + win.Show(winData.DockState, winData.DockedTo, winData.SelectOnShow, winData.SplitterValue); + if (winData.DockState == DockState.Float) + { + var window = win.Window.RootWindow.Window; + window.Position = winData.FloatPosition; + if (winData.Maximize) + { + window.Maximize(); + } + else if (winData.Minimize) + { + window.Minimize(); + } + else + { + window.ClientSize = winData.FloatSize; } } } diff --git a/Source/Editor/Windows/Assets/PrefabWindow.cs b/Source/Editor/Windows/Assets/PrefabWindow.cs index 533735c93..120ffda57 100644 --- a/Source/Editor/Windows/Assets/PrefabWindow.cs +++ b/Source/Editor/Windows/Assets/PrefabWindow.cs @@ -194,7 +194,6 @@ namespace FlaxEditor.Windows.Assets Editor.Prefabs.PrefabApplied += OnPrefabApplied; ScriptsBuilder.ScriptsReloadBegin += OnScriptsReloadBegin; - ScriptsBuilder.ScriptsReloadEnd += OnScriptsReloadEnd; // Setup input actions InputActions.Add(options => options.Undo, () => @@ -311,24 +310,18 @@ namespace FlaxEditor.Windows.Assets } } + if (!IsHidden) + { + Editor.Instance.Windows.AddToRestore(this); + } + // Cleanup Deselect(); Graph.MainActor = null; _viewport.Prefab = null; _undo?.Clear(); // TODO: maybe don't clear undo? - } - private void OnScriptsReloadEnd() - { - _isScriptsReloading = false; - - if (_asset == null || !_asset.IsLoaded) - return; - - // Restore - OnPrefabOpened(); - _undo.Clear(); - ClearEditedFlag(); + Close(); } private void OnUndoEvent(IUndoAction action) @@ -547,7 +540,6 @@ namespace FlaxEditor.Windows.Assets { Editor.Prefabs.PrefabApplied -= OnPrefabApplied; ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin; - ScriptsBuilder.ScriptsReloadEnd -= OnScriptsReloadEnd; _undo.Dispose(); Graph.Dispose(); diff --git a/Source/Engine/Engine/NativeInterop.Unmanaged.cs b/Source/Engine/Engine/NativeInterop.Unmanaged.cs index 618dd8564..8626f187c 100644 --- a/Source/Engine/Engine/NativeInterop.Unmanaged.cs +++ b/Source/Engine/Engine/NativeInterop.Unmanaged.cs @@ -1146,12 +1146,6 @@ namespace FlaxEngine.Interop MethodInfo clearCacheMethod = TypeDescriptionProviderType?.Assembly.GetType("System.ComponentModel.ReflectionCachesUpdateHandler")?.GetMethod("ClearCache"); if (clearCacheMethod != null) clearCacheMethod.Invoke(null, new object[] { null }); - else - { - MethodInfo beforeUpdateMethod = TypeDescriptionProviderType?.Assembly.GetType("System.ComponentModel.ReflectionCachesUpdateHandler")?.GetMethod("BeforeUpdate"); - if (beforeUpdateMethod != null) - beforeUpdateMethod.Invoke(null, new object[] { null }); - } Type TypeDescriptorType = typeof(System.ComponentModel.TypeDescriptor); From 980a112c57287323b1980bdd96673de5c93423c2 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Tue, 23 Apr 2024 21:38:42 +0300 Subject: [PATCH 05/13] Clear VisjectSurface node caches and context menus during scripts reload --- Source/Editor/Surface/AnimGraphSurface.cs | 2 ++ Source/Editor/Surface/BehaviorTreeSurface.cs | 7 +++++++ Source/Editor/Surface/VisjectSurface.cs | 11 +++++++++++ Source/Editor/Surface/VisualScriptSurface.cs | 7 +++++++ 4 files changed, 27 insertions(+) diff --git a/Source/Editor/Surface/AnimGraphSurface.cs b/Source/Editor/Surface/AnimGraphSurface.cs index c33224072..30a15d7f4 100644 --- a/Source/Editor/Surface/AnimGraphSurface.cs +++ b/Source/Editor/Surface/AnimGraphSurface.cs @@ -161,6 +161,8 @@ namespace FlaxEditor.Surface private void OnScriptsReloadBegin() { + _nodesCache.Clear(); + // Check if any of the nodes comes from the game scripts - those can be reloaded at runtime so prevent crashes bool hasTypeFromGameScripts = Editor.Instance.CodeEditing.AnimGraphNodes.HasTypeFromGameScripts; diff --git a/Source/Editor/Surface/BehaviorTreeSurface.cs b/Source/Editor/Surface/BehaviorTreeSurface.cs index 647c438e8..2dba081b0 100644 --- a/Source/Editor/Surface/BehaviorTreeSurface.cs +++ b/Source/Editor/Surface/BehaviorTreeSurface.cs @@ -24,6 +24,7 @@ namespace FlaxEditor.Surface public BehaviorTreeSurface(IVisjectSurfaceOwner owner, Action onSave, FlaxEditor.Undo undo) : base(owner, onSave, undo, CreateStyle()) { + ScriptsBuilder.ScriptsReloadBegin += OnScriptsReloadBegin; } private static SurfaceStyle CreateStyle() @@ -35,6 +36,11 @@ namespace FlaxEditor.Surface return style; } + private void OnScriptsReloadBegin() + { + _nodesCache.Clear(); + } + private static void DrawBox(Box box) { var rect = new Rectangle(Float2.Zero, box.Size); @@ -186,6 +192,7 @@ namespace FlaxEditor.Surface { if (IsDisposing) return; + ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin; _nodesCache.Wait(); base.OnDestroy(); diff --git a/Source/Editor/Surface/VisjectSurface.cs b/Source/Editor/Surface/VisjectSurface.cs index 28ec17d53..66572324d 100644 --- a/Source/Editor/Surface/VisjectSurface.cs +++ b/Source/Editor/Surface/VisjectSurface.cs @@ -415,6 +415,15 @@ namespace FlaxEditor.Surface // Init drag handlers DragHandlers.Add(_dragAssets = new DragAssets(ValidateDragItem)); DragHandlers.Add(_dragParameters = new DragNames(SurfaceParameter.DragPrefix, ValidateDragParameter)); + + ScriptsBuilder.ScriptsReloadBegin += OnScriptsReloadBegin; + } + + private void OnScriptsReloadBegin() + { + _activeVisjectCM = null; + _cmPrimaryMenu?.Dispose(); + _cmPrimaryMenu = null; } /// @@ -1023,6 +1032,8 @@ namespace FlaxEditor.Surface _activeVisjectCM = null; _cmPrimaryMenu?.Dispose(); + ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin; + base.OnDestroy(); } } diff --git a/Source/Editor/Surface/VisualScriptSurface.cs b/Source/Editor/Surface/VisualScriptSurface.cs index ff0153e8a..672a527a9 100644 --- a/Source/Editor/Surface/VisualScriptSurface.cs +++ b/Source/Editor/Surface/VisualScriptSurface.cs @@ -62,6 +62,12 @@ namespace FlaxEditor.Surface { _supportsImplicitCastFromObjectToBoolean = true; DragHandlers.Add(_dragActors = new DragActors(ValidateDragActor)); + ScriptsBuilder.ScriptsReloadBegin += OnScriptsReloadBegin; + } + + private void OnScriptsReloadBegin() + { + _nodesCache.Clear(); } private bool ValidateDragActor(ActorNode actor) @@ -631,6 +637,7 @@ namespace FlaxEditor.Surface { if (IsDisposing) return; + ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin; _nodesCache.Wait(); base.OnDestroy(); From 30257929e57e64601598c85405f4297fd68c160a Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Sun, 28 Apr 2024 20:47:29 +0300 Subject: [PATCH 06/13] Fix error when ContentDatabase gets rebuilt while initializing editor --- Source/Editor/Windows/ContentWindow.cs | 40 +++++++++++++------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/Source/Editor/Windows/ContentWindow.cs b/Source/Editor/Windows/ContentWindow.cs index 6089b2e07..f30b9b0b6 100644 --- a/Source/Editor/Windows/ContentWindow.cs +++ b/Source/Editor/Windows/ContentWindow.cs @@ -144,26 +144,6 @@ namespace FlaxEditor.Windows FlaxEditor.Utilities.Utils.SetupCommonInputActions(this); - // Content database events - editor.ContentDatabase.WorkspaceModified += () => _isWorkspaceDirty = true; - editor.ContentDatabase.ItemRemoved += OnContentDatabaseItemRemoved; - editor.ContentDatabase.WorkspaceRebuilding += () => { _workspaceRebuildLocation = SelectedNode?.Path; }; - editor.ContentDatabase.WorkspaceRebuilt += () => - { - var selected = Editor.ContentDatabase.Find(_workspaceRebuildLocation); - if (selected is ContentFolder selectedFolder) - { - _navigationUnlocked = false; - RefreshView(selectedFolder.Node); - _tree.Select(selectedFolder.Node); - UpdateItemsSearch(); - _navigationUnlocked = true; - UpdateUI(); - } - else - ShowRoot(); - }; - var options = Editor.Options; options.OptionsChanged += OnOptionsChanged; @@ -1037,6 +1017,26 @@ namespace FlaxEditor.Windows /// public override void OnInit() { + // Content database events + Editor.ContentDatabase.WorkspaceModified += () => _isWorkspaceDirty = true; + Editor.ContentDatabase.ItemRemoved += OnContentDatabaseItemRemoved; + Editor.ContentDatabase.WorkspaceRebuilding += () => { _workspaceRebuildLocation = SelectedNode?.Path; }; + Editor.ContentDatabase.WorkspaceRebuilt += () => + { + var selected = Editor.ContentDatabase.Find(_workspaceRebuildLocation); + if (selected is ContentFolder selectedFolder) + { + _navigationUnlocked = false; + RefreshView(selectedFolder.Node); + _tree.Select(selectedFolder.Node); + UpdateItemsSearch(); + _navigationUnlocked = true; + UpdateUI(); + } + else if (_root != null) + ShowRoot(); + }; + // Setup content root node _root = new RootContentTreeNode { From 5fd64ead62308582c2f4b518b28928fef3b99601 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Sun, 28 Apr 2024 22:28:34 +0300 Subject: [PATCH 07/13] Fix custom content and importers blocking scripting reload --- .../CustomEditors/Editors/GenericEditor.cs | 1 + .../Editor/Modules/ContentDatabaseModule.cs | 44 ++++++++++++++++++ .../Editor/Modules/ContentImportingModule.cs | 15 +++++++ Source/Editor/Modules/WindowsModule.cs | 1 - Source/Editor/Windows/ContentWindow.cs | 45 ++++++++++++++++--- Source/Editor/Windows/ToolboxWindow.cs | 16 +++++++ Source/Engine/Scripting/Scripting.cs | 21 +++++++++ 7 files changed, 135 insertions(+), 8 deletions(-) diff --git a/Source/Editor/CustomEditors/Editors/GenericEditor.cs b/Source/Editor/CustomEditors/Editors/GenericEditor.cs index 6943f042d..aba1ddb81 100644 --- a/Source/Editor/CustomEditors/Editors/GenericEditor.cs +++ b/Source/Editor/CustomEditors/Editors/GenericEditor.cs @@ -819,6 +819,7 @@ namespace FlaxEditor.CustomEditors.Editors OnGroupsEnd(); } + /// protected override void Deinitialize() { _visibleIfCaches = null; diff --git a/Source/Editor/Modules/ContentDatabaseModule.cs b/Source/Editor/Modules/ContentDatabaseModule.cs index 4de7b593b..3fdac16e5 100644 --- a/Source/Editor/Modules/ContentDatabaseModule.cs +++ b/Source/Editor/Modules/ContentDatabaseModule.cs @@ -88,6 +88,8 @@ namespace FlaxEditor.Modules // Register AssetItems serialization helper (serialize ref ID only) FlaxEngine.Json.JsonSerializer.Settings.Converters.Add(new AssetItemConverter()); + + ScriptsBuilder.ScriptsReloadBegin += OnScriptsReloadBegin; } private void OnContentAssetDisposing(Asset asset) @@ -1313,6 +1315,47 @@ namespace FlaxEditor.Modules } } + private void OnScriptsReloadBegin() + { + var enabledEvents = _enableEvents; + _enableEvents = false; + _isDuringFastSetup = true; + var startItems = _itemsCreated; + foreach (var project in Projects) + { + if (project.Content != null) + { + //Dispose(project.Content.Folder); + for (int i = 0; i < project.Content.Folder.Children.Count; i++) + { + Dispose(project.Content.Folder.Children[i]); + i--; + } + } + if (project.Source != null) + { + //Dispose(project.Source.Folder); + for (int i = 0; i < project.Source.Folder.Children.Count; i++) + { + Dispose(project.Source.Folder.Children[i]); + i--; + } + } + } + + List removeProxies = new List(); + foreach (var proxy in Editor.Instance.ContentDatabase.Proxy) + { + if (proxy.GetType().IsCollectible) + removeProxies.Add(proxy); + } + foreach (var proxy in removeProxies) + RemoveProxy(proxy, false); + + _isDuringFastSetup = false; + _enableEvents = enabledEvents; + } + /// public override void OnUpdate() { @@ -1340,6 +1383,7 @@ namespace FlaxEditor.Modules public override void OnExit() { FlaxEngine.Content.AssetDisposing -= OnContentAssetDisposing; + ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin; // Disable events _enableEvents = false; diff --git a/Source/Editor/Modules/ContentImportingModule.cs b/Source/Editor/Modules/ContentImportingModule.cs index 65821cd43..d235a792e 100644 --- a/Source/Editor/Modules/ContentImportingModule.cs +++ b/Source/Editor/Modules/ContentImportingModule.cs @@ -391,6 +391,20 @@ namespace FlaxEditor.Modules public override void OnInit() { ImportFileEntry.RegisterDefaultTypes(); + ScriptsBuilder.ScriptsReloadBegin += OnScriptsReloadBegin; + } + + private void OnScriptsReloadBegin() + { + // Remove import file types from scripting assemblies + List removeFileTypes = new List(); + foreach (var pair in ImportFileEntry.FileTypes) + { + if (pair.Value.Method.IsCollectible || (pair.Value.Target != null && pair.Value.Target.GetType().IsCollectible)) + removeFileTypes.Add(pair.Key); + } + foreach (var fileType in removeFileTypes) + ImportFileEntry.FileTypes.Remove(fileType); } /// @@ -451,6 +465,7 @@ namespace FlaxEditor.Modules /// public override void OnExit() { + ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin; EndWorker(); } } diff --git a/Source/Editor/Modules/WindowsModule.cs b/Source/Editor/Modules/WindowsModule.cs index ba29bea4f..f3c3b06e4 100644 --- a/Source/Editor/Modules/WindowsModule.cs +++ b/Source/Editor/Modules/WindowsModule.cs @@ -52,7 +52,6 @@ namespace FlaxEditor.Modules public Float2 FloatPosition; public AssetItem Item; - public AssetItem Item2; // Constructor, to allow for default values public WindowRestoreData() diff --git a/Source/Editor/Windows/ContentWindow.cs b/Source/Editor/Windows/ContentWindow.cs index f30b9b0b6..f452c485a 100644 --- a/Source/Editor/Windows/ContentWindow.cs +++ b/Source/Editor/Windows/ContentWindow.cs @@ -29,6 +29,7 @@ namespace FlaxEditor.Windows private const string ProjectDataLastViewedFolder = "LastViewedFolder"; private bool _isWorkspaceDirty; private string _workspaceRebuildLocation; + private string _lastViewedFolderBeforeReload; private SplitPanel _split; private Panel _contentViewPanel; private Panel _contentTreePanel; @@ -1037,6 +1038,41 @@ namespace FlaxEditor.Windows ShowRoot(); }; + Refresh(); + + // Load last viewed folder + if (Editor.ProjectCache.TryGetCustomData(ProjectDataLastViewedFolder, out string lastViewedFolder)) + { + if (Editor.ContentDatabase.Find(lastViewedFolder) is ContentFolder folder) + _tree.Select(folder.Node); + } + + ScriptsBuilder.ScriptsReloadBegin += OnScriptsReloadBegin; + ScriptsBuilder.ScriptsReloadEnd += OnScriptsReloadEnd; + } + + private void OnScriptsReloadBegin() + { + var lastViewedFolder = _tree.Selection.Count == 1 ? _tree.SelectedNode as ContentTreeNode : null; + _lastViewedFolderBeforeReload = lastViewedFolder?.Path ?? string.Empty; + + _tree.RemoveChild(_root); + _root = null; + } + + private void OnScriptsReloadEnd() + { + Refresh(); + + if (!string.IsNullOrEmpty(_lastViewedFolderBeforeReload)) + { + if (Editor.ContentDatabase.Find(_lastViewedFolderBeforeReload) is ContentFolder folder) + _tree.Select(folder.Node); + } + } + + private void Refresh() + { // Setup content root node _root = new RootContentTreeNode { @@ -1072,13 +1108,6 @@ namespace FlaxEditor.Windows // Update UI layout _isLayoutLocked = false; PerformLayout(); - - // Load last viewed folder - if (Editor.ProjectCache.TryGetCustomData(ProjectDataLastViewedFolder, out string lastViewedFolder)) - { - if (Editor.ContentDatabase.Find(lastViewedFolder) is ContentFolder folder) - _tree.Select(folder.Node); - } } /// @@ -1226,6 +1255,8 @@ namespace FlaxEditor.Windows _viewDropdown = null; Editor.Options.OptionsChanged -= OnOptionsChanged; + ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin; + ScriptsBuilder.ScriptsReloadEnd -= OnScriptsReloadEnd; base.OnDestroy(); } diff --git a/Source/Editor/Windows/ToolboxWindow.cs b/Source/Editor/Windows/ToolboxWindow.cs index 4b347ea83..dd550b0f1 100644 --- a/Source/Editor/Windows/ToolboxWindow.cs +++ b/Source/Editor/Windows/ToolboxWindow.cs @@ -150,6 +150,22 @@ namespace FlaxEditor.Windows _searchBox.Clear(); _groupSearch.DisposeChildren(); _groupSearch.PerformLayout(); + + // Remove tabs + var tabs = new List(); + foreach (var child in _actorGroups.Children) + { + if (child is Tab tab) + { + if (tab.Text != "Search") + tabs.Add(tab); + } + } + foreach (var tab in tabs) + { + var group = _actorGroups.Children.Find(T => T == tab); + group.Dispose(); + } } private void OnScriptsReloadEnd() diff --git a/Source/Engine/Scripting/Scripting.cs b/Source/Engine/Scripting/Scripting.cs index 66a34faf0..33aea8a26 100644 --- a/Source/Engine/Scripting/Scripting.cs +++ b/Source/Engine/Scripting/Scripting.cs @@ -170,6 +170,10 @@ namespace FlaxEngine AppDomain.CurrentDomain.UnhandledException += OnUnhandledException; TaskScheduler.UnobservedTaskException += OnUnobservedTaskException; Localization.LocalizationChanged += OnLocalizationChanged; +#if FLAX_EDITOR + FlaxEditor.ScriptsBuilder.ScriptsReloadBegin += OnScriptsReloadBegin; + FlaxEditor.ScriptsBuilder.ScriptsReloadEnd += OnScriptsReloadEnd; +#endif OnLocalizationChanged(); if (!Engine.IsEditor) @@ -178,6 +182,19 @@ namespace FlaxEngine } } +#if FLAX_EDITOR + private static void OnScriptsReloadBegin() + { + // Tooltip might hold references to scripting assemblies + Style.Current.SharedTooltip = null; + } + + private static void OnScriptsReloadEnd() + { + Style.Current.SharedTooltip = new Tooltip(); + } +#endif + private static void OnLocalizationChanged() { // Invariant-globalization only (see InitHostfxr with Mono) @@ -359,6 +376,10 @@ namespace FlaxEngine MainThreadTaskScheduler.Dispose(); Json.JsonSerializer.Dispose(); +#if FLAX_EDITOR + FlaxEditor.ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin; + FlaxEditor.ScriptsBuilder.ScriptsReloadEnd -= OnScriptsReloadEnd; +#endif } /// From 4ddc765ee4cf72651368ddc50c6812617b8c6871 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Tue, 30 Jul 2024 17:17:27 +0300 Subject: [PATCH 08/13] Fix ContentDatabase not being loaded at the end of editor initialization --- Source/Editor/Modules/ContentDatabaseModule.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Source/Editor/Modules/ContentDatabaseModule.cs b/Source/Editor/Modules/ContentDatabaseModule.cs index 3fdac16e5..f5d7dfe61 100644 --- a/Source/Editor/Modules/ContentDatabaseModule.cs +++ b/Source/Editor/Modules/ContentDatabaseModule.cs @@ -90,6 +90,7 @@ namespace FlaxEditor.Modules FlaxEngine.Json.JsonSerializer.Settings.Converters.Add(new AssetItemConverter()); ScriptsBuilder.ScriptsReloadBegin += OnScriptsReloadBegin; + ScriptsBuilder.ScriptsReloadEnd += OnScriptsReloadEnd; } private void OnContentAssetDisposing(Asset asset) @@ -1232,8 +1233,6 @@ namespace FlaxEditor.Modules LoadProjects(Game.Project); } - RebuildInternal(); - Editor.ContentImporting.ImportFileEnd += (obj, failed) => { var path = obj.ResultUrl; @@ -1356,6 +1355,11 @@ namespace FlaxEditor.Modules _enableEvents = enabledEvents; } + private void OnScriptsReloadEnd() + { + RebuildInternal(); + } + /// public override void OnUpdate() { From 3382aabefe577a64fe8b5575caca68213ddc2956 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Wed, 31 Jul 2024 23:08:52 +0300 Subject: [PATCH 09/13] Close and restore AssetEditorWindows on scripting reload --- Source/Editor/Modules/ContentDatabaseModule.cs | 9 +++++---- Source/Editor/Modules/WindowsModule.cs | 18 ++++++++++-------- .../Editor/Windows/Assets/AssetEditorWindow.cs | 13 +++++++++++++ .../Windows/Assets/BehaviorTreeWindow.cs | 5 ++++- .../Editor/Windows/Assets/JsonAssetWindow.cs | 4 +++- Source/Editor/Windows/Assets/PrefabWindow.cs | 9 +++------ 6 files changed, 38 insertions(+), 20 deletions(-) diff --git a/Source/Editor/Modules/ContentDatabaseModule.cs b/Source/Editor/Modules/ContentDatabaseModule.cs index f5d7dfe61..2eee39221 100644 --- a/Source/Editor/Modules/ContentDatabaseModule.cs +++ b/Source/Editor/Modules/ContentDatabaseModule.cs @@ -61,7 +61,7 @@ namespace FlaxEditor.Modules public event Action WorkspaceModified; /// - /// Occurs when workspace has will be rebuilt. + /// Occurs when workspace will be rebuilt. /// public event Action WorkspaceRebuilding; @@ -89,7 +89,7 @@ namespace FlaxEditor.Modules // Register AssetItems serialization helper (serialize ref ID only) FlaxEngine.Json.JsonSerializer.Settings.Converters.Add(new AssetItemConverter()); - ScriptsBuilder.ScriptsReloadBegin += OnScriptsReloadBegin; + ScriptsBuilder.ScriptsReload += OnScriptsReload; ScriptsBuilder.ScriptsReloadEnd += OnScriptsReloadEnd; } @@ -1314,7 +1314,7 @@ namespace FlaxEditor.Modules } } - private void OnScriptsReloadBegin() + private void OnScriptsReload() { var enabledEvents = _enableEvents; _enableEvents = false; @@ -1387,7 +1387,8 @@ namespace FlaxEditor.Modules public override void OnExit() { FlaxEngine.Content.AssetDisposing -= OnContentAssetDisposing; - ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin; + ScriptsBuilder.ScriptsReload -= OnScriptsReload; + ScriptsBuilder.ScriptsReloadEnd -= OnScriptsReloadEnd; // Disable events _enableEvents = false; diff --git a/Source/Editor/Modules/WindowsModule.cs b/Source/Editor/Modules/WindowsModule.cs index f3c3b06e4..3a9778c85 100644 --- a/Source/Editor/Modules/WindowsModule.cs +++ b/Source/Editor/Modules/WindowsModule.cs @@ -51,7 +51,7 @@ namespace FlaxEditor.Modules public Float2 FloatSize; public Float2 FloatPosition; - public AssetItem Item; + public Guid AssetItemID; // Constructor, to allow for default values public WindowRestoreData() @@ -808,11 +808,11 @@ namespace FlaxEditor.Modules Level.SceneSaving += OnSceneSaving; Level.SceneUnloaded += OnSceneUnloaded; Level.SceneUnloading += OnSceneUnloading; - ScriptsBuilder.ScriptsReloadEnd += OnScriptsReloadEnd; + Editor.ContentDatabase.WorkspaceRebuilt += OnWorkspaceRebuilt; Editor.StateMachine.StateChanged += OnEditorStateChanged; } - internal void AddToRestore(PrefabWindow win) + internal void AddToRestore(AssetEditorWindow win) { var type = win.GetType(); var winData = new WindowRestoreData(); @@ -858,7 +858,7 @@ namespace FlaxEditor.Modules } winData.AssemblyName = type.Assembly.GetName().Name; winData.TypeName = type.FullName; - winData.Item = win.Item; + winData.AssetItemID = win.Item.ID; _restoreWindows.Add(winData); } @@ -907,7 +907,7 @@ namespace FlaxEditor.Modules _restoreWindows.Add(winData); } - private void OnScriptsReloadEnd() + private void OnWorkspaceRebuilt() { // Go in reverse order to create floating Prefab windows first before docked windows for (int i = _restoreWindows.Count - 1; i >= 0; i--) @@ -924,9 +924,11 @@ namespace FlaxEditor.Modules if (type == null) continue; - if (type == typeof(PrefabWindow)) + if (type.IsAssignableTo(typeof(AssetEditorWindow))) { - var win = new PrefabWindow(Editor.Instance, winData.Item); + var ctor = type.GetConstructor(new Type[] { typeof(Editor), typeof(AssetItem) }); + var assetItem = Editor.ContentDatabase.FindAsset(winData.AssetItemID); + var win = (AssetEditorWindow)ctor.Invoke(new object[] { Editor.Instance, assetItem }); win.Show(winData.DockState, winData.DockState != DockState.Float ? winData.DockedTo : null, winData.SelectOnShow, winData.SplitterValue); if (winData.DockState == DockState.Float) { @@ -1157,7 +1159,7 @@ namespace FlaxEditor.Modules Level.SceneSaving -= OnSceneSaving; Level.SceneUnloaded -= OnSceneUnloaded; Level.SceneUnloading -= OnSceneUnloading; - ScriptsBuilder.ScriptsReloadEnd -= OnScriptsReloadEnd; + Editor.ContentDatabase.WorkspaceRebuilt -= OnWorkspaceRebuilt; Editor.StateMachine.StateChanged -= OnEditorStateChanged; // Close main window diff --git a/Source/Editor/Windows/Assets/AssetEditorWindow.cs b/Source/Editor/Windows/Assets/AssetEditorWindow.cs index f781d49dc..e97cb7371 100644 --- a/Source/Editor/Windows/Assets/AssetEditorWindow.cs +++ b/Source/Editor/Windows/Assets/AssetEditorWindow.cs @@ -58,6 +58,8 @@ namespace FlaxEditor.Windows.Assets InputActions.Add(options => options.Save, Save); UpdateTitle(); + + ScriptsBuilder.ScriptsReloadBegin += OnScriptsReloadBegin; } /// @@ -151,6 +153,8 @@ namespace FlaxEditor.Windows.Assets /// public override void OnDestroy() { + ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin; + if (_item != null) { // Ensure to remove linkage to the item @@ -160,6 +164,15 @@ namespace FlaxEditor.Windows.Assets base.OnDestroy(); } + /// + protected virtual void OnScriptsReloadBegin() + { + if (!IsHidden) + { + Editor.Instance.Windows.AddToRestore(this); + } + } + #region IEditable Implementation private bool _isEdited; diff --git a/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs b/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs index 3d6399365..212e0c82f 100644 --- a/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs +++ b/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs @@ -268,8 +268,11 @@ namespace FlaxEditor.Windows.Assets UpdateKnowledge(); } - private void OnScriptsReloadBegin() + /// + protected override void OnScriptsReloadBegin() { + base.OnScriptsReloadBegin(); + // TODO: impl hot-reload for BT to nicely refresh state (save asset, clear undo/properties, reload surface) Close(); } diff --git a/Source/Editor/Windows/Assets/JsonAssetWindow.cs b/Source/Editor/Windows/Assets/JsonAssetWindow.cs index dc1e1e71f..fec7ccb63 100644 --- a/Source/Editor/Windows/Assets/JsonAssetWindow.cs +++ b/Source/Editor/Windows/Assets/JsonAssetWindow.cs @@ -124,8 +124,10 @@ namespace FlaxEditor.Windows.Assets UpdateToolstrip(); } - private void OnScriptsReloadBegin() + /// + protected override void OnScriptsReloadBegin() { + base.OnScriptsReloadBegin(); Close(); } diff --git a/Source/Editor/Windows/Assets/PrefabWindow.cs b/Source/Editor/Windows/Assets/PrefabWindow.cs index 120ffda57..f5d738880 100644 --- a/Source/Editor/Windows/Assets/PrefabWindow.cs +++ b/Source/Editor/Windows/Assets/PrefabWindow.cs @@ -286,8 +286,10 @@ namespace FlaxEditor.Windows.Assets return false; } - private void OnScriptsReloadBegin() + /// + protected override void OnScriptsReloadBegin() { + base.OnScriptsReloadBegin(); _isScriptsReloading = true; if (_asset == null || !_asset.IsLoaded) @@ -310,11 +312,6 @@ namespace FlaxEditor.Windows.Assets } } - if (!IsHidden) - { - Editor.Instance.Windows.AddToRestore(this); - } - // Cleanup Deselect(); Graph.MainActor = null; From e24bb71e91699d2a96aff64721e30c107bb62b09 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Wed, 31 Jul 2024 23:09:44 +0300 Subject: [PATCH 10/13] Fix stack overflow when restoring tab in floating window --- Source/Editor/GUI/Docking/DockWindow.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Editor/GUI/Docking/DockWindow.cs b/Source/Editor/GUI/Docking/DockWindow.cs index 937d6232f..88ae18df7 100644 --- a/Source/Editor/GUI/Docking/DockWindow.cs +++ b/Source/Editor/GUI/Docking/DockWindow.cs @@ -464,7 +464,7 @@ namespace FlaxEditor.GUI.Docking { base.Focus(); - SelectTab(); + SelectTab(false); BringToFront(); } From 1c17b77d1ef278f05278df9f52a9cf2706e65dc8 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Thu, 1 Aug 2024 00:27:37 +0300 Subject: [PATCH 11/13] Refocus editor after restoring windows --- Source/Editor/Modules/WindowsModule.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Source/Editor/Modules/WindowsModule.cs b/Source/Editor/Modules/WindowsModule.cs index 3a9778c85..a91835bae 100644 --- a/Source/Editor/Modules/WindowsModule.cs +++ b/Source/Editor/Modules/WindowsModule.cs @@ -1004,6 +1004,11 @@ namespace FlaxEditor.Modules Editor.LogWarning(string.Format("Failed to restore window {0} (assembly: {1})", winData.TypeName, winData.AssemblyName)); } } + + // Restored windows stole the focus from Editor + if (_restoreWindows.Count > 0) + Editor.Instance.Windows.MainWindow.Focus(); + _restoreWindows.Clear(); } From 5c06d413b03302e3ef0283435de369d6bb1e7936 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 25 Feb 2025 17:42:15 +0100 Subject: [PATCH 12/13] Add code sharing #2469 --- Source/Editor/Modules/WindowsModule.cs | 68 +++++++------------------- 1 file changed, 19 insertions(+), 49 deletions(-) diff --git a/Source/Editor/Modules/WindowsModule.cs b/Source/Editor/Modules/WindowsModule.cs index a91835bae..6c4af3b2f 100644 --- a/Source/Editor/Modules/WindowsModule.cs +++ b/Source/Editor/Modules/WindowsModule.cs @@ -814,15 +814,31 @@ namespace FlaxEditor.Modules internal void AddToRestore(AssetEditorWindow win) { - var type = win.GetType(); - var winData = new WindowRestoreData(); + AddToRestore(win, win.GetType(), new WindowRestoreData + { + AssetItemID = win.Item.ID, + }); + } + + internal void AddToRestore(CustomEditorWindow win) + { + AddToRestore(win.Window, win.GetType(), new WindowRestoreData()); + } + + private void AddToRestore(EditorWindow win, Type type, WindowRestoreData winData) + { + // Validate if can restore type + var constructor = type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null); + if (constructor == null || type.IsGenericType) + return; + var panel = win.ParentDockPanel; // Ensure that this window is only selected following recompilation // if it was the active tab in its dock panel. Otherwise, there is a // risk of interrupting the user's workflow by potentially selecting // background tabs. - var window = win.RootWindow?.Window; + var window = win.RootWindow.Window; winData.SelectOnShow = panel.SelectedTab == win; winData.DockedTabIndex = 0; if (panel is FloatWindowDockPanel && window != null && panel.TabsCount == 1) @@ -858,52 +874,6 @@ namespace FlaxEditor.Modules } winData.AssemblyName = type.Assembly.GetName().Name; winData.TypeName = type.FullName; - winData.AssetItemID = win.Item.ID; - _restoreWindows.Add(winData); - } - - internal void AddToRestore(CustomEditorWindow win) - { - var type = win.GetType(); - - // Validate if can restore type - var constructor = type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null); - if (constructor == null || type.IsGenericType) - return; - - var winData = new WindowRestoreData(); - var panel = win.Window.ParentDockPanel; - - // Ensure that this window is only selected following recompilation - // if it was the active tab in its dock panel. Otherwise, there is a - // risk of interrupting the user's workflow by potentially selecting - // background tabs. - winData.SelectOnShow = panel.SelectedTab == win.Window; - if (panel is FloatWindowDockPanel) - { - winData.DockState = DockState.Float; - var window = win.Window.RootWindow.Window; - winData.FloatPosition = window.Position; - winData.FloatSize = window.ClientSize; - winData.Maximize = window.IsMaximized; - winData.Minimize = window.IsMinimized; - } - else - { - if (panel.TabsCount > 1) - { - winData.DockState = DockState.DockFill; - winData.DockedTo = panel; - } - else - { - winData.DockState = panel.TryGetDockState(out var splitterValue); - winData.DockedTo = panel.ParentDockPanel; - winData.SplitterValue = splitterValue; - } - } - winData.AssemblyName = type.Assembly.GetName().Name; - winData.TypeName = type.FullName; _restoreWindows.Add(winData); } From 56b2c96b3b821a519973a736166fb08e68ac6fb4 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 25 Feb 2025 17:43:56 +0100 Subject: [PATCH 13/13] Fix missing content database load when opening project without code compilation #2469 --- Source/Editor/Modules/ContentDatabaseModule.cs | 11 +++++++++++ Source/Editor/Modules/SceneModule.cs | 2 +- Source/Editor/Modules/UIModule.cs | 2 +- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/Source/Editor/Modules/ContentDatabaseModule.cs b/Source/Editor/Modules/ContentDatabaseModule.cs index 2eee39221..a21fb3b53 100644 --- a/Source/Editor/Modules/ContentDatabaseModule.cs +++ b/Source/Editor/Modules/ContentDatabaseModule.cs @@ -21,6 +21,7 @@ namespace FlaxEditor.Modules private bool _enableEvents; private bool _isDuringFastSetup; private bool _rebuildFlag; + private bool _rebuildInitFlag; private int _itemsCreated; private int _itemsDeleted; private readonly HashSet _dirtyNodes = new HashSet(); @@ -820,6 +821,7 @@ namespace FlaxEditor.Modules Profiler.BeginEvent("ContentDatabase.Rebuild"); var startTime = Platform.TimeSeconds; _rebuildFlag = false; + _rebuildInitFlag = false; _enableEvents = false; // Load all folders @@ -1240,6 +1242,15 @@ namespace FlaxEditor.Modules FlaxEngine.Scripting.InvokeOnUpdate(() => OnImportFileDone(path)); }; _enableEvents = true; + _rebuildInitFlag = true; + } + + /// + public override void OnEndInit() + { + // Handle init when project was loaded without scripts loading () + if (_rebuildInitFlag) + RebuildInternal(); } private void OnImportFileDone(string path) diff --git a/Source/Editor/Modules/SceneModule.cs b/Source/Editor/Modules/SceneModule.cs index a144d6f4c..f1fae600d 100644 --- a/Source/Editor/Modules/SceneModule.cs +++ b/Source/Editor/Modules/SceneModule.cs @@ -58,7 +58,7 @@ namespace FlaxEditor.Modules : base(editor) { // After editor cache but before the windows - InitOrder = -900; + InitOrder = -800; } /// diff --git a/Source/Editor/Modules/UIModule.cs b/Source/Editor/Modules/UIModule.cs index cbd94ef41..67df5a2e8 100644 --- a/Source/Editor/Modules/UIModule.cs +++ b/Source/Editor/Modules/UIModule.cs @@ -160,7 +160,7 @@ namespace FlaxEditor.Modules internal UIModule(Editor editor) : base(editor) { - InitOrder = -90; + InitOrder = -70; VisjectSurfaceBackground = FlaxEngine.Content.LoadAsyncInternal("Editor/VisjectSurface"); ColorValueBox.ShowPickColorDialog += ShowPickColorDialog; }