#memo

indiedev太郎

IWYUでコーディングしよう

IWYUとは

4.15で追加された一つの要素として、C++コンパイルの高速化が上げられている

アンリアル 4.15 がリリースされました!

これは、UEのソースコード全体がIWYU(Include-What-You-Use)という方法で再構築された事によるもので、
従来、Engine.h等、同モジュール内の全ヘッダーが網羅されたヘッダー(Monolithic Headerというらしい)をインクルードしていたが、実際に使用している機能はその一部分であることがほとんどであったため、これを必要最低限のインクルードに書き換えることでコンパイル時間の短縮が実現されたのこと、元ネタはそういうツールっぽい

GitHub - include-what-you-use/include-what-you-use: A tool for use with clang to analyze #includes in C and C++ source files

またプラグインのモジュールでも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]に置換

https://i.gyazo.com/053f73aefbe6644d7a11256b2da49c74.png

  • [ProjectName]\Source\[ModuleName]にPublic、Privateフォルダをそれぞれ作成

https://i.gyazo.com/449df907f142f592204259fe87f1586b.png

  • Publicフォルダ内に新規テキスト作成、[ModuleName].hにリネーム

https://i.gyazo.com/6fe380aae4728789d4452efe3a425eb1.png

  • Privateフォルダ内に新規テキスト作成、[ModuleName]Module.cppにリネーム

https://i.gyazo.com/99427f5577873a073422ee280fb3e190.png

  • [ProjectName]\Source\[ProjectName].Target.csを開く
  • 最終行のOutExtraModuleNames.AddRangeに新しく作成するModuleを追加

https://i.gyazo.com/8720447dd8e26daa61a5322c21cdb4d0.png

  • [ProjectName]\Source\[ProjectName]Editor.Target.csにも同じことを
  • [ProjectName]\[ProjectName].uprojectを開く
  • Modules欄に追加するModuleを追加

https://i.gyazo.com/0f55cbdd032a0e2193dd1e7c88aa2135.png

  • [ProjectName]\[ProjectName].uprojectを右クリック > Generate Visualstudio project file
  • 生成された[ProjectName].slnをクリックしてVSを起動

これで空のモジュールのたたき台が用意できた

https://i.gyazo.com/646ecc1a529daacaf06223dea4e96735.png

いま作成した3ファイルが表示されていたらOK、3つとも開いておく


IWYUの準備

現在自分が確認できた公式でのIWYUについてのページがおそらくこれだけ、エンジンのソースの方は全てIWYUになっているので、そこを見まくる

IWYU Reference Guide | Unreal Engine

とりあえず最初に必要なことが、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の記述ルール

これは先程のページに書いてあるのを一度読んで下さい

IWYU Reference Guide | Unreal Engine

まとめると

  1. .hファイルでは、先頭に#include "CoreMinimal.h"をする
  2. .cppファイルでは、先頭に#include "同名.h"をする
  3. 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は出来ているので、まだ機能は何もないけれど、一度ビルドしてエディタからロード出来ているかの確認をする

https://i.gyazo.com/3d64c4778bbcea13afaf59cf0af1674c.png


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をインクルードするようにする

これで実行してみて、問題なく使えているようならとりあえず全クリです、このモジュールのプラグイン化もかなり楽だし、ビルドも早いっぽいしなので、ちょっと規則を理解すればそんなに苦じゃなさそうだと思いました、おわり