Skip to content

Commit

Permalink
Add ToGDTaskSignal method
Browse files Browse the repository at this point in the history
  • Loading branch information
Atlinx committed Mar 28, 2024
1 parent 2bd1311 commit 6175b95
Show file tree
Hide file tree
Showing 2 changed files with 150 additions and 3 deletions.
101 changes: 101 additions & 0 deletions addons/GDTask/GDTask.ToSignal.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
using Godot;
using System;
using System.Runtime.CompilerServices;
using System.Threading;

namespace Fractural.Tasks
{
/// <summary>
/// A cancellable signal awaiter that wraps the Godot <see cref="SignalAwaiter"/>. Using ToSignal
/// with an additional <see cref="CancellationToken"/> as parameter automatically returns this awaiter.
/// See <see cref="GodotObjectExtensions.ToSignal(GodotObject, GodotObject, StringName, CancellationToken)"/>.
///
/// Originally from <see href="https://github.com/altamkp/GodotEx/blob/master/src/GodotEx.Async/src/Core/CancellableSignalAwaiter.cs">GodotEx</see>
/// </summary>
public class CancellableSignalAwaiter : IAwaiter<Variant[]>, INotifyCompletion, IAwaitable<Variant[]>
{
private readonly SignalAwaiter _signalAwaiter;
private readonly CancellationToken _cancellationToken;
private readonly CancellationTokenRegistration _cancellationTokenRegistration;

private Action? _continuation;
private bool _isCancelled;

/// <summary>
/// Creates a new <see cref="CancellableSignalAwaiter"/> that wraps the Godot <see cref="SignalAwaiter"/>.
/// </summary>
/// <param name="signalAwaiter">Godot <see cref="SignalAwaiter"/>.</param>
/// <param name="cancellationToken">Cancellation token for cancellation request.</param>
public CancellableSignalAwaiter(SignalAwaiter signalAwaiter, CancellationToken cancellationToken)
{
_signalAwaiter = signalAwaiter;
_cancellationToken = cancellationToken;
_cancellationTokenRegistration = _cancellationToken.Register(() =>
{
_cancellationTokenRegistration.Dispose();
_isCancelled = true;
OnAwaiterCompleted();
});
}

/// <summary>
/// Completion status of the awaiter. True if canceled or if signal is emitted.
/// </summary>
public bool IsCompleted => _isCancelled || _signalAwaiter.IsCompleted;

/// <summary>
/// Registers action delegate upon completion.
/// </summary>
/// <param name="continuation">Action delegate up completion.</param>
public void OnCompleted(Action continuation)
{
_continuation = continuation;
_signalAwaiter.OnCompleted(OnAwaiterCompleted);
}

/// <summary>
/// Returns current awaiter as <see cref="IAwaiter"/> that can be used with the await keyword.
/// </summary>
/// <returns>Current awaiter as <see cref="IAwaiter"/>.</returns>
public IAwaiter<Variant[]> GetAwaiter() => this;

/// <summary>
/// Returns result upon completion.
/// </summary>
/// <returns>Result upon completion.</returns>
public Variant[] GetResult() => _signalAwaiter.GetResult();

private void OnAwaiterCompleted()
{
var continuation = _continuation;
_continuation = null;
continuation?.Invoke();
}
}

public partial struct GDTask
{
public static async GDTask<Variant[]> ToSignal(GodotObject self, string signal)
{
return await self.ToSignal(self, signal);
}

public static async GDTask<Variant[]> ToSignal(GodotObject self, string signal, CancellationToken ct)
{
var cancellableSignalAwaiter = new CancellableSignalAwaiter(self.ToSignal(self, signal), ct);
return await cancellableSignalAwaiter;
}
}

public static partial class GDTaskExtensions
{
public static GDTask<Variant[]> ToGDTaskSignal(this GodotObject self, string signal)
{
return GDTask.ToSignal(self, signal);
}
public static GDTask<Variant[]> ToGDTaskSignal(this GodotObject self, string signal, CancellationToken ct)
{
return GDTask.ToSignal(self, signal, ct);
}
}
}
52 changes: 49 additions & 3 deletions tests/manual/Test.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ namespace Tests.Manual
{
public partial class Test : Node
{
[Signal]
public delegate void MyEmptySignalEventHandler();
[Signal]
public delegate void MyArgSignalEventHandler(int number, bool boolean);

[Export]
private bool runTestOnReady;
[Export]
Expand Down Expand Up @@ -41,6 +46,23 @@ public override void _Input(InputEvent @event)
}
}

private async GDTaskVoid WaitAndEmitMyEmptySignal(TimeSpan delay)
{
await GDTask.Delay(delay);
EmitSignal(nameof(MyEmptySignal));
}

private async GDTaskVoid WaitAndEmitMyArgSignal(TimeSpan delay)
{
await GDTask.Delay(delay);
EmitSignal(nameof(MyArgSignal), 10, true);
}
private async GDTaskVoid WaitAndCancelToken(TimeSpan delay, CancellationTokenSource cts)
{
await GDTask.Delay(delay);
cts.Cancel();
}

private async GDTaskVoid Run()
{
GD.Print("Run: Pre delay");
Expand All @@ -49,12 +71,36 @@ private async GDTaskVoid Run()
sprite.Visible = true;
GD.Print("Run: Post delay after 3 seconds");

GD.Print("Run: Await MyEmptySignal");
WaitAndEmitMyEmptySignal(TimeSpan.FromSeconds(1)).Forget();
var signalResult = await GDTask.ToSignal(this, nameof(MyEmptySignal));
GD.Print("Run: Await MyEmptySignal Complete, result: ", Json.Stringify(new Godot.Collections.Array(signalResult)));

GD.Print("Run: Await MyArgSignal");
WaitAndEmitMyArgSignal(TimeSpan.FromSeconds(1)).Forget();
signalResult = await GDTask.ToSignal(this, nameof(MyArgSignal));
GD.Print("Run: Await MyArgSignal Complete, result: ", Json.Stringify(new Godot.Collections.Array(signalResult)));

var cts = new CancellationTokenSource();
GD.Print("Run: Await Cancellable MyEmptySignal");
WaitAndEmitMyEmptySignal(TimeSpan.FromSeconds(3)).Forget();
WaitAndCancelToken(TimeSpan.FromSeconds(0.5), cts).Forget();
signalResult = await this.ToGDTaskSignal(nameof(MyEmptySignal), cts.Token);
GD.Print("Run: Await Cancellable MyEmptySignal Cancelled, result: ", signalResult);

cts = new CancellationTokenSource();
GD.Print("Run: Await Cancellable MyArgSignal");
WaitAndEmitMyArgSignal(TimeSpan.FromSeconds(3)).Forget();
WaitAndCancelToken(TimeSpan.FromSeconds(0.5), cts).Forget();
signalResult = await this.ToGDTaskSignal(nameof(MyArgSignal), cts.Token);
GD.Print("Run: Await Cancellable MyArgSignal Cancelled, result: ", signalResult);

GD.Print("Run: Pre RunWithResult");
string result = await RunWithResult();
GD.Print($"Run: Post got result: {result}");
string runResult = await RunWithResult();
GD.Print($"Run: Post got result: {runResult}");

GD.Print("Run: LongTask started");
var cts = new CancellationTokenSource();
cts = new CancellationTokenSource();

CancellableReallyLongTask(cts.Token).Forget();

Expand Down

0 comments on commit 6175b95

Please sign in to comment.