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(); } }