Skip unnecessary (de-)serializations when reloading script assembly

This commit is contained in:
Simon Lübeß
2026-09-13 21:11:35 +02:00
parent e3619ae93c
commit 8244033ac5
5 changed files with 91 additions and 54 deletions
+49 -33
View File
@@ -712,6 +712,8 @@ namespace GlitchyEditor
private void CloseCurrentProject() private void CloseCurrentProject()
{ {
Log.EngineLogger.Trace("Closing current Project...");
CloseCurrentScene(); CloseCurrentScene();
SetEditorScene(null); SetEditorScene(null);
@@ -721,6 +723,8 @@ namespace GlitchyEditor
// TODO: Do actual work here! // TODO: Do actual work here!
delete _currentProject; delete _currentProject;
_currentProject = null; _currentProject = null;
Log.EngineLogger.Trace("Project closed.");
} }
private Result<void> LoadAndOpenProject(StringView workspacePath) private Result<void> LoadAndOpenProject(StringView workspacePath)
@@ -744,29 +748,43 @@ namespace GlitchyEditor
CloseCurrentProject(); CloseCurrentProject();
Log.EngineLogger.Trace("Opening Project...");
_currentProject = project; _currentProject = project;
_editor.CurrentProject = _currentProject; _editor.CurrentProject = _currentProject;
Log.EngineLogger.Trace("Preparing asset manager...");
String appAssemblyPath = scope String(); String appAssemblyPath = scope String();
_contentManager.SetAssetCacheDirectory(_currentProject.GetScopedPath!(".cache")); _contentManager.SetAssetCacheDirectory(_currentProject.GetScopedPath!(".cache"));
_contentManager.SetAssetDirectory(_currentProject.AssetsFolder); _contentManager.SetAssetDirectory(_currentProject.AssetsFolder);
_currentProject.PathInProject(appAssemblyPath, scope $".cache/bin/{_currentProject.Name}.dll"); _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) if (!_currentProject.UserSettings.LastOpenedScene.IsWhiteSpace)
{ {
String lastSceneFile = _currentProject.GetScopedPath!(_currentProject.UserSettings.LastOpenedScene); String lastSceneFile = _currentProject.GetScopedPath!(_currentProject.UserSettings.LastOpenedScene);
if (File.Exists(lastSceneFile)) if (File.Exists(lastSceneFile))
{
Log.EngineLogger.Trace("Opening last opened scene...");
LoadSceneFile(lastSceneFile); LoadSceneFile(lastSceneFile);
}
else else
{
Log.EngineLogger.Trace("Opening new scene...");
CreateAndOpenNewScene(); CreateAndOpenNewScene();
}
} }
else else
{
Log.EngineLogger.Trace("Opening new scene...");
CreateAndOpenNewScene(); CreateAndOpenNewScene();
}
UpdateWindowTitle(); UpdateWindowTitle();
@@ -776,6 +794,8 @@ namespace GlitchyEditor
EditorApp.Instance.Settings.Save(); EditorApp.Instance.Settings.Save();
} }
Log.EngineLogger.Trace("Project opened.");
return .Ok; return .Ok;
} }
@@ -800,27 +820,34 @@ namespace GlitchyEditor
append ScriptInstanceSerializer _scriptSerializer = .(); append ScriptInstanceSerializer _scriptSerializer = .();
/// Starts the play mode for the current scene /// Starts the play mode for the current scene
private void OnScenePlay() private void OnScenePlay(SceneState newSceneState)
{ {
Debug.Profiler.ProfileFunction!(); 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."); Log.EngineLogger.AssertDebug(_scriptSerializer.SerializedObjectCount == 0, "Somehow some entities are serialized.");
if (EditorApp.Instance.Settings.EditorSettings.ClearLogOnPlay) if (EditorApp.Instance.Settings.EditorSettings.ClearLogOnPlay)
Editor.Instance.LogWindow.ClearLog(); Editor.Instance.LogWindow.ClearLog();
/// Save the in memory state of the script intances so se can recreate it after stopping play/simulation
_scriptSerializer.SerializeScriptInstances(); _scriptSerializer.SerializeScriptInstances();
_editor.SceneViewportWindow.EditorMode = false; _editor.SceneViewportWindow.EditorMode = false;
_sceneState = .Play; _sceneState = newSceneState;
using (Scene runtimeScene = new Scene()) 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; _editor.CurrentScene = _activeScene;
@@ -867,25 +894,6 @@ namespace GlitchyEditor
SetReference!(_activeScene, scene); 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 /// Pauses the scene
private void OnScenePause() private void OnScenePause()
{ {
@@ -916,9 +924,9 @@ namespace GlitchyEditor
if (_activeScene == null) if (_activeScene == null)
return; return;
SetActiveScene(_editorScene, startRuntime: true, startSimulation: false, newPlayMode: .Editor); Log.EngineLogger.Trace("Stopping current scene and returing to edit mode...");
// TODO: _editorScene.DeserializeScripts(scriptData); SetActiveScene(_editorScene, startRuntime: true, startSimulation: false, newPlayMode: .Editor);
_editor.SceneViewportWindow.EditorMode = true; _editor.SceneViewportWindow.EditorMode = true;
_sceneState = .Edit; _sceneState = .Edit;
@@ -935,14 +943,19 @@ namespace GlitchyEditor
* into "Editor"-mode (because Game-Mode works on a copy of the scene). * into "Editor"-mode (because Game-Mode works on a copy of the scene).
*/ */
GameViewportSizeChanged(null, _editor.GameViewportWindow.RenderedViewportSize); GameViewportSizeChanged(null, _editor.GameViewportWindow.RenderedViewportSize);
if (_scriptSerializer.SerializedObjectCount != 0)
{
// Reconstruct state before play
_scriptSerializer.DeserializeScriptInstances();
_scriptSerializer.Clear();
}
} }
if (EditorApp.Instance.Settings.EditorSettings.SwitchToEditorOnStop) if (EditorApp.Instance.Settings.EditorSettings.SwitchToEditorOnStop)
SwitchToEditorWindow(); SwitchToEditorWindow();
// Reconstruct state before play Log.EngineLogger.Trace("Switched to edit mode.");
_scriptSerializer.DeserializeScriptInstances();
_scriptSerializer.Clear();
} }
/// Stops the scene and cleans up the subsystems to allow loading another scene. /// Stops the scene and cleans up the subsystems to allow loading another scene.
@@ -975,6 +988,8 @@ namespace GlitchyEditor
/// Sets the current editor scene /// Sets the current editor scene
private void SetEditorScene(Scene scene, ScriptInstanceSerializer serializedObjects = null) private void SetEditorScene(Scene scene, ScriptInstanceSerializer serializedObjects = null)
{ {
Log.EngineLogger.Trace($"Switching to scene \"{(scene?.Name ?? "<none>")}\"...");
_editorScene?.Stop(); _editorScene?.Stop();
SetReference!(_editorScene, scene); SetReference!(_editorScene, scene);
@@ -994,6 +1009,7 @@ namespace GlitchyEditor
} }
UpdateWindowTitle(); UpdateWindowTitle();
Log.EngineLogger.Trace($"Switched scene to \"{(scene?.Name ?? "<none>")}\".");
} }
/// Creates a new default scene. /// Creates a new default scene.
@@ -1231,7 +1247,7 @@ namespace GlitchyEditor
ImGui.PushID(0); ImGui.PushID(0);
if (ImGui.ImageButton("", _editorIcons.Play, .(size, size), .Zero, .Ones)) if (ImGui.ImageButton("", _editorIcons.Play, .(size, size), .Zero, .Ones))
OnScenePlay(); OnScenePlay(.Play);
ImGui.PopID(); ImGui.PopID();
@@ -1242,7 +1258,7 @@ namespace GlitchyEditor
ImGui.PushID(1); ImGui.PushID(1);
if (ImGui.ImageButton("", _editorIcons.Simulate, .(size, size), .Zero, .Ones)) if (ImGui.ImageButton("", _editorIcons.Simulate, .(size, size), .Zero, .Ones))
OnSceneSimulate(); OnScenePlay(.Simulate);
ImGui.PopID(); ImGui.PopID();
@@ -1514,7 +1530,7 @@ namespace GlitchyEditor
if(ImGui.BeginMenu("Tools", true)) if(ImGui.BeginMenu("Tools", true))
{ {
if(ImGui.MenuItem("Reload Scripts")) if(ImGui.MenuItem("Reload Scripts"))
ScriptEngine.ReloadAssemblies(); ScriptEngine.ReloadAssemblies(retainScriptData: true);
#if GE_EDITOR_IMGUI_DEMO #if GE_EDITOR_IMGUI_DEMO
ImGui.Checkbox("Show ImGui Demo", &_showImguiDemoWindow); ImGui.Checkbox("Show ImGui Demo", &_showImguiDemoWindow);
+16 -9
View File
@@ -192,11 +192,11 @@ static class ScriptEngine
} }
/// Changes the path to the current app assembly. /// 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); _appAssemblyPath.Set(appAssemblyPath);
ReloadAssemblies(); ReloadAssemblies(retainScriptData);
} }
static void InitRuntime() static void InitRuntime()
@@ -239,7 +239,7 @@ static class ScriptEngine
Application.Instance.InvokeOnMainThread(new () => Application.Instance.InvokeOnMainThread(new () =>
{ {
ReloadAssemblies(); ReloadAssemblies(retainScriptData: true);
_requestingReload = false; _requestingReload = false;
_userAssemblyWatcher.StartRaisingEvents(); _userAssemblyWatcher.StartRaisingEvents();
@@ -290,12 +290,12 @@ static class ScriptEngine
} }
/// Starts the script runtime and sets the context scene. /// 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!"); Debug.Assert(s_Context == null, "StartRuntime was called twice without StopRuntime in between!");
Context = context; Context = context;
ReloadAssemblies(); ReloadAssemblies(retainScriptData);
} }
/// Stopts the script runtime and disposes of all script instances. /// Stopts the script runtime and disposes of all script instances.
@@ -415,7 +415,7 @@ static class ScriptEngine
CoreClrHelper.FreeScriptClassNames(); CoreClrHelper.FreeScriptClassNames();
} }
public static void ReloadAssemblies() public static void ReloadAssemblies(bool retainScriptData = true)
{ {
Debug.Profiler.ProfileFunction!(); Debug.Profiler.ProfileFunction!();
@@ -423,9 +423,13 @@ static class ScriptEngine
Log.EngineLogger.Info("Reloading script assemblies."); Log.EngineLogger.Info("Reloading script assemblies.");
ScriptInstanceSerializer contextSerializer = scope .(); ScriptInstanceSerializer contextSerializer = null;
contextSerializer.SerializeScriptInstances(); if (retainScriptData)
{
contextSerializer = scope:: .();
contextSerializer.SerializeScriptInstances();
}
UnloadScriptAssemblies(); UnloadScriptAssemblies();
@@ -449,7 +453,10 @@ static class ScriptEngine
} }
} }
contextSerializer.DeserializeScriptInstances(); if (retainScriptData)
{
contextSerializer.DeserializeScriptInstances();
}
Log.EngineLogger.Info("Script assemblies reloaded!"); Log.EngineLogger.Info("Script assemblies reloaded!");
} }
+14 -6
View File
@@ -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)] [AttributeUsage(.Struct)]
struct EngineFunctionsGeneratorAttribute : Attribute, IComptimeTypeApply 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] [EngineFunctionsGenerator]
struct EngineFunctions struct EngineFunctions
{ {
static Self _functions; static Self _functions;
/// Sends the references to all ScriptGlue functions to the script library.
[RegisterMethod] [RegisterMethod]
public static void FillEngineFunctions() public static void FillEngineFunctions()
{ {
@@ -210,7 +213,7 @@ struct EngineFunctions
} }
} }
/* Adding this attribute to a method will log method entry and returned Result<T> errors */ /// Collects all methods in ScriptGlue that have a RegisterCallAttribute. Generates the assignment of the corresponding function pointer in EngineFunctions.
[AttributeUsage(.Method | .Constructor)] [AttributeUsage(.Method | .Constructor)]
public struct RegisterMethodAttribute : Attribute, IOnMethodInit public struct RegisterMethodAttribute : Attribute, IOnMethodInit
{ {
@@ -262,6 +265,7 @@ static class ScriptGlue
private static String _lastExceptionMessage = new .() ~ delete _; private static String _lastExceptionMessage = new .() ~ delete _;
/// Initializes the glue layer between native engine and script library.
public static void Init() public static void Init()
{ {
if (_setEngineFunctions == null) if (_setEngineFunctions == null)
@@ -274,6 +278,13 @@ static class ScriptGlue
RegisterManagedComponents(); 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() public static void RegisterManagedComponents()
{ {
Debug.Profiler.ProfileFunction!(); Debug.Profiler.ProfileFunction!();
@@ -288,11 +299,7 @@ static class ScriptGlue
RegisterComponent<MeshRendererComponent>(); RegisterComponent<MeshRendererComponent>();
} }
private static void RegisterCalls() /// Register the function pointers to create, remove and check for components of the specified type and sends them to the script library.
{
EngineFunctions.FillEngineFunctions();
}
private static void RegisterComponent<T>() where T : struct, new private static void RegisterComponent<T>() where T : struct, new
{ {
String fullComponentTypeName = scope String(); String fullComponentTypeName = scope String();
@@ -1558,6 +1565,7 @@ static class ScriptGlue
asset asset
}*/ }*/
/// Set an exception message that the script library can collect when a glue function returned an error.
static void SetExceptionMessage(StringView message) static void SetExceptionMessage(StringView message)
{ {
_lastExceptionMessage.Set(message); _lastExceptionMessage.Set(message);
@@ -1,6 +1,7 @@
using System.Collections; using System.Collections;
using GlitchyEngine.Core; using GlitchyEngine.Core;
using GlitchyEngine.Serialization; using GlitchyEngine.Serialization;
using System;
namespace GlitchyEngine.Scripting; namespace GlitchyEngine.Scripting;
@@ -45,6 +46,8 @@ public class ScriptInstanceSerializer
{ {
Debug.Profiler.ProfileFunction!(); Debug.Profiler.ProfileFunction!();
Log.EngineLogger.Trace($"Serializing {Internal.UnsafeCastToPtr(this)}...");
Init(); Init();
for (let (id, scriptInstance) in ScriptEngine._entityScriptInstances) for (let (id, scriptInstance) in ScriptEngine._entityScriptInstances)
@@ -79,6 +82,8 @@ public class ScriptInstanceSerializer
{ {
Debug.Profiler.ProfileFunction!(); Debug.Profiler.ProfileFunction!();
Log.EngineLogger.Trace($"Deserializing {Internal.UnsafeCastToPtr(this)}...");
Init(); Init();
for (let (id, script) in ScriptEngine._entityScriptInstances) for (let (id, script) in ScriptEngine._entityScriptInstances)
+2 -1
View File
@@ -293,7 +293,8 @@ namespace GlitchyEngine.World
private void StartRuntime() 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 // Initialize ScriptComponents
for (let (handle, scriptComponent) in _ecsWorld.Enumerate<ScriptComponent>()) for (let (handle, scriptComponent) in _ecsWorld.Enumerate<ScriptComponent>())