using Nullframes.Threading.Coroutines; using Nullframes.Threading.Tasks; using System; using System.Collections; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using UnityEngine; namespace Nullframes.Threading { public sealed partial class Dispatcher : IDisableCallback { #region --- Dispatch: Coroutine --- /// /// Dispatch an that will be started and executed as a on the main thread. /// /// that is started as a . /// /// public static void Invoke(IEnumerator enumerator) { Invoke(() => { #if UNITY_EDITOR if (!Application.isPlaying) { throw new InvalidOperationException($"{nameof(Coroutine)} can only be dispatched in playmode!"); } #endif Current.StartCoroutine(enumerator); }); } /// /// Dispatch an that will be started and executed as a on the main thread. /// /// that is started as a . /// the execution cycle during which the passed is started. /// /// public static void Invoke(IEnumerator enumerator, ExecutionCycle cycle) { Invoke(() => { #if UNITY_EDITOR if (!Application.isPlaying) { Debug.LogWarning($"{nameof(Coroutine)} can only be dispatched in playmode!"); return; } #endif Current.StartCoroutine(enumerator); }, cycle); } /// /// Dispatch an that will be started and executed as a on the main thread. /// /// that is started as a . /// the target on which the coroutine will run. /// /// public static void Invoke(IEnumerator enumerator, MonoBehaviour target) { Invoke(() => { #if UNITY_EDITOR if (!Application.isPlaying) { Debug.LogWarning($"{nameof(Coroutine)} can only be dispatched in playmode!"); return; } #endif target.StartCoroutine(enumerator); }); } /// /// Dispatch an that will be started and executed as a on the main thread. /// /// that is started as a . /// the target on which the coroutine will run. /// the execution cycle during which the passed is started. /// /// public static void Invoke(IEnumerator enumerator, ExecutionCycle cycle, MonoBehaviour target) { Invoke(() => { #if UNITY_EDITOR if (!Application.isPlaying) { Debug.LogWarning($"{nameof(Coroutine)} can only be dispatched in playmode!"); return; } #endif target.StartCoroutine(enumerator); }, cycle); } //-------------------------------------------------------------------------------------------------------------- /// /// Dispatch an that will be started and executed as a on the main thread. /// /// that is started as a . /// /// optional cancellation token that can be passed to abort the coroutine prematurely. /// public static void Invoke(IEnumerator enumerator, CancellationToken ct) { Invoke(() => { if (ct.IsCancellationRequested) { return; } #if UNITY_EDITOR if (!Application.isPlaying) { throw new InvalidOperationException($"{nameof(Coroutine)} can only be dispatched in playmode!"); } #endif Current.StartCoroutine(enumerator); }); } /// /// Dispatch an that will be started and executed as a on the main thread. /// /// that is started as a . /// the execution cycle during which the passed is started. /// optional cancellation token that can be passed to abort the coroutine prematurely. /// /// public static void Invoke(IEnumerator enumerator, ExecutionCycle cycle, CancellationToken ct) { Invoke(() => { if (ct.IsCancellationRequested) { return; } #if UNITY_EDITOR if (!Application.isPlaying) { Debug.LogWarning($"{nameof(Coroutine)} can only be dispatched in playmode!"); return; } #endif Current.StartCoroutine(enumerator); }, cycle); } /// /// Dispatch an that will be started and executed as a on the main thread. /// /// that is started as a . /// the target on which the coroutine will run. /// optional cancellation token that can be passed to abort the coroutine prematurely. /// /// public static void Invoke(IEnumerator enumerator, MonoBehaviour target, CancellationToken ct) { Invoke(() => { if (ct.IsCancellationRequested) { return; } #if UNITY_EDITOR if (!Application.isPlaying) { Debug.LogWarning($"{nameof(Coroutine)} can only be dispatched in playmode!"); return; } #endif target.StartCoroutine(enumerator); }); } /// /// Dispatch an that will be started and executed as a on the main thread. /// /// that is started as a . /// the target on which the coroutine will run. /// the execution cycle during which the passed is started. /// optional cancellation token that can be passed to abort the coroutine prematurely. /// /// public static void Invoke(IEnumerator enumerator, ExecutionCycle cycle, MonoBehaviour target, CancellationToken ct) { Invoke(() => { if (ct.IsCancellationRequested) { return; } #if UNITY_EDITOR if (!Application.isPlaying) { Debug.LogWarning($"{nameof(Coroutine)} can only be dispatched in playmode!"); return; } #endif target.StartCoroutine(enumerator); }, cycle); } #endregion //-------------------------------------------------------------------------------------------------------------- #region --- Dispatch: Coroutine Async : Await Start --- /// /// Dispatch an that will be started and executed as a /// on the main thread; and return a , that when awaited on the calling thread, returns /// the after it was started on the main thread. /// /// that is started as a . /// exception is thrown if an is dispatched during edit mode. /// public static Task InvokeAsyncAwaitStart(IEnumerator enumerator) { var tcs = new TaskCompletionSource(); Invoke(() => { try { #if UNITY_EDITOR if (!Application.isPlaying) { Debug.LogWarning($"{nameof(Coroutine)} can only be dispatched in playmode!"); return; } #endif var result = Current.StartCoroutine(enumerator); tcs.TrySetResult(result); } catch (Exception exception) { tcs.TrySetException(exception); } }); return tcs.Task; } /// /// Dispatch an that will be started and executed as a /// on the main thread; and return a , that when awaited on the calling thread, returns /// the after it was started on the main thread. /// /// that is started as a . /// the target on which the coroutine will run. /// exception is thrown if an is dispatched during edit mode. /// public static Task InvokeAsyncAwaitStart(IEnumerator enumerator, MonoBehaviour target) { var tcs = new TaskCompletionSource(); Invoke(() => { #if UNITY_EDITOR if (!Application.isPlaying) { Debug.LogWarning($"{nameof(Coroutine)} can only be dispatched in playmode!"); return; } #endif var result = target.StartCoroutine(enumerator); tcs.TrySetResult(result); }); return tcs.Task; } /// /// Dispatch an that will be started and executed as a /// on the main thread; and return a , that when awaited on the calling thread, returns /// the after it was started on the main thread. /// /// that is started as a . /// optional cancellation token that can be passed to abort the task prematurely. /// exception is thrown if an is dispatched during edit mode. /// exception is thrown if the task is cancelled prematurely. /// public static Task InvokeAsyncAwaitStart(IEnumerator enumerator, CancellationToken ct) { ct.ThrowIfCancellationRequested(); var tcs = new TaskCompletionSource(); Invoke(() => { try { ct.ThrowIfCancellationRequested(); #if UNITY_EDITOR if (!Application.isPlaying) { throw new InvalidOperationException($"{nameof(Coroutine)} can only be dispatched in playmode!"); } #endif var result = Current.StartCoroutine(enumerator); tcs.TrySetResult(result); } catch (Exception exception) { tcs.TrySetException(exception); } }); return tcs.Task; } /// /// Dispatch an that will be started and executed as a /// on the main thread; and return a , that when awaited on the calling thread, returns /// the after it was started on the main thread. /// /// that is started as a . /// the target on which the coroutine will run. /// optional cancellation token that can be passed to abort the task prematurely. /// exception is thrown if an is dispatched during edit mode. /// exception is thrown if the task is cancelled prematurely. /// public static Task InvokeAsyncAwaitStart(IEnumerator enumerator, MonoBehaviour target, CancellationToken ct) { ct.ThrowIfCancellationRequested(); var tcs = new TaskCompletionSource(); Invoke(() => { ct.ThrowIfCancellationRequested(); #if UNITY_EDITOR if (!Application.isPlaying) { throw new InvalidOperationException($"{nameof(Coroutine)} can only be dispatched in playmode!"); } #endif var result = target.StartCoroutine(enumerator); tcs.TrySetResult(result); }); return tcs.Task; } /// /// Dispatch an that will be started and executed as a /// on the main thread; and return a , that when awaited on the calling thread, returns /// the after it was started on the main thread. /// /// that is started as a . /// the execution cycle during which the passed is started. /// exception is thrown if an is dispatched during edit mode. /// public static Task InvokeAsyncAwaitStart(IEnumerator enumerator, ExecutionCycle cycle) { var tcs = new TaskCompletionSource(); Invoke(() => { try { #if UNITY_EDITOR if (!Application.isPlaying) { Debug.LogWarning($"{nameof(Coroutine)} can only be dispatched in playmode!"); return; } #endif var result = Current.StartCoroutine(enumerator); tcs.TrySetResult(result); } catch (Exception exception) { tcs.TrySetException(exception); } }, cycle); return tcs.Task; } /// /// Dispatch an that will be started and executed as a /// on the main thread; and return a , that when awaited on the calling thread, returns /// the after it was started on the main thread. /// /// that is started as a . /// the execution cycle during which the passed is started. /// the target on which the coroutine will run. /// exception is thrown if an is dispatched during edit mode. /// public static Task InvokeAsyncAwaitStart(IEnumerator enumerator, ExecutionCycle cycle, MonoBehaviour target) { var tcs = new TaskCompletionSource(); Invoke(() => { #if UNITY_EDITOR if (!Application.isPlaying) { Debug.LogWarning($"{nameof(Coroutine)} can only be dispatched in playmode!"); return; } #endif var result = target.StartCoroutine(enumerator); tcs.TrySetResult(result); }, cycle); return tcs.Task; } /// /// Dispatch an that will be started and executed as a /// on the main thread; and return a , that when awaited on the calling thread, returns /// the after it was started on the main thread. /// /// that is started as a . /// the execution cycle during which the passed is started. /// optional cancellation token that can be passed to abort the task prematurely. /// exception is thrown if an is dispatched during edit mode. /// exception is thrown if the task is cancelled prematurely. /// public static Task InvokeAsyncAwaitStart(IEnumerator enumerator, ExecutionCycle cycle, CancellationToken ct) { ct.ThrowIfCancellationRequested(); var tcs = new TaskCompletionSource(); Invoke(() => { try { ct.ThrowIfCancellationRequested(); #if UNITY_EDITOR if (!Application.isPlaying) { throw new InvalidOperationException($"{nameof(Coroutine)} can only be dispatched in playmode!"); } #endif var result = Current.StartCoroutine(enumerator); tcs.TrySetResult(result); } catch (Exception exception) { tcs.TrySetException(exception); } }, cycle); return tcs.Task; } /// /// Dispatch an that will be started and executed as a /// on the main thread; and return a , that when awaited on the calling thread, returns /// the after it was started on the main thread. /// /// that is started as a . /// the execution cycle during which the passed is started. /// the target on which the coroutine will run. /// optional cancellation token that can be passed to abort the task prematurely. /// exception is thrown if an is dispatched during edit mode. /// exception is thrown if the task is cancelled prematurely. /// public static Task InvokeAsyncAwaitStart(IEnumerator enumerator, ExecutionCycle cycle, MonoBehaviour target, CancellationToken ct) { ct.ThrowIfCancellationRequested(); var tcs = new TaskCompletionSource(); Invoke(() => { ct.ThrowIfCancellationRequested(); #if UNITY_EDITOR if (!Application.isPlaying) { throw new InvalidOperationException($"{nameof(Coroutine)} can only be dispatched in playmode!"); } #endif var result = target.StartCoroutine(enumerator); tcs.TrySetResult(result); }, cycle); return tcs.Task; } #endregion //-------------------------------------------------------------------------------------------------------------- #region --- Dispatch: Coroutine Async : Await Completion --- /// /// Dispatch an that is executed as a /// on the main thread and return a , that can be awaited and returns /// after the has completed on the main thread. /// /// that is started as a . /// /// exception is thrown if an is dispatched during edit mode. /// public static Task InvokeAsyncAwaitCompletion(IEnumerator enumerator, bool throwExceptions = true) { var tcs = new TaskCompletionSource(); Invoke(() => { try { StartCoroutineInternal(enumerator, tcs, CancellationToken.None, throwExceptions); } catch (Exception exception) { tcs.TrySetException(exception); } }); return tcs.Task; } /// /// Dispatch an that will be started and executed as a /// on the main thread; and return a , that when awaited on the calling thread, returns /// the after the has completed on the main thread. /// /// that is started as a . /// /// /// exception is thrown if an is dispatched during edit mode. /// public static Task InvokeAsyncAwaitCompletion(IEnumerator enumerator, CancellationToken ct, bool throwExceptions = true) { var tcs = new TaskCompletionSource(); Invoke(() => { try { StartCoroutineInternal(enumerator, tcs, ct, throwExceptions); } catch (Exception exception) { tcs.TrySetException(exception); } }); return tcs.Task; } /// /// Dispatch an that will be started and executed as a /// on the main thread; and return a , that when awaited on the calling thread, returns /// the after the has completed on the main thread. /// /// that is started as a . /// the target on which the coroutine will run. /// /// exception is thrown if an is dispatched during edit mode. /// public static Task InvokeAsyncAwaitCompletion(IEnumerator enumerator, MonoBehaviour target, bool throwExceptions = true) { var tcs = new TaskCompletionSource(); Invoke(() => { try { StartCoroutineInternal(enumerator, tcs, CancellationToken.None, throwExceptions, target); } catch (Exception exception) { tcs.TrySetException(exception); } }); return tcs.Task; } /// /// Dispatch an that will be started and executed as a /// on the main thread; and return a , that when awaited on the calling thread, returns /// the after the has completed on the main thread. /// /// that is started as a . /// the target on which the coroutine will run. /// /// /// exception is thrown if an is dispatched during edit mode. /// public static Task InvokeAsyncAwaitCompletion(IEnumerator enumerator, MonoBehaviour target, CancellationToken ct, bool throwExceptions = true) { var tcs = new TaskCompletionSource(); Invoke(() => { try { StartCoroutineInternal(enumerator, tcs, ct, throwExceptions, target); } catch (Exception exception) { tcs.TrySetException(exception); } }); return tcs.Task; } //-------------------------------------------------------------------------------------------------------------- /// /// Dispatch an that will be started and executed as a /// on the main thread; and return a , that when awaited on the calling thread, returns /// the after the has completed on the main thread. /// /// that is started as a . /// /// /// exception is thrown if an is dispatched during edit mode. /// public static Task InvokeAsyncAwaitCompletion(IEnumerator enumerator, ExecutionCycle cycle, bool throwExceptions = true) { var tcs = new TaskCompletionSource(); Invoke(() => { try { StartCoroutineInternal(enumerator, tcs, CancellationToken.None, throwExceptions); } catch (Exception exception) { tcs.TrySetException(exception); } }, cycle); return tcs.Task; } /// /// Dispatch an that will be started and executed as a /// on the main thread; and return a , that when awaited on the calling thread, returns /// the after the has completed on the main thread. /// /// that is started as a . /// /// /// /// exception is thrown if an is dispatched during edit mode. /// public static Task InvokeAsyncAwaitCompletion(IEnumerator enumerator, ExecutionCycle cycle, CancellationToken ct, bool throwExceptions = true) { var tcs = new TaskCompletionSource(); Invoke(() => { try { StartCoroutineInternal(enumerator, tcs, ct, throwExceptions); } catch (Exception exception) { tcs.TrySetException(exception); } }, cycle); return tcs.Task; } /// /// Dispatch an that will be started and executed as a /// on the main thread; and return a , that when awaited on the calling thread, returns /// the after the has completed on the main thread. /// /// that is started as a . /// /// the target on which the coroutine will run. /// /// exception is thrown if an is dispatched during edit mode. /// public static Task InvokeAsyncAwaitCompletion(IEnumerator enumerator, ExecutionCycle cycle, MonoBehaviour target, bool throwExceptions = true) { var tcs = new TaskCompletionSource(); Invoke(() => { try { StartCoroutineInternal(enumerator, tcs, CancellationToken.None, throwExceptions, target); } catch (Exception exception) { tcs.TrySetException(exception); } }, cycle); return tcs.Task; } /// /// Dispatch an that will be started and executed as a /// on the main thread; and return a , that when awaited on the calling thread, returns /// the after the has completed on the main thread. /// /// that is started as a . /// /// the target on which the coroutine will run. /// Optional cancellation token /// /// exception is thrown if an is dispatched during edit mode. /// public static Task InvokeAsyncAwaitCompletion(IEnumerator enumerator, ExecutionCycle cycle, MonoBehaviour target, CancellationToken ct, bool throwExceptions = true) { var tcs = new TaskCompletionSource(); Invoke(() => { try { StartCoroutineInternal(enumerator, tcs, ct, throwExceptions, target); } catch (Exception exception) { tcs.TrySetException(exception); } }, cycle); return tcs.Task; } #endregion //-------------------------------------------------------------------------------------------------------------- #region --- Stop Coroutine --- /// /// Stop a coroutine that is running on the Dispatcher. /// /// The coroutine that should be cancelled public static void CancelCoroutine(Coroutine coroutine) { Invoke(() => { Current.StopCoroutine(coroutine); }); } /// /// Stop a coroutine that is running on the Dispatcher and return a Task that when awaited will yield after /// the coroutine was successfully stopped on the main thread. /// /// The coroutine that should be cancelled public static Task CancelCoroutineAsync(Coroutine coroutine) { var tcs = new TaskCompletionSource(); Invoke(() => { try { Current.StopCoroutine(coroutine); tcs.SetCompleted(); } catch (Exception exception) { tcs.SetException(exception); } }); return tcs.Task; } /// /// Stop a coroutine that is running on the Dispatcher and return a Task that when awaited will yield after /// the coroutine was successfully stopped on the main thread. /// /// The coroutine that should be cancelled /// Optional cancellation token /// Determines if an exception should be thrown on cancellation public static Task CancelCoroutineAsync(Coroutine coroutine, CancellationToken ct, bool throwOnCancellation = true) { var tcs = new TaskCompletionSource(); Invoke(() => { try { ct.ThrowIfCancellationRequested(); Current.StopCoroutine(coroutine); tcs.SetCompleted(); } catch (OperationCanceledException oce) { if (throwOnCancellation) { tcs.SetException(oce); } else { tcs.SetCompleted(); } } catch (Exception exception) { tcs.SetException(exception); } }); return tcs.Task; } #endregion //-------------------------------------------------------------------------------------------------------------- #region --- Internal Coroutine --- /// /// Start an internal coroutine with cancellation support and exception handling. /// z [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void StartCoroutineInternal(IEnumerator coroutine, TaskCompletionSource tcs, CancellationToken ct, bool throwExceptions, MonoBehaviour target) { if (throwExceptions) { target.StartCoroutineExceptionSensitive(coroutine, tcs.TrySetException, tcs.TrySetCompleted, ct); } else { target.StartCoroutineExceptionSensitive(coroutine, tcs.TrySetResult, tcs.TrySetCompleted, ct); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void StartCoroutineInternal(IEnumerator coroutine, TaskCompletionSource tcs, CancellationToken ct, bool throwExceptions) { if (throwExceptions) { Current.StartCoroutineExceptionSensitive(coroutine, tcs.TrySetException, tcs.TrySetCompleted, ct, current); } else { Current.StartCoroutineExceptionSensitive(coroutine, tcs.TrySetResult, tcs.TrySetCompleted, ct, current); } } #endregion #region --- Interface: IDisableCallback--- /// /// Event is be invoked when the target behaviour is disabled or destroyed. /// public event Action Disabled; private void OnDisable() { Disabled?.Invoke(); Disabled = null; } #endregion } }