Godotのソースコードを理解せよ！メインループ編

# はじめに
どうも、ゲームプログラマーのChocolaMintです。

[Godot](https://godotengine.org/)という、最近「なぜか」流行っているオープンソースのゲームエンジン、ご存知でしょうか。

ここの「オープンソース」について、皆さんはどう認識しているのでしょうか。
* 無料で使える！（例外もありますが、Godotの場合は本当に無料）
* MITライセンスなので、例えメインブランチが突然有料化宣言を出しても、フォークして無料バージョンを作れる！
* [ソースコード](https://github.com/godotengine/godot)が見られる！

…で？

そうですよね。ソースコードが見られるのって、別にいいじゃない？どうせエンジン改造なんて、企業レベルの開発チームじゃないとできないことでしょう。しかもC++ってめんどくさそうですし…

別のオープンソースのゲームエンジンの場合は、たしかにそうかもしれませんね。が、Godotは実は読みやすい、改造しやすい方なんです！

今回の新シリーズ、「Godotのソースコードを理解せよ！」では、毎回Godotの一部機能を抜粋して、関連のソースコードの実装を紹介します。Godotのソースコードの理解を深めることで、Godotをもっと自由自在に使えるはずです。少しでもみんなのお役に立てれば幸いです。

では、本題に入りましょうか。

# Godotのメインループ

ほとんどのゲームに不可欠な無限ループ。大体のゲームエンジンのメインループはこんな感じです：
1. 1回以上の物理処理の更新（FixedUpdate、PhysicsTick、_physics_processなど）
2. ゲーム内のオブジェクトを更新（Update、Tick、_processなど）
3. 描画処理

もちろん、Godotにもメインループがあります。今回は以下の二つの質問を中心に解説します。
* メインループの中に**どんな処理**が発生するのか
* メインループは**どこから**実行されているのか

解説の便宜上、今回はマスターブランチ（4.3）のバージョンを解説します。別のバージョンを使っている方はバージョンの違いに気をつけてください。もっとも、メインループの仕組みはそんなに変わらないはずですが。

## メインループ（`Main::iteration()`）にある処理

Godotのメインループは[**`Main::iteration()`**](https://github.com/godotengine/godot/blob/3978628c6cc1227250fc6ed45c8d854d24c30c30/main/main.cpp#L4001)というstatic関数で実装されています。

ざっくりまとめると、以下のような処理が発生します：（一部省略）
```cpp
bool Main::iteration() {
    // 1. 時間関連の変数の初期化、そしてタイマーの同期
    // 2. XR（いわゆるVR/AR）処理
    // 3. NavigationServer2D/3D（探索AI機能）の同期
    // 4. 物理処理
    // 5. MainLoop（≒シーンツリー）のprocess関数を呼び出して、シーン内のノードを更新
    // 6. 描画処理
    // 7. サウンド処理

    // 終了したい場合はfalseをreturn
}
```

今回は「5. MainLoop処理」だけを紹介します。ゲームの開発者として、ノードはどうやって更新されているのか、やはり気になりますよね。

MainLoop処理では、今使っている`MainLoop`クラスを取得して、`MainLoop::process()`を実行します。ここの`process_step * time_scale`で、タイムスケールを適応して、ゲーム内の時間の速さを制御しています。
```cpp
if (OS::get_singleton()->get_main_loop()->process(process_step * time_scale)) {
    exit = true;
}
```
:::note
#### OSクラスとは？
オペレーティングシステムの機能を管理するシングルトンクラスです。対応プラットフォームによって、異なる実装があります。例えば、WindowsのOSクラスは[**`OS_Windows`**](https://github.com/godotengine/godot/blob/3978628c6cc1227250fc6ed45c8d854d24c30c30/platform/windows/os_windows.h#L97)で、WebのOSクラスは[**`OS_Web`**](https://github.com/godotengine/godot/blob/3978628c6cc1227250fc6ed45c8d854d24c30c30/platform/web/os_web.h#L44)です。

Godotのコードベースにはたくさんのシングルトンがあって、`get_singleton()`で取得できます。
:::

ここの[`MainLoop`](https://docs.godotengine.org/en/stable/classes/class_mainloop.html)はGDScriptで実装できますが、デフォルト設定は[`SceneTree`](https://docs.godotengine.org/en/stable/classes/class_scenetree.html#class-scenetree)を使います。`SceneTree`というのは、ゲーム内の全ての「ノード」のヒエラルキー（ツリー構造）を管理するクラスです。GDScriptの[`Node.get_tree()`](https://docs.godotengine.org/ja/4.x/tutorials/scripting/scene_tree.html)で取得するのは普通ですね。

`scene/main/scene_tree.cpp`の[**`SceneTree::process()`**](https://github.com/godotengine/godot/blob/3978628c6cc1227250fc6ed45c8d854d24c30c30/scene/main/scene_tree.cpp#L506)では、シーン内のノードを更新する処理が書かれてあります。
1. `MultiplayerAPI`（マルチプレイ）の[ポーリング](https://docs.godotengine.org/ja/4.x/classes/class_multiplayerapi.html#class-multiplayerapi-method-poll)を行う
2. [`"process_frame"`](https://docs.godotengine.org/en/stable/classes/class_scenetree.html#class-scenetree-signal-process-frame)シグナルを発信する
3. シーン内のノードを「`process_priority`が低い方から」、そして「上から下まで」`Node::notification(Node::NOTIFICATION_PROCESS)`を呼び出して（`SceneTree::_process(false)` ）、間接的にGDScriptの`Node._process()`を呼び出すことになる
4. [Timer](https://docs.godotengine.org/ja/4.x/classes/class_timer.html)ノードを更新する
5. [Tween](https://docs.godotengine.org/ja/4.x/classes/class_tween.html)ノードを更新する

実はSceneTreeには[**`SceneTree::physics_process`**](https://github.com/godotengine/godot/blob/3978628c6cc1227250fc6ed45c8d854d24c30c30/scene/main/scene_tree.cpp#L475)という関数もあります。こっちは物理処理のループで呼び出されて、`SceneTree::_process(false)`じゃなくて`SceneTree::_process(true)`を呼び出します。

:::note
#### ノード更新の細かい順番について
まず、[**`SceneTree::_process`**](https://github.com/godotengine/godot/blob/3978628c6cc1227250fc6ed45c8d854d24c30c30/scene/main/scene_tree.cpp#L506)で、全てのノードは[`process_thread_group`](https://docs.godotengine.org/en/stable/classes/class_node.html#class-node-property-process-thread-group)でいくつかのグループに分けられています。ほとんどのノードはメインスレッドで更新されますが、マルチスレッドの場合は別のグループになります。

デフォルト設定の`PROCESS_THREAD_GROUP_INHERIT`だと親ノードのグループを継承しますが、`PROCESS_THREAD_GROUP_MAIN_THREAD`だと別のグループとしてメインスレッドで更新されて、そして`PROCESS_THREAD_GROUP_SUB_THREAD`だと`WorkerThreadPool`のスレッドで更新されます。`process_thread_group_order`を指定することで、グループの間の順番を指定することもできます。実際、以下のコードでソートしています。
```cpp
// SceneTree::_process(bool b_physics)の抜粋。ProcessGroupSortファンクターでソートする。
process_groups.sort_custom<ProcessGroupSort>();
```
```cpp
bool SceneTree::ProcessGroupSort::operator()(const ProcessGroup *p_left, const ProcessGroup *p_right) const {
	int left_order = p_left->owner ? p_left->owner->data.process_thread_group_order : 0;
	int right_order = p_right->owner ? p_right->owner->data.process_thread_group_order : 0;

    // process_thread_group_orderが同じの場合、サブスレッドのグループが先
	if (left_order == right_order) {
		int left_threaded = p_left->owner != nullptr && p_left->owner->data.process_thread_group == Node::PROCESS_THREAD_GROUP_SUB_THREAD ? 0 : 1;
		int right_threaded = p_right->owner != nullptr && p_right->owner->data.process_thread_group == Node::PROCESS_THREAD_GROUP_SUB_THREAD ? 0 : 1;
		return left_threaded < right_threaded;
	} else {
		return left_order < right_order;
	}
}
```

ノード側は[**「ツリーに入る時」（`NOTIFICATION_ENTER_TREE`）**](https://github.com/godotengine/godot/blob/3978628c6cc1227250fc6ed45c8d854d24c30c30/scene/main/node.cpp#L96)、`Node::_add_to_process_thread_group`を通じてSceneTreeのグループに登録して、そして[**「ツリーから出る時」（`NOTIFICATION_EXIT_TREE`）**](https://github.com/godotengine/godot/blob/3978628c6cc1227250fc6ed45c8d854d24c30c30/scene/main/node.cpp#L165)、`Node::_remove_from_process_thread_group`で登録を解除します。

それから、[**`SceneTree::_process_group`**](https://github.com/godotengine/godot/blob/3978628c6cc1227250fc6ed45c8d854d24c30c30/scene/main/scene_tree.cpp#L909)で、`ComparatorWithPriority`もしくは`ComparatorWithPhysicsPriority`でグループ内のノードをソートし、`process_priority`の小さい順で、`Node::notification`を呼び出します。同じ`process_priority`の場合は深度が浅い方から呼び出します。
```cpp
if (p_physics) {
    if (p_group->physics_node_order_dirty) {
        nodes.sort_custom<Node::ComparatorWithPhysicsPriority>();
        p_group->physics_node_order_dirty = false;
    }
} else {
    if (p_group->node_order_dirty) {
        nodes.sort_custom<Node::ComparatorWithPriority>();
        p_group->node_order_dirty = false;
    }
}
```
```cpp
struct ComparatorWithPriority {
    bool operator()(const Node *p_a, const Node *p_b) const { 
        return p_b->data.process_priority == p_a->data.process_priority 
            ? p_b->is_greater_than(p_a) // 深度を比較
            : p_b->data.process_priority > p_a->data.process_priority; 
    }
};

struct ComparatorWithPhysicsPriority {
    bool operator()(const Node *p_a, const Node *p_b) const { 
        return p_b->data.physics_process_priority == p_a->data.physics_process_priority 
            ? p_b->is_greater_than(p_a) // 深度を比較
            : p_b->data.physics_process_priority > p_a->data.physics_process_priority; }
};
```

余談ですが、[**`Node::is_greater_than`**](https://github.com/godotengine/godot/blob/3978628c6cc1227250fc6ed45c8d854d24c30c30/scene/main/node.cpp#L1964)の実装には、実は「深度を計算するループ」が入っているんです。ほとんどの場合、ノードの深度は言うほど深くないからパフォーマンス的に別にいいと思います。（気にはなりますが）「壊れてないものを直すな」ということですね。
:::


## メインループはそもそもどこから実行されるのか

ほとんどの場合、`Main::iteration()`は各プラットフォーム（Windows、Androidなど）の`OS`クラスのどこかにある`while(true)`の無限ループから呼び出されます。

例として、`platform/windows/os_windows.cpp`の[**`OS_Windows::run()`**](https://github.com/godotengine/godot/blob/3978628c6cc1227250fc6ed45c8d854d24c30c30/platform/windows/os_windows.cpp#L1657)では、以下のようなコードが書いてあります：
```cpp
void OS_Windows::run() {
	if (!main_loop) {
		return;
	}

	main_loop->initialize();

	while (true) {
		DisplayServer::get_singleton()->process_events(); // get rid of pending events
		if (Main::iteration()) {
			break;
		}
	}

	main_loop->finalize();
}
```
:::note
#### では、`OS_Windows::run()`はどこから実行されるのか？
すぐ隣の`platform/windows/godot_windows.cpp`にて、Windows対応の[**`WinMain`**](https://github.com/godotengine/godot/blob/3978628c6cc1227250fc6ed45c8d854d24c30c30/scene/main/scene_tree.cpp#L909)関数があります。

`WinMain`->`main`->`_main`->`widechar_main`の順で、コマンドライン引数をWCHAR（ワイド文字、UTF-8対応）として取得してから、`OS_Windows::run()`を実行します。
:::

もう一つ面白い例は、Web対応の`OS_Web`のメインループの実装です。こっちの場合は、`run()`ではなくて、[**`OS_Web::main_loop_iterate`**](https://github.com/godotengine/godot/blob/3978628c6cc1227250fc6ed45c8d854d24c30c30/platform/web/os_web.cpp#L75)を使っています。この関数は`platform/web/web_main.cpp`の[**`main_loop_callback`**](https://github.com/godotengine/godot/blob/3978628c6cc1227250fc6ed45c8d854d24c30c30/platform/web/web_main.cpp#L96)から呼び出されて、そして`main_loop_callback`は[**`godot_web_main`**](https://github.com/godotengine/godot/blob/3978628c6cc1227250fc6ed45c8d854d24c30c30/platform/web/web_main.cpp#L138)でEmscriptenの[メインループ](https://emscripten.org/docs/api_reference/emscripten.h.html#c.emscripten_set_main_loop)として設定されています。Emscriptenの仕組みを使って、ブラウザーの[`requestAnimationFrame`](https://developer.mozilla.org/ja/docs/Web/API/Window/requestAnimationFrame)コールバックに登録したから、「無限ループ」を動かしているのは実はブラウザーの方なんです。なので`while(true)`みたいな構文が見つからなかったです。

:::note
#### [Emscripten](https://emscripten.org/)とは？
LLVMを使うプログラミング言語（C、C++、Rustなど）をWebAssemblyにコンパイルして、ブラウザーで動けるようにするツールチェーンです。
:::


ところが、「`Main::iteration()`」でソースコードを検索してみると、無限ループ以外のところからメインループを実行することは意外とあります。

例えば`editor/progress_dialog.cpp`の[**`ProgressDialog::_update_ui()`**](https://github.com/godotengine/godot/blob/3978628c6cc1227250fc6ed45c8d854d24c30c30/editor/progress_dialog.cpp#L129)では、プログレスバーの描画の更新のため、`Main::iteration()`をもう一回実行します。
```cpp
void ProgressDialog::_update_ui() {
	// Run main loop for two frames.
	if (is_inside_tree()) {
		DisplayServer::get_singleton()->process_events();
		Main::iteration();
	}
}
```
:::note
#### なぜプログレスバーはこんなことしないと更新できないのか
プログレスバーの使い方をざっくり言うと：
* 重い処理に入る前、表示させる
* 処理を段階に分けて、各段階に入る前プログレスバーの表示を更新させ、段階の内容と処理全般の進捗（プログレス）を更新する

つまり、「次のフレーム」はここの処理が終わるまで来ません。普通は[`queue_redraw`](https://docs.godotengine.org/en/stable/classes/class_canvasitem.html#class-canvasitem-method-queue-redraw)で次のフレームでUIを更新させるが、ここでは`Main::iteration()`を直接に呼び出すことで、描画処理を強制的に実行して、プログレスバーの更新を行います。

こうしないと、この処理自体を複数フレームに分けて実行するしかないですね。

ProgressDialogの実際の使用例は`platform/windows/export/export_plugin.cpp`の[**`EditorExportPlatformWindows::run`**](https://github.com/godotengine/godot/blob/3978628c6cc1227250fc6ed45c8d854d24c30c30/platform/windows/export/export_plugin.cpp#L939)を参考してください。
:::

# まとめ

Godotのメインループのコードを読んで、普段使わなくても聞いたことがある機能の名前が出たりしまして、意外と親しく見えますね。「`process_priority`」のシステムがあるから、ソーティングしないといけないとか、納得できる実装がほとんどでした。

次回のテーマはまだ決めていないですが、ソースコード解説の上、**エンジン改造**の話を入れてみたい気持ちはあります。では、お楽しみに！