From bd6906792d14b38e4c404f424d413fd9844dfdd3 Mon Sep 17 00:00:00 2001 From: neuecc Date: Tue, 12 May 2020 11:51:50 +0900 Subject: [PATCH] UnityAsyncEventHandler as IUniTaskAsyncEnumerable --- src/UniTask.NetCoreSandbox/Program.cs | 4 +- .../Plugins/UniTask/Internal/PromisePool.cs | 6 +- .../UniTask/UnityAsyncExtensions.uGUI.cs | 261 +++++++++++++++++- src/UniTask/Assets/Scenes/SandboxMain.cs | 37 ++- 4 files changed, 292 insertions(+), 16 deletions(-) diff --git a/src/UniTask.NetCoreSandbox/Program.cs b/src/UniTask.NetCoreSandbox/Program.cs index 24807f2..ff83580 100644 --- a/src/UniTask.NetCoreSandbox/Program.cs +++ b/src/UniTask.NetCoreSandbox/Program.cs @@ -43,7 +43,7 @@ namespace NetCoreSandbox .SelectAwait(x => UniTask.Run(() => x)) .TakeLast(6) - + ) { @@ -51,7 +51,7 @@ namespace NetCoreSandbox Console.WriteLine(item); } - +// AsyncEnumerable.Range(1,10).FirstAsync( // AsyncEnumerable.Range(1, 10).GroupBy(x=>x).Select(x=>x.first diff --git a/src/UniTask/Assets/Plugins/UniTask/Internal/PromisePool.cs b/src/UniTask/Assets/Plugins/UniTask/Internal/PromisePool.cs index e43aad2..83c3ada 100644 --- a/src/UniTask/Assets/Plugins/UniTask/Internal/PromisePool.cs +++ b/src/UniTask/Assets/Plugins/UniTask/Internal/PromisePool.cs @@ -4,12 +4,14 @@ using System.Threading; namespace Cysharp.Threading.Tasks.Internal { - internal interface IPromisePoolItem + // public, allow to user create custom operator with pool. + + public interface IPromisePoolItem { void Reset(); } - internal class PromisePool + public class PromisePool where T : class, IPromisePoolItem { int count = 0; diff --git a/src/UniTask/Assets/Plugins/UniTask/UnityAsyncExtensions.uGUI.cs b/src/UniTask/Assets/Plugins/UniTask/UnityAsyncExtensions.uGUI.cs index b90e183..c324ac5 100644 --- a/src/UniTask/Assets/Plugins/UniTask/UnityAsyncExtensions.uGUI.cs +++ b/src/UniTask/Assets/Plugins/UniTask/UnityAsyncExtensions.uGUI.cs @@ -162,22 +162,25 @@ namespace Cysharp.Threading.Tasks } } - public interface IAsyncClickEventHandler : IDisposable + public interface IAsyncClickEventHandler : IDisposable, IUniTaskAsyncEnumerable { UniTask OnClickAsync(); + IAsyncClickEventHandler DisableAutoClose(); } - public interface IAsyncValueChangedEventHandler : IDisposable + public interface IAsyncValueChangedEventHandler : IDisposable, IUniTaskAsyncEnumerable { UniTask OnValueChangedAsync(); + IAsyncValueChangedEventHandler DisableAutoClose(); } - public interface IAsyncEndEditEventHandler : IDisposable + public interface IAsyncEndEditEventHandler : IDisposable, IUniTaskAsyncEnumerable { UniTask OnEndEditAsync(); + IAsyncEndEditEventHandler DisableAutoClose(); } - public class AsyncUnityEventHandler : IUniTaskSource, IDisposable, IAsyncClickEventHandler + public class AsyncUnityEventHandler : IUniTaskSource, IDisposable, IAsyncClickEventHandler, IUniTaskAsyncEnumerable { static Action cancellationCallback = CancellationCallback; @@ -222,6 +225,7 @@ namespace Cysharp.Threading.Tasks void Invoke() { + asyncEnumerator?.SetResult(); core.TrySetResult(AsyncUnit.Default); } @@ -230,6 +234,13 @@ namespace Cysharp.Threading.Tasks var self = (AsyncUnityEventHandler)state; self.Dispose(); + // call child cancel + if (self.asyncEnumerator != null) + { + self.asyncEnumerator.CancelFromParent(self.cancellationToken); + self.asyncEnumerator = null; + } + self.core.TrySetCanceled(self.cancellationToken); } @@ -281,9 +292,118 @@ namespace Cysharp.Threading.Tasks { core.OnCompleted(continuation, state, token); } + + // AsyncEnumerator + + bool disableAutoClose; + Enumerator asyncEnumerator; + + public AsyncUnityEventHandler DisableAutoClose() + { + disableAutoClose = true; + return this; + } + + IAsyncClickEventHandler IAsyncClickEventHandler.DisableAutoClose() + { + disableAutoClose = true; + return this; + } + + IUniTaskAsyncEnumerator IUniTaskAsyncEnumerable.GetAsyncEnumerator(CancellationToken cancellationToken) + { + if (this.asyncEnumerator != null) + { + throw new InvalidOperationException("Already acquired GetAsyncEnumerator, does not allow get twice before previous enumerator completed."); + } + + this.asyncEnumerator = new Enumerator(this, cancellationToken); + return asyncEnumerator; + } + + class Enumerator : Cysharp.Threading.Tasks.Linq.MoveNextSource, IUniTaskAsyncEnumerator + { + static Action cancellationCallback = CancellationCallback; + + AsyncUnityEventHandler parent; + CancellationToken cancellationToken; + CancellationTokenRegistration registration; + bool isDisposed; + + public Enumerator(AsyncUnityEventHandler parent, CancellationToken cancellationToken) + { + this.parent = parent; + this.cancellationToken = cancellationToken; + + if (cancellationToken.CanBeCanceled && parent.cancellationToken != cancellationToken) + { + registration = cancellationToken.RegisterWithoutCaptureExecutionContext(cancellationCallback, this); + } + + TaskTracker.TrackActiveTask(this, 3); + } + + static void CancellationCallback(object state) + { + var self = (Enumerator)state; + self.DisposeCore(self.cancellationToken); + } + + public void CancelFromParent(CancellationToken cancellationToken) + { + // call from parent, avoid parent close. + parent.disableAutoClose = true; + DisposeCore(cancellationToken); + } + + public void SetResult() + { + completionSource.TrySetResult(true); + } + + public AsyncUnit Current { get; private set; } + + public UniTask MoveNextAsync() + { + completionSource.Reset(); + return new UniTask(this, completionSource.Version); + } + + public UniTask DisposeAsync() + { + DisposeCore(CancellationToken.None); + return default; + } + + void DisposeCore(CancellationToken cancellationToken) + { + if (!isDisposed) + { + isDisposed = true; + registration.Dispose(); + TaskTracker.RemoveTracking(this); + + if (!parent.disableAutoClose) + { + parent.Dispose(); // dispose parent. + } + + if (parent.asyncEnumerator == this) + { + parent.asyncEnumerator = null; + } + + try + { + completionSource.TrySetCanceled(cancellationToken); + } + catch (OperationCanceledException) { } + } + } + } } - public class AsyncUnityEventHandler : IUniTaskSource, IDisposable, IAsyncValueChangedEventHandler, IAsyncEndEditEventHandler + public class AsyncUnityEventHandler : IUniTaskSource, IDisposable, IAsyncValueChangedEventHandler, IAsyncEndEditEventHandler, IUniTaskAsyncEnumerable { static Action cancellationCallback = CancellationCallback; @@ -328,6 +448,7 @@ namespace Cysharp.Threading.Tasks void Invoke(T result) { + asyncEnumerator?.SetResult(result); core.TrySetResult(result); } @@ -336,6 +457,13 @@ namespace Cysharp.Threading.Tasks var self = (AsyncUnityEventHandler)state; self.Dispose(); + // call child cancel + if (self.asyncEnumerator != null) + { + self.asyncEnumerator.CancelFromParent(self.cancellationToken); + self.asyncEnumerator = null; + } + self.core.TrySetCanceled(self.cancellationToken); } @@ -350,6 +478,13 @@ namespace Cysharp.Threading.Tasks { unityEvent.RemoveListener(action); } + + asyncEnumerator?.DisposeAsync().Forget(); + try + { + core.TrySetCanceled(); + } + catch (OperationCanceledException) { } } } @@ -397,5 +532,121 @@ namespace Cysharp.Threading.Tasks { core.OnCompleted(continuation, state, token); } + + // AsyncEnumerator + + bool disableAutoClose; + Enumerator asyncEnumerator; + + public AsyncUnityEventHandler DisableAutoClose() + { + disableAutoClose = true; + return this; + } + + IAsyncValueChangedEventHandler IAsyncValueChangedEventHandler.DisableAutoClose() + { + disableAutoClose = true; + return this; + } + + IAsyncEndEditEventHandler IAsyncEndEditEventHandler.DisableAutoClose() + { + disableAutoClose = true; + return this; + } + + IUniTaskAsyncEnumerator IUniTaskAsyncEnumerable.GetAsyncEnumerator(CancellationToken cancellationToken) + { + if (this.asyncEnumerator != null) + { + throw new InvalidOperationException("Already acquired GetAsyncEnumerator, does not allow get twice before previous enumerator completed."); + } + + this.asyncEnumerator = new Enumerator(this, cancellationToken); + return asyncEnumerator; + } + + class Enumerator : Cysharp.Threading.Tasks.Linq.MoveNextSource, IUniTaskAsyncEnumerator + { + static Action cancellationCallback = CancellationCallback; + + AsyncUnityEventHandler parent; + CancellationToken cancellationToken; + CancellationTokenRegistration registration; + bool isDisposed; + + public Enumerator(AsyncUnityEventHandler parent, CancellationToken cancellationToken) + { + this.parent = parent; + this.cancellationToken = cancellationToken; + + if (cancellationToken.CanBeCanceled && parent.cancellationToken != cancellationToken) + { + registration = cancellationToken.RegisterWithoutCaptureExecutionContext(cancellationCallback, this); + } + + TaskTracker.TrackActiveTask(this, 3); + } + + static void CancellationCallback(object state) + { + var self = (Enumerator)state; + self.DisposeCore(self.cancellationToken); + } + + public void CancelFromParent(CancellationToken cancellationToken) + { + // call from parent, avoid parent close. + parent.disableAutoClose = true; + DisposeCore(cancellationToken); + } + + public void SetResult(T result) + { + Current = result; + completionSource.TrySetResult(true); + } + + public T Current { get; private set; } + + public UniTask MoveNextAsync() + { + completionSource.Reset(); + return new UniTask(this, completionSource.Version); + } + + public UniTask DisposeAsync() + { + DisposeCore(CancellationToken.None); + return default; + } + + void DisposeCore(CancellationToken cancellationToken) + { + if (!isDisposed) + { + isDisposed = true; + registration.Dispose(); + TaskTracker.RemoveTracking(this); + + if (!parent.disableAutoClose) + { + parent.Dispose(); // dispose parent. + } + + if (parent.asyncEnumerator == this) + { + parent.asyncEnumerator = null; + } + + try + { + completionSource.TrySetCanceled(cancellationToken); + } + catch (OperationCanceledException) { } + } + } + } } } \ No newline at end of file diff --git a/src/UniTask/Assets/Scenes/SandboxMain.cs b/src/UniTask/Assets/Scenes/SandboxMain.cs index ff9c1e6..66a82bb 100644 --- a/src/UniTask/Assets/Scenes/SandboxMain.cs +++ b/src/UniTask/Assets/Scenes/SandboxMain.cs @@ -1,4 +1,5 @@ using System; +using Cysharp.Threading.Tasks.Linq; using Cysharp.Threading.Tasks.Triggers; using System.Collections; using System.Collections.Generic; @@ -12,7 +13,6 @@ using Unity.Jobs; using UnityEngine; using UnityEngine.Networking; using UnityEngine.UI; -using Cysharp.Threading.Tasks.Linq; public struct MyJob : IJob { @@ -105,16 +105,16 @@ public class SandboxMain : MonoBehaviour } - void Start() + async void Start() { Application.SetStackTraceLogType(LogType.Error, StackTraceLogType.Full); Application.SetStackTraceLogType(LogType.Exception, StackTraceLogType.Full); var playerLoop = UnityEngine.LowLevel.PlayerLoop.GetCurrentPlayerLoop(); - ShowPlayerLoop.DumpPlayerLoop("Current", playerLoop); + //ShowPlayerLoop.DumpPlayerLoop("Current", playerLoop); - Update2().Forget(); + //Update2().Forget(); //RunStandardDelayAsync().Forget(); @@ -144,7 +144,6 @@ public class SandboxMain : MonoBehaviour //StartCoroutine(cor); - Debug.Log(EqualityComparer.Default.GetType().FullName); //this.TryGetComponent( @@ -163,12 +162,36 @@ public class SandboxMain : MonoBehaviour Application.logMessageReceived += Application_logMessageReceived; - UniTask foo = UniTask.FromResult(10); + // foo.Status.IsCanceled + // 5回クリックされるまで待つ、とか。 + Debug.Log("Await start."); + + + + await okButton.GetAsyncClickEventHandler().DisableAutoClose() + .Select((_, clickCount) => clickCount + 1) + .FirstAsync(x => x == 5); + + Debug.Log("Click 5 times."); + + + + + + + + + + + + + + + - Foo(foo); //ucs = new UniTaskCompletionSource();