0
0

【誤解でした】UniTask.Yield(PlayerLoopTiming.Update) が思ったとおりのタイミングで動いていないようだ

Last updated at Posted at 2024-04-01

UniTask の GitHub に質問を投げたら Tim Cassell さんが答えてくれて、解決しました。私が誤解していました。記事の最後に追記します。(見出し「概要」~「考察」が最初の疑問で、「解決」が追記です)

概要

UniTask.Yield() の引数に enum PlayerLoopTiming を与えると、そのタイミングまで待ってから動作を再開する…と一般には理解されています。例えば

  await UniTask.Yield(PlayerLoopTiming.Update);
  Debug.Log("OK");

と書けば、Update のタイミングで OK が Console に表示されるはずです。

ところが実験したところ、そうでもないような気がします。しかも実行するたびにタイミングが変わるようです。

環境

Unity 2023.2.11f1
UniTask 2.5.4

実験

Scene に "Create Empty" で GameObject を作り、これに MyUniTaskTest コンポーネント (後述のソースコード) を加えてあります。
image.png

MyUniTaskTest コンポーネントの実装は以下のようになっています。
_phase に現在のタイミングを記録しています。
Update() の中で最初の1回だけ MyTaskAsync() を起動しています。

MyUniTaskTest.cs
using Cysharp.Threading.Tasks;
using System.Threading;
using UnityEngine;

public class MyUniTaskTest : MonoBehaviour
{
    string _phase;
    bool _done;

    void OnEnable()
    {
        _done = false;
    }

    void FixedUpdate()
    {
        _phase = "FixedUpdate_Begin";
        _phase = "FixedUpdate_End";
    }

    void Update()
    {
        _phase = "Update_Begin";
        if (!_done)
        {
            _done = true;
            MyTaskAsync(destroyCancellationToken).Forget();
        }
        _phase = "Update_End";        
    }

    void LateUpdate()
    {
        _phase = "LateUpdate_Begin";
        _phase = "LateUpdate_End";        
    }

    async UniTaskVoid MyTaskAsync(CancellationToken cancel)
    {
        print($"MyTaskAsync");
        for (int i = 0; i<30; i++)
        {
            print($"{i}: {Time.frameCount} {_phase}");
            await UniTask.Yield(PlayerLoopTiming.Update, cancellationToken: cancel);
        }
    }
}

結果

これを実行し、 Console に表示されたものを貼り付けます。

1回目

image.png

2回目

image.png

3回目

image.png

表にすると

1回目 2回目 3回目
0 Update_Begin Update_Begin Update_Begin
1 FixedUpdate_End FixedUpdate_End FixedUpdate_End
2 FixedUpdate_End FixedUpdate_End FixedUpdate_End
3 FixedUpdate_End FixedUpdate_End FixedUpdate_End
4 FixedUpdate_End LateUpdate_End LateUpdate_End
5 FixedUpdate_End LateUpdate_End LateUpdate_End
6 LateUpdate_End LateUpdate_End FixedUpdate_End
7 FixedUpdate_End FixedUpdate_End LateUpdate_End
8 LateUpdate_End LateUpdate_End LateUpdate_End
9 LateUpdate_End FixedUpdate_End LateUpdate_End

考察

わかりません。Update のタイミングであれば、_phase の内容は FixedUpdate_End, Update_Begin, Update_End のいずれかだと思います。なぜ LateUpdate_End が表示されることがあるのか。

追記

Tim Cassell さんによると、 PlayerLoopTiming.Update は MonoBehaviour の Update よりも前に実行されるそうです。また FixedUpdate は毎フレーム実行されるわけではないので、

第 n フレームの最後まで実行する。変数 _phase には LateUpdate_End が入る。
第 n+1 フレームの実行が始まる。このフレームでは FixedUpdate が呼ばれないとする。MyUniTaskTest.Update よりも前に、MyTaskAsync の実行が再開される。だから (第 n フレームで変数 _phaseに代入された) LateUpdate_End が表示される。

という順に実行される、ということでした。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0