async / await
Work that does not fit in one frame goes in an async FrameTask method. The method suspends at each await, returns to Udon, and resumes from there on a later frame (nothing runs in parallel, and by default only one instance runs at a time).
Forms that compile (10)
Section titled “Forms that compile (10)”| Syntax | Description | Note |
|---|---|---|
async FrameTask M() | Suspending and resuming | The caller does not wait for completion; it proceeds to the statement after the call within the same frame. |
await Async.Frames(30) | Specifying the frame count | Waits are counted in frames only; seconds are not available. |
[MaxTasks(4)] | How many can run at once | The attribute is only needed when you want a count (without it, one at a time). Each one keeps its own arguments and intermediate values, and they advance independently. A call made while every slot is taken does not start. Note that the body is duplicated once per slot, so the program grows with the number. |
FrameTask t = M(); if (!t.Started) | Checking whether it started | A call made while the previous run has not finished does not start, and Started is false. |
if (i % 256 == 255) await Async.Frame(); | Splitting a loop | The loop variable i and the running total keep their values across the suspension. |
async FrameTask Fade(int frames, int target) | Passing arguments | The argument target still reads the value it had at the call, even after await. |
await Async.Frames(speed * 2 + 1) | An expression for the frame count | The expression is evaluated when the await is reached. |
private static async FrameTask Tick() | static methods | |
await Fade(30) | Waiting for another frame-spanning method | If it is still running, the caller waits for it to finish and then starts it (this is where await differs from a plain call, which does not start it at all). If it never suspends, the rest continues without crossing a frame. |
async FrameTask Start() | Making an event span frames | Udon still calls Start as before, and the suspended remainder resumes on a later frame. Field initializers run before the body. If you do this to an event that fires every frame, such as Update, the later calls do not start because the previous run has not finished. An event inherited from the base class, such as Interact(), cannot span frames: C# does not allow an override to change its return type. |
Forms that don’t compile (9)
Section titled “Forms that don’t compile (9)”| Syntax | Description | Error | Reason | Alternative |
|---|---|---|---|---|
async void M() | No return value, so you cannot read whether it started | TUKI0001 | by design | Return async FrameTask |
[MaxTasks(0)] | Writing a count below one | TUKI0001 | by design | Write one or more |
[MaxTasks(100000)] | Writing a count above the limit | TUKI0001 | by design | Lower the count (the body is duplicated, so the program grows with it) |
await AMethodWithTwoOrMoreSlots() | Awaiting a method that has more than one slot | TUKI0001 | by design | Call it without await, or give the awaited method a single slot |
await B() in A(), await A() in B() | Two awaiting methods wait for each other | TUKI0001 | by design | Call it without await, or break the cycle |
async FrameTask M<T>() | Take a type parameter | TUKI0001 | not yet | Rewrite it without the type parameter |
Async.Frame(); | Write the wait without await | TUKI0001 | by design | Add await |
FrameTask t = M(); await t; | Awaiting a FrameTask held in a variable | TUKI0001 | by design | Write await M(), awaiting the call itself |
yield return | Return values one at a time | TUKI0001 | runtime | Use async and await to span frames; fill an array to return values |
The constructs allowed in the body are in Constructs allowed in an async method; combining it with the GPU is in GPU kernels and await.
Syntax
Section titled “Syntax”Suspending and resuming
Section titled “Suspending and resuming”The method suspends where await Async.Frame() is written and resumes from there on the next 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(); }}Specifying the frame count
Section titled “Specifying the frame count”Async.Frames(n) waits the specified number of frames before resuming.
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(); }}How many can run at once
Section titled “How many can run at once”Writing a number in the attribute lets that many copies of the method run at once.
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); }}Checking whether it started
Section titled “Checking whether it started”Started on the return value tells whether that call started the work.
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; }}Splitting a loop
Section titled “Splitting a loop”Placing await Async.Frame() every 256 iterations advances 256 iterations per frame.
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(); }}Method declaration
Section titled “Method declaration”Passing arguments
Section titled “Passing arguments”A frame-spanning method can take arguments.
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); }}An expression for the frame count
Section titled “An expression for the frame count”The number of frames to wait can be an expression.
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 methods
Section titled “static methods”A frame-spanning method can also be 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(); }}Waiting for another frame-spanning method
Section titled “Waiting for another frame-spanning method”Awaiting one of your own frame-spanning methods runs the rest only after that one finishes.
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(); }}Making an event span frames
Section titled “Making an event span frames”A Unity event can itself be a frame-spanning method.
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; }}