diff --git a/GlitchyEditor/src/Editor.bf b/GlitchyEditor/src/Editor.bf index 0878b86..71cdb91 100644 --- a/GlitchyEditor/src/Editor.bf +++ b/GlitchyEditor/src/Editor.bf @@ -7,6 +7,9 @@ using GlitchyEditor.EditWindows; using GlitchyEngine; using GlitchyEditor.Assets; using GlitchyEditor.CodeEditors; +using GlitchyEditor.Settings; +using GlitchyEditor.Multithreading; +using GlitchyEngine.Scripting; namespace GlitchyEditor { @@ -227,5 +230,20 @@ namespace GlitchyEditor IdeAdapter.OpenScriptProject(); } } + + /// Rebuilds the C# scripts. + public BuildScriptsBackgroundTask BuildScripts() + { + BuildScriptsBackgroundTask task = new .(EditorApp.Instance.Settings.ScriptSettings.DotNetExecutablePath, EditorApp.Instance.EditorLayer.CurrentProject, false){ DeleteWhenEnded = true }; + + EditorApp.Instance.BackgroundTaskManager.StartBackgroundTask(task); + + return task; + } + + public void ReloadScripts() + { + ScriptEngine.ReloadAssemblies(retainScriptData: true); + } } } diff --git a/GlitchyEditor/src/EditorLayer.bf b/GlitchyEditor/src/EditorLayer.bf index 756aec8..2dc11e4 100644 --- a/GlitchyEditor/src/EditorLayer.bf +++ b/GlitchyEditor/src/EditorLayer.bf @@ -820,10 +820,26 @@ namespace GlitchyEditor append ScriptInstanceSerializer _scriptSerializer = .(); /// Starts the play mode for the current scene - private void OnScenePlay(SceneState newSceneState) + private void OnScenePlay(SceneState newSceneState, bool allowRebuild = true) { Debug.Profiler.ProfileFunction!(); + if (allowRebuild && EditorApp.Instance.Settings.ScriptSettings.RebuildOnPlay) + { + var task = _editor.BuildScripts(); + task.ContinueWith(new (t) => { + // Continuations run in worker threads, invoke play on main thread! + EditorApp.Instance.InvokeOnMainThread(new () => { + // Remember not to rebuild this time! + OnScenePlay(newSceneState, allowRebuild: false); + return true; + }); + }, .RunOnFinished); + + // Don't play just yet, the continuation will do that. + return; + } + Runtime.Assert(newSceneState == .Play || newSceneState == .Simulate, "newSceneState must either be Play or Simulate"); Log.EngineLogger.AssertDebug(_scriptSerializer.SerializedObjectCount == 0, "Somehow some entities are serialized."); @@ -1520,8 +1536,15 @@ namespace GlitchyEditor if(ImGui.BeginMenu("Tools", true)) { - if(ImGui.MenuItem("Reload Scripts")) - ScriptEngine.ReloadAssemblies(retainScriptData: true); + if(ImGui.MenuItem("Build Scripts", "Ctrl+B")) + _editor.BuildScripts(); + + ImGui.AttachTooltip("Compiles the C# script project."); + + if(ImGui.MenuItem("Reload Scripts", "Ctrl+Shift+R")) + _editor.ReloadScripts(); + + ImGui.AttachTooltip("Unloads and reloads the compiled scripts."); #if GE_EDITOR_IMGUI_DEMO ImGui.Checkbox("Show ImGui Demo", &_showImguiDemoWindow); @@ -1624,6 +1647,17 @@ namespace GlitchyEditor // "Save Scene" SaveCurrentScene(); + return true; + case .B: + // "Compile scripts" + _editor.BuildScripts(); + + return true; + case .R: + if (shift) + // "Compile scripts" + _editor.ReloadScripts(); + return true; default: } diff --git a/GlitchyEditor/src/Multithreading/BackgroundTask.bf b/GlitchyEditor/src/Multithreading/BackgroundTask.bf index 2b7fd2e..4db0047 100644 --- a/GlitchyEditor/src/Multithreading/BackgroundTask.bf +++ b/GlitchyEditor/src/Multithreading/BackgroundTask.bf @@ -17,6 +17,15 @@ abstract class BackgroundTask internal BackgroundTaskManager _taskManager; + public delegate void Continuation(BackgroundTask); + + internal append List<(TaskContinuationOptions ContinuationOptions, Continuation Continuation)> _continuations = .() ~ { + for (let entry in _) + { + delete entry.Continuation; + } + }; + public enum RunState { Ready, @@ -24,7 +33,8 @@ abstract class BackgroundTask Paused, Running, Aborted, - Finished + Finished, + Failed } public enum RunResult @@ -34,9 +44,11 @@ abstract class BackgroundTask /// The task gives up it's runtime, because it cannot continue without user intervention through the UI. Pause, /// The task finished and wont run again. - Finished, + Finish, /// The operation was aborted, the task will not run again. - Abort + Abort, + /// The task failed and wont run again. + Fail } public RunState State { get; internal set; } = .Ready; @@ -46,9 +58,10 @@ abstract class BackgroundTask public bool Blocked => State == .Blocked; public bool Running => State == .Running; public bool Aborted => State == .Aborted; + public bool Failed => State == .Failed; public bool Finished => State == .Finished; - public bool Ended => Finished || Aborted; + public bool Ended => Finished || Aborted || Failed; public bool DeleteWhenEnded { get; set; } @@ -70,4 +83,36 @@ abstract class BackgroundTask { _taskManager.AbortTask(this); } + + public enum TaskContinuationOptions + { + /// Same as Default + None = 0, + + RunOnFinished = 1, + RunOnFailed = 2, + RunOnAborted = 4, + + Default = RunOnFinished | RunOnFailed | RunOnAborted + } + + /// Creates a continuation that executes when the target Task completes. + public void ContinueWith(Continuation ownContinuation, TaskContinuationOptions continuationOptions = .Default) + { + var continuationOptions; + + if (continuationOptions == .None) + { + continuationOptions = .Default; + } + + if (Ended) + { + ownContinuation(this); + } + else + { + _continuations.Add((continuationOptions, ownContinuation)); + } + } } diff --git a/GlitchyEditor/src/Multithreading/BackgroundTaskList.bf b/GlitchyEditor/src/Multithreading/BackgroundTaskList.bf index c535e09..1190e53 100644 --- a/GlitchyEditor/src/Multithreading/BackgroundTaskList.bf +++ b/GlitchyEditor/src/Multithreading/BackgroundTaskList.bf @@ -9,7 +9,7 @@ class BackgroundTaskList { BackgroundTask _sentinel = new BackgroundTask() { - public override BackgroundTask.RunResult Run() => .Finished; + public override BackgroundTask.RunResult Run() => .Finish; public override void OnRenderPopup() { } _next = _, _previous = _, diff --git a/GlitchyEditor/src/Multithreading/BackgroundTaskManager.bf b/GlitchyEditor/src/Multithreading/BackgroundTaskManager.bf index 386a68e..377563f 100644 --- a/GlitchyEditor/src/Multithreading/BackgroundTaskManager.bf +++ b/GlitchyEditor/src/Multithreading/BackgroundTaskManager.bf @@ -59,7 +59,7 @@ class BackgroundTaskManager if (_deferredStateChanges.TryGetValue(task, let desiredState)) { // If the task already finished or aborted by itself, we don't care about the desired state, because it no longer applies. - if (runResult != .Finished && runResult != .Abort) + if (runResult != .Finish && runResult != .Abort && runResult != .Fail) { switch (desiredState) { @@ -74,7 +74,9 @@ class BackgroundTaskManager _deferredStateChanges.Remove(task); } - + + BackgroundTask.TaskContinuationOptions endStateFlag = .None; + switch (runResult) { case .Continue: @@ -89,15 +91,27 @@ class BackgroundTaskManager { _waitingTasks.AddLast(task); } - case .Finished: + case .Finish: task.State = .Finished; - - if (task.DeleteWhenEnded) - { - delete task; - } + endStateFlag = .RunOnFinished; case .Abort: task.State = .Aborted; + endStateFlag = .RunOnAborted; + case .Fail: + task.State = .Failed; + endStateFlag = .RunOnFailed; + } + + if (task.Ended) + { + // Run the appropriate continuations + for (let (options, continuation) in task._continuations) + { + if (options.HasFlag(endStateFlag)) + { + continuation(task); + } + } if (task.DeleteWhenEnded) { diff --git a/GlitchyEditor/src/Multithreading/BuildScriptsBackgroundTask.bf b/GlitchyEditor/src/Multithreading/BuildScriptsBackgroundTask.bf new file mode 100644 index 0000000..6984b77 --- /dev/null +++ b/GlitchyEditor/src/Multithreading/BuildScriptsBackgroundTask.bf @@ -0,0 +1,131 @@ +using GlitchyEngine; +using System.Collections; +using GlitchyEditor.CodeEditors; +using System.IO; +using System; +using System.Diagnostics; +using ImGui; +using GlitchyEditor.Multithreading; +using GlitchyEngine.Scripting; +using GlitchyEditor.Settings; +using System.Threading; + +namespace GlitchyEditor.Multithreading; + +class BuildScriptsBackgroundTask : BackgroundTask +{ + private bool _isAborting = false; + + private Project _project; + private bool _releaseBuild; + private StringView _dotnetPath; + + private String _buildLog = new .() ~ delete _; + + private SpawnedProcess _process ~ delete _; + + public this(StringView dotnetPath, Project project, bool releaseBuild) + { + _dotnetPath = dotnetPath; + _project = project; + _releaseBuild = releaseBuild; + } + + public override RunResult Run() + { + return BuildScripts(); + } + + public override void OnRenderPopup() + { + if (ImGui.Begin(!_isAborting ? "Compiling scripts..." : "Aborting compilation...", null, .NoDocking | .NoCollapse | .Modal | .NoResize | .AlwaysAutoResize)) + { + if (ImGui.BeginChild("##BuildLog", .(550, ImGui.GetTextLineHeightWithSpacing() * 6), .None, .HorizontalScrollbar)) + { + ImGui.TextUnformatted(_buildLog); + } + + if (ImGui.GetScrollY() >= ImGui.GetScrollMaxY()) + ImGui.SetScrollHereY(1.0f); + + ImGui.EndChild(); + + if (ImGui.Button("Cancel")) + { + _isAborting = true; + + _process.Kill(); + } + + ImGui.End(); + } + } + + private RunResult BuildScripts() + { + RunResult result; + + Log.EngineLogger.Info($"Building Scripts ({(_releaseBuild ? "Release" : "Debug")})..."); + + String exeFile = scope .(); + Environment.GetExecutableFilePath(exeFile); + + String exeDirectory = scope .(); + Path.GetDirectoryPath(exeFile, exeDirectory); + + ProcessStartInfo processInfo = scope .(); + processInfo.UseShellExecute = false; + processInfo.RedirectStandardOutput = true; + processInfo.CreateNoWindow = true; + processInfo.SetWorkingDirectory(_project.WorkspacePath); + + processInfo.SetFileName(EditorApp.Instance.Settings.ScriptSettings.DotNetExecutablePath); + + // -sc|--self-contained... Publish the .NET runtime with your application so the runtime doesn't need to be installed on the target machine. + // -> This might be useful to build the final game + // -o|--ouput ... might be useful for building the final game + processInfo.SetArguments(scope $"build -c {(_releaseBuild ? "Release" : "Debug")}"); + + _process = new SpawnedProcess(); + + if (_process.Start(processInfo) case .Ok) + { + FileStream outputStream = scope FileStream(); + _process.AttachStandardOutput(outputStream); + + StreamReader outputReader = scope .(outputStream); + + while (!_process.HasExited) + { + outputReader.ReadToEnd(_buildLog).IgnoreError(); + } + + _process.WaitFor(-1); + + if (_isAborting) + { + Log.EngineLogger.Warning($"Build was aborted:\n{_buildLog}"); + result = .Abort; + } + else if (_process.ExitCode == 0) + { + Log.EngineLogger.Info($"Scripts compiled successfully:\n{_buildLog}"); + result = .Finish; + } + else + { + Log.EngineLogger.Error($"Script compilation failed:\n{_buildLog}"); + result = .Fail; + } + + DeleteAndNullify!(_process); + } + else + { + Log.EngineLogger.Error("Failed to execute `dotnet build`"); + result = .Fail; + } + + return result; + } +} diff --git a/GlitchyEditor/src/Multithreading/CopyBackgroundTask.bf b/GlitchyEditor/src/Multithreading/CopyBackgroundTask.bf index 7c2e5b5..286d47b 100644 --- a/GlitchyEditor/src/Multithreading/CopyBackgroundTask.bf +++ b/GlitchyEditor/src/Multithreading/CopyBackgroundTask.bf @@ -249,7 +249,7 @@ class CopyBackgroundTask : BackgroundTask delete currentPath; } - return .Finished; + return .Finish; } private Result CopyPath(CopyInfo copyInfo) diff --git a/GlitchyEditor/src/Multithreading/DeleteBackgroundTask.bf b/GlitchyEditor/src/Multithreading/DeleteBackgroundTask.bf index 5d73236..6eec1de 100644 --- a/GlitchyEditor/src/Multithreading/DeleteBackgroundTask.bf +++ b/GlitchyEditor/src/Multithreading/DeleteBackgroundTask.bf @@ -141,7 +141,7 @@ class DeleteBackgroundTask : BackgroundTask delete currentPath; } - return .Finished; + return .Finish; } private Result DeletePath(StringView deletePath) diff --git a/GlitchyEditor/src/Multithreading/MoveFilesBackgroundTask.bf b/GlitchyEditor/src/Multithreading/MoveFilesBackgroundTask.bf index 84dcc8c..35bb199 100644 --- a/GlitchyEditor/src/Multithreading/MoveFilesBackgroundTask.bf +++ b/GlitchyEditor/src/Multithreading/MoveFilesBackgroundTask.bf @@ -252,7 +252,7 @@ class MoveFilesBackgroundTask : BackgroundTask delete currentPath; } - return .Finished; + return .Finish; } private Result MovePath(CopyInfo copyInfo) diff --git a/GlitchyEditor/src/Multithreading/TestDotnetBackgroundTask.bf b/GlitchyEditor/src/Multithreading/TestDotnetBackgroundTask.bf new file mode 100644 index 0000000..884d298 --- /dev/null +++ b/GlitchyEditor/src/Multithreading/TestDotnetBackgroundTask.bf @@ -0,0 +1,267 @@ +using GlitchyEngine; +using System.Collections; +using GlitchyEditor.CodeEditors; +using System.IO; +using System; +using System.Diagnostics; +using ImGui; +using GlitchyEditor.Multithreading; +using GlitchyEngine.Scripting; +using GlitchyEditor.Settings; + +namespace GlitchyEditor.Multithreading; + +class TestDotnetBackgroundTask : BackgroundTask +{ + private ScriptSettings _settings; + private Project _project; + + public enum TestResult + { + None, + Ok, + Timeout, + FailedToExecute, + NoSdksFound, + TooOld + } + + private TestResult _testResult; + + public TestResult TestResult => _testResult; + + public this(ScriptSettings settings, Project project) + { + _settings = settings; + _project = project; + } + + public override RunResult Run() + { + if (QueryAvailableSdks() case .Err) + { + return .Fail; + } + + if (CheckMinimumVersion() case .Err) + { + return .Fail; + } + + if (_project != null) + { + CheckProjectVersion(); + } + + _testResult = .Ok; + + return .Finish; + } + + public override void OnRenderPopup() + { + if (ImGui.Begin("Testing .Net Installation...", null, .NoDocking | .NoCollapse | .Modal | .NoResize | .AlwaysAutoResize)) + { + ImGui.Text("..."); + + ImGui.End(); + } + } + + private Result QueryAvailableSdks() + { + _settings.DotNetSdks.Clear(); + + Log.EngineLogger.Info("Listing .Net SDKs..."); + + String exeFile = scope .(); + Environment.GetExecutableFilePath(exeFile); + + String exeDirectory = scope .(); + Path.GetDirectoryPath(exeFile, exeDirectory); + + ProcessStartInfo processInfo = scope .(); + processInfo.UseShellExecute = false; + processInfo.RedirectStandardOutput = true; + processInfo.CreateNoWindow = true; + + processInfo.SetFileName(_settings.DotNetExecutablePath); + // Lists all installed sdks + StringView argumentList = "--list-sdks"; + processInfo.SetArguments(argumentList); + + let process = scope SpawnedProcess(); + + if (process.Start(processInfo) case .Ok) + { + FileStream outputStream = scope FileStream(); + process.AttachStandardOutput(outputStream); + + if (!process.WaitFor(1000)) + { + Log.EngineLogger.Error($"`dotnet --list-sdks` timed out."); + process.Kill(); + _testResult = .Timeout; + return .Err; + } + + StreamReader sr = scope .(outputStream); + + String line = scope .(256); + while (sr.ReadLine(line..Clear()) case .Ok) + { + Log.EngineLogger.Info($"Detecting .Net SDK version from line: {line}"); + // A line for a normal verion looks like this: + // 10.0.402 [C:\Program Files\dotnet\sdk] + // A line for a snapshot looks like this: + // 9.0.100-preview.7.24407.12 [C:\Program Files\dotnet\sdk] + // A line for a release candidate looks like this: + // 11.0.100-rc.2.26504.105 [C:\Program Files\dotnet\sdk] + + // We must have a number at the start so we can at least grab a major version. + if (!line[0].IsDigit) + { + Log.EngineLogger.Error("This line doesn't appear to show an SDK version."); + continue; + } + + DotNetVersion detectedVersion = default; + + int indexOfSpace = line.IndexOf(' '); + int indexOfDash = line.IndexOf('-'); + + int versionNumberEnd = line.Length; + + if (indexOfDash > 0) + versionNumberEnd = indexOfDash; + else if (indexOfSpace > 0) + versionNumberEnd = indexOfSpace; + + StringView versionString = line.Substring(0, versionNumberEnd); + + List versionSplits = scope .(versionString.Split('.')); + + if (versionSplits.IsEmpty) + { + Log.EngineLogger.Error("Line doesn't contain a valid version number."); + continue; + } + + if (versionSplits.Count >= 1 && int.Parse(versionSplits[0]) not case .Ok(out detectedVersion.Major)) + { + Log.EngineLogger.Error($"Failed to parse \"major\" part of version ({versionSplits[0]})."); + continue; + } + + if (versionSplits.Count >= 2 && int.Parse(versionSplits[1]) not case .Ok(out detectedVersion.Minor)) + { + Log.EngineLogger.Error($"Failed to parse \"minor\" part of version ({versionSplits[1]})."); + continue; + } + + if (versionSplits.Count >= 3 && int.Parse(versionSplits[2]) not case .Ok(out detectedVersion.Patch)) + { + Log.EngineLogger.Error($"Failed to parse \"patch\" part of version ({versionSplits[2]})."); + continue; + } + + if (indexOfDash > 0) + { + StringView previewString = line.Substring(indexOfDash + 1); + + int previewNumberStart = previewString.IndexOf('.') + 1; + int secondDot = previewString.IndexOf('.', previewNumberStart); + + int previewNumber = 0; + + if (previewNumberStart > 0) + { + int length = previewString.Length - previewNumberStart; + + if (secondDot > 0) + { + length = secondDot - previewNumberStart; + } + + StringView previewNumberString = previewString.Substring(previewNumberStart, length); + + if (int.Parse(previewNumberString) not case .Ok(out previewNumber)) + { + Log.EngineLogger.Warning("Could not determine the preview number."); + } + } + + if (previewString.StartsWith("preview")) + { + detectedVersion.Type = .Preview(previewNumber); + } + else if (previewString.StartsWith("rc")) + { + detectedVersion.Type = .ReleaseCandidate(previewNumber); + } + else + { + detectedVersion.Type = .Unknown; + Log.EngineLogger.Warning("Could not determine the preview-type for SDK."); + } + } + else + { + detectedVersion.Type = .Normal; + } + + Log.EngineLogger.Info($"Detected .Net SDK: {detectedVersion}."); + + _settings.DotNetSdks.Add(detectedVersion); + } + } + else + { + Log.EngineLogger.Error($"Failed to execute `{_settings.DotNetExecutablePath} {argumentList}`"); + _testResult = .FailedToExecute; + return .Err; + } + + if (_settings.DotNetSdks.Count == 0) + { + _testResult = .NoSdksFound; + return .Err; + } + + return .Ok; + } + + /// Checks if one of the detected SDKs is at least the minimum required dotnet version. + private Result CheckMinimumVersion() + { + DotNetVersion maxVersion = _settings.GetLatestInstalledDotNetVersion(); + + if (maxVersion < ScriptEngine.MinDotNetVersion) + { + Log.EngineLogger.Error($"The latest detected .Net SDK ({maxVersion}) is older than the required minimum Version ({ScriptEngine.MinDotNetVersion}). Please install a newer .Net SDK or configure the dotnet executable in the settings."); + _testResult = .TooOld; + return .Err; + } + else + { + Log.EngineLogger.Info($"Latest detected .Net SDK: {maxVersion}."); + + return .Ok; + } + } + + /// Checks if one of the detected SDKs is at least the minimum required dotnet version for the current project. + void CheckProjectVersion() + { + /*DotNetVersion maxVersion = GetMaxVersion(); + + if (maxVersion < ScriptEngine.MinDotNetVersion) + { + Log.EngineLogger.Error($"The latest detected .Net SDK ({maxVersion}) is older than the required minimum Version ({ScriptEngine.MinDotNetVersion}). Please install a newer .Net SDK or configure the dotnet executable in the settings."); + } + else + { + Log.EngineLogger.Info($"Latest detected .Net SDK: {maxVersion}."); + }*/ + } +} diff --git a/GlitchyEditor/src/Settings/DetectIDEsBackgroundTask.bf b/GlitchyEditor/src/Settings/DetectIDEsBackgroundTask.bf index 76c0f1d..5536255 100644 --- a/GlitchyEditor/src/Settings/DetectIDEsBackgroundTask.bf +++ b/GlitchyEditor/src/Settings/DetectIDEsBackgroundTask.bf @@ -51,7 +51,7 @@ class DetectIDEsBackgroundTask : BackgroundTask _stage = .SearchRider; FindRider(); - return .Finished; + return .Finish; } public override void OnRenderPopup() diff --git a/GlitchyEditor/src/Settings/Settings.bf b/GlitchyEditor/src/Settings/Settings.bf index 4021d70..717cf73 100644 --- a/GlitchyEditor/src/Settings/Settings.bf +++ b/GlitchyEditor/src/Settings/Settings.bf @@ -6,6 +6,9 @@ using Bon; using GlitchyEngine; using System.Threading; using System.Linq; +using GlitchyEngine.Scripting; +using GlitchyEditor.Multithreading; +using GlitchyEngine.Math; using static GlitchyEditor.SettingsWindow; @@ -239,6 +242,19 @@ class IdeInstallation [Reflect] class ScriptSettings { + private List _dotNetSdks = new .() ~ delete _; + public List DotNetSdks => _dotNetSdks; + + [BonInclude] + private String _dotnetExePath = new .("dotnet") ~ delete _; + private TestDotnetBackgroundTask.TestResult _dotnetTestResult = .None; + + public StringView DotNetExecutablePath + { + get => _dotnetExePath; + set => String.NewOrSet!(_dotnetExePath, value); + } + [BonInclude] private List _ideInstallations = new .() ~ DeleteContainerAndItems!(_); @@ -254,6 +270,13 @@ class ScriptSettings private bool _lookedForVs = false; private bool _lookedForRider = false; + + [Setting("Tools", "Rebuild scripts on play", "If active, the scripts will be rebuilt every time the user presses play."), BonInclude] + public bool RebuildOnPlay = true; + + [Setting("Tools", "Automatically rebuild changed scripts", "If active, the scripts will be rebuilt every time the user presses play."), BonInclude] + public bool AutoRebuildOnChange = true; + /// Enters the lock and gets the list of Ide installations. public Monitor.MonitorLockInstance EnterAndGetIdeInstallations(out List ideInstallations) { @@ -275,6 +298,18 @@ class ScriptSettings } } } + + if (_dotNetSdks.Count == 0) + { + TestDotnetInstallation(); + } + } + + private TestDotnetBackgroundTask TestDotnetInstallation() + { + TestDotnetBackgroundTask task = new TestDotnetBackgroundTask(this, null){ DeleteWhenEnded = true }; + EditorApp.Instance.BackgroundTaskManager.StartBackgroundTask(task); + return task; } [CustomSettingRenderer("Tools")] @@ -282,8 +317,17 @@ class ScriptSettings { bool settingsChanged = false; - ImGui.Text("IDE:"); + ImGui.Text("IDE"); + + ImGui.SameLine(ImGui.GetContentRegionAvail().x - ImGui.CalcTextSize("Detect IDEs...").x - 2 * ImGui.GetStyle().FramePadding.x); + if (ImGui.Button("Detect IDEs...")) + { + EditorApp.Instance.BackgroundTaskManager.StartBackgroundTask(new DetectIDEsBackgroundTask(this){ DeleteWhenEnded = true }); + } + + ImGui.TableNextColumn(); + if (ImGui.BeginCombo("##ide", ActiveIde?.Name.ToScopeCStr!())) { using (EnterAndGetIdeInstallations(let installations)) @@ -327,13 +371,71 @@ class ScriptSettings ImGui.EndCombo(); } - if (ImGui.Button("Detect IDEs...")) + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + + ImGui.Text("DotNet Executable Path"); + + ImGui.SameLine(ImGui.GetContentRegionAvail().x - ImGui.CalcTextSize("Test").x - 2 * ImGui.GetStyle().FramePadding.x); + + if (ImGui.Button("Test")) { - EditorApp.Instance.BackgroundTaskManager.StartBackgroundTask(new DetectIDEsBackgroundTask(this){ DeleteWhenEnded = true }); + _dotnetTestResult = .None; + TestDotnetInstallation().ContinueWith(new (task) => { + _dotnetTestResult = ((TestDotnetBackgroundTask)task).TestResult; + }); + } + + ImGui.TableNextColumn(); + + if (ImGui.InputText("##dotnetPath", _dotnetExePath)) + { + settingsChanged = true; + } + + switch (_dotnetTestResult) + { + case .None: + // Nothing to do. + case .Ok: + ImGui.PushStyleColor(.Text, ColorRGBA.Green); + ImGui.TextUnformatted(scope $"Dotnet works and found latest version: {GetLatestInstalledDotNetVersion()}"); + ImGui.PopStyleColor(); + case .Timeout: + ImGui.PushStyleColor(.Text, ColorRGBA.Red); + ImGui.TextUnformatted(scope $"Dotnet timed out."); + ImGui.PopStyleColor(); + case .FailedToExecute: + ImGui.PushStyleColor(.Text, ColorRGBA.Red); + ImGui.TextUnformatted(scope $"Failed to execute dotnet."); + ImGui.PopStyleColor(); + case .NoSdksFound: + ImGui.PushStyleColor(.Text, ColorRGBA.Red); + ImGui.TextUnformatted(scope $"Dotnet works but no SDK found."); + ImGui.PopStyleColor(); + case .TooOld: + ImGui.PushStyleColor(.Text, ColorRGBA.Red); + ImGui.TextUnformatted(scope $"Dotnet works but latest installed version {GetLatestInstalledDotNetVersion()} is too old."); + ImGui.PopStyleColor(); } return settingsChanged; } + + public DotNetVersion GetLatestInstalledDotNetVersion() + { + DotNetVersion maxVersion = .(-1, -1, -1, .Unknown); + + for (DotNetVersion version in _dotNetSdks) + { + if (version > maxVersion) + { + maxVersion = version; + } + } + + return maxVersion; + } } [Reflect] diff --git a/GlitchyEditor/src/Settings/SettingsWindow.bf b/GlitchyEditor/src/Settings/SettingsWindow.bf index d727159..1db44e3 100644 --- a/GlitchyEditor/src/Settings/SettingsWindow.bf +++ b/GlitchyEditor/src/Settings/SettingsWindow.bf @@ -420,6 +420,9 @@ namespace GlitchyEditor for (let methodBinding in category._methodBindings) { + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + if (methodBinding.Method.Invoke(methodBinding.Method.IsStatic ? null : methodBinding.SettingsObject, category) case .Ok(var returnValue)) { _settingsChanged |= returnValue.Get(); diff --git a/GlitchyEngine/src/Scripting/DotNetVersion.bf b/GlitchyEngine/src/Scripting/DotNetVersion.bf new file mode 100644 index 0000000..06ea287 --- /dev/null +++ b/GlitchyEngine/src/Scripting/DotNetVersion.bf @@ -0,0 +1,87 @@ +using System; +namespace GlitchyEngine.Scripting; + +enum DotNetVersionType +{ + case Normal; + case Preview(int Number); + case ReleaseCandidate(int Number); + case Unknown; + + public int GetSortingOrder() + { + switch (this) + { + case .Normal: + return 3; + case .ReleaseCandidate: + return 2; + case .Preview: + return 1; + case .Unknown: + return 0; + } + } +} + +struct DotNetVersion +{ + public int Major; + public int Minor; + public int Patch; + + public DotNetVersionType Type; + + public this() + { + this = default; + } + + public this(int major, int minor, int patch, DotNetVersionType type) + { + Major = major; + Minor = minor; + Patch = patch; + Type = type; + } + + public override void ToString(String outBuffer) + { + outBuffer.AppendF($"{Major}.{Minor}.{Patch}"); + + switch (Type) + { + case .Normal: + // Nothing to do. + case .Preview(int Number): + outBuffer.AppendF($" (Preview {Number})"); + case .ReleaseCandidate(int Number): + outBuffer.AppendF($" (Release Candidate {Number})"); + case .Unknown: + outBuffer.Append(" (Unknown release type)"); + } + } + + public static int operator <=>(DotNetVersion lhs, DotNetVersion rhs) + { + if (lhs.Major != rhs.Major) + if (lhs.Major > rhs.Major) + return 1; + else + return -1; + + if (lhs.Minor != rhs.Minor) + if (lhs.Minor > rhs.Minor) + return 1; + else + return -1; + + if (lhs.Patch != rhs.Patch) + if (lhs.Patch > rhs.Patch) + return 1; + else + return -1; + + return lhs.Type.GetSortingOrder() <=> rhs.Type.GetSortingOrder(); + } +} diff --git a/GlitchyEngine/src/Scripting/ScriptEngine.bf b/GlitchyEngine/src/Scripting/ScriptEngine.bf index 5a192a6..5201e58 100644 --- a/GlitchyEngine/src/Scripting/ScriptEngine.bf +++ b/GlitchyEngine/src/Scripting/ScriptEngine.bf @@ -89,6 +89,9 @@ static class ScriptEngine // This would probably require a separate small project that just handles the script-engine interface. public const String ScriptCorePath = "Resources/Scripts/ScriptCore.dll"; + /// The minimum supported .Net Version. + public const DotNetVersion MinDotNetVersion = .(10, 0, 0, .Normal); + private static Scene s_Context ~ _?.ReleaseRef(); private static EngineClasses _classes = new .() ~ delete _;