Add UniTask.WaitForEndOfFrame(MonoBehaviour), Obsolete no args.

pull/369/head
neuecc 2022-03-03 07:22:58 +09:00
parent 5c668717d8
commit 3c99010ba0
3 changed files with 141 additions and 57 deletions

View File

@ -86,8 +86,8 @@ async UniTask<string> 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`.

View File

@ -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);
}
/// <summary>
/// Same as UniTask.Yield(PlayerLoopTiming.LastPostLateUpdate).
/// </summary>
[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);
}
/// <summary>
/// Same as UniTask.Yield(PlayerLoopTiming.LastPostLateUpdate, cancellationToken).
/// </summary>
[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);
}
/// <summary>
/// Same as UniTask.Yield(PlayerLoopTiming.FixedUpdate).
/// </summary>
@ -352,6 +354,113 @@ namespace Cysharp.Threading.Tasks
}
}
sealed class WaitForEndOfFramePromise : IUniTaskSource, ITaskPoolNode<WaitForEndOfFramePromise>, System.Collections.IEnumerator
{
static TaskPool<WaitForEndOfFramePromise> pool;
WaitForEndOfFramePromise nextNode;
public ref WaitForEndOfFramePromise NextNode => ref nextNode;
static WaitForEndOfFramePromise()
{
TaskPool.RegisterSizeGetter(typeof(WaitForEndOfFramePromise), () => pool.Size);
}
CancellationToken cancellationToken;
UniTaskCompletionSourceCore<object> 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<object> 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<DelayFramePromise>
{
static TaskPool<DelayFramePromise> pool;

View File

@ -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 () =>