From 5d8c1b111ae6f5998a5da390c91347e68caea7bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20L=C3=BCbe=C3=9F?= Date: Mon, 31 Jul 2023 20:09:56 +0200 Subject: [PATCH] Project user settings, load recent scenes, properly cleanup ScriptEngine when switching scenes --- GlitchyEditor/SandboxProject/.gitignore | 1 + .../Assets/Scenes/Simple3D.scene | 44 ++++----- .../resources/ProjectTemplate/.gitignore | 1 + GlitchyEditor/src/EditorLayer.bf | 95 ++++++++++++++----- GlitchyEditor/src/Project.bf | 56 ++++++++++- GlitchyEditor/src/ProjectUserSettings.bf | 79 +++++++++++++++ GlitchyEngine/src/Scripting/ScriptEngine.bf | 29 ++++-- 7 files changed, 250 insertions(+), 55 deletions(-) create mode 100644 GlitchyEditor/SandboxProject/.gitignore create mode 100644 GlitchyEditor/src/ProjectUserSettings.bf diff --git a/GlitchyEditor/SandboxProject/.gitignore b/GlitchyEditor/SandboxProject/.gitignore new file mode 100644 index 0000000..4144d89 --- /dev/null +++ b/GlitchyEditor/SandboxProject/.gitignore @@ -0,0 +1 @@ +project_user.bon diff --git a/GlitchyEditor/SandboxProject/Assets/Scenes/Simple3D.scene b/GlitchyEditor/SandboxProject/Assets/Scenes/Simple3D.scene index 421c22e..c72ba39 100644 --- a/GlitchyEditor/SandboxProject/Assets/Scenes/Simple3D.scene +++ b/GlitchyEditor/SandboxProject/Assets/Scenes/Simple3D.scene @@ -30,10 +30,10 @@ } }, MeshComponent = { - Mesh = "Models/sphere.glb" + Mesh = "Assets/Models/sphere.glb" }, MeshRendererComponent = { - Material = "Textures/TestMaterial.mat" + Material = "Assets/Textures/TestMaterial.mat" } }, { @@ -49,12 +49,12 @@ }, Rotation = { X = 0.614328027, - Y = 0.239776045, + Y = 0.23977606, Z = 0.0315669999, - W = 0.751073837 + W = 0.751073778 }, Scale = { - X = 0.999998868, + X = 0.999998808, Y = 1, Z = 1.00000095 }, @@ -70,7 +70,7 @@ Color = { R = 1, G = 0.991771996, - B = 0.74086225 + B = 0.74086231 } } }, @@ -103,10 +103,10 @@ } }, MeshComponent = { - Mesh = "Models/plane.glb" + Mesh = "Assets/Models/plane.glb" }, MeshRendererComponent = { - Material = "Textures/TestMaterial.mat" + Material = "Assets/Textures/TestMaterial.mat" } }, { @@ -146,7 +146,7 @@ OrthographicHeight = 10, OrthographicNearPlane = 0, OrthographicFarPlane = 10, - AspectRatio = 2.19888878, + AspectRatio = 1.87453413, FixedAspectRatio = false } }, @@ -164,7 +164,7 @@ Rotation = { X = 0.18301262, Y = -0.683013558, - Z = 0.683012128, + Z = 0.683012187, W = 0.183013007 }, Scale = { @@ -173,13 +173,13 @@ Z = 1 }, EditorEulerRotation = { - X = 1.57079446, - Y = -2.61799312, + X = 1.57079458, + Y = -2.61799288, Z = 0 } }, MeshComponent = { - Mesh = "Models/plane.glb" + Mesh = "" }, MeshRendererComponent = { Material = "" @@ -192,32 +192,32 @@ }, TransformComponent = { Position = { - X = 2.37353587, + X = 2.37353563, Y = 0.629615188, Z = -1.34831977 }, Rotation = { X = 0.270598024, - Y = -0.65328145, + Y = -0.65328151, Z = 0.65328151, W = 0.270598054 }, Scale = { - X = 0.999999881, - Y = 0.999999821, + X = 0.999999821, + Y = 0.999999762, Z = 0.999999702 }, EditorEulerRotation = { - X = 1.57079637, - Y = -2.3561945, - Z = 8.94069743e-08 + X = 1.57079649, + Y = -2.35619426, + Z = 8.94069601e-08 } }, MeshComponent = { - Mesh = "Models/plane.glb" + Mesh = "Assets/Models/plane.glb" }, MeshRendererComponent = { - Material = "Textures/RocketMaterial.mat" + Material = "Assets/Textures/RocketMaterial.mat" } } ] diff --git a/GlitchyEditor/resources/ProjectTemplate/.gitignore b/GlitchyEditor/resources/ProjectTemplate/.gitignore index e69de29..4144d89 100644 --- a/GlitchyEditor/resources/ProjectTemplate/.gitignore +++ b/GlitchyEditor/resources/ProjectTemplate/.gitignore @@ -0,0 +1 @@ +project_user.bon diff --git a/GlitchyEditor/src/EditorLayer.bf b/GlitchyEditor/src/EditorLayer.bf index 4b4c2bf..e696525 100644 --- a/GlitchyEditor/src/EditorLayer.bf +++ b/GlitchyEditor/src/EditorLayer.bf @@ -62,6 +62,8 @@ namespace GlitchyEditor SettingsWindow _settingsWindow = new .() ~ delete _; + ProjectUserSettings _projectUserSettings; + EditorCamera _camera ~ _.Dispose(); EditorIcons _editorIcons ~ _.ReleaseRef(); @@ -135,6 +137,11 @@ namespace GlitchyEditor CreateAndOpenNewScene(); } + public ~this() + { + CloseCurrentProject(); + } + private void RegisterAssetCreators() { Editor.Instance.ContentBrowserWindow.RegisterAssetCreator(new AssetCreator("Scene", "New Scene.scene", new (path) => @@ -393,6 +400,12 @@ namespace GlitchyEditor Renderer2D.DrawCircle(transform.WorldTransform * Matrix.Translation(collider.Offset.X, collider.Offset.Y, 0) * Matrix.Scaling(collider.Radius * 2), (Texture2D)null, ColorRGBA(0f, 1f, 0f), 0.01f); } } + + /// Updates the title of the window. + private void UpdateWindowTitle() + { + Application.Instance.Window.Title = scope $"Glitchy Engine - {_currentProject.Name}"; + } #region Project Management @@ -605,11 +618,11 @@ namespace GlitchyEditor private void CloseCurrentProject() { - OnSceneStop(); + CloseCurrentScene(); - SetReference!(_editorScene, null); - SetReference!(_activeScene, null); - _editor.CurrentScene = null; + SetEditorScene(null); + + _currentProject?.SaveUserSettings(); // TODO: Do actual work here! delete _currentProject; @@ -629,6 +642,7 @@ namespace GlitchyEditor return .Ok; } + /// Opens the given project and makes it the current project. private Result OpenProject(Project project) { if (project == null) @@ -645,8 +659,19 @@ namespace GlitchyEditor ScriptEngine.SetAppAssemblyPath(appAssemblyPath); - // TODO: Load last opened scene - CreateAndOpenNewScene(); + if (!_currentProject.UserSettings.LastOpenedScene.IsWhiteSpace) + { + String lastSceneFile = _currentProject.GetScopedPath!(_currentProject.UserSettings.LastOpenedScene); + + if (File.Exists(lastSceneFile)) + LoadSceneFile(lastSceneFile); + else + CreateAndOpenNewScene(); + } + else + CreateAndOpenNewScene(); + + UpdateWindowTitle(); return .Ok; } @@ -737,11 +762,18 @@ namespace GlitchyEditor } } + /// Stops the scene and cleans up the subsystems to allow loading another scene. + private void CloseCurrentScene() + { + OnSceneStop(); + + ScriptEngine.ClearEntityScriptFields(); + } /// Creates a new scene and openes it. private void CreateAndOpenNewScene() { - OnSceneStop(); + CloseCurrentScene(); SceneFilePath = null; @@ -749,13 +781,19 @@ namespace GlitchyEditor { _camera.Position = .(-1.5f, 1.5f, -2.5f); _camera.RotationEuler = .(MathHelper.ToRadians(25), MathHelper.ToRadians(35), 0); - - SetReference!(_editorScene, newScene); - _editor.CurrentScene = _editorScene; - SetReference!(_activeScene, _editorScene); + + SetEditorScene(newScene); } } + /// Sets the current editor scene + private void SetEditorScene(Scene scene) + { + SetReference!(_editorScene, scene); + _editor.CurrentScene = _editorScene; + SetReference!(_activeScene, _editorScene); + } + /// Creates a new default scene. private static Scene CreateNewScene() { @@ -801,6 +839,11 @@ namespace GlitchyEditor { SaveScene(_editorScene, SceneFilePath); } + + // Note: We only update the last opened scene if the CURRENT scene is saved, NOT when ANY scene is saved. + String relativePath = scope .(); + Path.GetRelativePath(SceneFilePath, _currentProject.WorkspacePath, relativePath); + _currentProject.UserSettings.LastOpenedScene = relativePath; } /// Saves the given scene with the specified file name. @@ -838,7 +881,9 @@ namespace GlitchyEditor /// Loads the given scene file and opens it as the current scene. private void LoadSceneFile(StringView filename) { - OnSceneStop(); + CloseCurrentScene(); + + ScriptEngine.ClearEntityScriptFields(); SceneFilePath = scope String(filename); @@ -850,9 +895,11 @@ namespace GlitchyEditor // Make sure we actually loaded something! if (result case .Ok) { - SetReference!(_editorScene, newScene); - _editor.CurrentScene = _editorScene; - SetReference!(_activeScene, _editorScene); + SetEditorScene(newScene); + + String relativePath = scope .(); + Path.GetRelativePath(filename, _currentProject.WorkspacePath, relativePath); + _currentProject.UserSettings.LastOpenedScene = relativePath; } else { @@ -1042,22 +1089,26 @@ namespace GlitchyEditor private void ShowOpenRecentSceneMenu() { + if (_currentProject == null) + return; + int i = 0; - for (let (name, path) in _recentScenePaths) + for (let scenePath in _currentProject.UserSettings.RecentScenes) { i++; - if (i >= 10) - break; - if (!File.Exists(path)) + String fullScenePath = _currentProject.GetScopedPath!(scenePath); + + if (!File.Exists(fullScenePath)) { - // TODO! + delete scenePath; + @scenePath.Remove(); } - if (ImGui.MenuItem(scope $"{i}: {name} ({path})")) + if (ImGui.MenuItem(scope $"{i}: {scenePath}")) { - LoadSceneFile(path); + LoadSceneFile(fullScenePath); } } } diff --git a/GlitchyEditor/src/Project.bf b/GlitchyEditor/src/Project.bf index 22edca9..3799ee7 100644 --- a/GlitchyEditor/src/Project.bf +++ b/GlitchyEditor/src/Project.bf @@ -19,12 +19,17 @@ class Project [BonIgnore] private String _scriptFolder ~ delete _; + + [BonIgnore] + private ProjectUserSettings _userSettings ~ delete _; public StringView Name => _projectName; public StringView WorkspacePath => _workspacePath; public StringView AssetsFolder => _assetsFolder; + public ProjectUserSettings UserSettings => _userSettings; + [AllowAppend] private this(StringView workspacePath) { @@ -32,13 +37,62 @@ class Project _assetsFolder = new String(); PathInProject(_assetsFolder, "Assets"); - } + InitUserSettings(); + } + + /// Writes the the absolute path of the give project-file into target. + /// @param target The string that will contain the full path. + /// @param relativePath The path of the file relative to the workspace folder. public void PathInProject(String target, StringView relativePath) { Path.Combine(target, WorkspacePath, relativePath); } + /// Creates a new scoped string that contains the absolute path of the give project-file + /// @param relativePath The path of the file relative to the workspace folder. + public mixin GetScopedPath(StringView relativePath) + { + String target = scope:mixin String(); + + PathInProject(target, relativePath); + + target + } + + /// Loads or creates the user specific settings for this project. + private void InitUserSettings() + { + _userSettings = new ProjectUserSettings(); + + String userSettingsFile = scope .(); + PathInProject(userSettingsFile, ProjectUserSettings.FileName); + + // It's not a problem if we couldn't find or deserialize the settings. We will simply restore the defaults. + if (Bon.DeserializeFromFile(ref _userSettings, userSettingsFile) case .Err) + { + // Log anyway, just in case... + Log.EngineLogger.Info($"Failed to deserialize project user settings file \"{userSettingsFile}\"."); + _userSettings.RestoreDefaults(); + } + } + + public void SaveUserSettings() + { + if (_userSettings == null || !_userSettings.SettignsDirty) + return; + + String userSettingsFile = scope .(); + PathInProject(userSettingsFile, ProjectUserSettings.FileName); + + // It's not a big problem, if we fail to serialize this file. + if (Bon.SerializeIntoFile(_userSettings, userSettingsFile) case .Err) + { + // Log anyway, just in case... + Log.EngineLogger.Error($"Failed to deserialize project user settings file \"{userSettingsFile}\"."); + } + } + /// Creates a new project with the given directory and name. public static Project CreateNew(StringView projectDirectory, StringView projectName) { diff --git a/GlitchyEditor/src/ProjectUserSettings.bf b/GlitchyEditor/src/ProjectUserSettings.bf new file mode 100644 index 0000000..6934e43 --- /dev/null +++ b/GlitchyEditor/src/ProjectUserSettings.bf @@ -0,0 +1,79 @@ +using Bon; +using System; +using System.Collections; + +namespace GlitchyEditor; + +/// The user settings of this project +[BonTarget] +class ProjectUserSettings +{ + public static StringView FileName = "project_user.bon"; + + /*[BonInclude] + private String _lastOpenedScene ~ delete _;*/ + + [BonInclude] + private List _recentScenes ~ DeleteContainerAndItems!(_); + + [BonIgnore] + private bool _settingsDirty; + + /// Gets or sets the path of the Scene that was last open. (Relative to the workspace) + public StringView LastOpenedScene + { + get + { + if (_recentScenes == null || _recentScenes.Count == 0) + return ""; + + return _recentScenes[0]; + } + set + { + if (_recentScenes == null) + _recentScenes = new List(); + + // Remove duplicates + for (var entry in _recentScenes) + { + if (entry == value) + { + delete entry; + @entry.Remove(); + } + } + + _recentScenes.Insert(0, new String(value)); + + if (_recentScenes.Count > 10) + { + // We only store 10 entries, delete the rest + for (int i = 10; i < _recentScenes.Count; i++) + { + delete _recentScenes[i]; + } + + _recentScenes.Count = 10; + } + } + } + + /// The scenes that were recently used. + public List RecentScenes => _recentScenes; + + public bool SettignsDirty + { + get => _settingsDirty; + set => _settingsDirty = value; + } + + public void RestoreDefaults() + { + //_lastOpenedScene?.Clear(); + if (_recentScenes != null) + ClearAndDeleteItems!(_recentScenes); + + _settingsDirty = true; + } +} diff --git a/GlitchyEngine/src/Scripting/ScriptEngine.bf b/GlitchyEngine/src/Scripting/ScriptEngine.bf index 4c15a52..ddcb651 100644 --- a/GlitchyEngine/src/Scripting/ScriptEngine.bf +++ b/GlitchyEngine/src/Scripting/ScriptEngine.bf @@ -189,6 +189,17 @@ static class ScriptEngine _userAssemblyWatcher.StartRaisingEvents(); } + /// Gets rid of all entity fields. + public static void ClearEntityScriptFields() + { + for (var value in _entityFields.Values) + { + DeleteDictionaryAndKeys!(value); + } + + _entityFields.Clear(); + } + static void LoadScriptAssemblies() { CreateAppDomain("GlitchyEngineScriptRuntime"); @@ -201,11 +212,13 @@ static class ScriptEngine s_AppAssembly = null; s_AppAssemblyImage = null; } - - ClearDictionaryAndReleaseValues!(_sharpClasses); - + GetCoreAttributes(); GetCoreClasses(); + + ClearDictionaryAndReleaseValues!(_sharpClasses); + ClearDictionaryAndReleaseValues!(_componentClasses); + ClearDictionaryAndReleaseValues!(_entityScripts); GetComponentsFromAssemblies(); @@ -366,9 +379,9 @@ static class ScriptEngine /// Retrieves the base classes from which every Component or Entity inherits static void GetCoreClasses() { - s_EngineObject?.ReleaseRef(); - s_EntityRoot?.ReleaseRef(); - s_ComponentRoot?.ReleaseRef(); + ReleaseRefAndNullify!(s_EngineObject); + ReleaseRefAndNullify!(s_EntityRoot); + ReleaseRefAndNullify!(s_ComponentRoot); s_EngineObject = new ScriptClass("GlitchyEngine.Core", "EngineObject", s_CoreAssemblyImage, .Class); s_EntityRoot = new ScriptClass("GlitchyEngine", "Entity", s_CoreAssemblyImage, .Entity); @@ -383,8 +396,6 @@ static class ScriptEngine private static void GetEntitiesFromAssemblies() { - ClearDictionaryAndReleaseValues!(_entityScripts); - if (s_AppAssemblyImage == null) return; @@ -413,8 +424,6 @@ static class ScriptEngine } private static void GetComponentsFromAssemblies() { - ClearDictionaryAndReleaseValues!(_componentClasses); - MonoTableInfo* typeDefinitionsTable = Mono.mono_image_get_table_info(s_CoreAssemblyImage, .MONO_TABLE_TYPEDEF); int32 numTypes = Mono.mono_table_info_get_rows(typeDefinitionsTable);