IWYUでコーディングしよう
IWYUとは
4.15で追加された一つの要素として、C++コンパイルの高速化が上げられている
これは、UEのソースコード全体がIWYU(Include-What-You-Use)という方法で再構築された事によるもので、
従来、Engine.h等、同モジュール内の全ヘッダーが網羅されたヘッダー(Monolithic Headerというらしい)をインクルードしていたが、実際に使用している機能はその一部分であることがほとんどであったため、これを必要最低限のインクルードに書き換えることでコンパイル時間の短縮が実現されたのこと、元ネタはそういうツールっぽい
またプラグインのモジュールでもPCHファイルを明示的にインクルードしなくて良くなったなど、なんか微妙に色々変わっている
従来のMonolithic Headerをインクルードするタイプのコーディングは現在も有効だけれども、ビルド時にユーザーが作成したコードに対してもIWYUのオプションが使えるようになっているので、つかっていきましょう
準備
UnrealBuildToolに対してIWYUでやる旨を伝えることで、ビルド時にIWYUの規則を満たしているかを確認してくれるようになる、これはModule単位なので新しいModuleを作成し、それでいろいろ確認していきます、適当なC++プロジェクトは用意しておいて下さい
UE | 4.15.1 |
VS | 2015 |
新規Moduleの作成
出来る方は飛ばして次へ
- ModuleNameを決める(Sakanaにします)
- [ProjectName]\Source\ に ModuleNameフォルダを作る
- [ProjectName]\Source\[ProjectName] にある Build.csを [ProjectName]\Source\[ModuleName]にコピー
- コピーしたBuild.csを[ModuleName].Build.csにリネーム
- リネームしたBuild.csを開き、[ProjectName]を全て[ModuleName]に置換
- [ProjectName]\Source\[ModuleName]にPublic、Privateフォルダをそれぞれ作成
- Publicフォルダ内に新規テキスト作成、[ModuleName].hにリネーム
- Privateフォルダ内に新規テキスト作成、[ModuleName]Module.cppにリネーム
- [ProjectName]\Source\[ProjectName].Target.csを開く
- 最終行のOutExtraModuleNames.AddRangeに新しく作成するModuleを追加
- [ProjectName]\Source\[ProjectName]Editor.Target.csにも同じことを
- [ProjectName]\[ProjectName].uprojectを開く
- Modules欄に追加するModuleを追加
- [ProjectName]\[ProjectName].uprojectを右クリック > Generate Visualstudio project file
- 生成された[ProjectName].slnをクリックしてVSを起動
これで空のモジュールのたたき台が用意できた
いま作成した3ファイルが表示されていたらOK、3つとも開いておく
IWYUの準備
現在自分が確認できた公式でのIWYUについてのページがおそらくこれだけ、エンジンのソースの方は全てIWYUになっているので、そこを見まくる
とりあえず最初に必要なことが、UnrealBuildToolにIWYUでやっていくということを伝えるため、その設定をBuild.csに書く必要がある
public Sakana(TargetInfo Target) { PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; //IWYU PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore" });
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHsを書くと、ビルド時にIWYUの記述ルールで書かれているかのチェックが入るようになる
IWYUの記述ルール
これは先程のページに書いてあるのを一度読んで下さい
まとめると
- .hファイルでは、先頭に#include "CoreMinimal.h"をする
- .cppファイルでは、先頭に#include "同名.h"をする
- IMPLEMENT_MODULEマクロがある[ModuleName]Module.cppファイルが存在する
いままでは、.cppは必ず先頭に[ModuleName].hをインクルードして下さいという規則だったけれど、これの代わりに同名.h(A.cppならA.h)のインクルードが求められるようになった、
また.hでは初めにCoreMinimal.hのインクルードが推奨され、これは実際にほぼすべてのUEソースのヘッダーファイルがこうなっていることを確認できる
\Engine\Source\Runtime\Core\Public\CoreMinimal.hがこれなので、中身が気になる人は自分で確認して下さい
3つめはIWYUは関係なくて、Moduleたりうる要件なので、これは以前からです、名前はなんでもいいっぽいけど
つぎはこれをつくる
Private\SakanaModule.cpp
#include "CoreMinimal.h" #include "Modules/ModuleInterface.h" #include "Modules/ModuleManager.h" class FSakanaModule : public IModuleInterface { public: virtual void StartupModule() override { } virtual void ShutdownModule() override { } virtual bool IsGameModule() const override { return true; } }; IMPLEMENT_MODULE(FSakanaModule, Sakana);
Sakanaを自分の環境のものに置換して下さい、これはJsonモジュール辺りの物を参考にしました、今回はプラグインではなくGameModuleなので、IsGameModule関数をオーバーライドしてtrueを返すようにしている、これは別にProject.cppのようにFDefaultGameModuleImplクラスを渡しても良い
Public\Sakana.h
#pragma once /* Boilerplate *****************************************************************************/ #include "Misc/MonolithicHeaderBoilerplate.h" MONOLITHIC_HEADER_BOILERPLATE() /* Public Dependencies *****************************************************************************/ #include "Core.h" #include "CoreUObject.h" #include "Engine.h" #include "InputCore.h" /* Public includes *****************************************************************************/
このファイルは必須ではなく、作らなくてもビルドは通る、外部からこのモジュールを使う時のMonolithic Headerです
これもJsonModule辺りが参考になるので、必要な人は自分で見て下さい
この時点で最低限のIWYUベースのModuleは出来ているので、まだ機能は何もないけれど、一度ビルドしてエディタからロード出来ているかの確認をする
Actor継承のクラスを作ってみる
まだ何もIWYUっぽい物を書いてないので適当にActor継承のクラスを作ってみる
IWYUの設定をしていても、エディタからのC++クラス作成で自動生成されるものは従来の仕様でコンパイルが通らないため、最初から自分で書くか修正する必要がある
Sakana\Public\Maguro.h
#pragma once #include "CoreMinimal.h" #include "UObject/ObjectMacros.h" #include "GameFramework/Actor.h" #include "Maguro.generated.h" UCLASS() class SAKANA_API AMaguro : public AActor { GENERATED_BODY() public: // Sets default values for this actor's properties AMaguro(); protected: // Called when the game starts or when spawned virtual void BeginPlay() override; public: // Called every frame virtual void Tick(float DeltaTime) override; };
一行目はCoreMinimal.hのインクルードで、IWYUコーディングだと全てのヘッダーでこれを守る
2行目のObjectMacros.hは、ヘッダー内にUCLASS()マクロやらUENUM()やらがある場合は必要っぽい(なんか書かなくてもコンパイルはとおる)、あとはいつも通り
ここらへんのどのインクルードファイルが必要で云々、Referenceには
compile your game project in non-unity mode with PCH files disabled.
って書いてるのだけれど、よくわからんので、APIとにらめっこしてやっています(詳しい人教えてください)
あとはエンジンプラグインにあったりする同じようなクラスのソースを見に行ったり
Sakana\Private\Maguro.cpp
#include "Maguro.h" // Sets default values AMaguro::AMaguro() { // Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it. PrimaryActorTick.bCanEverTick = true; } // Called when the game starts or when spawned void AMaguro::BeginPlay() { Super::BeginPlay(); } // Called every frame void AMaguro::Tick(float DeltaTime) { Super::Tick(DeltaTime); }
.cppでは同名.hをインクルードするようにする
これで実行してみて、問題なく使えているようならとりあえず全クリです、このモジュールのプラグイン化もかなり楽だし、ビルドも早いっぽいしなので、ちょっと規則を理解すればそんなに苦じゃなさそうだと思いました、おわり