diff --git a/README.md b/README.md index 960bc75..467e5f1 100644 --- a/README.md +++ b/README.md @@ -86,8 +86,8 @@ async UniTask DemoAsync() await UniTask.Yield(); await UniTask.NextFrame(); - // replacement of WaitForEndOfFrame(same as UniTask.Yield(PlayerLoopTiming.LastPostLateUpdate)) - await UniTask.WaitForEndOfFrame(); + // replacement of WaitForEndOfFrame(requires MonoBehaviour(CoroutineRunner)) + await UniTask.WaitForEndOfFrame(this); // this is MonoBehaviour // replacement of yield return new WaitForFixedUpdate(same as UniTask.Yield(PlayerLoopTiming.FixedUpdate)) await UniTask.WaitForFixedUpdate(); @@ -496,9 +496,9 @@ public enum PlayerLoopTiming It indicates when to run, you can check [PlayerLoopList.md](https://gist.github.com/neuecc/bc3a1cfd4d74501ad057e49efcd7bdae) to Unity's default playerloop and injected UniTask's custom loop. -`PlayerLoopTiming.Update` is similar to `yield return null` in a coroutine, but it is called before Update(Update and uGUI events(button.onClick, etc...) are called on `ScriptRunBehaviourUpdate`, yield return null is called on `ScriptRunDelayedDynamicFrameRate`). `PlayerLoopTiming.FixedUpdate` is similar to `WaitForFixedUpdate`, `PlayerLoopTiming.LastPostLateUpdate` is similar to `WaitForEndOfFrame` in coroutine. +`PlayerLoopTiming.Update` is similar to `yield return null` in a coroutine, but it is called before Update(Update and uGUI events(button.onClick, etc...) are called on `ScriptRunBehaviourUpdate`, yield return null is called on `ScriptRunDelayedDynamicFrameRate`). `PlayerLoopTiming.FixedUpdate` is similar to `WaitForFixedUpdate`. -> `await UniTask.WaitForEndOfFrame()` is not equivalent to coroutine's `yield return new WaitForEndOfFrame()`. Coroutine's WaitForEndOfFrame seems to run after the PlayerLoop is done. Some methods that require coroutine's end of frame(`ScreenCapture.CaptureScreenshotAsTexture`, `CommandBuffer`, etc) do not work correctly when replaced with async/await. In these cases, use a coroutine instead. +> `PlayerLoopTiming.LastPostLateUpdate` is not equivalent to coroutine's `yield return new WaitForEndOfFrame()`. Coroutine's WaitForEndOfFrame seems to run after the PlayerLoop is done. Some methods that require coroutine's end of frame(`Texture2D.ReadPixels`, `ScreenCapture.CaptureScreenshotAsTexture`, `CommandBuffer`, etc) do not work correctly when replaced with async/await. In these cases, pass MonoBehaviour(coroutine runnner) to `UniTask.WaitForEndOfFrame`. For example, `await UniTask.WaitForEndOfFrame(this);` is lightweight allocation free alternative of `yield return new WaitForEndOfFrame()`. `yield return null` and `UniTask.Yield` are similar but different. `yield return null` always returns next frame but `UniTask.Yield` returns next called. That is, call `UniTask.Yield(PlayerLoopTiming.Update)` on `PreUpdate`, it returns same frame. `UniTask.NextFrame()` guarantees return next frame, you can expect this to behave exactly the same as `yield return null`. diff --git a/src/UniTask/Assets/Plugins/UniTask/Runtime/UniTask.Delay.cs b/src/UniTask/Assets/Plugins/UniTask/Runtime/UniTask.Delay.cs index 5c56747..7e922f4 100644 --- a/src/UniTask/Assets/Plugins/UniTask/Runtime/UniTask.Delay.cs +++ b/src/UniTask/Assets/Plugins/UniTask/Runtime/UniTask.Delay.cs @@ -2,6 +2,7 @@ using Cysharp.Threading.Tasks.Internal; using System; +using System.Collections; using System.Runtime.CompilerServices; using System.Threading; using UnityEngine; @@ -74,23 +75,24 @@ namespace Cysharp.Threading.Tasks return new UniTask(NextFramePromise.Create(timing, cancellationToken, out var token), token); } - - /// - /// Same as UniTask.Yield(PlayerLoopTiming.LastPostLateUpdate). - /// + [Obsolete("Use WaitForEndOfFrame(MonoBehaviour) instead or UniTask.Yield(PlayerLoopTiming.LastPostLateUpdate). Equivalent for coroutine's WaitForEndOfFrame requires MonoBehaviour(runner of Coroutine).")] public static YieldAwaitable WaitForEndOfFrame() { return UniTask.Yield(PlayerLoopTiming.LastPostLateUpdate); } - /// - /// Same as UniTask.Yield(PlayerLoopTiming.LastPostLateUpdate, cancellationToken). - /// + [Obsolete("Use WaitForEndOfFrame(MonoBehaviour) instead or UniTask.Yield(PlayerLoopTiming.LastPostLateUpdate). Equivalent for coroutine's WaitForEndOfFrame requires MonoBehaviour(runner of Coroutine).")] public static UniTask WaitForEndOfFrame(CancellationToken cancellationToken) { return UniTask.Yield(PlayerLoopTiming.LastPostLateUpdate, cancellationToken); } + public static UniTask WaitForEndOfFrame(MonoBehaviour coroutineRunner, CancellationToken cancellationToken = default) + { + var source = WaitForEndOfFramePromise.Create(coroutineRunner, cancellationToken, out var token); + return new UniTask(source, token); + } + /// /// Same as UniTask.Yield(PlayerLoopTiming.FixedUpdate). /// @@ -352,6 +354,113 @@ namespace Cysharp.Threading.Tasks } } + sealed class WaitForEndOfFramePromise : IUniTaskSource, ITaskPoolNode, System.Collections.IEnumerator + { + static TaskPool pool; + WaitForEndOfFramePromise nextNode; + public ref WaitForEndOfFramePromise NextNode => ref nextNode; + + static WaitForEndOfFramePromise() + { + TaskPool.RegisterSizeGetter(typeof(WaitForEndOfFramePromise), () => pool.Size); + } + + CancellationToken cancellationToken; + UniTaskCompletionSourceCore core; + + WaitForEndOfFramePromise() + { + } + + public static IUniTaskSource Create(MonoBehaviour coroutineRunner, CancellationToken cancellationToken, out short token) + { + if (cancellationToken.IsCancellationRequested) + { + return AutoResetUniTaskCompletionSource.CreateFromCanceled(cancellationToken, out token); + } + + if (!pool.TryPop(out var result)) + { + result = new WaitForEndOfFramePromise(); + } + + result.cancellationToken = cancellationToken; + + TaskTracker.TrackActiveTask(result, 3); + + coroutineRunner.StartCoroutine(result); + + token = result.core.Version; + return result; + } + + public void GetResult(short token) + { + try + { + core.GetResult(token); + } + finally + { + TryReturn(); + } + } + + 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); + } + + bool TryReturn() + { + TaskTracker.RemoveTracking(this); + core.Reset(); + Reset(); // Reset Enumerator + cancellationToken = default; + return pool.TryPush(this); + } + + // Coroutine Runner implementation + + static readonly WaitForEndOfFrame waitForEndOfFrameYieldInstruction = new WaitForEndOfFrame(); + bool isFirst = true; + + object IEnumerator.Current => waitForEndOfFrameYieldInstruction; + + bool IEnumerator.MoveNext() + { + if (isFirst) + { + isFirst = false; + return true; // start WaitForEndOfFrame + } + + if (cancellationToken.IsCancellationRequested) + { + core.TrySetCanceled(cancellationToken); + return false; + } + + core.TrySetResult(null); + return false; + } + + public void Reset() + { + isFirst = true; + } + } + sealed class DelayFramePromise : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode { static TaskPool pool; diff --git a/src/UniTask/Assets/Scenes/SandboxMain.cs b/src/UniTask/Assets/Scenes/SandboxMain.cs index 8babe00..175e024 100644 --- a/src/UniTask/Assets/Scenes/SandboxMain.cs +++ b/src/UniTask/Assets/Scenes/SandboxMain.cs @@ -543,7 +543,7 @@ public class SandboxMain : MonoBehaviour Debug.LogError(e); return; - + } } Debug.Log("TestAsync Finished."); @@ -555,6 +555,7 @@ public class SandboxMain : MonoBehaviour async UniTaskVoid Start() { + // UniTask.Delay(TimeSpan.FromSeconds(1)).TimeoutWithoutException @@ -562,61 +563,35 @@ public class SandboxMain : MonoBehaviour PlayerLoopHelper.Initialize(ref currentLoop, InjectPlayerLoopTimings.Minimum); // minimum is Update | FixedUpdate | LastPostLateUpdate - - - - var cancelToken = new CancellationTokenSource(); - cancelButton.onClick.AddListener(()=> - { - cancelToken.Cancel(); // cancel from button click. - }); - - var timeoutToken = new CancellationTokenSource(); - timeoutToken.CancelAfterSlim(TimeSpan.FromSeconds(5)); // 5sec timeout. - - try - { - // combine token - var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancelToken.Token, timeoutToken.Token); - - await UnityWebRequest.Get("http://foo").SendWebRequest().WithCancellation(linkedTokenSource.Token); - } - catch (OperationCanceledException ex) - { - if (timeoutToken.IsCancellationRequested) - { - UnityEngine.Debug.Log("Timeout."); - } - else if (cancelToken.IsCancellationRequested) - { - UnityEngine.Debug.Log("Cancel clicked."); - } - _ = ex; - } - // TestAsync(cts.Token).Forget(); okButton.onClick.AddListener(UniTask.UnityAction(async () => { - // try timeout - try - { - //await UniTask.Delay(TimeSpan.FromSeconds(2), cancellationToken: timeoutController.Timeout(TimeSpan.FromSeconds(3))); - UnityEngine.Debug.Log("Delay Complete, Reset(and reuse)."); - //timeoutController.Reset(); - } - catch (OperationCanceledException ex) - { - //UnityEngine.Debug.Log("Timeout! FromTimeout?:" + timeoutController.IsTimeout()); - _ = ex; - } + await UniTask.WaitForEndOfFrame(this); + var texture = new Texture2D(Screen.width, Screen.height); + texture.ReadPixels(new Rect(0, 0, Screen.width, Screen.height), 0, 0); + texture.Apply(); - await UniTask.Yield(); + var jpg = texture.EncodeToJPG(); + File.WriteAllBytes("testscreencapture.jpg", jpg); + Debug.Log("ok?"); + + //var texture = ScreenCapture.CaptureScreenshotAsTexture(); + //if (texture == null) + //{ + // Debug.Log("fail"); + //} + //else + //{ + // var jpg = texture.EncodeToJPG(); + // File.WriteAllBytes("testscreencapture.jpg", jpg); + // Debug.Log("ok?"); + //} })); cancelButton.onClick.AddListener(UniTask.UnityAction(async () =>