using Sandbox.Engine;
using System.Threading;
namespace Sandbox;
///
/// A generic .
///
public static class GameTask
{
private static TaskSource source => GlobalContext.Current.TaskSource;
///
public static Task Yield() => source.Yield();
///
public static Task Delay( int ms ) => source.Delay( ms );
///
public static Task Delay( int ms, CancellationToken ct ) => source.Delay( ms, ct );
///
public static Task DelaySeconds( float seconds ) => Delay( (int)(seconds * 1000.0f) );
///
public static Task DelaySeconds( float seconds, CancellationToken ct ) => Delay( (int)(seconds * 1000.0f), ct );
///
public static Task DelayRealtime( int ms ) => source.DelayRealtime( ms );
///
public static Task DelayRealtime( int ms, CancellationToken ct ) => source.DelayRealtime( ms, ct );
///
public static Task DelayRealtimeSeconds( float seconds ) => DelayRealtime( (int)(seconds * 1000.0f) );
///
public static Task DelayRealtimeSeconds( float seconds, CancellationToken ct ) => DelayRealtime( (int)(seconds * 1000.0f), ct );
///
public static Task RunInThreadAsync( Action action ) => source.RunInThreadAsync( action );
///
public static Task RunInThreadAsync( Func func ) => source.RunInThreadAsync( func );
///
public static Task RunInThreadAsync( Func task ) => source.RunInThreadAsync( task );
///
public static Task RunInThreadAsync( Func> task ) => source.RunInThreadAsync( task );
///
public static Task CompletedTask => source.CompletedTask;
///
public static Task FromResult( T t ) => source.FromResult( t );
///
public static Task WhenAll( params Task[] tasks ) => source.WhenAll( tasks );
///
public static Task WhenAll( IEnumerable tasks ) => source.WhenAll( tasks );
///
public static Task WhenAll( params Task[] tasks ) => source.WhenAll( tasks );
///
public static Task WhenAll( IEnumerable> tasks ) => source.WhenAll( tasks );
///
public static Task WhenAny( params Task[] tasks ) => source.WhenAny( tasks );
///
public static Task WhenAny( IEnumerable tasks ) => source.WhenAny( tasks );
///
public static Task> WhenAny( params Task[] tasks ) => source.WhenAny( tasks );
///
public static Task> WhenAny( IEnumerable> tasks ) => source.WhenAny( tasks );
///
public static void WaitAll( params Task[] tasks ) => source.WaitAll( tasks );
///
public static void WaitAny( params Task[] tasks ) => source.WaitAny( tasks );
///
public static SyncTask MainThread()
{
return new SyncTask( SyncContext.MainThread, allowSynchronous: true );
}
///
public static SyncTask MainThread( CancellationToken cancellation )
{
return new SyncTask( SyncContext.MainThread, allowSynchronous: true, cancellation: cancellation );
}
///
public static SyncTask WorkerThread()
{
TaskSource.EnsureWorkerThreadsStarted();
return new SyncTask( SyncContext.WorkerThread, allowSynchronous: true );
}
///
public static SyncTask WorkerThread( CancellationToken cancellation )
{
TaskSource.EnsureWorkerThreadsStarted();
return new SyncTask( SyncContext.WorkerThread, allowSynchronous: true, cancellation: cancellation );
}
}
///
/// Provides a way for us to cancel tasks after common async shit is executed.
///
public struct TaskSource
{
private static CancellationTokenSource currentGenerationCts => GlobalContext.Current.CancellationTokenSource;
internal static CancellationToken Cancellation => currentGenerationCts.Token;
internal static readonly TaskSource Cancelled = new TaskSource( new CancellationToken( true ) );
internal TaskSource( int i = 0 )
{
_isValid = true;
_cancellation = Cancellation;
if ( Cancellation.IsCancellationRequested )
{
Log.Warning( $"new TaskSource is already cancelled" );
}
}
internal TaskSource( CancellationToken token )
{
if ( token == default )
token = Cancellation;
_isValid = true;
_cancellation = token;
}
private static string FormatAction( Delegate action )
{
return action.ToSimpleString();
}
public static TaskSource Create( CancellationToken token = default )
{
return new TaskSource( token );
}
///
/// Create a token source, which will also be cancelled when sessions end
///
public static CancellationTokenSource CreateLinkedTokenSource()
{
return CancellationTokenSource.CreateLinkedTokenSource( Cancellation );
}
private readonly CancellationToken _cancellation;
private bool _isValid;
///
public bool IsValid => _isValid && !_cancellation.IsCancellationRequested;
///
/// Marks this task source as invalid. All associated running tasks will be canceled ASAP.
///
internal void Expire()
{
_isValid = false;
}
internal void CancelIfInvalid()
{
if ( !IsValid )
{
throw new TaskCanceledException();
}
}
///
/// A task that does nothing for given amount of time in milliseconds.
///
/// Time to wait in milliseconds.
public Task Delay( int ms )
{
return DelayInternal( ms, _cancellation );
}
///
/// A task that does nothing for given amount of time in milliseconds.
///
/// Time to wait in milliseconds.
/// Token to cancel the delay early.
public async Task Delay( int ms, CancellationToken ct )
{
using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource( ct, _cancellation );
await DelayInternal( ms, linkedCts.Token );
}
private async Task DelayInternal( int ms, CancellationToken ct )
{
var time = Time.Now + ms / 1000.0f;
while ( Time.Now < time )
{
await Task.Delay( 1, ct );
CancelIfInvalid();
}
CancelIfInvalid();
}
///
/// A task that does nothing for given amount of time in seconds.
///
/// >Time to wait in seconds.
public Task DelaySeconds( float seconds ) => Delay( (int)(seconds * 1000.0f) );
///
/// A task that does nothing for given amount of time in seconds.
///
/// >Time to wait in seconds.
/// Token to cancel the delay early.
public Task DelaySeconds( float seconds, CancellationToken ct ) => Delay( (int)(seconds * 1000.0f), ct );
private record struct WorkerThreadAction( Action Action, TaskCompletionSource Tcs )
{
public static SendOrPostCallback PostCallback { get; } = state =>
{
var item = (WorkerThreadAction)state!;
try
{
item.Action();
item.Tcs.SetResult();
}
catch ( Exception e )
{
item.Tcs.SetException( e );
}
};
public override string ToString()
{
return FormatAction( Action );
}
}
internal static void EnsureWorkerThreadsStarted()
{
if ( !Tasks.WorkerThread.HasStarted ) Tasks.WorkerThread.Start();
}
public async Task RunInThreadAsync( Action action )
{
var tcs = new TaskCompletionSource();
SyncContext.WorkerThread.Post( WorkerThreadAction.PostCallback, new WorkerThreadAction( action, tcs ) );
EnsureWorkerThreadsStarted();
await tcs.Task;
CancelIfInvalid();
}
private record struct WorkerThreadFunc( Func Func, TaskCompletionSource Tcs )
{
public static SendOrPostCallback PostCallback { get; } = state =>
{
var item = (WorkerThreadFunc)state!;
try
{
var result = item.Func();
item.Tcs.SetResult( result );
}
catch ( Exception e )
{
item.Tcs.SetException( e );
}
};
public override string ToString()
{
return FormatAction( Func );
}
}
public async Task RunInThreadAsync( Func func )
{
var tcs = new TaskCompletionSource();
SyncContext.WorkerThread.Post( WorkerThreadFunc.PostCallback, new WorkerThreadFunc( func, tcs ) );
EnsureWorkerThreadsStarted();
var result = await tcs.Task;
CancelIfInvalid();
return result;
}
private record struct WorkerThreadTask( Func TaskFunc, TaskCompletionSource Tcs )
{
private static Action ContinuationAction { get; } = ( task, state ) =>
{
var tcs = (TaskCompletionSource)state;
if ( task.IsCanceled )
{
tcs.SetCanceled();
}
else if ( task.IsFaulted )
{
tcs.SetException( task.Exception! );
}
else
{
Assert.True( task.IsCompletedSuccessfully );
tcs.SetResult( task );
}
};
public static SendOrPostCallback PostCallback { get; } = state =>
{
var item = (WorkerThreadTask)state!;
try
{
var task = item.TaskFunc();
task.ContinueWith( ContinuationAction, item.Tcs );
}
catch ( Exception e )
{
item.Tcs.SetException( e );
}
};
public override string ToString()
{
return FormatAction( TaskFunc );
}
}
public async Task RunInThreadAsync( Func task )
{
var tcs = new TaskCompletionSource();
SyncContext.WorkerThread.Post( WorkerThreadTask.PostCallback, new WorkerThreadTask( task, tcs ) );
EnsureWorkerThreadsStarted();
await await tcs.Task;
CancelIfInvalid();
}
private record struct WorkerThreadTask( Func> TaskFunc, TaskCompletionSource> Tcs )
{
private static Action, object> ContinuationAction { get; } = ( task, state ) =>
{
var tcs = (TaskCompletionSource>)state;
if ( task.IsCanceled )
{
tcs.SetCanceled();
}
else if ( task.IsFaulted )
{
tcs.SetException( task.Exception! );
}
else
{
Assert.True( task.IsCompletedSuccessfully );
tcs.SetResult( task );
}
};
public static SendOrPostCallback PostCallback { get; } = state =>
{
var item = (WorkerThreadTask)state!;
try
{
var task = item.TaskFunc();
task.ContinueWith( ContinuationAction, item.Tcs );
}
catch ( Exception e )
{
item.Tcs.SetException( e );
}
};
public override string ToString()
{
return FormatAction( TaskFunc );
}
}
public async Task RunInThreadAsync( Func> task )
{
var tcs = new TaskCompletionSource>();
SyncContext.WorkerThread.Post( WorkerThreadTask.PostCallback, new WorkerThreadTask( task, tcs ) );
EnsureWorkerThreadsStarted();
var result = await await tcs.Task;
CancelIfInvalid();
return result;
}
public async Task DelayRealtime( int ms )
{
await Task.Delay( ms, _cancellation );
CancelIfInvalid();
}
public async Task DelayRealtime( int ms, CancellationToken ct )
{
using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource( ct, _cancellation );
await Task.Delay( ms, linkedCts.Token );
CancelIfInvalid();
}
public Task DelayRealtimeSeconds( float seconds ) => DelayRealtime( (int)(seconds * 1000.0f) );
public Task DelayRealtimeSeconds( float seconds, CancellationToken ct ) => DelayRealtime( (int)(seconds * 1000.0f), ct );
///
/// Continues on the main thread.
///
public SyncTask MainThread()
{
return GameTask.MainThread( Cancellation );
}
///
/// Continues on a worker thread.
///
public SyncTask WorkerThread()
{
return GameTask.WorkerThread( Cancellation );
}
///
public Task CompletedTask => Task.CompletedTask;
///
public Task FromResult( T t ) => Task.FromResult( t );
///
public Task FromCanceled( CancellationToken token ) => Task.FromCanceled( token );
///
public Task FromException( Exception e ) => Task.FromException( e );
///
public async Task WhenAll( params Task[] tasks )
{
await Task.WhenAll( tasks ).WaitAsync( _cancellation );
CancelIfInvalid();
}
///
public async Task WhenAll( IEnumerable tasks )
{
await Task.WhenAll( tasks ).WaitAsync( _cancellation );
CancelIfInvalid();
}
///
public async Task WhenAll( params Task[] tasks )
{
var result = await Task.WhenAll( tasks ).WaitAsync( _cancellation );
CancelIfInvalid();
return result;
}
///
public async Task WhenAll( IEnumerable> tasks )
{
var result = await Task.WhenAll( tasks ).WaitAsync( _cancellation );
CancelIfInvalid();
return result;
}
///
public async Task WhenAny( params Task[] tasks )
{
await Task.WhenAny( tasks ).WaitAsync( _cancellation );
CancelIfInvalid();
}
///
public async Task WhenAny( IEnumerable tasks )
{
await Task.WhenAny( tasks ).WaitAsync( _cancellation );
CancelIfInvalid();
}
///
public void WaitAny( params Task[] tasks )
{
Task.WaitAny( tasks, _cancellation );
}
///
public void WaitAll( params Task[] tasks )
{
Task.WaitAll( tasks, _cancellation );
}
///
public async Task> WhenAny( params Task[] tasks )
{
var result = await Task.WhenAny( tasks ).WaitAsync( _cancellation );
CancelIfInvalid();
return result;
}
///
public async Task> WhenAny( IEnumerable> tasks )
{
var result = await Task.WhenAny( tasks ).WaitAsync( _cancellation );
CancelIfInvalid();
return result;
}
///
public async Task Yield()
{
await Task.Yield();
CancelIfInvalid();
}
///
/// Wait until the start of the next frame
///
public async Task Frame()
{
await SyncContext.FrameStage.Update.Await();
CancelIfInvalid();
}
///
/// Wait until the end of the frame
///
public async Task FrameEnd()
{
await SyncContext.FrameStage.PreRender.Await();
CancelIfInvalid();
}
///
/// Wait until the next fixed update
///
public async Task FixedUpdate()
{
await SyncContext.FrameStage.FixedUpdate.Await();
CancelIfInvalid();
}
}