C++ ときどき ごはん、わりとてぃーぶれいく☆

USAGI.NETWORKのなかのひとのブログ。主にC++。

UE4 の UCLASS の定義の基底クラスに名前空間を使っている子が居る場合の不幸と安直な回避策について

現状の UE4C++namespace を使おうと思っても面倒事が増えるだけなので、現実問題としてはわざわざ UE4C++ コードを記述するに当たっては namespace を用いないほうが賢い。少なくとも UE-4.16 現在は。

しかし、そうは言っても、世の中の素敵な C++ ライブラリーを UE4 プロジェクトへ取り込んで使いたい場合もある。単にどこかのメンバー関数から呼び出すだけだとか、そういう場合はたいてい問題は起こらない。しかし、深刻な問題となる場合に遭遇したので tips として紹介したい。

UCLASS で定義する UHogeComponent なり AHogeActor なり、そういったクラスの定義で、素敵な C++er 仕様のきちんと namespace を使ったライブラリーのクラスを継承して使いたい場合があったとしよう。継承じゃなくてコンポジションすればいいとかは本題ではないのでここでの議論は割愛する。

具体例は次の通り:

// MyComponent.h
#pragma once
#include "CoreMinimal.h"
#include <memory>
#include "MyComponent.generated.h"

// UCLASS() 付きで定義するクラスの基底クラスに名前空間付きの
// クラスを使用するコードを書いてしまうとビルド不能に陥る例
UCLASS()
class MyComponent
  : public std::enable_shared_from_this< MyComponent >
{
  GENERATED_BODY()
};

なお、 UE4 では std::enable_shared_from_thisstd::shared_ptr に対応する TSharedFromThisTSharedPtr が用意されているのでこの例の場合はそれらを使うだけでも問題は回避できるが、この例で std::enable_shared_from_this を使ったのは、誰でも C++ 標準ライブラリーのクラスとしてすぐに include してどのような問題が発生するのか試しやすいために選んだ。ついで、 enable_shared_from_thisコンポジションではなく継承で使う理由のある例としても有意性が高い事も選んだ理由に含まれる。

このコードはビルドすると次のようなエラーに見舞われる。

MyComponent.h(11) : Error: Missing '{' in 'Class'

このエラーは C++ コンパイラーの前に UE4UCLASS() などを解釈して独自の追加のヘッダーファイル “MyComponent.generated.h” 等を生成するために動かす UnrealHeaderTool が UCLASS で定義されるクラスについて基底クラス部分やその他あちらこちらでそもそも C++ の namespace 機能が使われる事をまったく想定していないためにパースに失敗して発生してしまう。

悲しい。

しかし、現実問題として我々プログラマーUE4 の世界とは無関係に開発された有用な多くの C++ コード資源を活用して、さくっと目的のプロダクトを開発したい。このような開発に対しては本質的でないトラブルの対応のためにオレオレライブラリーやオレオレラッパーを書いていいのはそれ自体が趣味の場合だけだ。そこで、

// MyComponent.h
#pragma once
#include "CoreMinimal.h"
#include <memory>
#include "MyComponent.generated.h"

// 名前空間を使わないエイリアスを定義
template < typename T > using std_enable_shared_from_this = std::enable_shared_from_this< T >;
// 名前空間を使わないエイリアスを介して UCLASS() 付きのクラスの基底クラスとして使用
UCLASS()
class MyComponent
  : public std_enable_shared_from_this< MyComponent >
{
  GENERATED_BODY()
};

これでいい。少なくとも UnrealHeaderTool が namespace (というか名前空間解決演算子)を考慮していない UE-4.16 執筆現在は。悲しい事だがプログラマーは現実の製品を作ってなんぼ・w・ 未来の UE4 が namespace に美しく対応する日を夢見て現実を乗り越えよう。

UE4 における enum class の「フラグ」化

// ↓ブループリントで使わない場合はこの1行は不要
UENUM( BlueprintType, meta=( Bitflags ) )
enum class EHoge: uint8
{ EV_Nothing = 0
, EV_Flag1 = 0x01
, EV_Flag2 = 0x02
, EV_Flag3 = 0x04
, EV_Flag4 = 0x08
, EV_FlagAll =
    EV_Flag1 |
    EV_Flag2 |
    EV_Flag3 |
    EV_Flag4
};
// ↑ こんな enum class に ↓ このマクロを通しておく(今回の本題)
ENUM_CLASS_FLAGS( EHoge )

ENUM_CLASS_FLAGS マクロに通しておくと、 EHoge で次のようなフラグ計算でお馴染みの機能についてユーザー独自に定義したり、 underlying_type_tstatic_cast して云々など必要なくなり、楽ができるようになる。

auto a = EHoge::Nothing;
// フラグ追加
a |= EHoge::EV_Flag3;
// フラグ判定
auto result_of_any = ( a & EHoge::EV_FlagAll ) != EHoge::EV_Nothing;

参考

  1. Bitmask Enum declaration in 4.12, what's missing? - UE4 AnswerHub

UE4: C++er 視点での実行中の AActor への USceneComponent の動的なオブジェクトの実行中の生成、レジスター、アタッチ、デタッチ、アンレジスター、破棄の方法

状況 (½) おさらい編: AActor::AActor (アクターのコンストラクター) で生成する場合

// .h
  UHogeComponent* HogeComponent;
// .cpp
AActor::AActor()
{
  RootComponent = CreateDefaultSubobject< USceneComponent >( TEXT( "RootComponent" ) );
  HogeComponent = CreateDefaultSubobject< UHogeComponent >( TEXT( "Hoge" ) );
  HogeComponent->SetupAttachment( RootComponent );
}

以上のように ctor で"デフォルト"のサブオブジェクトとして生成する場合には CreateDefaultSubobject を使う。

Note: コンポーネント間のアタッチメントは SetupAttachment を使うのがナウい方式。やや古い参考文献を見ると [[deprecated]]API を使っている事があるのでコンパイラーの warning を見てモダン化する。

状況 (2/2) 本編: 実行中の任意のタイミングで生成する場合

// .h
  UHogeComponent* HogeComponent;
  void Fuga();
  void Piyo();
// .cpp
AActor::AActor()
{
  // 比較用: ctor では CreateDefaultSubobject を使った。
  RootComponent = CreateDefaultSubobject< USceneComponent >( TEXT( "RootComponent" ) );
}

// 実行中の任意のタイミングでコンポーネントを生成
AActor::Fuga()
{
  // 実行中の任意のタイミングで生成する場合は NewObject を使う。
  // (ここでは AActor のデフォルトではないオブジェクト生成なので CreateDefaultSubobject は使えない事に注意)
  HogeComponent = NewObject< UHogeComponent >( this );
  // NewObject で生成したオブジェクトは RegisterComponent が必要。
  HogeComponent->RegisterComponent();
  // ctor 以外でのコンポーネントのアタッチメント(取り付け)も SetupAttachemnt ではなく AttachToComponent を使う
  // ( SetupAttachment を使っても 4.16 時点では大量の警告とエラーを垂れ流しつつ一応は動作するのでうっかり使わないよう注意)
  HogeComponent->AttachToComponent( RootComponent, { EAttachmentRule::SnapToTarget, true } );
}

// 実行中の任意のタイミングでコンポーネントを破棄
AActor::Piyo()
{
  // RootComponent への Attachement (取り付け)を Detach (解除)する
  HogeComponent->DetachFromComponent( { EDetachmentRule::KeepRelative, true } );
  // RegisterComponent によるコンポーネントの登録を UnregisterComponent で解除する
  HogeComponent->UnregisterComponent();
  // コンポーネントオブジェクトの破棄をGCへ明示する
  // (ここで突然 DestroyComponent を呼び出すコードを書くとアクセス違反で死ぬことになるので注意)
  HogeComponent->ConditionalBeginDestroy();
  // "立つ鳥跡を濁さず"
  HogeComponent = nullptr;
}

実行中の AActor へ任意のタイミングで USceneComponent (というか実際にはその派生コンポーネントなど)を生成したり、付け足したり、消したり。公式の C++er 向けチュートリアルでも軽く触れるような ctor での生成とは違うポイントがいくつもあるので少し手間取った@w@

参考

  1. Unreal Engine | UObject インスタンスの作成
  2. What is the correct way to create and add components at runtime ?
  3. Explicitely Delete a UObject - UE4 AnswerHub
  4. その他としてコンパイラーの deprecated 警告、 Debug/Development ビルドの実行中の Warning, Error レベルのログなど。

事後修正

  1. 2017-06-19T22:20+09:00: 記事中のソース例で AttachToComponent をモダンなAPIとしてコードすべきところが AttachTo になっていて [[deprecated]] 警告を受ける実装を示していたので修正しました。
  2. 2017-06-21T15:17+09:00: 記事中のソース例で NewObject の引数が誤って TEXT( "Hoge" ) となっていたところを this に変更。

UE4/C++ FORCEINLINE にしたいけど Debug ビルドの時は inline 最適化されたくない関数を定義する方法、あるいは UE_BUILD_xxxx シリーズのマクロについて。

UE4/C++ FORCEINLINE にしたいけど Debug ビルドの時は inline 最適化されたくない関数を定義する方法

#ifndef UE_BUILD_DEBUG
  FORCEINLINE
#endif
  void hogehoge()
  { /* fugafuga */ }

と、いうわけで #ifdef でOK。 Debug ビルドで定義される UE_BUILD_DEBUG の他に Development ビルドで定義される UE_BUILD_DEVELOPMENT もあるので用途に応じて使う。

あるいは UE_BUILD_xxxx シリーズのマクロについて

これらのマクロのシリーズは公式ドキュメント的にはたぶんアンドキュメント。ソースを見るとこのプリフィックスのシリーズは Engine/Source/Runtime/Core/Public/Misc/Build.h に、

  1. UE_BUILD_DEBUG
  2. UE_BUILD_DEVELOPMENT
  3. UE_BUILD_TEST
  4. UE_BUILD_SHIPPING
  5. UE_BUILD_SHIPPIING_WITH_EDITOR
  6. UE_BUILD_DOCS
  7. UE_BUILD_FINAL_RELEASE
  8. UE_BUILD_MINIMAL

の存在を UE-4.16 現在、確認できる。

追記: FORCEINLINE_DEBUGGABLE について

↑までの内容でブログを書いた後もしばらくソースを眺めていると、 FORCEINLINE_DEBUGGABLE マクロの定義もあることに気付いた。これを使うと、

FORCEINLINE_DEBUGGABLE void hogehoge()
{ /* fugafuga */ }

こんな具合で簡単化できて嬉しいのかなー・・・と思ったものの、ちょっと違った。冒頭で紹介した #ifdef の代わりに使ってしまうと FORCEINLINE ほどではないもののデバッグ実行は実際問題としては難しくなってしまう。

UE_BUILD_xxxx シリーズや FORCEINLINE_DEBUGGABLEEngine/Source/Runtime/Core/Public/Misc/Build.h で定義されていて、 FORCEINLINE_DEBUGGABLEFORCEINLINE_DEBUGGABLE_ACTUAL へ置換される。それから Engine/Source/Runtime/Core/Public/HAL/Platform.hFORCEINLINE_DEBUGGABLE_ACTUAL が"未定義"の場合には FORCEINLINE_DEBUGGABLE_ACTUALinline と定義される。

つまり、そんなこんなで少し入り組んでいるものの、ともかく FORCEINLINE_DEBUGGABLE を使うと C++ キーワードの inline と同様の挙動で inline 最適化が指示された関数を定義した事になる。

ちなみに、 FORCEINLINEC++ キーワードの inline ではなくて、コンパイラーの独自拡張によるより強制力の高い inline 関数を指示するキーワードへ置換される UE4 定義のマクロで、例えば cl.exe(MSVC++) なら __forceinline に置換されたりする。UE4 が対応する FORCEINLINE のプラットフォームごとの対応は Engine/Source/Runtime/Core/Public/<PLATFORM>/<PLATFORM>Platform.h でそれぞれに定義されていて、

置換先
Android __attribute__ ((always_inline))
iOS __attribute__ ((always_inline))
Linux __attribute__ ((always_inline))
Mac __attribute__ ((always_inline))
Windows __forceinline

こんな感じに定義されている。

参考

  1. https://github.com/EpicGames/UnrealEngine/blob/4.16/Engine/Source/Runtime/Core/Public/Misc/Build.h
  2. How can I differentiate between Macros for DebugGame Editor vs Development Editor? - UE4 AnswerHub

追記参考

  1. https://github.com/EpicGames/UnrealEngine/blob/4.16/Engine/Source/Runtime/Core/Public/Misc/Build.h

続・ VSCode で UE4 デバッグの launch.json: DebugGame と Development の切り替え追加と違いについて

概要

前回のポスト「VSCode で UE4/C++: デバッグ実行に必要な launch.json の設定の仕方 - C++ ときどき ごはん、わりとてぃーぶれいく☆」で VSCode の launch.json を設定して UE4 のデバッグ実行を VSCode から"えせ"スタンドアローン風に実行する方法を紹介した。

今回のポストでは UE4/C++ プロジェクトで標準的に用意される DebugGame と Development を切り替える方法と何が違うのかについて追加的に解説したい。

DebugGame と Development の切り替え

前回は launch.json の初手の作成方法の解説として AttachLaunch で2つのデバッグ実行モードを VSCode へ設定する方法を示した。今回はこのうちの LaunchDebugDevelopment の2つに分けて、 Attach Debug Development の3つに分けた launch.json を示す。

{ "version": "0.2.0"
, "configurations":
  [ { "name": "Attach"
    , "type": "cppvsdbg"
    , "request": "attach"
    , "processId": "${command:pickProcess}"
    }
  , { "name":"Debug"
    , "type": "cppvsdbg"
    , "request": "launch"
    , "program": "C:/Program Files/Epic Games/UE_4.16/Engine/Binaries/Win64/UE4Editor.exe"
    , "args": [ "C:/<YOUR-UE4-PROJECT-PATH>/<YOUR-PROJECT-NAME>.uproject", "-debug", "-game" ]
    , "cwd": "${workspaceRoot}"
    }
  , { "name":"Development"
    , "type": "cppvsdbg"
    , "request": "launch"
    , "program": "C:/Program Files/Epic Games/UE_4.16/Engine/Binaries/Win64/UE4Editor.exe"
    , "args": [ "C:/<YOUR-UE4-PROJECT-PATH>/<YOUR-PROJECT-NAME>.uproject", "-game" ]
    , "cwd": "${workspaceRoot}"
    }
  ]
}

DebugDevelopment に対して args で指示する引数が1つ多く -debug が追加されている。

DebugGame と Development の違い

-debug を追加で渡して実行する Debug モードでは、実行時に <YOUR-UE4-PROJECT-PATH>/Binaries/Win64/<YOUR-PROJECT-NAME>.exe と一連の .dll の代わりに、 <YOUR-UE4-PROJECT-PATH>/Binaries/Win64/<YOUR-PROJECT-NAME>-Win64-DebugGame.exe と一連の .dll を用いた実行を試みるようになる。

DebugGame と Development 、2つのコンフィグレーションで実際にどのようにデバッグ性が変化するのか図を交えて紹介する。

DebugGame:

f:id:USAGI-WRP:20170614095428p:plain

Development:

f:id:USAGI-WRP:20170614101525p:plain

適当なブレイクポイントで止めてみると、 Debug 版では全ての VARIABLES が丸見えでまさにデバッグ実行と言った風だが、 Development 版では VARIABLES の表示に幾つか Variable is optimized away and not available. となり扱えない状態で実行されている事がわかる。 Development 版は Debug 版に比べ、デバッガーがまるで役に立たないほどではないものの、あここちでちらほらと最適化によってソースコードと対応の取れない変数や制御が発生している。

通常、 UE4Editor が起動時に読み出す C++ コードの産物は Development コンフィグレーション版だが、 C++er が特に VSCode 等の外部のエディターや開発環境を用いて UE4 開発を行う際には Debug と Development (必要に応じて他に Shipping も)を切り替えながら便利に開発を進捗させる事になる。

参考

  1. VSCode で UE4/C++: デバッグ実行に必要な launch.json の設定の仕方 - C++ ときどき ごはん、わりとてぃーぶれいく☆

VSCode で UE4/C++: デバッグ実行に必要な launch.json の設定の仕方

概要

VSCode で UE4/C++ を開発する際にデバッグ実行に必要な launch.json の設定方法を紹介。

f:id:USAGI-WRP:20170613205409p:plain

↑こんな感じで VSCode しか起動していない状態から"えせ"スタンドアローン風の実行もできる。もちろん、起動中の UE4Editor へのアタッチもできる。

VSCode の設定の仕方

VSCode で .uproject のあるディレクトリーを開くなどしている状態で:

  1. 画面左 Debug アイコンをぽち –> 左側に展開する DEBUG カラムの上部の歯車アイコンをぽち
    • or メニュー > Debug > Open Configurations
  2. VSCode .vscode/launch.json を次のように編集
{ "version": "0.2.0"
, "configurations":
  [ { "name": "Attach"
    , "type": "cppvsdbg"
    , "request": "attach"
    , "processId": "${command:pickProcess}"
    }
  , { "name":"Launch"
    , "type": "cppvsdbg"
    , "request": "launch"
    , "program": "C:/Program Files/Epic Games/UE_4.16/Engine/Binaries/Win64/UE4Editor.exe"
    , "args": [ "C:/<YOUR UE4 PROJECT PATH>/<YOUR PROJECT NAME>.uproject", "-game" ]
    , "cwd": "${workspaceRoot}"
    }
  ]
}

Note: <YOUR UE4 PROJECT PATH>/<YOUR PROJECT NAME> は各自のUE4プロジェクトの配置パスとプロジェクト名に置き換える。

launch.json を保存した直後から DEBUG の緑のプレイ的なボタンの右側のコンボボックスに AttachLaunch が出現。Launch で緑のプレイ的なボタンをぽちると"えせ"スタンドアローン風の実行( UE4Editorから Launch した場合と同様の独立したウィンドウでの起動)が可能。既に起動中の UE4Editor へアタッチしたい場合は VSCode の DEBUG で Attach を選択した状態で緑のプレイ的なボタンをぽちり、プロセス選択で ue4 くらいまで入力するとインクリメンタルに UE4Editor を選択でき、 ENTER してアタッチできる。

デバッグ中は行番号の左側にブレイクポイントを仕込んで止めたり、ステップ実行したり、ローカル変数、ウォッチ、コールスタック、スレッドの監視、デバッグコンソールなどを活用してバグの原因を確認するなど、よくある便利なGUIデバッガーの必要十分な機能を使える。

VSCode の非常に軽快ながらパワフルな動作はプログラマーにとって憂鬱なムダに浪費されてしまう MOTTAINAI 的なコストを小さく抑えながら快適にソフトウェアの開発やドキュメントの記述が可能で、今回招待したように UE4/C++ 開発についても少しの設定で快適にGUIデバッガーとしても機能。嬉しいですねヽ(´ー`)ノ

参考

曰く「Project.uproject を右クリックして Generate Visual Studio project files」・・・そんなのダルい!今すぐ端末でコマンドを撃たせろ!!って思ったらどうすれば良いか・w・

問題

UE4 C++er は .cpp を追加したりリネームするたびに「Project.uproject を右クリックして Generate Visual Studio project files」しなければならない、事になっている(目につくドキュメントや AnswerHub 的なスタンダードな回答によれば)。

f:id:USAGI-WRP:20170612233457p:plain

だるい・w・

どうしたいのか?

  • いつもの zsh などのお気に入りのシェルからぱぱっとコマンドを打ちたい
  • Vim や VSCode などのお気に入りのテキストエディターのカスタムコマンドとしてさくっと使いたい

Windows の「右クリックメニュー」の中身の定義を見る

  1. Win + R –> regedit –> Enter
  2. CTRL + F –> Generate Visual Studio project files –> Enter
  3. しばらく検索を待つ –> Computer\HKEY_CLASSES_ROOT\Unreal.ProjectFile\shell\rungenproj がヒット
  4. 付近の構造を確認 –> rungenproj の下に command があるので値を選択して Modify
  5. Value data がテキストボックスなので値をコピーして後はお好みで使う

f:id:USAGI-WRP:20170612232623p:plain

f:id:USAGI-WRP:20170612234026p:plain

f:id:USAGI-WRP:20170612234115p:plain

手っ取り早い方法としては %1 を実際の Project.uproject のフルパスに置換して1行コマンドを書いただけの generate.bat をプロジェクトルートにでも入れておき、お好みのシェルなりテキストエディターなりから必要な時にそれを叩けばOK。

その他、そもそもシェル関数にするとか alias 張るとか .lnk ショートカットを作ってタスクバーにピン留めしたりグローバルキーボードショートカットを設定したりなどお好みの応用でどうぞ。

何れにしても↓のGUIが動作して .uproject を右クリックして云々と同様の動作となります。

f:id:USAGI-WRP:20170612235007p:plain

これでまたストレスが1つ減りました・w・