RustでstaticなEntity Component System


Rustでゲームプログラ厶を組む場合、Entity Component System（以下ECS）による設計が有力な候補となります。

RustのECSライブラリ/フレームワークはいくつかありますが、抽象化の恩恵を得るために、内部はダイナミックな機構が使われていることが多いようです。

しかし、せっかくゼロコスト抽象化を謳っているRustなので、スタティックなECSを作りたい！と思い立って、作り始めました。

そして、だいたい形になったので、今回はその紹介をさせていただきます。

### できたもの

先に、できたものの見た目を紹介します。
例えばこんなコンポーネントを作ったとして、

```Rust

struct Input(f32,f32);  //アナログコントローラ入力 
struct AI{ state: i32 }; //敵のAI とりあえず例として、何かのステートを持つ
struct Velocity(f32,f32); //速度 x,y
struct Position(f32,f32); //位置 x,y
```

下記の様に、ワールドを定義、生成します。

```Rust
world!(
    //型名
    World {
        //コンポーネント型を列挙
        Input,
        AI,
        Velocity,
        Position,
    }
}

//ワールドを生成
let world = World::default();
```

エンティティーの追加

```Rust

//プレイヤーキャラ（エンティティ）の作成
add_entity!(
    world;
    Input::default(),
    Velocity::default(),
    Position::default(),
);

//敵（エンティティ）の作成
add_entity!(
    world;
    AI::default(),
    Velocity::default(),
    Position::default(),
);
```

システムの定義と呼び出し

```Rust

//ゲームループ
while !game_end() {

    //Inputを参照してVelocityを計算するシステム
    system!(
        world,

        //ラムダ式風の書式で、
        //|エンティティIDを受け取る変数, 更新対象の変数:型, 参照する変数:型, 参照する変数:型...|
        |entity_id, vel:&Velocity, input:&Input| {
            //inputの5倍を速度とする
            let mut new_vel = vel.clone();
            new_vel.0 = input.0 * 5f32;
            new_vel.1 = input.1 * 5f32;

            //計算後の値を返す
            new_vel
        }
    );

    //AIからVelocityを計算するシステム
    system!(
        world,
        |entity_id, vel:&Velocity, ai: &AI| {
            let mut new_vel = vel.clone();
            //aiの情報を使って、new_velを決める
            // ... 何かのロジック ...
            //計算後の値を返す
            new_vel
        }
    );

    //VelocityからPositionを計算するシステム
    system!(
        world,
        |entity_id, pos:&Position, vel:&Velocity| {
            //位置に速度を加算
            let mut new_pos = pos.clone();
            new_pos.0 += vel.0;
            new_pos.1 += vel.1;

            //計算後の値を返す
            new_pos
        }
    );
} 
```

こんな感じになりました。
上記の例は、システムが「同じエンティティーIDを持つコンポーネントだけを参照する場合」ですが、もっと複雑なロジックを書くこともできます。


### 中身の説明


では中身の説明に入っていきます。
まず試しに、素直な実装でECSを組んでみると、こんな感じになると思います。

```Rust

//コンポーネント１種類を管理するコンテナを作って
struct ComponentContainer<T> {
    components: Vec<T>,
    //管理情報など
}

//ワールドの定義
struct World {
    inputs: ComponentContainer<Input>,
    ais: ComponentContainer<AI>,
    velocities: ComponentContainer<Velocity>,
    positions: ComponentContainer<Position>,
}
    
```

ここでまず、構造体のフィールド名と、コンポーネント型を１対１で対応させることになります。

```Rust
    positions: ComponentContainer<Position>,
    ^^^ここと                      ^^^ここが
```
同じになるので、わざわざ書くのが面倒な気持ちになります。
特にコンポーネントの種類が増えてくると、煩わしいですね。

かといってタプルを使って

```Rust
type World = (
    ComponentContainer<Input>, 
    ComponentContainer<AI>,
    ComponentContainer<Velocity>,
    ComponentContainer<Position>,
);
```
としても、使うときに、えーとVelocityは何番目だったっけ・・・てなりますね。

なんとか、型名だけを覚えていれば使えるようにできないものでしょうか？

concat_idents (https://doc.rust-lang.org/std/macro.concat_idents.html) を使えばいけそうな気もしますが、型名をidentityにつなげることは無理な様でした。


### そこで、考案したのがこちら

```Rust
macro_rules! recursive_tuple {
    ( $t: ty ) => {
        ($t, ())
    };
    ( $th:ty, $( $tt: ty ),+ $(,)? ) => {
        ($th, recursive_tuple!($($tt),+))
    };
}
```

これは、例えば

```Rust
recursive_tuple!(i32, f32, u32)
```

と書くと

```Rust
(i32, (f32, (u32, ())))
```

こんな風に展開されます。（再帰的なタプル）
これだと、例えば

```Rust
type IntFloatUint = recursive_tuple!(i32, f32, u32);
let ifu = IntFloatUint::default();
```

としたとして、

```Rust
//i32を参照するには
ifu.0
//f32を参照するには
ifu.1.0
//u32を参照するには
ifu.1.1.0
```

このように、再帰的に `1.` を繰り返すことで、目的の型を持つデータを参照できます。
そして、型を指定して参照するようにしたい場合は、ジェネリックなtraitを用いて、下記のようにできます。

```Rust
trait TypeRef<T> {
    fn type_ref(&self) -> &T;
}

impl TypeRef<i32> for IntFloatUint {
    fn type_ref(&self) -> &i32 {
        &self.0
    }
}
impl TypeRef<f32> for IntFloatUint {
    fn type_ref(&self) -> &f32 {
        &self.1.0
    }
}
impl TypeRef<u32> for IntFloatUint {
    fn type_ref(&self) -> &u32 {
        &self.1.1.0
    }
}
```

このimpl をマクロで定義できるようにしてみます。

```Rust
macro_rules! impl_typeref {
    ( $torg:ty, $t: ty ) => {
        impl TypeRef<$t> for $torg {
            fn type_ref(&self) -> &$t {
                &self.0
            }
        }
    };
    ( $torg:ty, $th:ty, $( $tt: ty ),+ $(,)? ) => {
        impl TypeRef<$th> for $torg {
            fn type_ref(&self) -> &$th {
                &self.0
            }
        }
        $(
            impl TypeRef<$tt> for $torg {
                fn type_ref(&self) -> &$tt {
                    self.1.type_ref()
                }
            }
        )+
        impl_typeref!{ recursive_tuple!($($tt),+), $($tt),+ }
    };
}
```

説明します。

```Rust
impl_typeref!(IntFloatUint, i32, f32, u32)
```

と書くと、以下のように再帰的に展開されます。

```Rust
//展開 第一段階
impl TypeRef<i32> for IntFloatUint {
    fn type_ref(&self) -> &i32 {
        &self.0
    }
}
impl TypeRef<f32> for IntFloatUint {
    fn type_ref(&self) -> &f32 {
        self.1.type_ref()
    }
}
impl TypeRef<u32> for IntFloatUint {
    fn type_ref(&self) -> &u32 {
        self.1.type_ref()
    }
}
impl_typeref!( recursive_tuple!(f32, u32) ); // (f32, (u32, ()))に対して再帰
```

```Rust
//展開第二段階
impl TypeRef<f32> for (f32, (u32, ())) {
    fn type_ref(&self) -> &f32 {
        &self.0
    }
}
impl TypeRef<u32> for (f32, (u32, ())) {
    fn type_ref(&self) -> &u32 {
        self.1.type_ref()
    }
}
impl_typeref!( recursive_tuple!(u32) ); // (u32, ())に対して再帰
```

```RUst
//第三段階
impl TypeRef<u32> for (u32, ()) {
    fn type_ref(&self) -> &u32 {
        &self.0
    }
}
```

そうすると、下記のように型を指定してアクセスできるようになります。

```Rust
let i = TypeRef::<i32>::type_ref(&ifu);
```

これもちょっと煩わしいので、マクロにします。

```Rust
macro_rules! typeref {
    ($e:expr, $t:ty) => {
        TypeRef::<$t>::type_ref(&$e);
    };
}
```

そうすると

```Rust
let i = typeref!(ifu, i32);
```

このように「型を指定するだけでフィールドを参照できるタプル」を作ることができます。
なお、型は同じものが２つ使われていないことが前提です。
もし使おうとすると、impl Traitのところで二重定義のエラーがでます。

これをECSのコンポーネントの管理に使うことで、最初に紹介した様な、スタティックにコンポーネントにアクセスするECSの仕組みができました。

ソースはこちらで公開しています（まだ開発中です）
https://github.com/mas-yo/static_ecs

これを使ってサンプルゲームも作っています。
https://github.com/mas-yo/rust-ecs-game

### さいごに

私もまだまだ勉強中なので、それならこんなクレートを使えば便利だよ！とか、ここはこうしたらいいんじゃない？とかあったら、教えてください。

いやーRustって、本当に、面白いものですね。
ではまた、お会いしましょう♪