メインコンテンツまでスキップ

Component Model ではじめる

WaaS の Component Model API を使用すると、WebAssembly Component Model に基づくコンポーネントを実行できます。

通常の WebAssembly モジュールでは引数や戻り値の型が数値に限られていましたが、Component Model では、文字列やリスト、構造体などより複雑な型を持つデータをやり取りすることができます。

このチュートリアルでは、Rust を使用してコンポーネントを作成し、Unity で実行する方法を説明します。

1. 必要なツールのインストール

Rust ツールチェインをインストールし、wasm32-unknown-unknownターゲットを追加します。

rustup target add wasm32-unknown-unknown

2. WITファイルを作成する

Component Model では、WIT (WebAssembly Interface Type) という IDL を使って型や関数のシグネチャを事前に定義します。

まずは Unity プロジェクトのルート下に wit/sequence.wit を作成します。
以下は簡単な会話シーンで使うことを想定した WIT の例です。

package my-game:my-sequencer;

world sequence {
import env;
export play: func();
}

interface env {
show-message: func(speaker: string, message: string);
}

world はこの WebAssembly コンポーネントのインポート・エクスポートする機能のセットを定義するものです。interface env はホスト環境側で実装しコンポーネントにインポートする機能のセットです。

2. Rust ソースファイルを作成する

Rust でコンポーネントを作成するには、上で作成した WIT ファイルから Rust 用のバインディングを生成する必要があります。これには wit-bindgen というツールを使います。使用する言語によって使い方は異なりますが、今回は Rust での使い方を紹介します。

Assets 下の任意の場所に sequence_0.rs を作成します。

use crate::my_game::my_sequencer::env::show_message;

wit_bindgen::generate!({
path: "../../../../../wit",
world: "my_game::my_sequencer/sequnce"
});

struct Sequence;

impl Guest for Sequence {
fn play() {
show_message("ぼく", "こんにちは!");
}
}

export!(Sequence);
備考

wit-bindgen WIT ファイルをもとに generate! マクロでバインディングを生成します。
wit-bindgen は WIT を検索する際、Rust ソースファイルではなく Library/com.ruccho.waas/rust/crates/~~~/Cargo.toml を基準とします。../../../../../wit というパスは、この Cargo.toml からプロジェクトルートを参照するためのものです。
詳しい背景については Rust Importer の使用 を参照してください。

3. 必要な設定を行ってインポートする

作成した Rust ソースファイルを Unity プロジェクトにインポートします。

  • Crate Root を有効化する
  • Dependenciesvia Registry な依存関係を追加し、Name に wit-bindgen を指定する
  • Componentize を有効化する
    • Componentization SettingsWit Directorywit を指定する
    • Worldmy-game::my-sequencer/sequnce を指定する
備考

各インポート設定について詳しくは Rust Importer の使用 を参照してください。

Apply でインポートします。コンパイルとコンポーネント化が完了すると、インスペクタにコンポーネントの情報が表示されます。

関数 play がエクスポートされていることが確認できました。

4. C# 用のバインディングの生成

作成したコンポーネントを簡単に実行するために、C# のバインディングを作成しましょう。WaaS では wit2waas という CLI ツールを提供しており、WIT ファイルから C# のインターフェースを生成することができます。

Unity プロジェクトのルートで wit2waas を実行します。

cargo install wit2waas
wit2waas --out "Assets/WaaS Generated"

すると、先ほどの WIT から Assets/WaaS Generated/my-game/my-sequencer に C# コードが生成されます。

// <auto-generated />
#nullable enable

namespace MyGame.MySequencer
{
// interface env
[global::WaaS.ComponentModel.Binding.ComponentInterface(@"env")]
public partial interface IEnv
{
[global::WaaS.ComponentModel.Binding.ComponentApi(@"show-message")]
global::System.Threading.Tasks.ValueTask ShowMessage(string @speaker, string @message);

}
}

いろいろと属性がついていますが、これによって Source Generator が具体的に WebAssembly コンポーネントと値をやり取りするためのコードを生成します。

IEnv (WIT では interface env) は、C# から WebAssembly 側にインポートする機能のセットを定義したものでした。これに実装を与えておきます。

internal class Env : IEnv
{
public ValueTask ShowMessage(string speaker, string message)
{
Debug.Log($"{speaker}{message}」");
return default;
}
}

5. 実行する

これで、Rust で作成したコンポーネントを Unity で実行する準備が整いました。

using System.Collections.Generic;
using MyGame.MySequencer;
using UnityEngine;
using WaaS.ComponentModel.Runtime;
using WaaS.Runtime;
using WaaS.Unity;

public class RunSequenceTest : MonoBehaviour
{
[SerializeField] private ComponentAsset componentAsset;

private async void Start()
{
var component = await componentAsset.LoadComponentAsync();
var instance = component.Instantiate(new Dictionary<string, ISortedExportable>()
{
{ "my-game:my-sequencer/env", IEnv.CreateWaaSInstance(new Env()) }
});
using var context = new ExecutionContext();
var sequence = new ISequence.Wrapper(instance, context);
await sequence.Play(); // ぼく「こんにちは!」
}
}

RunSequenceTest を GameObject にアタッチし、Component Asset に先ほどインポートしたコンポーネントをアサインします。
実行すると、コンソールに ぼく「こんにちは!」 と表示されるはずです。