From 680ce1098b9f99ad5107f7c5fd0fd21f9b0c6d8f Mon Sep 17 00:00:00 2001 From: neuecc Date: Mon, 8 Jun 2020 01:45:57 +0900 Subject: [PATCH] ReadMe --- README.md | 35 +++-- .../Assets/Scenes/ExceptionExamples.cs | 12 +- src/UniTask/Assets/Scenes/SandboxMain.cs | 120 +++++++++++++++--- src/UniTask/Assets/Tests/AsyncTest.cs | 20 ++- .../ProjectSettings/EditorBuildSettings.asset | 6 +- 5 files changed, 161 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 6f6693c..79715eb 100644 --- a/README.md +++ b/README.md @@ -53,21 +53,32 @@ async UniTask DemoAsync() { // You can await Unity's AsyncObject var asset = await Resources.LoadAsync("foo"); - + var txt = (await UnityWebRequest.Get("https://...").SendWebRequest()).downloadHandler.text; + await SceneManager.LoadSceneAsync("scene2"); + // .WithCancellation enables Cancel, GetCancellationTokenOnDestroy synchornizes with lifetime of GameObject - var asset2 = await Resources.LoadAsync("foo").WithCancellation(this.GetCancellationTokenOnDestroy()); + var asset2 = await Resources.LoadAsync("bar").WithCancellation(this.GetCancellationTokenOnDestroy()); // .ToUniTask accepts progress callback(and all options), Progress.Create is a lightweight alternative of IProgress - await SceneManager.LoadSceneAsync("scene2").ToUniTask(Progress.Create(x => Debug.Log(x))); - + var asset3 = await Resources.LoadAsync("baz").ToUniTask(Progress.Create(x => Debug.Log(x))); + // await frame-based operation like coroutine await UniTask.DelayFrame(100); - // replacement of WaitForSeconds/WaitForSecondsRealtime + // replacement of yield return new WaitForSeconds/WaitForSecondsRealtime await UniTask.Delay(TimeSpan.FromSeconds(10), ignoreTimeScale: false); - // replacement of WaitForEndOfFrame(or other timing like yield return null, yield return WaitForFixedUpdate) - await UniTask.Yield(PlayerLoopTiming.LastPostLateUpdate); + // yield any playerloop timing(PreUpdate, Update, LateUpdate, etc...) + await UniTask.Yield(PlayerLoopTiming.PreLateUpdate); + + // replacement of yield return null + await UniTask.NextFrame(); + + // replacement of WaitForEndOfFrame(same as UniTask.Yield(PlayerLoopTiming.LastPostLateUpdate)) + await UniTask.WaitForEndOfFrame(); + + // replacement of yield return new WaitForFixedUpdate + await UniTask.Yield(PlayerLoopTiming.FixedUpdate); // replacement of yield return WaitUntil await UniTask.WaitUntil(() => isActive == false); @@ -127,6 +138,8 @@ UniTask provides three pattern of extension methods. `WithCancellation` is a simple version of `ToUniTask`, both returns `UniTask`. Details of cancellation, see: [Cancellation and Exception handling](#cancellation-and-exception-handling) section. +> Note: WithCancellation is returned from native timing of PlayerLoop but ToUniTask is returned from specified PlayerLoopTiming. Details of timing, see: [PlayerLoop](#playerloop) section. + The type of `UniTask` can use utility like `UniTask.WhenAll`, `UniTask.WhenAny`. It is like Task.WhenAll/WhenAny but return type is more useful, returns value tuple so can deconsrtuct each result and pass multiple type. ```csharp @@ -321,10 +334,16 @@ 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 as `yield return null` in coroutine, but it is called before Update(Update is called on `ScriptRunBehaviourUpdate`, yield return null is called on `ScriptRunDelayedDynamicFrameRate`). If change timing to `PlayerLoopTiming.LastUpdate`, called after these Unity's update methods. +`PlayerLoopTiming.Update` is similar as `yield return null` in 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 as `WaitForFixedUpdate`, `PlayerLoopTiming.LastPostLateUpdate` is similar as `WaitForEndOfFrame` in coroutine. +`yield return null` and `UniTask.Yield` is similar but different. `yield return null` always return next frame but `UniTask.Yield` return next called, that is, call `UniTask.Yield(PlayerLoopTiming.Update)` on `PreUpdate`, it returns same frame. `UniTask.NextFrame()` gurantees return next frame, this would be expected to behave exactly the same as `yield return null`. + +AsyncOperation is returned from native timing. For example, await `SceneManager.LoadSceneAsync` is returned from `EarlyUpdate.UpdatePreloading` and after called, loaded scene called from `EarlyUpdate.ScriptRunDelayedStartupFrame`. Also `await UnityWebRequest` is returned from `EarlyUpdate.ExecuteMainThreadJobs`. + +In UniTask, await directly and `WithCancellation` use native timing, `ToUniTask` use specified timing. This is usually not a particular problem, but with `LoadSceneAsync`, causes different order of Start and continuation after await. so recommend not to use `LoadSceneAsync.ToUniTask`. + In stacktrace, you can check where is running in playerloop. ![image](https://user-images.githubusercontent.com/46207/83735571-83caea80-a68b-11ea-8d22-5e22864f0d24.png) diff --git a/src/UniTask/Assets/Scenes/ExceptionExamples.cs b/src/UniTask/Assets/Scenes/ExceptionExamples.cs index 360f070..d69cfc1 100644 --- a/src/UniTask/Assets/Scenes/ExceptionExamples.cs +++ b/src/UniTask/Assets/Scenes/ExceptionExamples.cs @@ -21,13 +21,15 @@ public class ExceptionExamples : MonoBehaviour private void Start() { - TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException; + UnityEngine.Debug.Log("ExceptionScene, LoopType:" + PlayerLoopInfo.CurrentLoopType + ":" + Time.frameCount); - ThrowFromAsyncVoid(); - _ = ThrowFromTask(); - _ = ThrowFromUniTask(); + //TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException; - ThrowFromNonAsync(); + //ThrowFromAsyncVoid(); + //_ = ThrowFromTask(); + //_ = ThrowFromUniTask(); + + //ThrowFromNonAsync(); } private void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e) diff --git a/src/UniTask/Assets/Scenes/SandboxMain.cs b/src/UniTask/Assets/Scenes/SandboxMain.cs index f55f569..8c2abe6 100644 --- a/src/UniTask/Assets/Scenes/SandboxMain.cs +++ b/src/UniTask/Assets/Scenes/SandboxMain.cs @@ -14,6 +14,7 @@ using UnityEngine; using UnityEngine.LowLevel; using UnityEngine.Networking; using UnityEngine.UI; +using UnityEngine.SceneManagement; // using DG.Tweening; @@ -265,11 +266,14 @@ public class SandboxMain : MonoBehaviour //var r = UniAsync("https://bing.com/", cts.Token); //cts.Cancel(); //await r; - _ = await UnityWebRequest.Get("https://bing.com/").SendWebRequest(); - Debug.Log("UNIASYNC1 "); + Debug.Log("SendWebRequestDone:" + PlayerLoopInfo.CurrentLoopType); + - _ = await UnityWebRequest.Get("https://bing.com/").SendWebRequest(); - Debug.Log("UNIASYNC2"); + // var foo = await UnityWebRequest.Get("https://bing.com/").SendWebRequest(); + // foo.downloadHandler.text; +// + _ = await UnityWebRequest.Get("https://bing.com/").SendWebRequest().WithCancellation(CancellationToken.None); + Debug.Log("SendWebRequestWithCancellationDone:" + PlayerLoopInfo.CurrentLoopType); } catch { @@ -310,17 +314,107 @@ public class SandboxMain : MonoBehaviour return 0; } + IEnumerator CoroutineRun() + { + UnityEngine.Debug.Log("Before Coroutine yield return null," + Time.frameCount + ", " + PlayerLoopInfo.CurrentLoopType); + yield return null; + UnityEngine.Debug.Log("After Coroutine yield return null," + Time.frameCount + ", " + PlayerLoopInfo.CurrentLoopType); + } + + IEnumerator CoroutineRun2() + { + UnityEngine.Debug.Log("Before Coroutine yield return WaitForEndOfFrame," + Time.frameCount); + yield return new WaitForEndOfFrame(); + UnityEngine.Debug.Log("After Coroutine yield return WaitForEndOfFrame," + Time.frameCount + ", " + PlayerLoopInfo.CurrentLoopType); + yield return new WaitForEndOfFrame(); + UnityEngine.Debug.Log("Onemore After Coroutine yield return WaitForEndOfFrame," + Time.frameCount + ", " + PlayerLoopInfo.CurrentLoopType); + } + + + async UniTaskVoid AsyncRun() + { + UnityEngine.Debug.Log("Before async Yield(default)," + Time.frameCount); + await UniTask.Yield(); + UnityEngine.Debug.Log("After async Yield(default)," + Time.frameCount + ", " + PlayerLoopInfo.CurrentLoopType); + } + + async UniTaskVoid AsyncLastUpdate() + { + UnityEngine.Debug.Log("Before async Yield(LastUpdate)," + Time.frameCount); + await UniTask.Yield(PlayerLoopTiming.LastUpdate); + UnityEngine.Debug.Log("After async Yield(LastUpdate)," + Time.frameCount); + } + + async UniTaskVoid AsyncLastLast() + { + UnityEngine.Debug.Log("Before async Yield(LastPostLateUpdate)," + Time.frameCount); + await UniTask.Yield(PlayerLoopTiming.LastPostLateUpdate); + UnityEngine.Debug.Log("After async Yield(LastPostLateUpdate)," + Time.frameCount); + } + + async UniTaskVoid Yieldding() + { + await UniTask.Yield(PlayerLoopTiming.PreUpdate); + StartCoroutine(CoroutineRun()); + } + void Start() { - _ = Ex(); + PlayerLoopInfo.Inject(); - _ = UniTask.Run(async () => + okButton.onClick.AddListener(UniTask.UnityAction(async () => { - var watch = System.Diagnostics.Stopwatch.StartNew(); - await UniTask.Delay(new TimeSpan(0, 0, seconds: 10)); - Debug.Log(watch.Elapsed); - }); + /* + UnityEngine.Debug.Log("click:" + PlayerLoopInfo.CurrentLoopType); + StartCoroutine(CoroutineRun()); + StartCoroutine(CoroutineRun2()); + _ = AsyncRun(); + _ = AsyncLastUpdate(); + _ = AsyncLastLast(); + */ + await UniTask.Yield(); + _ = Test2(); + // EarlyUpdate.ExecuteMainThreadJobs + // _ = Test2(); + + //var t = await Resources.LoadAsync(Application.streamingAssetsPath + "test.txt"); + //Debug.Log("LoadEnd" + PlayerLoopInfo.CurrentLoopType + ", " + (t != null)); + //Debug.Log("LoadEnd" + PlayerLoopInfo.CurrentLoopType + ", " + ((TextAsset)t).text); + + + //await UniTask.Yield(PlayerLoopTiming.LastUpdate); + //UnityEngine.Debug.Log("after update:" + Time.frameCount); + ////await UniTask.NextFrame(); + ////await UniTask.Yield(); + ////UnityEngine.Debug.Log("after update nextframe:" + Time.frameCount); + + //StartCoroutine(CoroutineRun2()); + ////StartCoroutine(CoroutineRun()); + //UnityEngine.Debug.Log("FOO?"); + })); + + cancelButton.onClick.AddListener(UniTask.UnityAction(async () => + { + //await UniTask.Yield(PlayerLoopTiming.LastPreUpdate); + //UnityEngine.Debug.Log("before update:" + Time.frameCount); + //await UniTask.NextFrame(); + //await UniTask.Yield(); + //UnityEngine.Debug.Log("before update nextframe:" + Time.frameCount); + + //StartCoroutine(CoroutineRun()); + + //UnityEngine.Debug.Log("click:" + PlayerLoopInfo.CurrentLoopType); + //_ = Yieldding(); + + var cts = new CancellationTokenSource(); + + UnityEngine.Debug.Log("click:" + PlayerLoopInfo.CurrentLoopType + ":" + Time.frameCount); + var la = SceneManager.LoadSceneAsync("Scenes/ExceptionExamples").WithCancellation(cts.Token); + //cts.Cancel(); + await la; + UnityEngine.Debug.Log("End LoadSceneAsync" + PlayerLoopInfo.CurrentLoopType + ":" + Time.frameCount); + })); //return; //await UniTask.SwitchToMainThread(); @@ -433,7 +527,7 @@ public class SandboxMain : MonoBehaviour //okButton.onClick.AddListener(UniTask.UnityAction(async () => await UniTask.Yield())); - PlayerLoopInfo.Inject(); + //UpdateUniTask().Forget(); @@ -447,10 +541,6 @@ public class SandboxMain : MonoBehaviour //GameObject.Destroy(this.gameObject); - SynchronizationContext.Current.Post(_ => - { - //UnityEngine.Debug.Log("Post:" + PlayerLoopInfo.CurrentLoopType); - }, null); } async UniTaskVoid UpdateUniTask() diff --git a/src/UniTask/Assets/Tests/AsyncTest.cs b/src/UniTask/Assets/Tests/AsyncTest.cs index 5f94e0f..55df71b 100644 --- a/src/UniTask/Assets/Tests/AsyncTest.cs +++ b/src/UniTask/Assets/Tests/AsyncTest.cs @@ -197,7 +197,7 @@ namespace Cysharp.Threading.TasksTests //await UniTask.SwitchToThreadPool(); - + @@ -369,6 +369,24 @@ namespace Cysharp.Threading.TasksTests throw new Exception("MyException"); } + [UnityTest] + public IEnumerator NextFrame1() => UniTask.ToCoroutine(async () => + { + await UniTask.Yield(PlayerLoopTiming.LastUpdate); + var frame = Time.frameCount; + await UniTask.NextFrame(); + Time.frameCount.Should().Be(frame + 1); + }); + + [UnityTest] + public IEnumerator NextFrame2() => UniTask.ToCoroutine(async () => + { + await UniTask.Yield(PlayerLoopTiming.PreUpdate); + var frame = Time.frameCount; + await UniTask.NextFrame(); + Time.frameCount.Should().Be(frame + 1); + }); + [UnityTest] public IEnumerator NestedEnumerator() => UniTask.ToCoroutine(async () => { diff --git a/src/UniTask/ProjectSettings/EditorBuildSettings.asset b/src/UniTask/ProjectSettings/EditorBuildSettings.asset index 4d57782..6fd131a 100644 --- a/src/UniTask/ProjectSettings/EditorBuildSettings.asset +++ b/src/UniTask/ProjectSettings/EditorBuildSettings.asset @@ -8,7 +8,7 @@ EditorBuildSettings: - enabled: 1 path: Assets/Scenes/SandboxMain.unity guid: 2cda990e2423bbf4892e6590ba056729 - - enabled: 0 - path: - guid: 00000000000000000000000000000000 + - enabled: 1 + path: Assets/Scenes/ExceptionExamples.unity + guid: b5fed17e3ece238439bc796d8747df5d m_configObjects: {}