From aadb971a75e9e35a719cd0d2abcef67af236f5df Mon Sep 17 00:00:00 2001 From: Kim Simmons Date: Sun, 9 Jul 2023 12:03:56 +0200 Subject: [PATCH] Remove the blocking Wait in CallInGodotMain Using a .net semaphore instead of godot's to be able to await with async rather than block. --- .../GodotXUnitApi/Internal/GodotTestCase.cs | 64 +++++++++---------- 1 file changed, 31 insertions(+), 33 deletions(-) diff --git a/addons/GodotXUnit/GodotXUnitApi/Internal/GodotTestCase.cs b/addons/GodotXUnit/GodotXUnitApi/Internal/GodotTestCase.cs index 5b28757..cc3414a 100644 --- a/addons/GodotXUnit/GodotXUnitApi/Internal/GodotTestCase.cs +++ b/addons/GodotXUnit/GodotXUnitApi/Internal/GodotTestCase.cs @@ -210,40 +210,38 @@ protected override object CreateTestClass() /// Runs the given function in Godot's main thread. /// /// - private void CallInGodotMain(Func fn) + private async Task CallInGodotMain(Func fn) { Exception caught = null; - var semaphore = new Godot.Semaphore(); - // Create a callable and use CallDeferred to add it to Godot's - // execution queue, which will run the function in the main thread. - // Callables do not (as of Godot 4.1) accept Task functions. - // Wrapping it in an action makes it fire-and-forget, which is - // fine; we're using a semaphore to signal completion anyway. - Callable.From(new Action(async () => + using (var semaphore = new SemaphoreSlim(1)) { - try + // Create a callable and use CallDeferred to add it to Godot's + // execution queue, which will run the function in the main thread. + // Callables do not (as of Godot 4.1) accept Task functions. + // Wrapping it in an action makes it fire-and-forget, which is + // fine; we're using a semaphore to signal completion anyway. + Callable.From(new Action(async () => { - await fn(); - } - catch (AggregateException aggregate) - { - caught = aggregate.InnerException; - } - catch (Exception e) - { - caught = e; - } - finally - { - semaphore.Post(); - } - })).CallDeferred(); - // Note: We're blocking the thread here. Is that a bad thing? - // It's probably a XUnit worker thread, so maybe its fine, but - // if any deadlocks are discovered we might want to spawn a new - // thread for this whole operation. It might be nicer if this whole - // method was async anyway. - semaphore.Wait(); + try + { + await fn(); + } + catch (AggregateException aggregate) + { + caught = aggregate.InnerException; + } + catch (Exception e) + { + caught = e; + } + finally + { + semaphore.Release(); + } + })).CallDeferred(); + // Note: A timeout is pertinent. Feature for the GodotFact attribute. + await semaphore.WaitAsync(); + } if (caught is not null) { throw caught; @@ -255,7 +253,7 @@ protected override async Task BeforeTestMethodInvokedAsync() var sceneCheck = attribute.GetNamedArgument(nameof(GodotFactAttribute.Scene)); try { - CallInGodotMain(async () => + await CallInGodotMain(async () => { if (!string.IsNullOrEmpty(sceneCheck)) { @@ -294,7 +292,7 @@ protected override async Task BeforeTestMethodInvokedAsync() protected override async Task InvokeTestMethodAsync(object testClassInstance) { decimal result = default; - CallInGodotMain(async () => + await CallInGodotMain(async () => { var sceneCheck = attribute.GetNamedArgument(nameof(GodotFactAttribute.Frame)); switch (sceneCheck) @@ -319,7 +317,7 @@ protected override async Task InvokeTestMethodAsync(object testClassIns protected override async Task AfterTestMethodInvokedAsync() { await base.AfterTestMethodInvokedAsync(); - CallInGodotMain(async () => + await CallInGodotMain(async () => { if (addingToTree != null) {