diff --git a/Prowl.Editor/Build/ProjectBuilder.cs b/Prowl.Editor/Build/ProjectBuilder.cs index e86960bb7..6f20be4f3 100644 --- a/Prowl.Editor/Build/ProjectBuilder.cs +++ b/Prowl.Editor/Build/ProjectBuilder.cs @@ -1,73 +1,87 @@ // This file is part of the Prowl Game Engine // Licensed under the MIT License. See the LICENSE file in the project root for details. -using Prowl.Runtime; +using System.Reflection; -namespace Prowl.Editor.Build; +using Prowl.Editor.Assets; +using Prowl.Editor.Utilities; +using Prowl.Runtime; -public abstract class ProjectBuilder +namespace Prowl.Editor.Build { - public void StartBuild(AssetRef[] scenes, DirectoryInfo output) + public abstract class ProjectBuilder { - if (!Project.HasProject) + public void StartBuild(AssetRef[] scenes, DirectoryInfo output) { - Debug.LogError($"No Project Loaded..."); - return; - } + if (!Project.HasProject) + { + Debug.LogError($"No Project Loaded..."); + return; + } - if (!AreScenesValid(scenes)) - return; + AssetDatabase.Update(); + if (!AreScenesValid(scenes)) + return; - Debug.Log($"Starting Project Build..."); - BoundedLog($"Creating Directories..."); + Debug.Log($"Starting Project Build..."); + Project.BoundedLog($"Creating Directories..."); - if (output.Exists) - { - BoundedLog($"Deleting existing build directory..."); - output.Delete(true); - } + if (output.Exists) + { + Project.BoundedLog($"Deleting existing build directory..."); + output.Delete(true); + } - try - { - Build(scenes, output); - } - catch (Exception e) - { - Debug.LogError($"Failed to build project: {e.Message}"); + try + { + Build(scenes, output); + } + catch (Exception e) + { + Debug.LogError($"Failed to build project: {e.Message}"); + } } - } - protected abstract void Build(AssetRef[] scenes, DirectoryInfo output); + protected abstract void Build(AssetRef[] scenes, DirectoryInfo output); - private bool AreScenesValid(AssetRef[] scenes) - { - if (scenes == null) + private bool AreScenesValid(AssetRef[] scenes) { - Debug.LogError($"Atleast 1 Scene must be assigned in the Build Project Settings Window"); - return false; - } - - if (scenes.Length <= 0) - { - Debug.LogError($"Atleast 1 Scene must be assigned in the Build Project Settings Window"); - return false; - } + if (scenes == null) + { + Debug.LogError($"At least 1 Scene must be assigned in the Build Project Settings Window"); + return false; + } - // Make sure all scenes are valid - foreach (var scene in scenes) - if (!scene.IsAvailable) + if (scenes.Length <= 0) { - Debug.LogError($"Scene {scene.Name} is not available, please assign a valid available scene"); + Debug.LogError($"At least 1 Scene must be assigned in the Build Project Settings Window"); return false; } - return true; - } + // Make sure all scenes are valid + foreach (var scene in scenes) + if (!scene.IsAvailable) + { + Debug.LogError($"Scene {scene.Name} is not available, please assign a valid available scene"); + return false; + } - protected void BoundedLog(string message) - { - Debug.Log("**********************************************************************************************************************"); - Debug.Log(message); - Debug.Log("**********************************************************************************************************************"); + return true; + } + + public static IEnumerable GetAll() + { + foreach (Assembly editorAssembly in AssemblyManager.ExternalAssemblies.Append(typeof(Program).Assembly)) + { + List derivedTypes = EditorUtils.GetDerivedTypes(typeof(ProjectBuilder), editorAssembly); + foreach (Type type in derivedTypes) + { + if (type.IsAbstract) + continue; + + yield return (ProjectBuilder)Activator.CreateInstance(type); + } + } + } } } diff --git a/Prowl.Editor/Editor/CLI/CliBuildOptions.cs b/Prowl.Editor/Editor/CLI/CliBuildOptions.cs new file mode 100644 index 000000000..debd042bc --- /dev/null +++ b/Prowl.Editor/Editor/CLI/CliBuildOptions.cs @@ -0,0 +1,19 @@ +// This file is part of the Prowl Game Engine +// Licensed under the MIT License. See the LICENSE file in the project root for details. + +using CommandLine; + +namespace Prowl.Editor.Editor.CLI; + +/// +/// Command line options for the `build` command. +/// +[Verb("build", false, HelpText = "build a given project")] +internal class CliBuildOptions : CliOptionsBase +{ + /// + /// The path of the output files. + /// + [Option('o', "output", Required = false, HelpText = "Output directory path")] + public required DirectoryInfo Output { get; set; } +} diff --git a/Prowl.Editor/Editor/CLI/CliCreateOptions.cs b/Prowl.Editor/Editor/CLI/CliCreateOptions.cs index 0e80d49f0..13072592b 100644 --- a/Prowl.Editor/Editor/CLI/CliCreateOptions.cs +++ b/Prowl.Editor/Editor/CLI/CliCreateOptions.cs @@ -9,11 +9,6 @@ namespace Prowl.Editor.Editor.CLI; /// Command line options for the `create` command. /// [Verb("create", false, HelpText = "create a project")] -internal class CliCreateOptions +internal class CliCreateOptions : CliOptionsBase { - /// - /// The path of the project to be created. - /// - [Option('p', "project", Required = false, HelpText = "Project path", Default = "./")] - public required DirectoryInfo ProjectPath { get; set; } } diff --git a/Prowl.Editor/Editor/CLI/CliOpenOptions.cs b/Prowl.Editor/Editor/CLI/CliOpenOptions.cs index 9a2fef7e7..213372657 100644 --- a/Prowl.Editor/Editor/CLI/CliOpenOptions.cs +++ b/Prowl.Editor/Editor/CLI/CliOpenOptions.cs @@ -9,11 +9,4 @@ namespace Prowl.Editor.Editor.CLI; /// Command line options for the `open` command. /// [Verb("open", true, HelpText = "Open a given project")] -internal class CliOpenOptions -{ - /// - /// The path of the project to be open. - /// - [Option('p', "project", Required = false, HelpText = "Project path")] - public required DirectoryInfo ProjectPath { get; set; } -} +internal class CliOpenOptions : CliOptionsBase { } diff --git a/Prowl.Editor/Editor/CLI/CliOptionsBase.cs b/Prowl.Editor/Editor/CLI/CliOptionsBase.cs new file mode 100644 index 000000000..fb647c887 --- /dev/null +++ b/Prowl.Editor/Editor/CLI/CliOptionsBase.cs @@ -0,0 +1,18 @@ +// This file is part of the Prowl Game Engine +// Licensed under the MIT License. See the LICENSE file in the project root for details. + +using CommandLine; + +namespace Prowl.Editor.Editor.CLI; + +/// +/// Repetitive options. +/// +internal class CliOptionsBase +{ + /// + /// The path of the project to perform the command (open, build, create, etc). + /// + [Option('p', "project", Required = false, HelpText = "Project path")] + public required DirectoryInfo? ProjectPath { get; init; } +} diff --git a/Prowl.Editor/Editor/CLI/README.md b/Prowl.Editor/Editor/CLI/README.md new file mode 100644 index 000000000..688068c67 --- /dev/null +++ b/Prowl.Editor/Editor/CLI/README.md @@ -0,0 +1,3 @@ +Command Line (CLI) options + +These classes map the optional commands that Prowl accept from the Console/Terminal/Command line. diff --git a/Prowl.Editor/Editor/ConsoleWindow.cs b/Prowl.Editor/Editor/ConsoleWindow.cs index e93330ec1..e63debc5b 100644 --- a/Prowl.Editor/Editor/ConsoleWindow.cs +++ b/Prowl.Editor/Editor/ConsoleWindow.cs @@ -53,20 +53,20 @@ private static bool FastCompareMessage(LogMessage log, string message, DebugStac if ((log.trace == null) != (stackTrace == null)) return false; - if (log.trace != null && log.trace.stackFrames.Length != stackTrace.stackFrames.Length) + if (log.trace != null && log.trace.StackFrames.Length != stackTrace.StackFrames.Length) return false; // Slower checks later // Check stack frame 0 earlier since it potentially avoids comparing big messages. - if (stackTrace != null && stackTrace.stackFrames.Length > 0) + if (stackTrace != null && stackTrace.StackFrames.Length > 0) { - DebugStackFrame frame = log.trace.stackFrames[0]; - DebugStackFrame frame2 = stackTrace.stackFrames[0]; + DebugStackFrame frame = log.trace.StackFrames[0]; + DebugStackFrame frame2 = stackTrace.StackFrames[0]; - if (frame.line != frame2.line && frame.column != frame2.column) + if (frame.Line != frame2.Line && frame.Column != frame2.Column) return false; - if (frame.fileName != frame2.fileName) + if (frame.FileName != frame2.FileName) return false; } @@ -76,15 +76,15 @@ private static bool FastCompareMessage(LogMessage log, string message, DebugStac // Potentially slowest check last if (log.trace != null) { - for (int i = 1; i < log.trace.stackFrames.Length; i++) + for (int i = 1; i < log.trace.StackFrames.Length; i++) { - DebugStackFrame frame = log.trace.stackFrames[i]; - DebugStackFrame frame2 = stackTrace.stackFrames[i]; + DebugStackFrame frame = log.trace.StackFrames[i]; + DebugStackFrame frame2 = stackTrace.StackFrames[i]; - if (frame.line != frame2.line && frame.column != frame2.column) + if (frame.Line != frame2.Line && frame.Column != frame2.Column) return false; - if (frame.fileName != frame2.fileName) + if (frame.FileName != frame2.FileName) return false; } } @@ -242,7 +242,7 @@ private void DrawMessage(LogMessage message, int index) textRect.x += 7.5; - bool hasTrace = message.trace != null && message.trace.stackFrames.Length > 0; + bool hasTrace = message.trace != null && message.trace.StackFrames.Length > 0; Vector2 textPos = textRect.Position; textPos.y += (rect.height / 2) - (7.5 + (hasTrace ? 5 : 0)); @@ -271,7 +271,7 @@ private void DrawMessage(LogMessage message, int index) { textPos.y += 15; - DebugStackFrame frame = message.trace!.stackFrames[0]; + DebugStackFrame frame = message.trace!.StackFrames[0]; string frameText = frame.ToString(); @@ -311,11 +311,11 @@ private bool DrawExpandedMessage(LogMessage message) gui.Draw2D.DrawText(Font.DefaultFont, selMsg, 20, textPos, color); } - if (message.trace != null && message.trace.stackFrames.Length != 0) + if (message.trace != null && message.trace.StackFrames.Length != 0) { - for (int i = 0; i < message.trace.stackFrames.Length; i++) + for (int i = 0; i < message.trace.StackFrames.Length; i++) { - DebugStackFrame frame = message.trace.stackFrames[i]; + DebugStackFrame frame = message.trace.StackFrames[i]; string frameText = frame.ToString(); Vector2 frameSize = Font.DefaultFont.CalcTextSize(frameText, font_size: 21, 0); @@ -364,10 +364,10 @@ private bool DrawExpandedMessage(LogMessage message) private static void OpenStackFrame(DebugStackFrame frame) { - if (frame.fileName == null) + if (frame.FileName == null) return; - AssetDatabase.OpenPath(new FileInfo(frame.fileName), frame.line, frame.column); + AssetDatabase.OpenPath(new FileInfo(frame.FileName), frame.Line, frame.Column); } diff --git a/Prowl.Editor/Program.cs b/Prowl.Editor/Program.cs index dc14a181f..faa764ac4 100644 --- a/Prowl.Editor/Program.cs +++ b/Prowl.Editor/Program.cs @@ -1,11 +1,15 @@ // This file is part of the Prowl Game Engine // Licensed under the MIT License. See the LICENSE file in the project root for details. +using System.Globalization; + using CommandLine; using Prowl.Editor.Assets; +using Prowl.Editor.Build; using Prowl.Editor.Editor.CLI; using Prowl.Editor.Preferences; +using Prowl.Editor.ProjectSettings; using Prowl.Runtime; using Prowl.Runtime.SceneManagement; using Prowl.Runtime.Utils; @@ -14,40 +18,43 @@ namespace Prowl.Editor; public static class Program { - private static bool IsReloadingExternalAssemblies { get; set; } public static void RegisterReloadOfExternalAssemblies() => IsReloadingExternalAssemblies = true; + private static bool IsReloadingExternalAssemblies { get; set; } private static bool s_createdDefaultWindows; private static bool s_opened; public static int Main(string[] args) { - return Parser.Default.ParseArguments(args) + // CommandLineParser what command line arguments where used. `open` is the default option, + // so there is no need to call "prowl.exe open", only "prowl.exe". + return Parser.Default.ParseArguments(args) .MapResult( + // the default option, so there is no need to call "prowl.exe open", only "prowl.exe" (CliOpenOptions options) => Run(options), (CliCreateOptions options) => CreateCommand(options), - errs => 1); // error + (CliBuildOptions options) => BuildCommand(options), + _ => 1); // the command do not exist, finish the program as an error } private static int CreateCommand(CliCreateOptions options) { Console.WriteLine("Creating a new project"); - if (options?.ProjectPath is not null && !options.ProjectPath.Exists) - { - Project.CreateNew(options.ProjectPath); - } - else + if (options.ProjectPath is null || options.ProjectPath.Exists) { Console.WriteLine("Path is not valid or already exists"); + return 1; } + Project.CreateNew(options.ProjectPath); + return 0; } private static int Run(CliOpenOptions options) { // set global Culture to invariant - Thread.CurrentThread.CurrentCulture = System.Globalization.CultureInfo.InvariantCulture; + Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture; Application.Initialize += () => { // Editor-specific initialization code @@ -55,90 +62,84 @@ private static int Run(CliOpenOptions options) ImporterAttribute.GenerateLookUp(); // Start with the project window open - new ProjectsWindow(); + _ = new ProjectsWindow(); }; Application.Update += () => { + AssetDatabase.InternalUpdate(); - if (!s_opened && options?.ProjectPath is not null && options.ProjectPath.Exists) + if (PlayMode.Current == PlayMode.Mode.Editing) // Don't recompile scripts unless were in editor mode + CheckReloadingAssemblies(); + + if (!Project.HasProject) { - Project.Open(new Project(options.ProjectPath)); - s_opened = true; + return; } - //EditorGui.SetupDock(); - - AssetDatabase.InternalUpdate(); - if (PlayMode.Current == PlayMode.Mode.Editing) // Dont recompile scripts unless were in editor mode - CheckReloadingAssemblies(); + Physics.Initialize(); // Editor-specific update code - if (Project.HasProject) + if (!s_createdDefaultWindows) + { + s_createdDefaultWindows = true; + + var console = EditorGuiManager.DockWindowTo(new ConsoleWindow(), null, Docking.DockZone.Center); + var assetBrowser = + EditorGuiManager.DockWindowTo(new AssetsBrowserWindow(), console, Docking.DockZone.Center); + // Add Asset Tree, When we do this AssetBrowser node will subdivide into two children + var assetTree = EditorGuiManager.DockWindowTo(new AssetsTreeWindow(), assetBrowser, + Docking.DockZone.Left, 0.2f); + // So for the Inspector we need to use the Child to dock now + EditorGuiManager.DockWindowTo(new InspectorWindow(), assetBrowser?.Child[1], + Docking.DockZone.Right, 0.75f); + // Now Asset Browser is Subdivided twice, + assetBrowser = assetBrowser?.Child[1].Child[0]; + var game = EditorGuiManager.DockWindowTo(new GameWindow(), assetBrowser, Docking.DockZone.Top, + 0.65f); + EditorGuiManager.DockWindowTo(new SceneViewWindow(), game, Docking.DockZone.Center); + + // and finally hierarchy on top of asset tree + EditorGuiManager.DockWindowTo(new HierarchyWindow(), assetTree, + Docking.DockZone.Top, 0.65f); + } + + Application.DataPath = Project.Active?.ProjectPath; + + if (GeneralPreferences.Instance.LockFPS) + { + Graphics.VSync = false; + Screen.FramesPerSecond = GeneralPreferences.Instance.TargetFPS; + } + else { - Physics.Initialize(); - - if (!s_createdDefaultWindows) - { - s_createdDefaultWindows = true; - //new EditorMainMenubar(); - var console = EditorGuiManager.DockWindowTo(new ConsoleWindow(), null, Docking.DockZone.Center); - var assetbrowser = EditorGuiManager.DockWindowTo(new AssetsBrowserWindow(), console, Docking.DockZone.Center); - // Add Asset Tree, When we do this AssetBrowser node will subdivide into two children - var assettree = EditorGuiManager.DockWindowTo(new AssetsTreeWindow(), assetbrowser, Docking.DockZone.Left, 0.2f); - // So for the Inspector we need to use the Child to dock now - var inspector = EditorGuiManager.DockWindowTo(new InspectorWindow(), assetbrowser.Child[1], Docking.DockZone.Right, 0.75f); - // Now Asset Browser is Subdivided twice, - assetbrowser = assetbrowser.Child[1].Child[0]; - var game = EditorGuiManager.DockWindowTo(new GameWindow(), assetbrowser, Docking.DockZone.Top, 0.65f); - var scene = EditorGuiManager.DockWindowTo(new SceneViewWindow(), game, Docking.DockZone.Center); - - // and finally hierarchy on top of asset tree - var hierarchy = EditorGuiManager.DockWindowTo(new HierarchyWindow(), assettree, Docking.DockZone.Top, 0.65f); - - // new ProjectSettingsWindow(); - // new PreferencesWindow(); - // new AssetSelectorWindow(typeof(Texture2D), (guid, fileid) => { }); - } - - Application.DataPath = Project.Active.ProjectPath; - - if (GeneralPreferences.Instance.LockFPS) - { - Graphics.VSync = false; - Screen.FramesPerSecond = GeneralPreferences.Instance.TargetFPS; - } - else - { - Graphics.VSync = GeneralPreferences.Instance.VSync; - Screen.FramesPerSecond = 0; - } - - if (Hotkeys.IsHotkeyDown("SaveSceneAs", new() { Key = Key.S, Ctrl = true, Shift = true })) - EditorGuiManager.SaveSceneAs(); - else if (Hotkeys.IsHotkeyDown("SaveScene", new() { Key = Key.S, Ctrl = true })) - EditorGuiManager.SaveScene(); - - Application.IsPlaying = PlayMode.Current == PlayMode.Mode.Playing; - - - try - { - bool hasGameWindow = GameWindow.LastFocused != null && GameWindow.LastFocused.IsAlive; - // Push GameWindow's input handler - if (hasGameWindow) Input.PushHandler((GameWindow.LastFocused.Target as GameWindow).InputHandler); - - PlayMode.GameTime.Update(); - Time.TimeStack.Push(PlayMode.GameTime); - SceneManager.Update(); - Time.TimeStack.Pop(); - - if (hasGameWindow) Input.PopHandler(); - } - catch (Exception e) - { - Debug.LogError("Scene Update Error: " + e.ToString()); - } + Graphics.VSync = GeneralPreferences.Instance.VSync; + Screen.FramesPerSecond = 0; + } + + if (Hotkeys.IsHotkeyDown("SaveSceneAs", new() { Key = Key.S, Ctrl = true, Shift = true })) + EditorGuiManager.SaveSceneAs(); + else if (Hotkeys.IsHotkeyDown("SaveScene", new() { Key = Key.S, Ctrl = true })) + EditorGuiManager.SaveScene(); + + Application.IsPlaying = PlayMode.Current == PlayMode.Mode.Playing; + + try + { + bool hasGameWindow = GameWindow.LastFocused.IsAlive; + // Push GameWindow's input handler + if (hasGameWindow) Input.PushHandler((GameWindow.LastFocused.Target as GameWindow)!.InputHandler); + + PlayMode.GameTime.Update(); + Time.TimeStack.Push(PlayMode.GameTime); + SceneManager.Update(); + Time.TimeStack.Pop(); + + if (hasGameWindow) Input.PopHandler(); + } + catch (Exception e) + { + Debug.LogError($"Scene Update Error: {e}"); } }; @@ -146,71 +147,119 @@ private static int Run(CliOpenOptions options) { EditorGuiManager.Update(); + if (!s_opened && options.ProjectPath is not null && options.ProjectPath.Exists) + { + Project.Open(new Project(options.ProjectPath)); + s_opened = true; + } + Graphics.EndFrame(); }; - Application.Quitting += () => + Application.Quitting += () => { }; + + Application.Run("Prowl Editor", 1920, 1080, new EditorAssetProvider(), true); + + return 0; + } + + /// + /// Build the final version of the game via command line `prowl build --project PATH` + /// + /// Command Line options for build command + /// + private static int BuildCommand(CliBuildOptions options) + { + Debug.Log($"Building project from\t'{options.ProjectPath}'"); + if (options.ProjectPath is null || !options.ProjectPath.Exists) { + Debug.LogError("Path is not valid or already exists"); + return 1; + } - }; + var pathBuild = new DirectoryInfo(Path.Combine(options.ProjectPath.ToString(), "Builds", + DateTime.UtcNow.ToString("yyyyMMddHHmmss"))); - Application.Run("Prowl Editor", 1920, 1080, new EditorAssetProvider(), true); + Debug.Log($"Building output path\t'{pathBuild}'"); + if (pathBuild.Exists) + { + Debug.LogError("Build path is not valid or already exists"); + return 1; + } + + // Open the project, and if it's ok, load it + var project = new Project(options.ProjectPath); + Project.Open(project); + + // Set up the app + Application.DataPath = options.ProjectPath.ToString(); + Application.AssetProvider = new EditorAssetProvider(); + + AssetDatabase.Update(); + // TODO: instead calling the builders[0], use a command line argument + pathBuild.Create(); + var builders = ProjectBuilder.GetAll().ToList(); + builders[0].StartBuild(BuildProjectSetting.Instance.Scenes, pathBuild); + + // TODO: since StartBuild return void, we cannot say if the code executed fine return 0; } - public static void CheckReloadingAssemblies() + private static void CheckReloadingAssemblies() { - if (IsReloadingExternalAssemblies) + if (!IsReloadingExternalAssemblies) + { + return; + } + + IsReloadingExternalAssemblies = false; + + if (Project.Active is not null && Project.HasProject) { - IsReloadingExternalAssemblies = false; + SceneManager.StoreScene(); + SceneManager.Clear(); - if (Project.HasProject) + try { - SceneManager.StoreScene(); - SceneManager.Clear(); - - try - { - // Unload External Assemblies - AssemblyManager.Unload(); - - Project active = Project.Active; - - DirectoryInfo temp = active.TempDirectory; - DirectoryInfo bin = new DirectoryInfo(Path.Combine(temp.FullName, "bin")); - DirectoryInfo debug = new DirectoryInfo(Path.Combine(bin.FullName, "Debug")); - - // Delete everything under Temp\Bin - if (bin.Exists) - Directory.Delete(bin.FullName, true); - bin.Create(); - - // Compile the Projects - Project.Compile(active.Assembly_Proj.FullName, debug); - Project.Compile(active.Editor_Assembly_Proj.FullName, debug); - - // Reload the External Assemblies - AssemblyManager.LoadExternalAssembly(active.Editor_Assembly_DLL.FullName, true); - AssemblyManager.LoadExternalAssembly(active.Assembly_DLL.FullName, true); - } - catch (Exception e) - { - Debug.LogError($"Error reloading assemblies: {e.Message}"); - Debug.LogError(e.StackTrace); - } - finally - { - OnAssemblyLoadAttribute.Invoke(); - - SceneManager.RestoreScene(); - SceneManager.ClearStoredScene(); - } + // Unload External Assemblies + AssemblyManager.Unload(); + + var active = Project.Active; + + DirectoryInfo temp = active.TempDirectory; + DirectoryInfo bin = new DirectoryInfo(Path.Combine(temp.FullName, "bin")); + DirectoryInfo debug = new DirectoryInfo(Path.Combine(bin.FullName, "Debug")); + + // Delete everything under Temp\Bin + if (bin.Exists) + Directory.Delete(bin.FullName, true); + bin.Create(); + + // Compile the Projects + Project.Compile(active.Assembly_Proj.FullName, debug); + Project.Compile(active.Editor_Assembly_Proj.FullName, debug); + + // Reload the External Assemblies + AssemblyManager.LoadExternalAssembly(active.Editor_Assembly_DLL.FullName, true); + AssemblyManager.LoadExternalAssembly(active.Assembly_DLL.FullName, true); } - else + catch (Exception e) + { + Debug.LogError($"Error reloading assemblies: {e.Message}"); + Debug.LogError($"{e.StackTrace}"); + } + finally { - Debug.LogError("Cannot reload assemblies, No project loaded."); + OnAssemblyLoadAttribute.Invoke(); + + SceneManager.RestoreScene(); + SceneManager.ClearStoredScene(); } } + else + { + Debug.LogError("Cannot reload assemblies, No project loaded."); + } } } diff --git a/Prowl.Editor/Project.cs b/Prowl.Editor/Project.cs index 0486265c7..37f630e42 100644 --- a/Prowl.Editor/Project.cs +++ b/Prowl.Editor/Project.cs @@ -43,7 +43,6 @@ public class Project #region Public Methods - internal void Refresh() { ProjectDirectory.Refresh(); @@ -96,7 +95,8 @@ public static bool Open(Project project) { if (!project.IsValid()) { - Runtime.Debug.LogError($"Invalid project '{project.Name}' at path '{project.ProjectPath}'. Validate that all core project directories are intact."); + Runtime.Debug.LogError( + $"Invalid project '{project.Name}' at path '{project.ProjectPath}'. Validate that all core project directories are intact."); return false; } @@ -115,7 +115,8 @@ public static bool Open(Project project) AssetDatabase.Update(false, true); // Ensure packages are all loaded in AssetDatabase.AddRootFolder("Assets"); - AssetDatabase.Update(true, true); // Not that all folders are in we can unload anything thats not in the project anymore since last session + AssetDatabase.Update(true, + true); // Not that all folders are in we can unload anything thats not in the project anymore since last session #warning TODO: Record last opened scene and try to open it SceneManager.InstantiateNewScene(); @@ -162,7 +163,7 @@ public bool IsValid() AssetDirectory.Refresh(); return ProjectDirectory.Exists && - AssetDirectory.Exists; + AssetDirectory.Exists; } @@ -182,7 +183,8 @@ private static void CreateDefaults(Project project) #warning TODO: Only copy if the file doesn't exist, or if somehow if the engine version is different or something... // Copy embedded defaults to rootFolder, this is just actual Files, so Image.png, not the asset variants - foreach (string file in Assembly.GetExecutingAssembly().GetManifestResourceNames().Where(x => x.StartsWith("Prowl.Editor.EmbeddedResources.DefaultAssets."))) + foreach (string file in Assembly.GetExecutingAssembly().GetManifestResourceNames() + .Where(x => x.StartsWith("Prowl.Editor.EmbeddedResources.DefaultAssets."))) { string[] nodes = file.Split('.'); string fileName = nodes[^2]; @@ -233,12 +235,14 @@ public static bool Compile(string csprojPath, DirectoryInfo output, bool isRelea // Default -> Windows processInfo.FileName = "cmd.exe"; - processInfo.Arguments = $"/c dotnet build \"{Path.GetFileName(csprojPath)}\"" + (isRelease ? " --configuration Release" : ""); + processInfo.Arguments = $"/c dotnet build \"{Path.GetFileName(csprojPath)}\"" + + (isRelease ? " --configuration Release" : ""); if (RuntimeUtils.IsMac() || RuntimeUtils.IsLinux()) { processInfo.FileName = "/bin/bash"; - processInfo.Arguments = $"-c \"dotnet build '{Path.GetFileName(csprojPath)}'\"" + (isRelease ? " --configuration Release" : ""); + processInfo.Arguments = $"-c \"dotnet build '{Path.GetFileName(csprojPath)}'\"" + + (isRelease ? " --configuration Release" : ""); } Process process = Process.Start(processInfo) ?? throw new Exception(); @@ -270,9 +274,11 @@ public static bool Compile(string csprojPath, DirectoryInfo output, bool isRelea int exitCode = process.ExitCode; process.Close(); - BoundedLog($"Exit Code: '{exitCode}'"); - - BoundedLog($"{(exitCode == 0 ? "Successfully" : "Failed to")} compile external assembly!"); + BoundedLog( + $""" + Exit Code: '{exitCode}' + {(exitCode == 0 ? "Successfully" : "Failed to")} compile external assembly! + """, exitCode == 0 ? LogSeverity.Success: LogSeverity.Error); return exitCode == 0; } @@ -281,7 +287,6 @@ public static bool Compile(string csprojPath, DirectoryInfo output, bool isRelea #region Private Methods - private static string GetIncludesFrom(IEnumerable filePaths) { List includeElements = new(); @@ -297,9 +302,11 @@ private static void GenerateCSProjectFiles(Project project, DirectoryInfo output if (!HasProject) throw new Exception("No Project Loaded, Cannot generate CS Project Files!"); Assembly[] loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies(); - Assembly gameEngineAssembly = loadedAssemblies.FirstOrDefault(assembly => assembly.GetName().Name == "Prowl.Runtime") + Assembly gameEngineAssembly = + loadedAssemblies.FirstOrDefault(assembly => assembly.GetName().Name == "Prowl.Runtime") ?? throw new Exception("Failed to find Prowl.Runtime Assembly!"); - Assembly gameEditorAssembly = loadedAssemblies.FirstOrDefault(assembly => assembly.GetName().Name == "Prowl.Editor") + Assembly gameEditorAssembly = + loadedAssemblies.FirstOrDefault(assembly => assembly.GetName().Name == "Prowl.Editor") ?? throw new Exception("Failed to find Prowl.Editor Assembly!"); // Get all references by Prowl.Runtime @@ -339,10 +346,10 @@ private static void GenerateCSProjectFiles(Project project, DirectoryInfo output "; string referencesXML = string.Join("\n", references.Select(assembly => - $"" + - $"{assembly.Location}" + - "false" + - "")); + $"" + + $"{assembly.Location}" + + "false" + + "")); string gameproj = @$" @@ -390,11 +397,17 @@ private static void GenerateCSProjectFiles(Project project, DirectoryInfo output Runtime.Debug.Log("Finished Updating Build Information"); } - private static void BoundedLog(string message) + public static void BoundedLog(string message, LogSeverity severity = LogSeverity.Normal) { - Runtime.Debug.Log("**********************************************************************************************************************"); - Runtime.Debug.Log(message); - Runtime.Debug.Log("**********************************************************************************************************************"); + Action logFunc = severity switch + { + LogSeverity.Success => Runtime.Debug.LogSuccess, + LogSeverity.Error => Runtime.Debug.LogError, + _ => Runtime.Debug.Log + }; + logFunc( + "**********************************************************************************************************************"); + logFunc(message); } #endregion diff --git a/Prowl.Runtime/Debug.cs b/Prowl.Runtime/Debug.cs index eba84d5b5..1c06da025 100644 --- a/Prowl.Runtime/Debug.cs +++ b/Prowl.Runtime/Debug.cs @@ -24,19 +24,18 @@ public enum LogSeverity public delegate void OnLog(string message, DebugStackTrace? stackTrace, LogSeverity logSeverity); -public record DebugStackFrame(int line, int column, string? fileName = null, MethodBase? methodBase = null) +public record DebugStackFrame(int Line, int Column, string? FileName = null, MethodBase? MethodBase = null) { public override string ToString() { - if (methodBase != null) - return $"In {methodBase.DeclaringType.Name}.{methodBase.Name} at {fileName}:{line}:{column}"; - else - return $"At {fileName}:{line}:{column}"; + return MethodBase != null + ? $"In {MethodBase.DeclaringType.Name}.{MethodBase.Name} at {FileName}:{Line}:{Column}" + : $"At {FileName}:{Line}:{Column}"; } } -public record DebugStackTrace(params DebugStackFrame[] stackFrames) +public record DebugStackTrace(params DebugStackFrame[] StackFrames) { public static explicit operator DebugStackTrace(StackTrace stackTrace) { @@ -56,8 +55,8 @@ public override string ToString() { StringBuilder sb = new(); - for (int i = 0; i < stackFrames.Length; i++) - sb.AppendLine($"\t{stackFrames[i]}"); + foreach (var stackFrame in StackFrames) + sb.AppendLine($"\t{stackFrame}"); return sb.ToString(); } @@ -206,12 +205,12 @@ public static (Mesh? wire, Mesh? solid) GetGizmoDrawData(bool cameraRelative, Ve public class GizmoBuilder { - private struct MeshData + private readonly struct MeshData { - public List s_vertices = []; - public List s_uvs = []; - public List s_colors = []; - public List s_indices = []; + public readonly List Vertices = []; + public readonly List Uvs = []; + public readonly List Colors = []; + public readonly List Indices = []; public MeshData() { @@ -219,27 +218,27 @@ public MeshData() public readonly void Clear() { - s_vertices.Clear(); - s_uvs.Clear(); - s_colors.Clear(); - s_indices.Clear(); + Vertices.Clear(); + Uvs.Clear(); + Colors.Clear(); + Indices.Clear(); } } - private MeshData _wireData = new(); - private MeshData _solidData = new(); + private readonly MeshData _wireData = new(); + private readonly MeshData _solidData = new(); private Mesh? _wire; private Mesh? _solid; public struct IconDrawCall { - public Texture2D texture; - public Vector3 center; - public float scale; - public Color color; + public Texture2D Texture; + public Vector3 Center; + public float Scale; + public Color Color; } - private List _icons = []; + private readonly List _icons = []; public void Clear() @@ -255,36 +254,36 @@ public void Clear() private void AddLine(Vector3 a, Vector3 b, Color color) { - int index = _wireData.s_vertices.Count; - _wireData.s_vertices.Add(a); - _wireData.s_vertices.Add(b); + int index = _wireData.Vertices.Count; + _wireData.Vertices.Add(a); + _wireData.Vertices.Add(b); - _wireData.s_colors.Add(color); - _wireData.s_colors.Add(color); + _wireData.Colors.Add(color); + _wireData.Colors.Add(color); - _wireData.s_indices.Add(index); - _wireData.s_indices.Add(index + 1); + _wireData.Indices.Add(index); + _wireData.Indices.Add(index + 1); } - private void AddTriangle(Vector3 a, Vector3 b, Vector3 c, Vector2 a_uv, Vector2 b_uv, Vector2 c_uv, Color color) + private void AddTriangle(Vector3 a, Vector3 b, Vector3 c, Vector2 aUv, Vector2 bUv, Vector2 cUv, Color color) { - int index = _solidData.s_vertices.Count; + int index = _solidData.Vertices.Count; - _solidData.s_vertices.Add(a); - _solidData.s_vertices.Add(b); - _solidData.s_vertices.Add(c); + _solidData.Vertices.Add(a); + _solidData.Vertices.Add(b); + _solidData.Vertices.Add(c); - _solidData.s_uvs.Add(a_uv); - _solidData.s_uvs.Add(b_uv); - _solidData.s_uvs.Add(c_uv); + _solidData.Uvs.Add(aUv); + _solidData.Uvs.Add(bUv); + _solidData.Uvs.Add(cUv); - _solidData.s_colors.Add(color); - _solidData.s_colors.Add(color); - _solidData.s_colors.Add(color); + _solidData.Colors.Add(color); + _solidData.Colors.Add(color); + _solidData.Colors.Add(color); - _solidData.s_indices.Add(index); - _solidData.s_indices.Add(index + 1); - _solidData.s_indices.Add(index + 2); + _solidData.Indices.Add(index); + _solidData.Indices.Add(index + 1); + _solidData.Indices.Add(index + 2); } public void DrawLine(Vector3 start, Vector3 end, Color color) => AddLine(start, end, color); @@ -489,7 +488,7 @@ public void DrawWireCone(Vector3 start, Vector3 direction, float radius, Color c private Vector3 GetPerpendicularVector(Vector3 v) { - Vector3 result = Vector3.right; + Vector3 result; if (Math.Abs(v.x) > 0.1f) result = new Vector3(v.y, -v.x, 0); else if (Math.Abs(v.y) > 0.1f) @@ -509,7 +508,7 @@ public void DrawArrow(Vector3 start, Vector3 direction, Color color) } - public void DrawIcon(Texture2D icon, Vector3 center, float scale, Color color) => _icons.Add(new IconDrawCall { texture = icon, center = center, scale = scale, color = color }); + public void DrawIcon(Texture2D icon, Vector3 center, float scale, Color color) => _icons.Add(new IconDrawCall { Texture = icon, Center = center, Scale = scale, Color = color }); public void DrawText(Font font, string text, Vector3 position, Color color) // TODO: Try to share the same code from UI rendering to avoid duplicate Text rendering code { @@ -518,7 +517,7 @@ public void DrawText(Font font, string text, Vector3 position, Color color) // T public (Mesh? wire, Mesh? solid) UpdateMesh(bool cameraRelative, Vector3 cameraPosition) { - bool hasWire = _wireData.s_vertices.Count > 0; + bool hasWire = _wireData.Vertices.Count > 0; if (hasWire) { _wire ??= new() @@ -527,25 +526,25 @@ public void DrawText(Font font, string text, Vector3 position, Color color) // T IndexFormat = IndexFormat.UInt16, }; - _wire.Vertices = [.. _wireData.s_vertices]; - _wire.Colors = [.. _wireData.s_colors]; - _wire.Indices16 = _wireData.s_indices.Select(i => (ushort)i).ToArray(); + _wire.Vertices = [.. _wireData.Vertices]; + _wire.Colors = [.. _wireData.Colors]; + _wire.Indices16 = _wireData.Indices.Select(i => (ushort)i).ToArray(); if (cameraRelative) { // Convert vertices to be relative to the camera - System.Numerics.Vector3[] vertices = new System.Numerics.Vector3[_wireData.s_vertices.Count]; - for (int i = 0; i < _wireData.s_vertices.Count; i++) - vertices[i] = _wireData.s_vertices[i] - cameraPosition; + System.Numerics.Vector3[] vertices = new System.Numerics.Vector3[_wireData.Vertices.Count]; + for (int i = 0; i < _wireData.Vertices.Count; i++) + vertices[i] = _wireData.Vertices[i] - cameraPosition; _wire.Vertices = vertices; } else { - _wire.Vertices = [.. _wireData.s_vertices]; + _wire.Vertices = [.. _wireData.Vertices]; } } - bool hasSolid = _solidData.s_vertices.Count > 0; + bool hasSolid = _solidData.Vertices.Count > 0; if (hasSolid) { _solid ??= new() @@ -557,19 +556,19 @@ public void DrawText(Font font, string text, Vector3 position, Color color) // T if (cameraRelative) { // Convert vertices to be relative to the camera - System.Numerics.Vector3[] vertices2 = new System.Numerics.Vector3[_solidData.s_vertices.Count]; - for (int i = 0; i < _solidData.s_vertices.Count; i++) - vertices2[i] = _solidData.s_vertices[i] - cameraPosition; + System.Numerics.Vector3[] vertices2 = new System.Numerics.Vector3[_solidData.Vertices.Count]; + for (int i = 0; i < _solidData.Vertices.Count; i++) + vertices2[i] = _solidData.Vertices[i] - cameraPosition; _solid.Vertices = vertices2; } else { - _solid.Vertices = [.. _solidData.s_vertices]; + _solid.Vertices = [.. _solidData.Vertices]; } - _solid.Colors = [.. _solidData.s_colors]; - _solid.UV = [.. _solidData.s_uvs]; - _solid.Indices16 = _solidData.s_indices.Select(i => (ushort)i).ToArray(); + _solid.Colors = [.. _solidData.Colors]; + _solid.UV = [.. _solidData.Uvs]; + _solid.Indices16 = _solidData.Indices.Select(i => (ushort)i).ToArray(); } return ( @@ -577,7 +576,7 @@ public void DrawText(Font font, string text, Vector3 position, Color color) // T hasSolid ? _solid : null ); } - + public List GetIcons() { return _icons; diff --git a/Prowl.Runtime/Utils/ScriptableSingleton.cs b/Prowl.Runtime/Utils/ScriptableSingleton.cs index 461f28998..e7a849691 100644 --- a/Prowl.Runtime/Utils/ScriptableSingleton.cs +++ b/Prowl.Runtime/Utils/ScriptableSingleton.cs @@ -72,7 +72,7 @@ protected string GetFilePath(string? dataPath) ArgumentNullException.ThrowIfNull(dataPath); // Persistent across sessions for a single project - if (Application.IsEditor == false) + if (Application.IsRunning && Application.IsEditor == false) throw new InvalidOperationException("Editor Settings are only available in the editor"); directory = Path.Combine(dataPath, "ProjectSettings", "Editor"); break;