diff --git a/README.md b/README.md index 73911c9..da969bc 100644 --- a/README.md +++ b/README.md @@ -46,8 +46,14 @@ public Test : Node var cts = new CancellationTokenSource(); WaitAndEmitMySignal(TimeSpan.FromSeconds(2)).Forget(); WaitAndCancelToken(TimeSpan.FromSeconds(1), cts).Forget(); - var signalResults = await GDTask.ToSignal(this, nameof(MySignal), cts.Token); - // signalResults = null, since we cancelled awaiting the signal before the signal could fire. + try + { + var signalResults = await GDTask.ToSignal(this, nameof(MySignal), cts.Token); + } + catch (OperationCanceledException _) + { + GD.Print("Awaiting MySignal cancelled!"); + } // Waiting a single frame await GDTask.Yield(); diff --git a/addons/GDTask/GDTask.ToSignal.cs b/addons/GDTask/GDTask.ToSignal.cs index 5146aa3..edf6df3 100644 --- a/addons/GDTask/GDTask.ToSignal.cs +++ b/addons/GDTask/GDTask.ToSignal.cs @@ -1,89 +1,25 @@ using Godot; -using System; -using System.Runtime.CompilerServices; using System.Threading; namespace Fractural.Tasks { - /// - /// A cancellable signal awaiter that wraps the Godot . Using ToSignal - /// with an additional as parameter automatically returns this awaiter. - /// See . - /// - /// Originally from GodotEx - /// - public class CancellableSignalAwaiter : IAwaiter, INotifyCompletion, IAwaitable - { - private readonly SignalAwaiter _signalAwaiter; - private readonly CancellationToken _cancellationToken; - private readonly CancellationTokenRegistration _cancellationTokenRegistration; - - private Action? _continuation; - private bool _isCancelled; - - /// - /// Creates a new that wraps the Godot . - /// - /// Godot . - /// Cancellation token for cancellation request. - public CancellableSignalAwaiter(SignalAwaiter signalAwaiter, CancellationToken cancellationToken) - { - _signalAwaiter = signalAwaiter; - _cancellationToken = cancellationToken; - _cancellationTokenRegistration = _cancellationToken.Register(() => - { - _cancellationTokenRegistration.Dispose(); - _isCancelled = true; - OnAwaiterCompleted(); - }); - } - - /// - /// Completion status of the awaiter. True if canceled or if signal is emitted. - /// - public bool IsCompleted => _isCancelled || _signalAwaiter.IsCompleted; - - /// - /// Registers action delegate upon completion. - /// - /// Action delegate up completion. - public void OnCompleted(Action continuation) - { - _continuation = continuation; - _signalAwaiter.OnCompleted(OnAwaiterCompleted); - } - - /// - /// Returns current awaiter as that can be used with the await keyword. - /// - /// Current awaiter as . - public IAwaiter GetAwaiter() => this; - - /// - /// Returns result upon completion. - /// - /// Result upon completion. - public Variant[] GetResult() => _signalAwaiter.GetResult(); - - private void OnAwaiterCompleted() - { - var continuation = _continuation; - _continuation = null; - continuation?.Invoke(); - } - } - public partial struct GDTask { - public static async GDTask ToSignal(GodotObject self, string signal) + public static async GDTask ToSignal(GodotObject self, StringName signal) { return await self.ToSignal(self, signal); } - public static async GDTask ToSignal(GodotObject self, string signal, CancellationToken ct) + public static async GDTask ToSignal(GodotObject self, StringName signal, CancellationToken ct) { - var cancellableSignalAwaiter = new CancellableSignalAwaiter(self.ToSignal(self, signal), ct); - return await cancellableSignalAwaiter; + var tcs = new GDTaskCompletionSource(); + ct.Register(() => tcs.TrySetCanceled(ct)); + Create(async () => + { + var result = await self.ToSignal(self, signal); + tcs.TrySetResult(result); + }).Forget(); + return await tcs.Task; } } } diff --git a/tests/manual/Test.cs b/tests/manual/Test.cs index 29bdbe9..c2edbfb 100644 --- a/tests/manual/Test.cs +++ b/tests/manual/Test.cs @@ -85,15 +85,29 @@ private async GDTaskVoid Run() GD.Print("Run: Await Cancellable MyEmptySignal"); WaitAndEmitMyEmptySignal(TimeSpan.FromSeconds(3)).Forget(); WaitAndCancelToken(TimeSpan.FromSeconds(0.5), cts).Forget(); - signalResult = await GDTask.ToSignal(this, nameof(MyEmptySignal), cts.Token); - GD.Print("Run: Await Cancellable MyEmptySignal Cancelled, result: ", signalResult); + try + { + signalResult = await GDTask.ToSignal(this, nameof(MyEmptySignal), cts.Token); + GD.Print("Run: Await Cancellable MyEmptySignal ran with result: ", signalResult); + } + catch (OperationCanceledException _) + { + GD.Print("Run: Await Cancellable MyEmptySignal Cancelled"); + } cts = new CancellationTokenSource(); GD.Print("Run: Await Cancellable MyArgSignal"); WaitAndEmitMyArgSignal(TimeSpan.FromSeconds(3)).Forget(); WaitAndCancelToken(TimeSpan.FromSeconds(0.5), cts).Forget(); - signalResult = await GDTask.ToSignal(this, nameof(MyArgSignal), cts.Token); - GD.Print("Run: Await Cancellable MyArgSignal Cancelled, result: ", signalResult); + try + { + signalResult = await GDTask.ToSignal(this, nameof(MyArgSignal), cts.Token); + GD.Print("Run: Await Cancellable MyArgSignal ran with result: ", signalResult); + } + catch (OperationCanceledException _) + { + GD.Print("Run: Await Cancellable MyArgSignal Cancelled"); + } GD.Print("Run: Pre RunWithResult"); string runResult = await RunWithResult();