Start of project system

+ added Directory.IsEmpty
This commit is contained in:
Simon Lübeß
2023-07-30 13:23:34 +02:00
parent 14a802d057
commit df67f32399
6 changed files with 356 additions and 37 deletions
@@ -470,6 +470,12 @@ namespace GlitchyEditor.EditWindows
{ {
StringView searchString = StringView(&_entitySearchChars); StringView searchString = StringView(&_entitySearchChars);
if (_scene == null)
{
ImGui.TextUnformatted("<No scene open>");
return;
}
if(searchString.IsWhiteSpace) if(searchString.IsWhiteSpace)
{ {
// Show entity hierarchy as tree // Show entity hierarchy as tree
+7 -2
View File
@@ -361,8 +361,13 @@ class EditorContentManager : IContentManager
IAssetLoader assetLoader = GetAssetLoader(file); IAssetLoader assetLoader = GetAssetLoader(file);
// TODO: what are we supposed to do if we don't find a loader? Sure not crash... // TODO: what are we supposed to do if we don't find a loader? Surely not crash...
Log.EngineLogger.AssertDebug(assetLoader != null); //Log.EngineLogger.AssertDebug(assetLoader != null);
if (assetLoader == null)
{
Log.EngineLogger.Error($"No asset loader registered for asset {identifier}.");
return .Invalid;
}
Asset loadedAsset; Asset loadedAsset;
+186 -8
View File
@@ -97,6 +97,12 @@ namespace GlitchyEditor
} }
} }
private Project _currentProject ~ delete _;
public Project CurrentProject => _currentProject;
public bool IsProjectLoaded => _currentProject != null;
[AllowAppend] [AllowAppend]
public this(String[] args, EditorContentManager contentManager) : base("Editor") public this(String[] args, EditorContentManager contentManager) : base("Editor")
{ {
@@ -115,9 +121,18 @@ namespace GlitchyEditor
InitEditor(); InitEditor();
if (args.Count >= 1) if (args.Count >= 1)
LoadSceneFile(args[0]); {
Result<void> result = OpenProject(args[0]);
// Create a new scene, if LoadSceneFile failed if (result == .Err)
Log.EngineLogger.Error($"Failed to open project \"{args[0]}\".");
}
/*if (args.Count >= 1)
LoadSceneFile(args[0]);
*/
// Create a new scene if no scene is loaded
if (_activeScene == null) if (_activeScene == null)
NewScene(); NewScene();
} }
@@ -188,14 +203,25 @@ namespace GlitchyEditor
_editor.EditorSceneRenderer = _editorSceneRenderer; _editor.EditorSceneRenderer = _editorSceneRenderer;
_editor.RequestOpenScene.Add(new (s, fileName) => { _editor.RequestOpenScene.Add(new (s, fileName) => {
LoadSceneFile(fileName); String fullName = scope .();
Path.Combine(fullName, _currentProject.AssetsFolder, fileName);
LoadSceneFile(fullName);
}); });
} }
private bool _showCreateNewProject = false;
public override void Update(GameTime gameTime) public override void Update(GameTime gameTime)
{ {
Debug.Profiler.ProfileFunction!(); Debug.Profiler.ProfileFunction!();
if (IsProjectLoaded)
UpdateScene(gameTime);
}
private void UpdateScene(GameTime gameTime)
{
_editor.CurrentScene = _activeScene; _editor.CurrentScene = _activeScene;
Scene.UpdateMode updateMode; Scene.UpdateMode updateMode;
@@ -244,7 +270,7 @@ namespace GlitchyEditor
// Clear the swapchain-buffer // Clear the swapchain-buffer
RenderCommand.Clear(null, .Color | .Depth, .(0.2f, 0.2f, 0.2f), 1.0f, 0); RenderCommand.Clear(null, .Color | .Depth, .(0.2f, 0.2f, 0.2f), 1.0f, 0);
RenderCommand.SetBlendState(_alphaBlendState); RenderCommand.SetBlendState(_alphaBlendState);
RenderCommand.SetDepthStencilState(_depthStencilState); RenderCommand.SetDepthStencilState(_depthStencilState);
@@ -261,7 +287,7 @@ namespace GlitchyEditor
RenderCommand.SetBlendState(_alphaBlendState); RenderCommand.SetBlendState(_alphaBlendState);
RenderCommand.SetDepthStencilState(_depthStencilState); RenderCommand.SetDepthStencilState(_depthStencilState);
if (_editor.SceneViewportWindow.Visible) if (_editor.SceneViewportWindow.Visible)
{ {
_camera.Update(gameTime); _camera.Update(gameTime);
@@ -383,6 +409,7 @@ namespace GlitchyEditor
_editor.Update(); _editor.Update();
_settingsWindow.Show(); _settingsWindow.Show();
ShowCreateNewProjectModal();
UI_Toolbar(); UI_Toolbar();
@@ -619,6 +646,154 @@ namespace GlitchyEditor
} }
} }
private Result<void> CreateNewProject(StringView directory, StringView projectName)
{
var createDirectoryResult = Directory.CreateDirectory(directory);
if (createDirectoryResult case .Err(let error))
{
Log.ClientLogger.Error($"Failed to create directory \"{directory}\".");
return .Err;
}
if (!Directory.IsEmpty(directory))
{
Log.ClientLogger.Error($"Cannot create project in non-empty directory.");
return .Err;
}
Project newProject = Project.CreateNew(directory, projectName);
var createAssetDirResult = Directory.CreateDirectory(newProject.AssetsFolder);
if (createAssetDirResult case .Err(let error))
{
Log.ClientLogger.Error($"Failed to create asset directory \"{newProject.AssetsFolder}\".");
return .Err;
}
// Copy .gitignore
String gitignoreTarget = scope .();
newProject.PathInProject(gitignoreTarget, ".gitignore");
var copyGitignore = File.Copy("resources/gitignore.txt", gitignoreTarget);
if (copyGitignore case .Err(let error))
{
Log.ClientLogger.Error($"Failed to copy .gitignore (\"{error}\").");
}
// TODO: VS Project
return .Ok;
}
private bool _openCreateProjectModal;
private void ShowCreateNewProjectModal()
{
static char8[128] projectNameBuffer = .();
static char8[256] projectDirectoryBuffer = .();
if (_openCreateProjectModal)
{
ImGui.OpenPopup("Create new Project");
_openCreateProjectModal = false;
projectNameBuffer = .();
}
// Always center this window when appearing
var center = ImGui.GetMainViewport().GetCenter();
ImGui.SetNextWindowPos(center, .Appearing, .(0.5f, 0.5f));
if (ImGui.BeginPopupModal("Create new Project", null, .AlwaysAutoResize))
{
ImGui.TextUnformatted("Project Name:");
ImGui.InputText("##projectName", &projectNameBuffer, projectNameBuffer.Count - 1);
ImGui.NewLine();
ImGui.TextUnformatted("Directory:");
ImGui.InputText("##directory", &projectDirectoryBuffer, projectDirectoryBuffer.Count - 1);
ImGui.SameLine();
if (ImGui.Button("..."))
{
FolderBrowserDialog folderDialog = scope FolderBrowserDialog();
Result<DialogResult> result = folderDialog.ShowDialog();
if (result case .Ok(let dialogResult) && dialogResult case .OK)
{
folderDialog.SelectedPath.CopyTo(projectDirectoryBuffer);
}
}
StringView projectName = StringView(&projectNameBuffer);
StringView directory = StringView(&projectDirectoryBuffer);
String target = scope String();
Path.Combine(target, directory, projectName);
ImGui.NewLine();
ImGui.Text($"The Project will be in:\n{target}");
ImGui.NewLine();
ImGui.BeginDisabled(directory.IsWhiteSpace || projectName.IsWhiteSpace);
if (ImGui.Button("Create"))
{
Result<void> result = CreateNewProject(target, projectName);
if (result case .Ok)
ImGui.CloseCurrentPopup();
}
ImGui.EndDisabled();
ImGui.SameLine();
if (ImGui.Button("Cancel"))
{
ImGui.CloseCurrentPopup();
}
ImGui.EndPopup();
}
}
private void CloseCurrentProject()
{
OnSceneStop();
SetReference!(_editorScene, null);
SetReference!(_activeScene, null);
_editor.CurrentScene = null;
// TODO: Do actual work here!
delete _currentProject;
_currentProject = null;
}
private Result<void> OpenProject(StringView workspacePath)
{
CloseCurrentProject();
_currentProject = Project.Load(workspacePath);
String appAssemblyPath = scope String();
// TODO: obviously change dll name, configurable?
Path.Combine(appAssemblyPath, _currentProject.AssetsFolder, "Scripts/bin/Sandbox.dll");
ScriptEngine.SetAppAssemblyPath(appAssemblyPath);
// TODO: Load last opened scene
NewScene();
return .Ok;
}
/// Creates a new scene. /// Creates a new scene.
private void NewScene() private void NewScene()
{ {
@@ -753,6 +928,9 @@ namespace GlitchyEditor
if(ImGui.BeginMenu("File", true)) if(ImGui.BeginMenu("File", true))
{ {
if (ImGui.MenuItem("Create new Project...", "Ctrl+N"))
_openCreateProjectModal = true;
if (ImGui.MenuItem("New", "Ctrl+N")) if (ImGui.MenuItem("New", "Ctrl+N"))
NewScene(); NewScene();
@@ -844,7 +1022,7 @@ namespace GlitchyEditor
_camera.OnViewportResize(sizeX, sizeY); _camera.OnViewportResize(sizeX, sizeY);
_activeScene.SetViewportSize(sizeX, sizeY); _activeScene?.SetViewportSize(sizeX, sizeY);
} }
private void GameViewportSizeChanged(Object sender, float2 viewportSize) private void GameViewportSizeChanged(Object sender, float2 viewportSize)
@@ -857,7 +1035,7 @@ namespace GlitchyEditor
_gameViewportTarget.Resize(sizeX, sizeY); _gameViewportTarget.Resize(sizeX, sizeY);
_activeScene.SetViewportSize(sizeX, sizeY); _activeScene?.SetViewportSize(sizeX, sizeY);
} }
private bool OnKeyPressed(KeyPressedEvent e) private bool OnKeyPressed(KeyPressedEvent e)
+84
View File
@@ -0,0 +1,84 @@
using System;
using System.IO;
using Bon;
using GlitchyEngine;
namespace GlitchyEditor;
[BonTarget]
class Project
{
[BonInclude]
private String _projectName ~ delete _;
[BonIgnore]
private String _workspacePath ~ delete _;
[BonIgnore]
private String _assetsFolder ~ delete _;
[BonIgnore]
private String _scriptFolder ~ delete _;
public StringView ProjectName => _projectName;
public StringView WorkspacePath => _workspacePath;
public StringView AssetsFolder => _assetsFolder;
[AllowAppend]
private this(StringView workspacePath)
{
_workspacePath = new String(workspacePath);
_assetsFolder = new String();
PathInProject(_assetsFolder, "Assets");
}
public void PathInProject(String target, StringView relativePath)
{
Path.Combine(target, WorkspacePath, relativePath);
}
public void GetRelativePath()
{
}
public static Project CreateNew(StringView projectDirectory, StringView projectName)
{
Project project = new Project(projectDirectory);
project._projectName = new String(projectName);
String settingsFile = scope .();
project.PathInProject(settingsFile, "project.gep");
Result<void> result = Bon.SerializeIntoFile(project, settingsFile);
if (result case .Err)
Log.EngineLogger.Warning($"Failed to save project file \"{settingsFile}\".");
return project;
}
public static Result<Project> Load(StringView projectDirectory)
{
Project project = new Project(projectDirectory);
String settingsFile = scope .();
project.PathInProject(settingsFile, "project.gep");
if (File.Exists(settingsFile))
{
Result<void> result = Bon.DeserializeFromFile(ref project, settingsFile);
if (result case .Err)
Log.EngineLogger.Warning($"Failed to deserialize project file \"{settingsFile}\".");
}
else
{
Log.EngineLogger.Warning($"Project file \"{settingsFile}\" doesn't exist.");
}
return project;
}
}
@@ -0,0 +1,13 @@
namespace System.IO;
extension Directory
{
[Import("Shlwapi.lib"), CLink, CallingConvention(.Stdcall)]
private static extern System.Windows.IntBool PathIsDirectoryEmptyW(char16* pszPath);
public static bool IsEmpty(StringView fileName)
{
// TODO: This is obviously windows only
return PathIsDirectoryEmptyW(fileName.ToScopedNativeWChar!());
}
}
+60 -27
View File
@@ -95,6 +95,8 @@ static class ScriptEngine
// TODO: This should be a global setting somewhere // TODO: This should be a global setting somewhere
private static bool _debuggingEnabled = true; private static bool _debuggingEnabled = true;
private static String _appAssemblyPath = new .() ~ delete _;
internal static class Attributes internal static class Attributes
{ {
internal static MonoClass* s_ShowInEditorAttribute; internal static MonoClass* s_ShowInEditorAttribute;
@@ -106,8 +108,14 @@ static class ScriptEngine
ScriptGlue.Init(); ScriptGlue.Init();
LoadScriptAssemblies(); LoadScriptAssemblies();
}
InitAssemblyWatcher(); /// Changes the path to the current app assembly.
public static void SetAppAssemblyPath(StringView appAssemblyPath)
{
_appAssemblyPath.Set(appAssemblyPath);
ReloadAssemblies();
} }
static void InitMono() static void InitMono()
@@ -138,33 +146,46 @@ static class ScriptEngine
static void InitAssemblyWatcher() static void InitAssemblyWatcher()
{ {
if (_userAssemblyWatcher == null) // We don't need a file system watcher, if we have nothing to watch...
if (!File.Exists(_appAssemblyPath))
return;
String directory = scope .();
Path.GetDirectoryPath(_appAssemblyPath, directory);
if (_userAssemblyWatcher != null && _userAssemblyWatcher.Directory != directory)
{ {
// TODO: Obviously don't hardcode path delete _userAssemblyWatcher;
_userAssemblyWatcher = new FileSystemWatcher("SandboxProject/Assets/Scripts/bin/", "*/Sandbox.dll");
_userAssemblyWatcher.OnChanged.Add(new (fileName) =>
{
// TODO: Temporary, we want to be able to reload while in play-mode. (+ Editor Scripts will be a thing some day)
if (_entityScriptInstances.Count > 0)
{
Log.EngineLogger.Warning("There are script instances. Skipping assembly reload.");
return;
}
Log.EngineLogger.Info("Script reload requested.");
_userAssemblyWatcher.StopRaisingEvents();
Application.Instance.InvokeOnMainThread(new () =>
{
Log.EngineLogger.Info("Reloading scripts...");
ReloadAssemblies();
Log.EngineLogger.Info("Scripts reloaded!");
_userAssemblyWatcher.StartRaisingEvents();
});
});
} }
String fileName = scope .("*/");
Path.GetFileName(_appAssemblyPath, fileName);
// TODO: Obviously don't hardcode path
_userAssemblyWatcher = new FileSystemWatcher(directory, fileName);
_userAssemblyWatcher.OnChanged.Add(new (fileName) =>
{
// TODO: Temporary, we want to be able to reload while in play-mode. (+ Editor Scripts will be a thing some day)
if (_entityScriptInstances.Count > 0)
{
Log.EngineLogger.Warning("There are script instances. Skipping assembly reload.");
return;
}
Log.EngineLogger.Info("Script reload requested.");
_userAssemblyWatcher.StopRaisingEvents();
Application.Instance.InvokeOnMainThread(new () =>
{
Log.EngineLogger.Info("Reloading scripts...");
ReloadAssemblies();
Log.EngineLogger.Info("Scripts reloaded!");
_userAssemblyWatcher.StartRaisingEvents();
});
});
_userAssemblyWatcher.StartRaisingEvents(); _userAssemblyWatcher.StartRaisingEvents();
} }
@@ -172,7 +193,14 @@ static class ScriptEngine
{ {
CreateAppDomain("GlitchyEngineScriptRuntime"); CreateAppDomain("GlitchyEngineScriptRuntime");
(s_CoreAssembly, s_CoreAssemblyImage) = LoadAssembly("resources/scripts/ScriptCore.dll", _debuggingEnabled); (s_CoreAssembly, s_CoreAssemblyImage) = LoadAssembly("resources/scripts/ScriptCore.dll", _debuggingEnabled);
(s_AppAssembly, s_AppAssemblyImage) = LoadAssembly("SandboxProject/Assets/Scripts/bin/Sandbox.dll", _debuggingEnabled);
if (File.Exists(_appAssemblyPath))
(s_AppAssembly, s_AppAssemblyImage) = LoadAssembly(_appAssemblyPath, _debuggingEnabled);
else
{
s_AppAssembly = null;
s_AppAssemblyImage = null;
}
ClearDictionaryAndReleaseValues!(_sharpClasses); ClearDictionaryAndReleaseValues!(_sharpClasses);
@@ -184,6 +212,8 @@ static class ScriptEngine
GetEntitiesFromAssemblies(); GetEntitiesFromAssemblies();
ScriptGlue.RegisterManagedComponents(); ScriptGlue.RegisterManagedComponents();
InitAssemblyWatcher();
} }
public static void SetContext(Scene scene) public static void SetContext(Scene scene)
@@ -355,6 +385,9 @@ static class ScriptEngine
{ {
ClearDictionaryAndReleaseValues!(_entityScripts); ClearDictionaryAndReleaseValues!(_entityScripts);
if (s_AppAssemblyImage == null)
return;
MonoTableInfo* typeDefinitionsTable = Mono.mono_image_get_table_info(s_AppAssemblyImage, .MONO_TABLE_TYPEDEF); MonoTableInfo* typeDefinitionsTable = Mono.mono_image_get_table_info(s_AppAssemblyImage, .MONO_TABLE_TYPEDEF);
int32 numTypes = Mono.mono_table_info_get_rows(typeDefinitionsTable); int32 numTypes = Mono.mono_table_info_get_rows(typeDefinitionsTable);