async / await
1 フレームに収まらない処理は、async FrameTask のメソッドに書きます。メソッドは await の位置で中断して Udon へ戻り、後のフレームで続きから再開します(並列には実行されず、同時に実行できるのは既定で 1 本です)。
対応(10)
Section titled “対応(10)”| 書き方 | 説明 | 注意 |
|---|---|---|
async FrameTask M() | 中断と再開 | 呼び出し元は完了を待たず、呼び出しの次の文へ同じフレームのうちに進みます。 |
await Async.Frames(30) | フレーム数の指定 | 待つ単位はフレームだけで、秒では待てません。 |
[MaxTasks(4)] | 同時に実行できる本数 | 属性は本数を書くときだけ付けます(書かなければ 1 本です)。それぞれが自分の引数と途中の値を持ち、別々に進みます。本数ぶんが全部埋まっているときの呼び出しは始まりません。⚠ 本体は本数のぶん複製されるので、プログラムはその数だけ大きくなります。 |
FrameTask t = M(); if (!t.Started) | 開始の確認 | 前の実行が終わっていない間の呼び出しは始まらず、Started は false になります。 |
if (i % 256 == 255) await Async.Frame(); | ループの分割 | ループ変数 i と途中の合計は、中断をまたいで値を保ちます。 |
async FrameTask Fade(int frames, int target) | 引数の受け渡し | 引数 target は、await の後でも呼び出し時の値のまま読めます。 |
await Async.Frames(speed * 2 + 1) | フレーム数の式 | 式は await に到達した時点で評価されます。 |
private static async FrameTask Tick() | static のメソッド | |
await Fade(30) | ほかの跨ぐメソッドを待つ | 相手がまだ実行中だったときは、終わるのを待ってから始めさせます(await を付けずに呼ぶ形とはここが違います。あちらは始まりません)。相手が 1 度も中断しなければ、フレームをまたがずにそのまま続きへ進みます。 |
async FrameTask Start() | イベントを跨がせる | Udon はこれまでどおり Start を呼び、中断した続きは後のフレームから再開します。フィールド初期化子は本体より先に実行されます。毎フレーム呼ばれるイベント(Update など)を跨がせると、2 回目以降は前の実行が終わっていないため始まりません。Interact() のように基底から継ぐイベントは、C# の規則で戻り値を変えられないため跨がせられません。 |
非対応(9)
Section titled “非対応(9)”| 書き方 | 説明 | エラー | 理由 | 代替 |
|---|---|---|---|---|
async void M() | 戻り値が無いので、始まったかを読めない | TUKI0001 | 設計 | async FrameTask にする |
[MaxTasks(0)] | 本数に 1 未満を書く | TUKI0001 | 設計 | 1 以上を書く |
[MaxTasks(100000)] | 本数に上限より大きい数を書く | TUKI0001 | 設計 | 本数を減らす(本体が複製されるので、プログラムがその数だけ大きくなります) |
await 本数が 2 以上のメソッド() | 本数を増やしたメソッドの完了を待つ | TUKI0001 | 設計 | await を付けずに呼ぶか、待ちたい側の本数を 1 にする |
A() で await B()・B() で await A() | 待つメソッドどうしが互いを待つ | TUKI0001 | 設計 | await を付けずに呼ぶか、輪を切る |
async FrameTask M<T>() | 型引数を取る | TUKI0001 | 未実装 | 型引数を使わない形に書き直す |
Async.Frame(); | 待ちを await なしで書く | TUKI0001 | 設計 | await を付ける |
FrameTask t = M(); await t; | 変数へ受けた待ち受けを待つ | TUKI0001 | 設計 | await M() と、呼びをそのまま待つ |
yield return | 少しずつ値を返す | TUKI0001 | 環境 | フレームを跨ぐなら async と await、値を少しずつ返すなら配列に詰める |
本体に書ける構文はasync メソッドの中に書ける構文、GPU との組み合わせはGPU カーネルと awaitにあります。
await Async.Frame() を書いた位置で中断し、続きは次のフレームから再開します。
using UnityEngine;using Tsukimi;
public class ParAsyncFrames : TsukimiBehaviour{ public int total;
private async FrameTask Sum() { int running = 0; for (int i = 0; i < 1000; i++) { running += i; if (i % 128 == 127) await Async.Frame(); } total = running; }
void Start() { Sum(); }}フレーム数の指定
Section titled “フレーム数の指定”Async.Frames(n) は、指定した数のフレームを待ってから再開します。
using UnityEngine;using Tsukimi;
public class ParAsyncWaitMany : TsukimiBehaviour{ public int step;
private async FrameTask Countdown() { step = 3; await Async.Frames(30); step = 2; await Async.Frames(30); step = 1; await Async.Frames(30); step = 0; }
void Start() { Countdown(); }}同時に実行できる本数
Section titled “同時に実行できる本数”属性に数を書くと、同じメソッドをその数まで同時に実行できます。
using UnityEngine;using Tsukimi;
public class ParAsyncSeats : TsukimiBehaviour{ public int finished;
[MaxTasks(4)] private async FrameTask Petal(int frames) { await Async.Frames(frames); finished = finished + 1; }
public override void Interact() { Petal(10); Petal(20); Petal(30); Petal(40); }}戻り値の Started で、その呼び出しで処理が始まったかを読めます。
using UnityEngine;using Tsukimi;
public class ParAsyncSeat : TsukimiBehaviour{ public int done;
private async FrameTask Once() { await Async.Frames(10); done = done + 1; }
public override void Interact() { FrameTask task = Once(); if (!task.Started) return; }}ループの分割
Section titled “ループの分割”256 周ごとに await Async.Frame() を置くと、1 フレームに進むのは 256 周分になります。
using UnityEngine;using Tsukimi;
public class ParAsyncLoopSplit : TsukimiBehaviour{ public int[] board; public int score;
private async FrameTask Evaluate() { int sum = 0; for (int i = 0; i < board.Length; i++) { sum += board[i] * board[i]; if (i % 256 == 255) await Async.Frame(); } score = sum; }
public override void Interact() { Evaluate(); }}メソッドの宣言
Section titled “メソッドの宣言”引数の受け渡し
Section titled “引数の受け渡し”フレームを跨ぐメソッドは引数を取れます。
using UnityEngine;using Tsukimi;
public class ParAsyncArgs : TsukimiBehaviour{ public int level;
private async FrameTask Fade(int frames, int target) { await Async.Frames(frames); level = target; }
public override void Interact() { Fade(30, 100); }}フレーム数の式
Section titled “フレーム数の式”待つフレーム数には式を書けます。
using UnityEngine;using Tsukimi;
public class ParAsyncComputedFrames : TsukimiBehaviour{ public int speed; public int done;
private async FrameTask Wait() { await Async.Frames(speed * 2 + 1); done = done + 1; }
void Start() { speed = 10; Wait(); }}static のメソッド
Section titled “static のメソッド”フレームを跨ぐメソッドは static にもできます。
using UnityEngine;using Tsukimi;
public class ParAsyncStatic : TsukimiBehaviour{ private static int ticks;
private static async FrameTask Tick() { await Async.Frame(); ticks = ticks + 1; }
void Start() { Tick(); }}ほかの跨ぐメソッドを待つ
Section titled “ほかの跨ぐメソッドを待つ”自分の跨ぐメソッドを await すると、そちらが終わってから続きが実行されます。
using UnityEngine;using Tsukimi;
public class ParAsyncAwaitAsync : TsukimiBehaviour{ public int step;
private async FrameTask Fade(int frames) { await Async.Frames(frames); }
private async FrameTask Sequence() { step = 1; await Fade(30); step = 2; await Fade(30); step = 3; }
public override void Interact() { Sequence(); }}イベントを跨がせる
Section titled “イベントを跨がせる”Unity のイベントそのものを跨ぐメソッドにできます。
using UnityEngine;using Tsukimi;
public class ParAsyncEntry : TsukimiBehaviour{ public int step;
private async FrameTask Start() { step = 1; await Async.Frames(30); step = 2; await Async.Frames(30); step = 3; }}