Bevyデバッグのすすめ

# はじめに
本記事では、Rust製ゲームエンジン[Bevy](https://bevyengine.org/)を使用時に役立つデバッグ設定を紹介します。

本記事はBevyやECSの紹介・解説ではありません。Bevyについて知りたい方は、以下のリンクを参照してください。

https://bevyengine.org/learn/quick-start/introduction/
https://bevyengine.org/examples/

# 環境
[bevy](https://docs.rs/bevy/0.15.2/bevy/index.html): `0.15.2`

# `dev`モードを作成
開発中のみ使用したい機能がある場合、`dev`featureを定義します。[cfg](
https://doc.rust-jp.rs/rust-by-example-ja/attribute/cfg.html)アトリビュートを活用して、デバッグ設定を簡単にできるようにします。

```toml:Cargo.toml
[features]
default = ["dev"]
dev = [
    # release時には追加したくないfeatureをここに
    "bevy/dynamic_linking",
    "bevy/bevy_dev_tools",
]
```

https://github.com/TheBevyFlock/bevy_new_2d/blob/main/Cargo.toml

# 自身のクレートのログレベルを設定
自身のクレートのみ、デバッグレベルのログを表示させます。
```rs:main.rs
use bevy::prelude::*;

#[cfg(feature = "dev")]
const LOG_FILTER: &str = "wgpu=error,my_crate=debug";
#[cfg(not(feature = "dev")]
const LOG_FILTER: &str = "wgpu=error";

fn main() {
    App::new()
        .add_plugins(DefaultPlugins.set(LogPlugin {
            filter: LOG_FILTER.to_string(),
            ..default()
        }))
        .add_systems(Startup, test_debug_message)
        .run();
}

fn test_debug_message() {
    debug!("some useful debug messages");
}
```

https://bevy-cheatbook.github.io/fundamentals/log.html

# `bevy-inspector-egui`の導入
[bevy-inspector-egui](https://github.com/jakobhellermann/bevy-inspector-egui)を導入することで、[Entity](https://docs.rs/bevy/0.15.2/bevy/ecs/entity/struct.Entity.html)などのデバッグが容易になります。また、コンポーネントの値をランタイムで調整することができるので、スタイルやカメラなどの調整にも便利。

## `bevy_remote_inspector`
`bevy-inspector-egui`のオルタナティブとして、[`bevy_remote_inspector`](https://github.com/notmd/bevy_remote_inspector)も同様の機能を持ちます。好きな方を使ってください。

# 各Entityに名前を付ける
Entityをスポーンする際は、できるだけ[Name](https://docs.rs/bevy/0.15.2/bevy/core/struct.Name.html)コンポーネントを追加しましょう。

`bevy-inspector-egui`でEntityリストを表示するとき、`Name`コンポーネントの文字列が、ラベルとして表示されます。`Name`コンポーネントを追加しないと、無名のEntityが大量に存在することになり、デバッグ時に困ります。

:::note warn
Entityを識別する目的で`Name`コンポーネントを使わないでください。Entityを識別したい場合、[Markerコンポーネントパターン](https://bevy-cheatbook.github.io/programming/ec.html#marker-components)を使います。
:::

## `spawn`関数を拡張して、`Name`を確実に設定する
`Commands`に対する拡張トレイトとして、`spawn_named`のようなメソッドを追加しても良いかもしれません。

```rs:utils.rs
use std::borrow::Cow;
use bevy::prelude::*;

pub trait SpawnNamedEntityExt {
    fn spawn_named<N, B>(&mut self, name: N, components: B) -> EntityCommands<'_>
    where
        N: Into<Cow<'static, str>>,
        B: Bundle;
}

impl SpawnNamedEntityExt for Commands<'_, '_> {
    fn spawn_named<N, B>(&mut self, name: N, components: B) -> EntityCommands<'_>
    where
        N: Into<Cow<'static, str>>,
        B: Bundle,
    {
        self.spawn((Name::new(name), components))
    }
}
```

## `Observer`にも名前を付ける
[Observer](https://docs.rs/bevy/0.15.2/bevy/ecs/prelude/struct.Observer.html)は、特定のイベントのトリガーを観測するシステムを保持するコンポーネントの一種です。[add_observer](https://docs.rs/bevy/0.15.2/bevy/app/struct.App.html#method.add_observer)を実行すると、`Observer`コンポーネントを持つEntityがスポーンします。

:::note warn
`Observer`はBevy0.14で追加された比較的新しいAPIであり、将来的に破壊的変更が行われます。本記事は、Bevy0.15.2のAPIを基準に書かれています。
:::

`Observer`はコンポーネントですが、その中身は[Trigger](https://docs.rs/bevy/0.15.2/bevy/prelude/struct.Trigger.html)を第一引数に取る関数です。したがって、`Observer`を持つEntityには、関数へのパスがわかる情報を追加するとデバッグ時に役立ちます。[std::any::type_name](https://docs.rs/rustc-std-workspace-std/latest/std/any/fn.type_name.html)を用いて自動で名前を付けるユーティリティを書いてみました。

```rs:utils.rs
use bevy::{ecs::system::IntoObserverSystem, prelude::*};
use std::borrow::Cow;

pub trait AddObserverExt {
    fn add_observer_named<E: Event, B: Bundle, M>(
        &mut self,
        observer: impl IntoObserverSystem<E, B, M>,
    ) -> &mut Self;
}

impl AddObserverExt for App {
    fn add_observer_named<E: Event, B: Bundle, M>(
        &mut self,
        observer: impl IntoObserverSystem<E, B, M>,
    ) -> &mut Self {
        let name_components = observer_name_components(&observer);

        self.world_mut()
            .spawn((Observer::new(observer), name_components));
        self
    }
}

/// Observer関数の型名をもとに、デバッグ情報を生成する
fn observer_name_components<T>(observer: &T) -> impl Bundle {
    let type_name = std::any::type_name_of_val(observer);

    // エンティティ名は簡潔に
    let name = type_name.split("::").last().unwrap();

    (
        Name::new(name),
        
        // フルパスも保持しておく
        ObserverSystemTypeName(Cow::Borrowed(type_name)),
    )
}

/// Observer関数へのフルパス
#[derive(Debug, Component, Reflect)]
struct ObserverSystemTypeName(Cow<'static, str>);
```

メイン例:
```rs:main.rs
use bevy::prelude::*;

mod utils;
use utils::*;

fn main() {
    App::new()
        .add_plugins((
            // ここではLogPluginの設定を省略しています
            DefaultPlugins,
            
            #[cfg(feature = "dev")]
            bevy_inspector_egui::quick::WorldInspectorPlugin::new(),
        ))
        .add_systems(Startup, setup)
        .add_observer_named(on_click)
        .run();
}

fn setup(mut commands: Commands) {
    commands.spawn_named("Camera2d", Camera2d);

    commands.spawn_named(
        "Interaction",
        Node {
            width: Val::Percent(100.0),
            height: Val::Percent(100.0),
            ..default()
        },
    );
}

fn on_click(
    trigger: Trigger<Pointer<Click>>,
    query: Query<(&Name, &ObserverSystemTypeName)>
) {
    let observer_entity = trigger.observer();
    let (name, path) = query.get(observer_entity).unwrap();
    debug!("name: {}", name);   // "on_click"
    debug!("path: {}", path.0); // "my_crate::on_click"
}
```