From 2337d705ec07c6e574b77f24e0a20bb966259131 Mon Sep 17 00:00:00 2001 From: neuecc Date: Mon, 8 Jun 2020 01:45:41 +0900 Subject: [PATCH] WithCancellation uses native timing of asyncOperation --- .../External/AddressableAsyncExtensions.cs | 261 ++++++- .../UniTask/Runtime/UnityAsyncExtensions.cs | 637 +++++++++++++++++- .../UniTask/Runtime/UnityAsyncExtensions.tt | 137 +++- 3 files changed, 1021 insertions(+), 14 deletions(-) diff --git a/src/UniTask/Assets/Plugins/UniTask/Runtime/External/AddressableAsyncExtensions.cs b/src/UniTask/Assets/Plugins/UniTask/Runtime/External/AddressableAsyncExtensions.cs index 8edda50..4df373b 100644 --- a/src/UniTask/Assets/Plugins/UniTask/Runtime/External/AddressableAsyncExtensions.cs +++ b/src/UniTask/Assets/Plugins/UniTask/Runtime/External/AddressableAsyncExtensions.cs @@ -23,7 +23,7 @@ namespace Cysharp.Threading.Tasks public static UniTask WithCancellation(this AsyncOperationHandle handle, CancellationToken cancellationToken) { if (handle.IsDone) return UniTask.CompletedTask; - return new UniTask(AsyncOperationHandleConfiguredSource.Create(handle, PlayerLoopTiming.Update, null, cancellationToken, out var token), token); + return new UniTask(AsyncOperationHandleWithCancellationSource.Create(handle, cancellationToken, out var token), token); } public static UniTask ToUniTask(this AsyncOperationHandle handle, IProgress progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken)) @@ -77,6 +77,132 @@ namespace Cysharp.Threading.Tasks } } + sealed class AsyncOperationHandleWithCancellationSource : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode + { + static TaskPool pool; + public AsyncOperationHandleWithCancellationSource NextNode { get; set; } + + static AsyncOperationHandleWithCancellationSource() + { + TaskPool.RegisterSizeGetter(typeof(AsyncOperationHandleWithCancellationSource), () => pool.Size); + } + + readonly Action continuationAction; + AsyncOperationHandle handle; + CancellationToken cancellationToken; + bool completed; + + UniTaskCompletionSourceCore core; + + AsyncOperationHandleWithCancellationSource() + { + continuationAction = Continuation; + } + + public static IUniTaskSource Create(AsyncOperationHandle handle, CancellationToken cancellationToken, out short token) + { + if (cancellationToken.IsCancellationRequested) + { + return AutoResetUniTaskCompletionSource.CreateFromCanceled(cancellationToken, out token); + } + + if (!pool.TryPop(out var result)) + { + result = new AsyncOperationHandleWithCancellationSource(); + } + + result.handle = handle; + result.cancellationToken = cancellationToken; + result.completed = false; + + TaskTracker.TrackActiveTask(result, 3); + + PlayerLoopHelper.AddAction(PlayerLoopTiming.Update, result); + + handle.Completed += result.continuationAction; + + token = result.core.Version; + return result; + } + + void Continuation(AsyncOperationHandle _) + { + handle.Completed -= continuationAction; + + if (completed) + { + TryReturn(); + } + else + { + completed = true; + if (handle.Status == AsyncOperationStatus.Failed) + { + core.TrySetException(handle.OperationException); + } + else + { + core.TrySetResult(AsyncUnit.Default); + } + } + } + + public void GetResult(short token) + { + core.GetResult(token); + } + + public UniTaskStatus GetStatus(short token) + { + return core.GetStatus(token); + } + + public UniTaskStatus UnsafeGetStatus() + { + return core.UnsafeGetStatus(); + } + + public void OnCompleted(Action continuation, object state, short token) + { + core.OnCompleted(continuation, state, token); + } + + public bool MoveNext() + { + if (completed) + { + TryReturn(); + return false; + } + + if (cancellationToken.IsCancellationRequested) + { + completed = true; + core.TrySetCanceled(cancellationToken); + return false; + } + + return true; + } + + bool TryReturn() + { + TaskTracker.RemoveTracking(this); + core.Reset(); + handle = default; + cancellationToken = default; + return pool.TryPush(this); + } + + ~AsyncOperationHandleWithCancellationSource() + { + if (TryReturn()) + { + GC.ReRegisterForFinalize(this); + } + } + } + sealed class AsyncOperationHandleConfiguredSource : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode { static TaskPool pool; @@ -210,7 +336,7 @@ namespace Cysharp.Threading.Tasks public static UniTask WithCancellation(this AsyncOperationHandle handle, CancellationToken cancellationToken) { if (handle.IsDone) return UniTask.FromResult(handle.Result); - return new UniTask(AsyncOperationHandleConfiguredSource.Create(handle, PlayerLoopTiming.Update, null, cancellationToken, out var token), token); + return new UniTask(AsyncOperationHandleWithCancellationSource.Create(handle, cancellationToken, out var token), token); } public static UniTask ToUniTask(this AsyncOperationHandle handle, IProgress progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken)) @@ -265,6 +391,137 @@ namespace Cysharp.Threading.Tasks } } + sealed class AsyncOperationHandleWithCancellationSource : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode> + { + static TaskPool> pool; + public AsyncOperationHandleWithCancellationSource NextNode { get; set; } + + static AsyncOperationHandleWithCancellationSource() + { + TaskPool.RegisterSizeGetter(typeof(AsyncOperationHandleWithCancellationSource), () => pool.Size); + } + + readonly Action> continuationAction; + AsyncOperationHandle handle; + CancellationToken cancellationToken; + bool completed; + + UniTaskCompletionSourceCore core; + + AsyncOperationHandleWithCancellationSource() + { + continuationAction = Continuation; + } + + public static IUniTaskSource Create(AsyncOperationHandle handle, CancellationToken cancellationToken, out short token) + { + if (cancellationToken.IsCancellationRequested) + { + return AutoResetUniTaskCompletionSource.CreateFromCanceled(cancellationToken, out token); + } + + if (!pool.TryPop(out var result)) + { + result = new AsyncOperationHandleWithCancellationSource(); + } + + result.handle = handle; + result.cancellationToken = cancellationToken; + result.completed = false; + + TaskTracker.TrackActiveTask(result, 3); + + PlayerLoopHelper.AddAction(PlayerLoopTiming.Update, result); + + handle.Completed += result.continuationAction; + + token = result.core.Version; + return result; + } + + void Continuation(AsyncOperationHandle _) + { + handle.Completed -= continuationAction; + + if (completed) + { + TryReturn(); + } + else + { + completed = true; + if (handle.Status == AsyncOperationStatus.Failed) + { + core.TrySetException(handle.OperationException); + } + else + { + core.TrySetResult(handle.Result); + } + } + } + + public T GetResult(short token) + { + return core.GetResult(token); + } + + void IUniTaskSource.GetResult(short token) + { + GetResult(token); + } + + public UniTaskStatus GetStatus(short token) + { + return core.GetStatus(token); + } + + public UniTaskStatus UnsafeGetStatus() + { + return core.UnsafeGetStatus(); + } + + public void OnCompleted(Action continuation, object state, short token) + { + core.OnCompleted(continuation, state, token); + } + + public bool MoveNext() + { + if (completed) + { + TryReturn(); + return false; + } + + if (cancellationToken.IsCancellationRequested) + { + completed = true; + core.TrySetCanceled(cancellationToken); + return false; + } + + return true; + } + + bool TryReturn() + { + TaskTracker.RemoveTracking(this); + core.Reset(); + handle = default; + cancellationToken = default; + return pool.TryPush(this); + } + + ~AsyncOperationHandleWithCancellationSource() + { + if (TryReturn()) + { + GC.ReRegisterForFinalize(this); + } + } + } + sealed class AsyncOperationHandleConfiguredSource : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode> { static TaskPool> pool; diff --git a/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.cs b/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.cs index 0483b48..9a85e37 100644 --- a/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.cs +++ b/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.cs @@ -25,7 +25,7 @@ namespace Cysharp.Threading.Tasks { Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation)); if (asyncOperation.isDone) return UniTask.CompletedTask; - return new UniTask(AsyncOperationConfiguredSource.Create(asyncOperation, PlayerLoopTiming.Update, null, cancellationToken, out var token), token); + return new UniTask(AsyncOperationWithCancellationSource.Create(asyncOperation, cancellationToken, out var token), token); } public static UniTask ToUniTask(this AsyncOperation asyncOperation, IProgress progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken)) @@ -75,7 +75,127 @@ namespace Cysharp.Threading.Tasks } } - class AsyncOperationConfiguredSource : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode + sealed class AsyncOperationWithCancellationSource : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode + { + static TaskPool pool; + public AsyncOperationWithCancellationSource NextNode { get; set; } + + static AsyncOperationWithCancellationSource() + { + TaskPool.RegisterSizeGetter(typeof(AsyncOperationWithCancellationSource), () => pool.Size); + } + + readonly Action continuationAction; + AsyncOperation asyncOperation; + CancellationToken cancellationToken; + bool completed; + + UniTaskCompletionSourceCore core; + + AsyncOperationWithCancellationSource() + { + continuationAction = Continuation; + } + + public static IUniTaskSource Create(AsyncOperation asyncOperation, CancellationToken cancellationToken, out short token) + { + if (cancellationToken.IsCancellationRequested) + { + return AutoResetUniTaskCompletionSource.CreateFromCanceled(cancellationToken, out token); + } + + if (!pool.TryPop(out var result)) + { + result = new AsyncOperationWithCancellationSource(); + } + + result.asyncOperation = asyncOperation; + result.cancellationToken = cancellationToken; + result.completed = false; + + TaskTracker.TrackActiveTask(result, 3); + + PlayerLoopHelper.AddAction(PlayerLoopTiming.Update, result); + + asyncOperation.completed += result.continuationAction; + + token = result.core.Version; + return result; + } + + void Continuation(AsyncOperation _) + { + asyncOperation.completed -= continuationAction; + + if (completed) + { + TryReturn(); + } + else + { + completed = true; + core.TrySetResult(AsyncUnit.Default); + } + } + + public void GetResult(short token) + { + core.GetResult(token); + } + + + public UniTaskStatus GetStatus(short token) + { + return core.GetStatus(token); + } + + public UniTaskStatus UnsafeGetStatus() + { + return core.UnsafeGetStatus(); + } + + public void OnCompleted(Action continuation, object state, short token) + { + core.OnCompleted(continuation, state, token); + } + + public bool MoveNext() + { + if (completed) + { + TryReturn(); + return false; + } + + if (cancellationToken.IsCancellationRequested) + { + completed = true; + core.TrySetCanceled(cancellationToken); + return false; + } + + return true; + } + + bool TryReturn() + { + TaskTracker.RemoveTracking(this); + core.Reset(); + asyncOperation = default; + cancellationToken = default; + return pool.TryPush(this); + } + + ~AsyncOperationWithCancellationSource() + { + if (TryReturn()) + { + GC.ReRegisterForFinalize(this); + } + } + } + + sealed class AsyncOperationConfiguredSource : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode { static TaskPool pool; public AsyncOperationConfiguredSource NextNode { get; set; } @@ -203,7 +323,7 @@ namespace Cysharp.Threading.Tasks { Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation)); if (asyncOperation.isDone) return UniTask.FromResult(asyncOperation.asset); - return new UniTask(ResourceRequestConfiguredSource.Create(asyncOperation, PlayerLoopTiming.Update, null, cancellationToken, out var token), token); + return new UniTask(ResourceRequestWithCancellationSource.Create(asyncOperation, cancellationToken, out var token), token); } public static UniTask ToUniTask(this ResourceRequest asyncOperation, IProgress progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken)) @@ -257,7 +377,131 @@ namespace Cysharp.Threading.Tasks } } - class ResourceRequestConfiguredSource : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode + sealed class ResourceRequestWithCancellationSource : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode + { + static TaskPool pool; + public ResourceRequestWithCancellationSource NextNode { get; set; } + + static ResourceRequestWithCancellationSource() + { + TaskPool.RegisterSizeGetter(typeof(ResourceRequestWithCancellationSource), () => pool.Size); + } + + readonly Action continuationAction; + ResourceRequest asyncOperation; + CancellationToken cancellationToken; + bool completed; + + UniTaskCompletionSourceCore core; + + ResourceRequestWithCancellationSource() + { + continuationAction = Continuation; + } + + public static IUniTaskSource Create(ResourceRequest asyncOperation, CancellationToken cancellationToken, out short token) + { + if (cancellationToken.IsCancellationRequested) + { + return AutoResetUniTaskCompletionSource.CreateFromCanceled(cancellationToken, out token); + } + + if (!pool.TryPop(out var result)) + { + result = new ResourceRequestWithCancellationSource(); + } + + result.asyncOperation = asyncOperation; + result.cancellationToken = cancellationToken; + result.completed = false; + + TaskTracker.TrackActiveTask(result, 3); + + PlayerLoopHelper.AddAction(PlayerLoopTiming.Update, result); + + asyncOperation.completed += result.continuationAction; + + token = result.core.Version; + return result; + } + + void Continuation(AsyncOperation _) + { + asyncOperation.completed -= continuationAction; + + if (completed) + { + TryReturn(); + } + else + { + completed = true; + core.TrySetResult(asyncOperation.asset); + } + } + + public UnityEngine.Object GetResult(short token) + { + return core.GetResult(token); + } + + void IUniTaskSource.GetResult(short token) + { + GetResult(token); + } + + public UniTaskStatus GetStatus(short token) + { + return core.GetStatus(token); + } + + public UniTaskStatus UnsafeGetStatus() + { + return core.UnsafeGetStatus(); + } + + public void OnCompleted(Action continuation, object state, short token) + { + core.OnCompleted(continuation, state, token); + } + + public bool MoveNext() + { + if (completed) + { + TryReturn(); + return false; + } + + if (cancellationToken.IsCancellationRequested) + { + completed = true; + core.TrySetCanceled(cancellationToken); + return false; + } + + return true; + } + + bool TryReturn() + { + TaskTracker.RemoveTracking(this); + core.Reset(); + asyncOperation = default; + cancellationToken = default; + return pool.TryPush(this); + } + + ~ResourceRequestWithCancellationSource() + { + if (TryReturn()) + { + GC.ReRegisterForFinalize(this); + } + } + } + + sealed class ResourceRequestConfiguredSource : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode { static TaskPool pool; public ResourceRequestConfiguredSource NextNode { get; set; } @@ -389,7 +633,7 @@ namespace Cysharp.Threading.Tasks { Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation)); if (asyncOperation.isDone) return UniTask.FromResult(asyncOperation.asset); - return new UniTask(AssetBundleRequestConfiguredSource.Create(asyncOperation, PlayerLoopTiming.Update, null, cancellationToken, out var token), token); + return new UniTask(AssetBundleRequestWithCancellationSource.Create(asyncOperation, cancellationToken, out var token), token); } public static UniTask ToUniTask(this AssetBundleRequest asyncOperation, IProgress progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken)) @@ -443,7 +687,131 @@ namespace Cysharp.Threading.Tasks } } - class AssetBundleRequestConfiguredSource : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode + sealed class AssetBundleRequestWithCancellationSource : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode + { + static TaskPool pool; + public AssetBundleRequestWithCancellationSource NextNode { get; set; } + + static AssetBundleRequestWithCancellationSource() + { + TaskPool.RegisterSizeGetter(typeof(AssetBundleRequestWithCancellationSource), () => pool.Size); + } + + readonly Action continuationAction; + AssetBundleRequest asyncOperation; + CancellationToken cancellationToken; + bool completed; + + UniTaskCompletionSourceCore core; + + AssetBundleRequestWithCancellationSource() + { + continuationAction = Continuation; + } + + public static IUniTaskSource Create(AssetBundleRequest asyncOperation, CancellationToken cancellationToken, out short token) + { + if (cancellationToken.IsCancellationRequested) + { + return AutoResetUniTaskCompletionSource.CreateFromCanceled(cancellationToken, out token); + } + + if (!pool.TryPop(out var result)) + { + result = new AssetBundleRequestWithCancellationSource(); + } + + result.asyncOperation = asyncOperation; + result.cancellationToken = cancellationToken; + result.completed = false; + + TaskTracker.TrackActiveTask(result, 3); + + PlayerLoopHelper.AddAction(PlayerLoopTiming.Update, result); + + asyncOperation.completed += result.continuationAction; + + token = result.core.Version; + return result; + } + + void Continuation(AsyncOperation _) + { + asyncOperation.completed -= continuationAction; + + if (completed) + { + TryReturn(); + } + else + { + completed = true; + core.TrySetResult(asyncOperation.asset); + } + } + + public UnityEngine.Object GetResult(short token) + { + return core.GetResult(token); + } + + void IUniTaskSource.GetResult(short token) + { + GetResult(token); + } + + public UniTaskStatus GetStatus(short token) + { + return core.GetStatus(token); + } + + public UniTaskStatus UnsafeGetStatus() + { + return core.UnsafeGetStatus(); + } + + public void OnCompleted(Action continuation, object state, short token) + { + core.OnCompleted(continuation, state, token); + } + + public bool MoveNext() + { + if (completed) + { + TryReturn(); + return false; + } + + if (cancellationToken.IsCancellationRequested) + { + completed = true; + core.TrySetCanceled(cancellationToken); + return false; + } + + return true; + } + + bool TryReturn() + { + TaskTracker.RemoveTracking(this); + core.Reset(); + asyncOperation = default; + cancellationToken = default; + return pool.TryPush(this); + } + + ~AssetBundleRequestWithCancellationSource() + { + if (TryReturn()) + { + GC.ReRegisterForFinalize(this); + } + } + } + + sealed class AssetBundleRequestConfiguredSource : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode { static TaskPool pool; public AssetBundleRequestConfiguredSource NextNode { get; set; } @@ -575,7 +943,7 @@ namespace Cysharp.Threading.Tasks { Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation)); if (asyncOperation.isDone) return UniTask.FromResult(asyncOperation.assetBundle); - return new UniTask(AssetBundleCreateRequestConfiguredSource.Create(asyncOperation, PlayerLoopTiming.Update, null, cancellationToken, out var token), token); + return new UniTask(AssetBundleCreateRequestWithCancellationSource.Create(asyncOperation, cancellationToken, out var token), token); } public static UniTask ToUniTask(this AssetBundleCreateRequest asyncOperation, IProgress progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken)) @@ -629,7 +997,131 @@ namespace Cysharp.Threading.Tasks } } - class AssetBundleCreateRequestConfiguredSource : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode + sealed class AssetBundleCreateRequestWithCancellationSource : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode + { + static TaskPool pool; + public AssetBundleCreateRequestWithCancellationSource NextNode { get; set; } + + static AssetBundleCreateRequestWithCancellationSource() + { + TaskPool.RegisterSizeGetter(typeof(AssetBundleCreateRequestWithCancellationSource), () => pool.Size); + } + + readonly Action continuationAction; + AssetBundleCreateRequest asyncOperation; + CancellationToken cancellationToken; + bool completed; + + UniTaskCompletionSourceCore core; + + AssetBundleCreateRequestWithCancellationSource() + { + continuationAction = Continuation; + } + + public static IUniTaskSource Create(AssetBundleCreateRequest asyncOperation, CancellationToken cancellationToken, out short token) + { + if (cancellationToken.IsCancellationRequested) + { + return AutoResetUniTaskCompletionSource.CreateFromCanceled(cancellationToken, out token); + } + + if (!pool.TryPop(out var result)) + { + result = new AssetBundleCreateRequestWithCancellationSource(); + } + + result.asyncOperation = asyncOperation; + result.cancellationToken = cancellationToken; + result.completed = false; + + TaskTracker.TrackActiveTask(result, 3); + + PlayerLoopHelper.AddAction(PlayerLoopTiming.Update, result); + + asyncOperation.completed += result.continuationAction; + + token = result.core.Version; + return result; + } + + void Continuation(AsyncOperation _) + { + asyncOperation.completed -= continuationAction; + + if (completed) + { + TryReturn(); + } + else + { + completed = true; + core.TrySetResult(asyncOperation.assetBundle); + } + } + + public AssetBundle GetResult(short token) + { + return core.GetResult(token); + } + + void IUniTaskSource.GetResult(short token) + { + GetResult(token); + } + + public UniTaskStatus GetStatus(short token) + { + return core.GetStatus(token); + } + + public UniTaskStatus UnsafeGetStatus() + { + return core.UnsafeGetStatus(); + } + + public void OnCompleted(Action continuation, object state, short token) + { + core.OnCompleted(continuation, state, token); + } + + public bool MoveNext() + { + if (completed) + { + TryReturn(); + return false; + } + + if (cancellationToken.IsCancellationRequested) + { + completed = true; + core.TrySetCanceled(cancellationToken); + return false; + } + + return true; + } + + bool TryReturn() + { + TaskTracker.RemoveTracking(this); + core.Reset(); + asyncOperation = default; + cancellationToken = default; + return pool.TryPush(this); + } + + ~AssetBundleCreateRequestWithCancellationSource() + { + if (TryReturn()) + { + GC.ReRegisterForFinalize(this); + } + } + } + + sealed class AssetBundleCreateRequestConfiguredSource : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode { static TaskPool pool; public AssetBundleCreateRequestConfiguredSource NextNode { get; set; } @@ -762,7 +1254,7 @@ namespace Cysharp.Threading.Tasks { Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation)); if (asyncOperation.isDone) return UniTask.FromResult(asyncOperation.webRequest); - return new UniTask(UnityWebRequestAsyncOperationConfiguredSource.Create(asyncOperation, PlayerLoopTiming.Update, null, cancellationToken, out var token), token); + return new UniTask(UnityWebRequestAsyncOperationWithCancellationSource.Create(asyncOperation, cancellationToken, out var token), token); } public static UniTask ToUniTask(this UnityWebRequestAsyncOperation asyncOperation, IProgress progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken)) @@ -816,7 +1308,132 @@ namespace Cysharp.Threading.Tasks } } - class UnityWebRequestAsyncOperationConfiguredSource : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode + sealed class UnityWebRequestAsyncOperationWithCancellationSource : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode + { + static TaskPool pool; + public UnityWebRequestAsyncOperationWithCancellationSource NextNode { get; set; } + + static UnityWebRequestAsyncOperationWithCancellationSource() + { + TaskPool.RegisterSizeGetter(typeof(UnityWebRequestAsyncOperationWithCancellationSource), () => pool.Size); + } + + readonly Action continuationAction; + UnityWebRequestAsyncOperation asyncOperation; + CancellationToken cancellationToken; + bool completed; + + UniTaskCompletionSourceCore core; + + UnityWebRequestAsyncOperationWithCancellationSource() + { + continuationAction = Continuation; + } + + public static IUniTaskSource Create(UnityWebRequestAsyncOperation asyncOperation, CancellationToken cancellationToken, out short token) + { + if (cancellationToken.IsCancellationRequested) + { + return AutoResetUniTaskCompletionSource.CreateFromCanceled(cancellationToken, out token); + } + + if (!pool.TryPop(out var result)) + { + result = new UnityWebRequestAsyncOperationWithCancellationSource(); + } + + result.asyncOperation = asyncOperation; + result.cancellationToken = cancellationToken; + result.completed = false; + + TaskTracker.TrackActiveTask(result, 3); + + PlayerLoopHelper.AddAction(PlayerLoopTiming.Update, result); + + asyncOperation.completed += result.continuationAction; + + token = result.core.Version; + return result; + } + + void Continuation(AsyncOperation _) + { + asyncOperation.completed -= continuationAction; + + if (completed) + { + TryReturn(); + } + else + { + completed = true; + core.TrySetResult(asyncOperation.webRequest); + } + } + + public UnityWebRequest GetResult(short token) + { + return core.GetResult(token); + } + + void IUniTaskSource.GetResult(short token) + { + GetResult(token); + } + + public UniTaskStatus GetStatus(short token) + { + return core.GetStatus(token); + } + + public UniTaskStatus UnsafeGetStatus() + { + return core.UnsafeGetStatus(); + } + + public void OnCompleted(Action continuation, object state, short token) + { + core.OnCompleted(continuation, state, token); + } + + public bool MoveNext() + { + if (completed) + { + TryReturn(); + return false; + } + + if (cancellationToken.IsCancellationRequested) + { + completed = true; + asyncOperation.webRequest.Abort(); + core.TrySetCanceled(cancellationToken); + return false; + } + + return true; + } + + bool TryReturn() + { + TaskTracker.RemoveTracking(this); + core.Reset(); + asyncOperation = default; + cancellationToken = default; + return pool.TryPush(this); + } + + ~UnityWebRequestAsyncOperationWithCancellationSource() + { + if (TryReturn()) + { + GC.ReRegisterForFinalize(this); + } + } + } + + sealed class UnityWebRequestAsyncOperationConfiguredSource : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode { static TaskPool pool; public UnityWebRequestAsyncOperationConfiguredSource NextNode { get; set; } diff --git a/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.tt b/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.tt index 218836e..93701ef 100644 --- a/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.tt +++ b/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.tt @@ -49,7 +49,7 @@ namespace Cysharp.Threading.Tasks { Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation)); if (asyncOperation.isDone) return <#= IsVoid(t) ? "UniTask.CompletedTask" : $"UniTask.FromResult(asyncOperation.{t.returnField})" #>; - return new <#= ToUniTaskReturnType(t.returnType) #>(<#= t.typeName #>ConfiguredSource.Create(asyncOperation, PlayerLoopTiming.Update, null, cancellationToken, out var token), token); + return new <#= ToUniTaskReturnType(t.returnType) #>(<#= t.typeName #>WithCancellationSource.Create(asyncOperation, cancellationToken, out var token), token); } public static <#= ToUniTaskReturnType(t.returnType) #> ToUniTask(this <#= t.typeName #> asyncOperation, IProgress progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken)) @@ -111,7 +111,140 @@ namespace Cysharp.Threading.Tasks } } - class <#= t.typeName #>ConfiguredSource : <#= ToIUniTaskSourceReturnType(t.returnType) #>, IPlayerLoopItem, ITaskPoolNode<<#= t.typeName #>ConfiguredSource> + sealed class <#= t.typeName #>WithCancellationSource : <#= ToIUniTaskSourceReturnType(t.returnType) #>, IPlayerLoopItem, ITaskPoolNode<<#= t.typeName #>WithCancellationSource> + { + static TaskPool<<#= t.typeName #>WithCancellationSource> pool; + public <#= t.typeName #>WithCancellationSource NextNode { get; set; } + + static <#= t.typeName #>WithCancellationSource() + { + TaskPool.RegisterSizeGetter(typeof(<#= t.typeName #>WithCancellationSource), () => pool.Size); + } + + readonly Action continuationAction; + <#= t.typeName #> asyncOperation; + CancellationToken cancellationToken; + bool completed; + + UniTaskCompletionSourceCore<<#= IsVoid(t) ? "AsyncUnit" : t.returnType #>> core; + + <#= t.typeName #>WithCancellationSource() + { + continuationAction = Continuation; + } + + public static <#= ToIUniTaskSourceReturnType(t.returnType) #> Create(<#= t.typeName #> asyncOperation, CancellationToken cancellationToken, out short token) + { + if (cancellationToken.IsCancellationRequested) + { + return AutoResetUniTaskCompletionSource<#= IsVoid(t) ? "" : $"<{t.returnType}>" #>.CreateFromCanceled(cancellationToken, out token); + } + + if (!pool.TryPop(out var result)) + { + result = new <#= t.typeName #>WithCancellationSource(); + } + + result.asyncOperation = asyncOperation; + result.cancellationToken = cancellationToken; + result.completed = false; + + TaskTracker.TrackActiveTask(result, 3); + + PlayerLoopHelper.AddAction(PlayerLoopTiming.Update, result); + + asyncOperation.completed += result.continuationAction; + + token = result.core.Version; + return result; + } + + void Continuation(AsyncOperation _) + { + asyncOperation.completed -= continuationAction; + + if (completed) + { + TryReturn(); + } + else + { + completed = true; + core.TrySetResult(<#= IsVoid(t) ? "AsyncUnit.Default" : $"asyncOperation.{t.returnField}" #>); + } + } + + public <#= t.returnType #> GetResult(short token) + { +<# if (!IsVoid(t)) { #> + return core.GetResult(token); +<# } else { #> + core.GetResult(token); +<# } #> + } + +<# if (!IsVoid(t)) { #> + void IUniTaskSource.GetResult(short token) + { + GetResult(token); + } +<# } #> + + public UniTaskStatus GetStatus(short token) + { + return core.GetStatus(token); + } + + public UniTaskStatus UnsafeGetStatus() + { + return core.UnsafeGetStatus(); + } + + public void OnCompleted(Action continuation, object state, short token) + { + core.OnCompleted(continuation, state, token); + } + + public bool MoveNext() + { + if (completed) + { + TryReturn(); + return false; + } + + if (cancellationToken.IsCancellationRequested) + { + completed = true; +<# if(t.returnType == "UnityWebRequest") { #> + asyncOperation.webRequest.Abort(); +<# } #> + core.TrySetCanceled(cancellationToken); + return false; + } + + return true; + } + + bool TryReturn() + { + TaskTracker.RemoveTracking(this); + core.Reset(); + asyncOperation = default; + cancellationToken = default; + return pool.TryPush(this); + } + + ~<#= t.typeName #>WithCancellationSource() + { + if (TryReturn()) + { + GC.ReRegisterForFinalize(this); + } + } + } + + sealed class <#= t.typeName #>ConfiguredSource : <#= ToIUniTaskSourceReturnType(t.returnType) #>, IPlayerLoopItem, ITaskPoolNode<<#= t.typeName #>ConfiguredSource> { static TaskPool<<#= t.typeName #>ConfiguredSource> pool; public <#= t.typeName #>ConfiguredSource NextNode { get; set; }