move godot events to wrapper

This commit is contained in:
Matt Parker
2025-10-07 21:01:55 +10:00
parent 9d4970aa67
commit a6b80c38f6
11 changed files with 45 additions and 107 deletions

View File

@@ -14,59 +14,3 @@ public class GlobalEvents
public EventWrapper<SharpIdeProjectModel, Task> ProjectStoppedRunning { get; private set; } = new(_ => Task.CompletedTask); public EventWrapper<SharpIdeProjectModel, Task> ProjectStoppedRunning { get; private set; } = new(_ => Task.CompletedTask);
public EventWrapper<ExecutionStopInfo, Task> DebuggerExecutionStopped { get; private set; } = new(_ => Task.CompletedTask); public EventWrapper<ExecutionStopInfo, Task> DebuggerExecutionStopped { get; private set; } = new(_ => Task.CompletedTask);
} }
public static class AsyncEventExtensions
{
public static void InvokeParallelFireAndForget(this MulticastDelegate @event) => FireAndForget(() => @event.InvokeParallelAsync());
public static void InvokeParallelFireAndForget<T>(this MulticastDelegate @event, T arg) => FireAndForget(() => @event.InvokeParallelAsync(arg));
public static void InvokeParallelFireAndForget<T, U>(this MulticastDelegate @event, T arg, U arg2) => FireAndForget(() => @event.InvokeParallelAsync(arg, arg2));
private static async void FireAndForget(Func<Task> action)
{
try
{
await action().ConfigureAwait(false);
}
catch (Exception ex)
{
Console.WriteLine($"An exception occurred in an event handler: {ex}");
}
}
public static Task InvokeParallelAsync(this MulticastDelegate @event)
{
return InvokeDelegatesAsync(@event.GetInvocationList(), del => ((Func<Task>)del)());
}
public static Task InvokeParallelAsync<T>(this MulticastDelegate @event, T arg)
{
return InvokeDelegatesAsync(@event.GetInvocationList(), del => ((Func<T, Task>)del)(arg));
}
public static Task InvokeParallelAsync<T, U>(this MulticastDelegate @event, T arg, U arg2)
{
return InvokeDelegatesAsync(@event.GetInvocationList(), del => ((Func<T, U, Task>)del)(arg, arg2));
}
private static async Task InvokeDelegatesAsync(IEnumerable<Delegate> invocationList, Func<Delegate, Task> delegateExecutorDelegate)
{
var tasks = invocationList.Select(async del =>
{
try
{
await delegateExecutorDelegate(del).ConfigureAwait(false);
return null;
}
catch (Exception ex)
{
return ex;
}
});
var results = await Task.WhenAll(tasks).ConfigureAwait(false);
var exceptions = results.Where(r => r is not null).Select(r => r!).ToList();
if (exceptions.Count != 0)
{
throw new AggregateException(exceptions);
}
}
}

View File

@@ -42,12 +42,12 @@ public partial class BottomPanelManager : Panel
{ BottomPanelType.IdeDiagnostics, _ideDiagnosticsPanel } { BottomPanelType.IdeDiagnostics, _ideDiagnosticsPanel }
}; };
GodotGlobalEvents.Instance.BottomPanelTabSelected += OnBottomPanelTabSelected; GodotGlobalEvents.Instance.BottomPanelTabSelected.Subscribe(OnBottomPanelTabSelected);
} }
public override void _ExitTree() public override void _ExitTree()
{ {
GodotGlobalEvents.Instance.BottomPanelTabSelected -= OnBottomPanelTabSelected; GodotGlobalEvents.Instance.BottomPanelTabSelected.Subscribe(OnBottomPanelTabSelected);
} }
private async Task OnBottomPanelTabSelected(BottomPanelType? type) private async Task OnBottomPanelTabSelected(BottomPanelType? type)
@@ -56,11 +56,11 @@ public partial class BottomPanelManager : Panel
{ {
if (type == null) if (type == null)
{ {
GodotGlobalEvents.Instance.InvokeBottomPanelVisibilityChangeRequested(false); GodotGlobalEvents.Instance.BottomPanelVisibilityChangeRequested.InvokeParallelFireAndForget(false);
} }
else else
{ {
GodotGlobalEvents.Instance.InvokeBottomPanelVisibilityChangeRequested(true); GodotGlobalEvents.Instance.BottomPanelVisibilityChangeRequested.InvokeParallelFireAndForget(true);
} }
foreach (var kvp in _panelTypeMap) foreach (var kvp in _panelTypeMap)
{ {

View File

@@ -0,0 +1,10 @@
namespace SharpIDE.Godot.Features.BottomPanel;
public enum BottomPanelType
{
Run,
Debug,
Build,
Problems,
IdeDiagnostics
}

View File

@@ -39,7 +39,7 @@ public partial class CodeEditorPanel : MarginContainer
private void OnTabClicked(long tab) private void OnTabClicked(long tab)
{ {
var sharpIdeFile = _tabContainer.GetChild<SharpIdeCodeEdit>((int)tab).SharpIdeFile; var sharpIdeFile = _tabContainer.GetChild<SharpIdeCodeEdit>((int)tab).SharpIdeFile;
GodotGlobalEvents.Instance.InvokeFileExternallySelected(sharpIdeFile); GodotGlobalEvents.Instance.FileExternallySelected.InvokeParallelFireAndForget(sharpIdeFile, null);
} }
private void OnTabClosePressed(long tabIndex) private void OnTabClosePressed(long tabIndex)
@@ -87,7 +87,7 @@ public partial class CodeEditorPanel : MarginContainer
if (executionStopInfo.FilePath != currentSharpIdeFile?.Path) if (executionStopInfo.FilePath != currentSharpIdeFile?.Path)
{ {
var file = Solution.AllFiles.Single(s => s.Path == executionStopInfo.FilePath); var file = Solution.AllFiles.Single(s => s.Path == executionStopInfo.FilePath);
await GodotGlobalEvents.Instance.InvokeFileExternallySelectedAndWait(file).ConfigureAwait(false); await GodotGlobalEvents.Instance.FileExternallySelected.InvokeParallelAsync(file, null).ConfigureAwait(false);
} }
var lineInt = executionStopInfo.Line - 1; // Debugging is 1-indexed, Godot is 0-indexed var lineInt = executionStopInfo.Line - 1; // Debugging is 1-indexed, Godot is 0-indexed
Guard.Against.Negative(lineInt, nameof(lineInt)); Guard.Against.Negative(lineInt, nameof(lineInt));

View File

@@ -1,4 +1,5 @@
using Godot; using Godot;
using SharpIDE.Godot.Features.BottomPanel;
namespace SharpIDE.Godot.Features.LeftSideBar; namespace SharpIDE.Godot.Features.LeftSideBar;
@@ -21,12 +22,12 @@ public partial class LeftSideBar : Panel
_debugButton = GetNode<Button>("%DebugButton"); _debugButton = GetNode<Button>("%DebugButton");
_ideDiagnosticsButton = GetNode<Button>("%IdeDiagnosticsButton"); _ideDiagnosticsButton = GetNode<Button>("%IdeDiagnosticsButton");
_problemsButton.Toggled += toggledOn => GodotGlobalEvents.Instance.InvokeBottomPanelTabSelected(toggledOn ? BottomPanelType.Problems : null); _problemsButton.Toggled += toggledOn => GodotGlobalEvents.Instance.BottomPanelTabSelected.InvokeParallelFireAndForget(toggledOn ? BottomPanelType.Problems : null);
_runButton.Toggled += toggledOn => GodotGlobalEvents.Instance.InvokeBottomPanelTabSelected(toggledOn ? BottomPanelType.Run : null); _runButton.Toggled += toggledOn => GodotGlobalEvents.Instance.BottomPanelTabSelected.InvokeParallelFireAndForget(toggledOn ? BottomPanelType.Run : null);
_buildButton.Toggled += toggledOn => GodotGlobalEvents.Instance.InvokeBottomPanelTabSelected(toggledOn ? BottomPanelType.Build : null); _buildButton.Toggled += toggledOn => GodotGlobalEvents.Instance.BottomPanelTabSelected.InvokeParallelFireAndForget(toggledOn ? BottomPanelType.Build : null);
_debugButton.Toggled += toggledOn => GodotGlobalEvents.Instance.InvokeBottomPanelTabSelected(toggledOn ? BottomPanelType.Debug : null); _debugButton.Toggled += toggledOn => GodotGlobalEvents.Instance.BottomPanelTabSelected.InvokeParallelFireAndForget(toggledOn ? BottomPanelType.Debug : null);
_ideDiagnosticsButton.Toggled += toggledOn => GodotGlobalEvents.Instance.InvokeBottomPanelTabSelected(toggledOn ? BottomPanelType.IdeDiagnostics : null); _ideDiagnosticsButton.Toggled += toggledOn => GodotGlobalEvents.Instance.BottomPanelTabSelected.InvokeParallelFireAndForget(toggledOn ? BottomPanelType.IdeDiagnostics : null);
GodotGlobalEvents.Instance.BottomPanelTabExternallySelected += OnBottomPanelTabExternallySelected; GodotGlobalEvents.Instance.BottomPanelTabExternallySelected.Subscribe(OnBottomPanelTabExternallySelected);
} }
private async Task OnBottomPanelTabExternallySelected(BottomPanelType arg) private async Task OnBottomPanelTabExternallySelected(BottomPanelType arg)

View File

@@ -124,6 +124,6 @@ public partial class ProblemsPanel : Control
var file = projectModel.Files var file = projectModel.Files
.Concat(projectModel.Folders.SelectMany(f => f.GetAllFiles())) .Concat(projectModel.Folders.SelectMany(f => f.GetAllFiles()))
.Single(s => s.Path == diagnostic.Location.SourceTree?.GetMappedLineSpan(diagnostic.Location.SourceSpan).Path); .Single(s => s.Path == diagnostic.Location.SourceTree?.GetMappedLineSpan(diagnostic.Location.SourceSpan).Path);
GodotGlobalEvents.Instance.InvokeFileExternallySelected(file); GodotGlobalEvents.Instance.FileExternallySelected.InvokeParallelFireAndForget(file, null);
} }
} }

View File

@@ -1,5 +1,6 @@
using Godot; using Godot;
using SharpIDE.Application.Features.SolutionDiscovery.VsPersistence; using SharpIDE.Application.Features.SolutionDiscovery.VsPersistence;
using SharpIDE.Godot.Features.BottomPanel;
namespace SharpIDE.Godot.Features.Run; namespace SharpIDE.Godot.Features.Run;
@@ -51,13 +52,13 @@ public partial class RunMenuItem : HBoxContainer
private async void OnRunButtonPressed() private async void OnRunButtonPressed()
{ {
GodotGlobalEvents.Instance.InvokeBottomPanelTabExternallySelected(BottomPanelType.Run); GodotGlobalEvents.Instance.BottomPanelTabExternallySelected.InvokeParallelFireAndForget(BottomPanelType.Run);
await Singletons.RunService.RunProject(Project).ConfigureAwait(false); await Singletons.RunService.RunProject(Project).ConfigureAwait(false);
} }
private async void OnDebugButtonPressed() private async void OnDebugButtonPressed()
{ {
GodotGlobalEvents.Instance.InvokeBottomPanelTabExternallySelected(BottomPanelType.Debug); GodotGlobalEvents.Instance.BottomPanelTabExternallySelected.InvokeParallelFireAndForget(BottomPanelType.Debug);
await Singletons.RunService.RunProject(Project, true).ConfigureAwait(false); await Singletons.RunService.RunProject(Project, true).ConfigureAwait(false);
} }
} }

View File

@@ -27,7 +27,7 @@ public partial class SearchResultComponent : MarginContainer
private void OnButtonPressed() private void OnButtonPressed()
{ {
var fileLinePosition = new SharpIdeFileLinePosition { Line = Result.Line, Column = Result.StartColumn }; var fileLinePosition = new SharpIdeFileLinePosition { Line = Result.Line, Column = Result.StartColumn };
GodotGlobalEvents.Instance.InvokeFileExternallySelected(Result.File, fileLinePosition); GodotGlobalEvents.Instance.FileExternallySelected.InvokeParallelFireAndForget(Result.File, fileLinePosition);
ParentSearchWindow.Hide(); ParentSearchWindow.Hide();
} }

View File

@@ -26,7 +26,7 @@ public partial class SolutionExplorerPanel : MarginContainer
{ {
_tree = GetNode<Tree>("Tree"); _tree = GetNode<Tree>("Tree");
_tree.ItemMouseSelected += TreeOnItemMouseSelected; _tree.ItemMouseSelected += TreeOnItemMouseSelected;
GodotGlobalEvents.Instance.FileExternallySelected += OnFileExternallySelected; GodotGlobalEvents.Instance.FileExternallySelected.Subscribe(OnFileExternallySelected);
} }
private void TreeOnItemMouseSelected(Vector2 mousePosition, long mouseButtonIndex) private void TreeOnItemMouseSelected(Vector2 mousePosition, long mouseButtonIndex)
@@ -37,13 +37,13 @@ public partial class SolutionExplorerPanel : MarginContainer
if (sharpIdeFileContainer is null) return; if (sharpIdeFileContainer is null) return;
var sharpIdeFile = sharpIdeFileContainer.Item; var sharpIdeFile = sharpIdeFileContainer.Item;
Guard.Against.Null(sharpIdeFile, nameof(sharpIdeFile)); Guard.Against.Null(sharpIdeFile, nameof(sharpIdeFile));
GodotGlobalEvents.Instance.InvokeFileSelected(sharpIdeFile); GodotGlobalEvents.Instance.FileSelected.InvokeParallelFireAndForget(sharpIdeFile, null);
} }
private async Task OnFileExternallySelected(SharpIdeFile file, SharpIdeFileLinePosition? fileLinePosition) private async Task OnFileExternallySelected(SharpIdeFile file, SharpIdeFileLinePosition? fileLinePosition)
{ {
await Task.CompletedTask.ConfigureAwait(ConfigureAwaitOptions.ForceYielding); await Task.CompletedTask.ConfigureAwait(ConfigureAwaitOptions.ForceYielding);
var task = GodotGlobalEvents.Instance.InvokeFileSelectedAndWait(file, fileLinePosition); var task = GodotGlobalEvents.Instance.FileSelected.InvokeParallelAsync(file, fileLinePosition);
var item = FindItemRecursive(_tree.GetRoot(), file); var item = FindItemRecursive(_tree.GetRoot(), file);
if (item is not null) if (item is not null)
{ {

View File

@@ -1,35 +1,16 @@
using SharpIDE.Application.Features.Analysis; using SharpIDE.Application.Features.Analysis;
using SharpIDE.Application.Features.Events; using SharpIDE.Application.Features.Events;
using SharpIDE.Application.Features.SolutionDiscovery; using SharpIDE.Application.Features.SolutionDiscovery;
using SharpIDE.Godot.Features.BottomPanel;
namespace SharpIDE.Godot; namespace SharpIDE.Godot;
public class GodotGlobalEvents public class GodotGlobalEvents
{ {
public static GodotGlobalEvents Instance { get; set; } = null!; public static GodotGlobalEvents Instance { get; set; } = null!;
public event Func<BottomPanelType, Task> BottomPanelTabExternallySelected = _ => Task.CompletedTask; public EventWrapper<BottomPanelType, Task> BottomPanelTabExternallySelected { get; } = new(_ => Task.CompletedTask);
public void InvokeBottomPanelTabExternallySelected(BottomPanelType type) => BottomPanelTabExternallySelected.InvokeParallelFireAndForget(type); public EventWrapper<BottomPanelType?, Task> BottomPanelTabSelected { get; } = new(_ => Task.CompletedTask);
public EventWrapper<bool, Task> BottomPanelVisibilityChangeRequested { get; } = new(_ => Task.CompletedTask);
public event Func<BottomPanelType?, Task> BottomPanelTabSelected = _ => Task.CompletedTask; public EventWrapper<SharpIdeFile, SharpIdeFileLinePosition?, Task> FileSelected { get; } = new((_, _) => Task.CompletedTask);
public void InvokeBottomPanelTabSelected(BottomPanelType? type) => BottomPanelTabSelected.InvokeParallelFireAndForget(type); public EventWrapper<SharpIdeFile, SharpIdeFileLinePosition?, Task> FileExternallySelected { get; } = new((_, _) => Task.CompletedTask);
public event Func<bool, Task> BottomPanelVisibilityChangeRequested = _ => Task.CompletedTask;
public void InvokeBottomPanelVisibilityChangeRequested(bool show) => BottomPanelVisibilityChangeRequested.InvokeParallelFireAndForget(show);
public event Func<SharpIdeFile, SharpIdeFileLinePosition?, Task> FileSelected = (_, _) => Task.CompletedTask;
public void InvokeFileSelected(SharpIdeFile file, SharpIdeFileLinePosition? fileLinePosition = null) => FileSelected.InvokeParallelFireAndForget(file, fileLinePosition);
public async Task InvokeFileSelectedAndWait(SharpIdeFile file, SharpIdeFileLinePosition? fileLinePosition) => await FileSelected.InvokeParallelAsync(file, fileLinePosition);
public event Func<SharpIdeFile, SharpIdeFileLinePosition?, Task> FileExternallySelected = (_, _) => Task.CompletedTask;
public void InvokeFileExternallySelected(SharpIdeFile file, SharpIdeFileLinePosition? fileLinePosition = null) => FileExternallySelected.InvokeParallelFireAndForget(file, fileLinePosition);
public async Task InvokeFileExternallySelectedAndWait(SharpIdeFile file, SharpIdeFileLinePosition? fileLinePosition = null) => await FileExternallySelected.InvokeParallelAsync(file, fileLinePosition);
}
public enum BottomPanelType
{
Run,
Debug,
Build,
Problems,
IdeDiagnostics
} }

View File

@@ -58,13 +58,13 @@ public partial class IdeRoot : Control
_bottomPanelManager = GetNode<BottomPanelManager>("%BottomPanel"); _bottomPanelManager = GetNode<BottomPanelManager>("%BottomPanel");
_runMenuButton.Pressed += OnRunMenuButtonPressed; _runMenuButton.Pressed += OnRunMenuButtonPressed;
GodotGlobalEvents.Instance.FileSelected += OnSolutionExplorerPanelOnFileSelected; GodotGlobalEvents.Instance.FileSelected.Subscribe(OnSolutionExplorerPanelOnFileSelected);
_openSlnButton.Pressed += () => IdeWindow.PickSolution(); _openSlnButton.Pressed += () => IdeWindow.PickSolution();
_buildSlnButton.Pressed += OnBuildSlnButtonPressed; _buildSlnButton.Pressed += OnBuildSlnButtonPressed;
_rebuildSlnButton.Pressed += OnRebuildSlnButtonPressed; _rebuildSlnButton.Pressed += OnRebuildSlnButtonPressed;
_cleanSlnButton.Pressed += OnCleanSlnButtonPressed; _cleanSlnButton.Pressed += OnCleanSlnButtonPressed;
_restoreSlnButton.Pressed += OnRestoreSlnButtonPressed; _restoreSlnButton.Pressed += OnRestoreSlnButtonPressed;
GodotGlobalEvents.Instance.BottomPanelVisibilityChangeRequested += async show => await this.InvokeAsync(() => _invertedVSplitContainer.InvertedSetCollapsed(!show)); GodotGlobalEvents.Instance.BottomPanelVisibilityChangeRequested.Subscribe(async show => await this.InvokeAsync(() => _invertedVSplitContainer.InvertedSetCollapsed(!show)));
_nodeReadyTcs.SetResult(); _nodeReadyTcs.SetResult();
} }
@@ -78,22 +78,22 @@ public partial class IdeRoot : Control
private async void OnBuildSlnButtonPressed() private async void OnBuildSlnButtonPressed()
{ {
GodotGlobalEvents.Instance.InvokeBottomPanelTabExternallySelected(BottomPanelType.Build); GodotGlobalEvents.Instance.BottomPanelTabExternallySelected.InvokeParallelFireAndForget(BottomPanelType.Build);
await Singletons.BuildService.MsBuildSolutionAsync(_solutionExplorerPanel.SolutionModel.FilePath); await Singletons.BuildService.MsBuildSolutionAsync(_solutionExplorerPanel.SolutionModel.FilePath);
} }
private async void OnRebuildSlnButtonPressed() private async void OnRebuildSlnButtonPressed()
{ {
GodotGlobalEvents.Instance.InvokeBottomPanelTabExternallySelected(BottomPanelType.Build); GodotGlobalEvents.Instance.BottomPanelTabExternallySelected.InvokeParallelFireAndForget(BottomPanelType.Build);
await Singletons.BuildService.MsBuildSolutionAsync(_solutionExplorerPanel.SolutionModel.FilePath, BuildType.Rebuild); await Singletons.BuildService.MsBuildSolutionAsync(_solutionExplorerPanel.SolutionModel.FilePath, BuildType.Rebuild);
} }
private async void OnCleanSlnButtonPressed() private async void OnCleanSlnButtonPressed()
{ {
GodotGlobalEvents.Instance.InvokeBottomPanelTabExternallySelected(BottomPanelType.Build); GodotGlobalEvents.Instance.BottomPanelTabExternallySelected.InvokeParallelFireAndForget(BottomPanelType.Build);
await Singletons.BuildService.MsBuildSolutionAsync(_solutionExplorerPanel.SolutionModel.FilePath, BuildType.Clean); await Singletons.BuildService.MsBuildSolutionAsync(_solutionExplorerPanel.SolutionModel.FilePath, BuildType.Clean);
} }
private async void OnRestoreSlnButtonPressed() private async void OnRestoreSlnButtonPressed()
{ {
GodotGlobalEvents.Instance.InvokeBottomPanelTabExternallySelected(BottomPanelType.Build); GodotGlobalEvents.Instance.BottomPanelTabExternallySelected.InvokeParallelFireAndForget(BottomPanelType.Build);
await Singletons.BuildService.MsBuildSolutionAsync(_solutionExplorerPanel.SolutionModel.FilePath, BuildType.Restore); await Singletons.BuildService.MsBuildSolutionAsync(_solutionExplorerPanel.SolutionModel.FilePath, BuildType.Restore);
} }
@@ -115,10 +115,11 @@ public partial class IdeRoot : Control
_searchWindow.Solution = solutionModel; _searchWindow.Solution = solutionModel;
Callable.From(_solutionExplorerPanel.RepopulateTree).CallDeferred(); Callable.From(_solutionExplorerPanel.RepopulateTree).CallDeferred();
RoslynAnalysis.StartSolutionAnalysis(solutionModel); RoslynAnalysis.StartSolutionAnalysis(solutionModel);
Singletons.FileWatcher.StartWatching(solutionModel);
var infraProject = solutionModel.AllProjects.SingleOrDefault(s => s.Name == "WebUi"); var infraProject = solutionModel.AllProjects.SingleOrDefault(s => s.Name == "WebUi");
var diFile = infraProject?.Folders.Single(s => s.Name == "Pages").Files.Single(s => s.Name == "TestPage.razor"); var diFile = infraProject?.Folders.Single(s => s.Name == "Pages").Files.Single(s => s.Name == "TestPage.razor");
if (diFile != null) await this.InvokeDeferredAsync(() => GodotGlobalEvents.Instance.InvokeFileExternallySelected(diFile)); if (diFile != null) await this.InvokeDeferredAsync(() => GodotGlobalEvents.Instance.FileExternallySelected.InvokeParallelFireAndForget(diFile, null));
var tasks = solutionModel.AllProjects.Select(p => p.MsBuildEvaluationProjectTask).ToList(); var tasks = solutionModel.AllProjects.Select(p => p.MsBuildEvaluationProjectTask).ToList();
await Task.WhenAll(tasks).ConfigureAwait(false); await Task.WhenAll(tasks).ConfigureAwait(false);