From 8244033ac5361e5985ee3caf4e76cff2773f4a94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20L=C3=BCbe=C3=9F?= Date: Sun, 13 Sep 2026 21:11:35 +0200 Subject: [PATCH] Skip unnecessary (de-)serializations when reloading script assembly --- GlitchyEditor/src/EditorLayer.bf | 86 +++++++++++-------- GlitchyEngine/src/Scripting/ScriptEngine.bf | 29 ++++--- GlitchyEngine/src/Scripting/ScriptGlue.bf | 22 +++-- .../src/Scripting/ScriptInstanceSerializer.bf | 5 ++ GlitchyEngine/src/World/Scene.bf | 3 +- 5 files changed, 91 insertions(+), 54 deletions(-) diff --git a/GlitchyEditor/src/EditorLayer.bf b/GlitchyEditor/src/EditorLayer.bf index 9bc9eaa..3d60f4a 100644 --- a/GlitchyEditor/src/EditorLayer.bf +++ b/GlitchyEditor/src/EditorLayer.bf @@ -712,6 +712,8 @@ namespace GlitchyEditor private void CloseCurrentProject() { + Log.EngineLogger.Trace("Closing current Project..."); + CloseCurrentScene(); SetEditorScene(null); @@ -721,6 +723,8 @@ namespace GlitchyEditor // TODO: Do actual work here! delete _currentProject; _currentProject = null; + + Log.EngineLogger.Trace("Project closed."); } private Result LoadAndOpenProject(StringView workspacePath) @@ -741,32 +745,46 @@ namespace GlitchyEditor { if (project == null) return .Err; - + CloseCurrentProject(); + + Log.EngineLogger.Trace("Opening Project..."); _currentProject = project; _editor.CurrentProject = _currentProject; + + Log.EngineLogger.Trace("Preparing asset manager..."); String appAssemblyPath = scope String(); _contentManager.SetAssetCacheDirectory(_currentProject.GetScopedPath!(".cache")); _contentManager.SetAssetDirectory(_currentProject.AssetsFolder); _currentProject.PathInProject(appAssemblyPath, scope $".cache/bin/{_currentProject.Name}.dll"); - - ScriptEngine.SetAppAssemblyPath(appAssemblyPath); + + Log.EngineLogger.Trace("Loading script assembly..."); + ScriptEngine.SetAppAssemblyPath(appAssemblyPath, retainScriptData: false); if (!_currentProject.UserSettings.LastOpenedScene.IsWhiteSpace) { String lastSceneFile = _currentProject.GetScopedPath!(_currentProject.UserSettings.LastOpenedScene); if (File.Exists(lastSceneFile)) + { + Log.EngineLogger.Trace("Opening last opened scene..."); LoadSceneFile(lastSceneFile); + } else + { + Log.EngineLogger.Trace("Opening new scene..."); CreateAndOpenNewScene(); + } } else + { + Log.EngineLogger.Trace("Opening new scene..."); CreateAndOpenNewScene(); + } UpdateWindowTitle(); @@ -776,6 +794,8 @@ namespace GlitchyEditor EditorApp.Instance.Settings.Save(); } + Log.EngineLogger.Trace("Project opened."); + return .Ok; } @@ -800,27 +820,34 @@ namespace GlitchyEditor append ScriptInstanceSerializer _scriptSerializer = .(); /// Starts the play mode for the current scene - private void OnScenePlay() + private void OnScenePlay(SceneState newSceneState) { Debug.Profiler.ProfileFunction!(); + Runtime.Assert(newSceneState == .Play || newSceneState == .Simulate, "newSceneState must either be Play or Simulate"); + Log.EngineLogger.AssertDebug(_scriptSerializer.SerializedObjectCount == 0, "Somehow some entities are serialized."); if (EditorApp.Instance.Settings.EditorSettings.ClearLogOnPlay) Editor.Instance.LogWindow.ClearLog(); + /// Save the in memory state of the script intances so se can recreate it after stopping play/simulation _scriptSerializer.SerializeScriptInstances(); _editor.SceneViewportWindow.EditorMode = false; - _sceneState = .Play; + _sceneState = newSceneState; using (Scene runtimeScene = new Scene()) { - _editorScene.CopyTo(runtimeScene, true); + // In simulation mode we don't want any scripts to run, just physics. + _editorScene.CopyTo(runtimeScene, _sceneState == .Play); - SetActiveScene(runtimeScene, startRuntime: true, startSimulation: true, newPlayMode: .Play); + SetActiveScene(runtimeScene, startRuntime: (_sceneState == .Play), startSimulation: true, newPlayMode: (_sceneState == .Play ? .Play : .Simulation)); - _scriptSerializer.DeserializeScriptInstances(); + if (_sceneState == .Play) + { + _scriptSerializer.DeserializeScriptInstances(); + } } _editor.CurrentScene = _activeScene; @@ -867,25 +894,6 @@ namespace GlitchyEditor SetReference!(_activeScene, scene); } - /// Starts the physics simulation mode for the current scene - private void OnSceneSimulate() - { - _editor.SceneViewportWindow.EditorMode = false; - _sceneState = .Simulate; - - using (Scene simulationScene = new Scene()) - { - _editorScene.CopyTo(simulationScene, false); - - SetActiveScene(simulationScene, startRuntime: false, startSimulation: true, newPlayMode: .Simulation); - } - - _editor.CurrentScene = _activeScene; - - if (EditorApp.Instance.Settings.EditorSettings.SwitchToPlayerOnSimulate) - SwitchToPlayWindow(); - } - /// Pauses the scene private void OnScenePause() { @@ -915,11 +923,11 @@ namespace GlitchyEditor { if (_activeScene == null) return; + + Log.EngineLogger.Trace("Stopping current scene and returing to edit mode..."); SetActiveScene(_editorScene, startRuntime: true, startSimulation: false, newPlayMode: .Editor); - // TODO: _editorScene.DeserializeScripts(scriptData); - _editor.SceneViewportWindow.EditorMode = true; _sceneState = .Edit; @@ -935,14 +943,19 @@ namespace GlitchyEditor * into "Editor"-mode (because Game-Mode works on a copy of the scene). */ GameViewportSizeChanged(null, _editor.GameViewportWindow.RenderedViewportSize); + + if (_scriptSerializer.SerializedObjectCount != 0) + { + // Reconstruct state before play + _scriptSerializer.DeserializeScriptInstances(); + _scriptSerializer.Clear(); + } } if (EditorApp.Instance.Settings.EditorSettings.SwitchToEditorOnStop) SwitchToEditorWindow(); - // Reconstruct state before play - _scriptSerializer.DeserializeScriptInstances(); - _scriptSerializer.Clear(); + Log.EngineLogger.Trace("Switched to edit mode."); } /// Stops the scene and cleans up the subsystems to allow loading another scene. @@ -975,6 +988,8 @@ namespace GlitchyEditor /// Sets the current editor scene private void SetEditorScene(Scene scene, ScriptInstanceSerializer serializedObjects = null) { + Log.EngineLogger.Trace($"Switching to scene \"{(scene?.Name ?? "")}\"..."); + _editorScene?.Stop(); SetReference!(_editorScene, scene); @@ -994,6 +1009,7 @@ namespace GlitchyEditor } UpdateWindowTitle(); + Log.EngineLogger.Trace($"Switched scene to \"{(scene?.Name ?? "")}\"."); } /// Creates a new default scene. @@ -1231,7 +1247,7 @@ namespace GlitchyEditor ImGui.PushID(0); if (ImGui.ImageButton("", _editorIcons.Play, .(size, size), .Zero, .Ones)) - OnScenePlay(); + OnScenePlay(.Play); ImGui.PopID(); @@ -1242,7 +1258,7 @@ namespace GlitchyEditor ImGui.PushID(1); if (ImGui.ImageButton("", _editorIcons.Simulate, .(size, size), .Zero, .Ones)) - OnSceneSimulate(); + OnScenePlay(.Simulate); ImGui.PopID(); @@ -1514,7 +1530,7 @@ namespace GlitchyEditor if(ImGui.BeginMenu("Tools", true)) { if(ImGui.MenuItem("Reload Scripts")) - ScriptEngine.ReloadAssemblies(); + ScriptEngine.ReloadAssemblies(retainScriptData: true); #if GE_EDITOR_IMGUI_DEMO ImGui.Checkbox("Show ImGui Demo", &_showImguiDemoWindow); diff --git a/GlitchyEngine/src/Scripting/ScriptEngine.bf b/GlitchyEngine/src/Scripting/ScriptEngine.bf index 2b029bd..e6289f3 100644 --- a/GlitchyEngine/src/Scripting/ScriptEngine.bf +++ b/GlitchyEngine/src/Scripting/ScriptEngine.bf @@ -192,11 +192,11 @@ static class ScriptEngine } /// Changes the path to the current app assembly. - public static void SetAppAssemblyPath(StringView appAssemblyPath) + public static void SetAppAssemblyPath(StringView appAssemblyPath, bool retainScriptData) { _appAssemblyPath.Set(appAssemblyPath); - ReloadAssemblies(); + ReloadAssemblies(retainScriptData); } static void InitRuntime() @@ -239,7 +239,7 @@ static class ScriptEngine Application.Instance.InvokeOnMainThread(new () => { - ReloadAssemblies(); + ReloadAssemblies(retainScriptData: true); _requestingReload = false; _userAssemblyWatcher.StartRaisingEvents(); @@ -290,12 +290,12 @@ static class ScriptEngine } /// Starts the script runtime and sets the context scene. - public static void StartRuntime(Scene context) + public static void StartRuntime(Scene context, bool retainScriptData = true) { Debug.Assert(s_Context == null, "StartRuntime was called twice without StopRuntime in between!"); Context = context; - ReloadAssemblies(); + ReloadAssemblies(retainScriptData); } /// Stopts the script runtime and disposes of all script instances. @@ -415,7 +415,7 @@ static class ScriptEngine CoreClrHelper.FreeScriptClassNames(); } - public static void ReloadAssemblies() + public static void ReloadAssemblies(bool retainScriptData = true) { Debug.Profiler.ProfileFunction!(); @@ -423,10 +423,14 @@ static class ScriptEngine Log.EngineLogger.Info("Reloading script assemblies."); - ScriptInstanceSerializer contextSerializer = scope .(); - - contextSerializer.SerializeScriptInstances(); + ScriptInstanceSerializer contextSerializer = null; + if (retainScriptData) + { + contextSerializer = scope:: .(); + contextSerializer.SerializeScriptInstances(); + } + UnloadScriptAssemblies(); LoadScriptAssemblies(); @@ -448,8 +452,11 @@ static class ScriptEngine } } } - - contextSerializer.DeserializeScriptInstances(); + + if (retainScriptData) + { + contextSerializer.DeserializeScriptInstances(); + } Log.EngineLogger.Info("Script assemblies reloaded!"); } diff --git a/GlitchyEngine/src/Scripting/ScriptGlue.bf b/GlitchyEngine/src/Scripting/ScriptGlue.bf index 00ee040..e2b1a65 100644 --- a/GlitchyEngine/src/Scripting/ScriptGlue.bf +++ b/GlitchyEngine/src/Scripting/ScriptGlue.bf @@ -86,6 +86,7 @@ struct GlueParamAttribute : Attribute } } +/// Collects all methods in ScriptGlue that have a RegisterCallAttribute. Generates a function pointer for them as well as outputting a json file that can be used to generate the script library side of the ScriptGlue construct. [AttributeUsage(.Struct)] struct EngineFunctionsGeneratorAttribute : Attribute, IComptimeTypeApply { @@ -195,11 +196,13 @@ struct EngineFunctionsGeneratorAttribute : Attribute, IComptimeTypeApply } } +/// A struct containing all pointers to functions that can be called from the script library. [EngineFunctionsGenerator] struct EngineFunctions { static Self _functions; - + + /// Sends the references to all ScriptGlue functions to the script library. [RegisterMethod] public static void FillEngineFunctions() { @@ -210,7 +213,7 @@ struct EngineFunctions } } -/* Adding this attribute to a method will log method entry and returned Result errors */ +/// Collects all methods in ScriptGlue that have a RegisterCallAttribute. Generates the assignment of the corresponding function pointer in EngineFunctions. [AttributeUsage(.Method | .Constructor)] public struct RegisterMethodAttribute : Attribute, IOnMethodInit { @@ -262,6 +265,7 @@ static class ScriptGlue private static String _lastExceptionMessage = new .() ~ delete _; + /// Initializes the glue layer between native engine and script library. public static void Init() { if (_setEngineFunctions == null) @@ -274,6 +278,13 @@ static class ScriptGlue RegisterManagedComponents(); } + /// Collects and sends the function pointers to all glue functions to the script library. + private static void RegisterCalls() + { + EngineFunctions.FillEngineFunctions(); + } + + /// Creates the function pointers to create, remove and check for components and sends them to the script library. public static void RegisterManagedComponents() { Debug.Profiler.ProfileFunction!(); @@ -288,11 +299,7 @@ static class ScriptGlue RegisterComponent(); } - private static void RegisterCalls() - { - EngineFunctions.FillEngineFunctions(); - } - + /// Register the function pointers to create, remove and check for components of the specified type and sends them to the script library. private static void RegisterComponent() where T : struct, new { String fullComponentTypeName = scope String(); @@ -1558,6 +1565,7 @@ static class ScriptGlue asset }*/ + /// Set an exception message that the script library can collect when a glue function returned an error. static void SetExceptionMessage(StringView message) { _lastExceptionMessage.Set(message); diff --git a/GlitchyEngine/src/Scripting/ScriptInstanceSerializer.bf b/GlitchyEngine/src/Scripting/ScriptInstanceSerializer.bf index 05e63d2..bb7a99d 100644 --- a/GlitchyEngine/src/Scripting/ScriptInstanceSerializer.bf +++ b/GlitchyEngine/src/Scripting/ScriptInstanceSerializer.bf @@ -1,6 +1,7 @@ using System.Collections; using GlitchyEngine.Core; using GlitchyEngine.Serialization; +using System; namespace GlitchyEngine.Scripting; @@ -45,6 +46,8 @@ public class ScriptInstanceSerializer { Debug.Profiler.ProfileFunction!(); + Log.EngineLogger.Trace($"Serializing {Internal.UnsafeCastToPtr(this)}..."); + Init(); for (let (id, scriptInstance) in ScriptEngine._entityScriptInstances) @@ -78,6 +81,8 @@ public class ScriptInstanceSerializer public void DeserializeScriptInstances() { Debug.Profiler.ProfileFunction!(); + + Log.EngineLogger.Trace($"Deserializing {Internal.UnsafeCastToPtr(this)}..."); Init(); diff --git a/GlitchyEngine/src/World/Scene.bf b/GlitchyEngine/src/World/Scene.bf index ee30b41..050d27d 100644 --- a/GlitchyEngine/src/World/Scene.bf +++ b/GlitchyEngine/src/World/Scene.bf @@ -293,7 +293,8 @@ namespace GlitchyEngine.World private void StartRuntime() { - ScriptEngine.StartRuntime(this); + // We don't retain data of script instances, because our callers do that if necessary + ScriptEngine.StartRuntime(this, retainScriptData: false); // Initialize ScriptComponents for (let (handle, scriptComponent) in _ecsWorld.Enumerate())