From dd9825b629e87f7142c17efd244e1b6f656c253f Mon Sep 17 00:00:00 2001 From: Matt Parker <61717342+MattParkerDev@users.noreply.github.com> Date: Wed, 27 Aug 2025 23:57:04 +1000 Subject: [PATCH] proper async events --- .../Features/Events/GlobalEvents.cs | 61 +++++++++++++++++-- src/SharpIDE.Godot/GodotGlobalEvents.cs | 10 +-- 2 files changed, 62 insertions(+), 9 deletions(-) diff --git a/src/SharpIDE.Application/Features/Events/GlobalEvents.cs b/src/SharpIDE.Application/Features/Events/GlobalEvents.cs index 29e62a5..29dba2e 100644 --- a/src/SharpIDE.Application/Features/Events/GlobalEvents.cs +++ b/src/SharpIDE.Application/Features/Events/GlobalEvents.cs @@ -7,17 +7,68 @@ namespace SharpIDE.Application.Features.Events; public static class GlobalEvents { public static event Func ProjectsRunningChanged = () => Task.CompletedTask; - public static void InvokeProjectsRunningChanged() => ProjectsRunningChanged?.Invoke(); + public static void InvokeProjectsRunningChanged() => ProjectsRunningChanged?.InvokeParallelFireAndForget(); public static event Func StartedRunningProject = () => Task.CompletedTask; - public static void InvokeStartedRunningProject() => StartedRunningProject?.Invoke(); + public static void InvokeStartedRunningProject() => StartedRunningProject?.InvokeParallelFireAndForget(); public static event Func ProjectStartedRunning = _ => Task.CompletedTask; - public static void InvokeProjectStartedRunning(SharpIdeProjectModel project) => ProjectStartedRunning?.Invoke(project); + public static void InvokeProjectStartedRunning(SharpIdeProjectModel project) => ProjectStartedRunning?.InvokeParallelFireAndForget(project); public static event Func ProjectStoppedRunning = _ => Task.CompletedTask; - public static void InvokeProjectStoppedRunning(SharpIdeProjectModel project) => ProjectStoppedRunning?.Invoke(project); + public static void InvokeProjectStoppedRunning(SharpIdeProjectModel project) => ProjectStoppedRunning?.InvokeParallelFireAndForget(project); public static event Func DebuggerExecutionStopped = _ => Task.CompletedTask; - public static void InvokeDebuggerExecutionStopped(ExecutionStopInfo executionStopInfo) => DebuggerExecutionStopped?.Invoke(executionStopInfo); + public static void InvokeDebuggerExecutionStopped(ExecutionStopInfo executionStopInfo) => DebuggerExecutionStopped?.InvokeParallelFireAndForget(executionStopInfo); +} + +public static class AsyncEventExtensions +{ + public static void InvokeParallelFireAndForget(this MulticastDelegate @event) => FireAndForget(() => @event.InvokeParallelAsync()); + public static void InvokeParallelFireAndForget(this MulticastDelegate @event, T arg) => FireAndForget(() => @event.InvokeParallelAsync(arg)); + + private static async void FireAndForget(Func 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)del)()); + } + + public static Task InvokeParallelAsync(this MulticastDelegate @event, T arg) + { + return InvokeDelegatesAsync(@event.GetInvocationList(), del => ((Func)del)(arg)); + } + + private static async Task InvokeDelegatesAsync(IEnumerable invocationList, Func 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); + } + } } diff --git a/src/SharpIDE.Godot/GodotGlobalEvents.cs b/src/SharpIDE.Godot/GodotGlobalEvents.cs index 5e31e1b..b2da3b7 100644 --- a/src/SharpIDE.Godot/GodotGlobalEvents.cs +++ b/src/SharpIDE.Godot/GodotGlobalEvents.cs @@ -1,15 +1,17 @@ -namespace SharpIDE.Godot; +using SharpIDE.Application.Features.Events; + +namespace SharpIDE.Godot; public static class GodotGlobalEvents { public static event Func BottomPanelTabExternallySelected = _ => Task.CompletedTask; - public static void InvokeBottomPanelTabExternallySelected(BottomPanelType type) => BottomPanelTabExternallySelected.Invoke(type); + public static void InvokeBottomPanelTabExternallySelected(BottomPanelType type) => BottomPanelTabExternallySelected.InvokeParallelFireAndForget(type); public static event Func BottomPanelTabSelected = _ => Task.CompletedTask; - public static void InvokeBottomPanelTabSelected(BottomPanelType? type) => BottomPanelTabSelected.Invoke(type); + public static void InvokeBottomPanelTabSelected(BottomPanelType? type) => BottomPanelTabSelected.InvokeParallelFireAndForget(type); public static event Func BottomPanelVisibilityChangeRequested = _ => Task.CompletedTask; - public static void InvokeBottomPanelVisibilityChangeRequested(bool show) => BottomPanelVisibilityChangeRequested.Invoke(show); + public static void InvokeBottomPanelVisibilityChangeRequested(bool show) => BottomPanelVisibilityChangeRequested.InvokeParallelFireAndForget(show); } public enum BottomPanelType