https://zenn.dev/matcha_choco010/articles/2022-09-03-rust-wasm-async-runtime
この記事ではWasmのゲーム用を想定したrequestAnimationFrame駆動/シングルスレッドのRust非同期ランタイムを作ります。
ゲームを扱う上でよく必要になる複数フレームにまたがる処理というのを考えてみます。 これはasync/awaitがとてもうまく使える場面です。
ゲーム分野のasync/awaitはUniTaskの事例が参考になります。 UniTaskを利用することで、複数フレームにまたがる処理をまっすぐ書くことができるようになりました。
例えば、簡単な例で「メッセージウィンドウをアニメーションで表示し、決定ボタンを押すまで待機し、ボタンが押されたらアニメーションでウィンドウを閉じる」という処理を書くとします。 async/awaitを使わないでRustで書いてみると次のようになります。
enum Phase {
WindowOpenAnimation,
WaitKey,
WindowCloseAnimation,
End,
}
struct State {
phase: Phase,
}
impl State {
fn update(&mut self) {
match self.phase {
Phase::WindowOpenAnimation => {
// アニメーション処理
update_window_open_animation();
if is_open_animation_finished() {
self.phase = Phase::WaitKey;
}
}
Phase::WaitKey => {
// キー入力待ち
if is_key_pressed() {
self.phase = Phase::WindowCloseAnimation;
}
}
Phase::WindowCloseAnimation => {
// アニメーション処理
update_close_animation();
if is_close_animation_finished() {
self.phase = Phase::End;
}
}
}
}
}
現在のフェーズを保持してupdateでの処理を分岐しています。
async/awaitを用いて書くイメージは次のとおりです。
async fn update() {
// アニメーション処理
window_open_animation().await;
// キー入力待ち
wait_key().await;
// アニメーション処理
window_close_animation().await;
}
async/awaitを使うことで直列に書くことができ、処理の流れが分かりやすくなりました。
先程のasync/awaitを利用した例のwindow_open_animation
の中身も想像してみましょう。
async/awaitを利用しない場合で使っているupdate_window_open_animation
とis_open_animation_finished
を使って書くと次のようになります。
async fn window_open_animation() {
while !is_open_animation_finished() {
update_window_open_animation();
runtime::next_frame().await;
}
}
ここで注目してほしいのがruntime::next_frame().await
です。
非同期ランタイムがゲームループと協調して動くのに重要な役割を果たしています。
Rustにはすでに素晴らしい非同期ランタイムとしてtokio、あるいは次点でasync-stdが存在します。 また、Wasmで利用する場合、wasm-bindgen-futuresが使えるでしょう。