https://zenn.dev/matcha_choco010/articles/2022-09-03-rust-wasm-async-runtime

はじめに

この記事ではWasmのゲーム用を想定したrequestAnimationFrame駆動/シングルスレッドのRust非同期ランタイムを作ります。

ゲームとasync/await

複数フレームにまたがる処理とasync/await

ゲームを扱う上でよく必要になる複数フレームにまたがる処理というのを考えてみます。 これは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_animationis_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が使えるでしょう。