中級 data-structures

状態管理

課題

Rust/Wasmアプリでは、状態は2つの場所に存在します:

  • Rust(Wasmリニアメモリ) — ゲームの状態、データモデル、計算結果
  • JavaScript — DOMの状態、UIの状態、ブラウザAPI

過剰なコピーなしにこれらを同期させるパターンが必要です。

パターン1:Rustが状態を所有する(推奨)

最もシンプルで高性能なパターン — Rustがすべてのアプリケーション状態を所有し、JavaScriptは読み取りのみ行います:

#[wasm_bindgen]
pub struct GameState {
    score: u32,
    level: u32,
    entities: Vec<Entity>,
}

#[wasm_bindgen]
impl GameState {
    // JSはメソッドを呼び出して状態を変更
    pub fn update(&mut self, dt: f64) { /* 物理演算 */ }
    pub fn add_entity(&mut self, x: f64, y: f64) { /* ... */ }

    // JSはゲッターで状態を読み取り
    pub fn score(&self) -> u32 { self.score }
    pub fn level(&self) -> u32 { self.level }
    pub fn entity_positions(&self) -> Vec<f64> { /* フラット配列 */ }
}
const state = new GameState();

function gameLoop() {
    state.update(16.67); // Rustで状態を変更
    render(state.entity_positions()); // JSから読み取り
    requestAnimationFrame(gameLoop);
}

パターン2:メッセージパッシング

複雑なアプリケーションでは、コマンドパターンを使います — JSがアクションを送り、Rustがそれを処理します:

#[wasm_bindgen]
pub enum Action {
    AddItem,
    RemoveItem,
    UpdateScore,
    Reset,
}

#[wasm_bindgen]
impl AppState {
    pub fn dispatch(&mut self, action: Action, payload: &str) -> String {
        match action {
            Action::AddItem => {
                self.items.push(payload.to_string());
                format!("Added: {}", payload)
            }
            Action::RemoveItem => {
                // payloadからインデックスをパース
                "Removed".to_string()
            }
            Action::Reset => {
                self.items.clear();
                "Reset".to_string()
            }
            _ => "Unknown action".to_string(),
        }
    }
}

パターン3:共有メモリ(上級)

大量データ(ゲームワールド、画像バッファ)の場合、メモリを直接共有します:

#[wasm_bindgen]
impl World {
    /// Wasmメモリ内のピクセルバッファへのポインタを返す
    pub fn pixels_ptr(&self) -> *const u8 {
        self.pixels.as_ptr()
    }

    pub fn pixels_len(&self) -> usize {
        self.pixels.len()
    }
}
// JSがWasmメモリから直接読み取り — ゼロコピー!
const ptr = world.pixels_ptr();
const len = world.pixels_len();
const pixels = new Uint8Array(wasm.memory.buffer, ptr, len);

// Canvasに描画
const imageData = new ImageData(
    new Uint8ClampedArray(pixels.buffer, pixels.byteOffset, pixels.byteLength),
    width, height
);
ctx.putImageData(imageData, 0, 0);

避けるべきアンチパターン

やってはいけないこと 理由 代わりにすべきこと
毎フレームJSONにシリアライズ 遅い、文字列を確保する フラット配列を返すか共有メモリを使用
状態全体をJSにクローン コピーのコストが高い JSが必要なもののゲッターを公開
JSがRustメモリを直接変更 安全でなく、状態が破損する可能性 バリデーション付きメソッドを使用
RustにDOM参照を保持 ライフタイムの問題が複雑 JSがDOMを管理、Rustがデータを管理

試してみよう

スターターコードは、追加・削除・クエリメソッドを持つ状態構造体を示しています。この「Rustが状態を所有する」パターンは、ほとんどのアプリケーションで有効です。

試してみる

チャプタークイズ

すべての問題に正解してレッスンを完了しましょう