98.4K Views
May 21, 20
スライド概要
"講演動画:https://www.youtube.com/watch?v=tqzJSVrnqgY
UE4.25にてUProperty は FProperty にリファクタリングされました。この対応によりロード時間・パフォーマンス・メモリ消費量が改善されましたが、プロジェクトやエンジンのカスタムした箇所のC++コードに変更を加える必要があります。この動画ではそれらの詳細について解説いたします。
講師:Software Engineer Developer Relations, 鈴木 孝司 ( https://twitter.com/wankotank )
#EGJオンラインラーニング
https://www.unrealengine.com/ja/blog/connect-with-the-unreal-engine-community-online"
Unreal Engineを開発・提供しているエピック ゲームズ ジャパンによる公式アカウントです。 勉強会や配信などで行った講演資料を公開しています。 公式サイトはこちら https://www.unrealengine.com/ja/
なぜなにFProperty 対応方法と改善点 Epic games Japan Software Engineer, Developer Relations 鈴木孝司
はじめに UnrealEngine4は常に開発者が使うエディタやプレイヤーが触れるランタイムの パフォーマンスについて全ての面で改善を続けています。 先ほどリリースされたUnrealEngine4.25でも多数の様々な華々しい新機能の他に も重要な改善がいくつも盛り込まれています。 今日はそのうちの一つ、FPropertyを解説します。 UnrealEngine 4.25 Release Highlights https://www.youtube.com/watch?v=TzfeMiJPlVs #UE4 | @UNREALENGINE
UnrealEngine 4.25 Release Note https://docs.unrealengine.com/ja/Support/Builds/ReleaseNotes/4_25/index.html #UE4 | @UNREALENGINE
目次 ・本日の主役紹介 ・[寄り道]リフレクション ・FProperty ・対応方法 ・改善点 ・まとめ #UE4 | @UNREALENGINE
目次 ・本日の主役紹介 ・[寄り道]リフレクション ・FProperty ・対応方法 ・改善点 ・まとめ #UE4 | @UNREALENGINE
本日の主役たちの紹介 UObject #UE4 | @UNREALENGINE UClass UProperty
UObject UE4で接頭詞UがつくクラスはUObject派生クラスです。 UObjectはクラス(UClass)を持ちます。 アンリアルエンジンのコアとなる部分でこれによってメモリ管理や各種機能の基 盤が作られています。 UObject GetClass() UMyObject::StaticClass() #UE4 | @UNREALENGINE UClass UClass
クラス UClass UClassはそのオブジェクトの構造を保持しています。 内部にUFunction,UEnum,UPropertyなどの要素のリストを持ち、 これらが組み合わさって「リフレクション」を提供します UClass UFunction NewObject() UProperty このUClassを参考に 新しいオブジェクトを 作ってください! #UE4 | @UNREALENGINE 生成されたオブジェクト
プロパティ UProperty プロパティはクラスの構成要素として変数を定義します。 ピチピチの代役が設定されて降板が決まった今回の主役です。 UClass #UE4 | @UNREALENGINE UFunction UFunction UMapProperty 名前 : ConvertionMap UFloatProperty 名前 : Health UVectorProperty 名前 : Velocity
プロパティ - ブループリント ネイティブクラスと同様にブループリントクラスにも クラスの構造などの定義のためにプロパティが利用されています。 C++ネイティブクラス ブループリントクラス UClass UBlueprintGeneratedClass 関数 UProperty(変数) 列挙子 関数 UProperty(変数) 列挙子 #UE4 | @UNREALENGINE
プロパティ - ブループリント ブループリントの変数やノード、実行ノードの戻り値などもプロパティです。 #UE4 | @UNREALENGINE
目次 ・本日の主役紹介 ・[寄り道]リフレクション ・FProperty ・対応方法 ・改善点 ・まとめ #UE4 | @UNREALENGINE
リフレクション 通常c++では関数名や変数名などのソースファイルが持っていた情報は コンパイル時にその本来の名前は省略された形で機械語に翻訳されます。 例えば関数 HogeHogeFunc を定義したとしてもプログラム中から HoegeHogeFunc という名前や定義を使ってなにか処理することは出来ません。 リフレクションはこれらの情報を静的または動的に構築しランタイム時に利用す ることです。 UE4は静的にリフレクションを作成します。 #UE4 | @UNREALENGINE
素のC++ class FYourClass { static void HogeHogeFunc(); FVector FooLocation; } #UE4 | @UNREALENGINE コンパイル
素のC++ class FYourClass { static void HogeHogeFunc(); FVector FooLocation; コンパイル } “FYourClass”の “HogeHogeFunc” はありますか? #UE4 | @UNREALENGINE
素のC++ class FYourClass { static void HogeHogeFunc(); FVector FooLocation; コンパイル } “FYourClass”の “HogeHogeFunc” はありますか? #UE4 | @UNREALENGINE 知りません
UnrealC++ UCLASS() Class UYourClass { UFUNCTION(BlueprintCallable) Static void HogeHogeFunc(); UPROPEPTY(BlueprintReadOnly) FVector FooLocation; } 解析 .cpp .h UHT(UnrealHeaderTool)がヘッダファイルを解析して 「ソースファイル名 .gen.cpp」と 「ソースファイル名 .generated.h」を生成します。 これらにはリフレクションや ブループリントから関数を呼び出すための グルーコードなどが入っています。 #UE4 | @UNREALENGINE
UnrealC++ UCLASS() Class UYourClass { UFUNCTION(BlueprintCallable) Static void HogeHogeFunc(); UPROPEPTY(BlueprintReadOnly) FVector FooLocation; } #UE4 | @UNREALENGINE コンパイル 解析 .cpp .h
UnrealC++ UCLASS() Class UYourClass { UFUNCTION(BlueprintCallable) Static void HogeHogeFunc(); UPROPEPTY(BlueprintReadOnly) FVector FooLocation; コンパイル 解析 } “UYourClass”の “HogeHogeFunc” はありますか? #UE4 | @UNREALENGINE .cpp .h
UnrealC++ UCLASS() Class UYourClass { UFUNCTION(BlueprintCallable) Static void HogeHogeFunc(); UPROPEPTY(BlueprintReadOnly) FVector FooLocation; コンパイル 解析 } “UYourClass”の “HogeHogeFunc” はありますか? .cpp .h 知りません 知っていますとも! #UE4 | @UNREALENGINE
リフレクションの使用例 - FindObject
クラスはFindObjectを通じて名前で獲得することが可能です。
次のコードはコマンドレットクラスを取得する例です。
//注 : Token はコマンドレットオプション(“run=xxx” )で与えられたクラス名が入っている
if (!bHasEditorToken)
{
UClass* CommandletClass = nullptr;
if (!bIsRegularClient)
{
CommandletClass = FindObject<UClass>(ANY_PACKAGE,*Token,false);
次のコードはSkeletalMeshComponentを名前から生成する例です。
UClass* SkeletalMeshClass = FindObject<UClass>(ANY_PACKAGE, TEXT("SkeletalMeshComponent"), false);
check( SkeletalMeshClass );
USceneComponent* Object = NewObject<USceneComponent>(this, SkeletalMeshClass, NAME_None);
#UE4 | @UNREALENGINE
リフレクションの使用例 - PostEditChangeProperty
エディタ上でパラメータが変更されたときに名前を頼りになにか処理をする
void UMyComponent ::PostEditChangeProperty (FPropertyChangedEvent & PropertyChangedEvent)
{
if( PropertyChangedEvent.Property )
{
FName PropertyName = PropertyChangedEvent.Property-> GetFName ();
if(PropertyName == GET_MEMBER_NAME_CHECKED( UMyComponent , MyVariable))
{
ApplyVariable ( MyVariable );
}
}
Super:: PostEditChangeProperty (PropertyChangedEvent);
}
#UE4 | @UNREALENGINE
リフレクションの使用例 - 関数呼び出し ● 【UE4】ConsoleCommand「CE,KE」について【★★☆】 - キンアジのブログ 様 ● ● UE4 Unreal C++のリフレクションを使って文字列で関数を呼び出す方法に ついて - Let’s Enjoy Unreal Engine 様 ● ● http://kinnaji.com/2019/11/20/cekeevent/ http://unrealengine.hatenablog.com/entry/2016/06/28/220854 [UE4] 関数を文字列で呼び出して実行する - Takezoh 様 ● https://qiita.com/Takezoh/items/9eff92f5da8f20c47fdc #UE4 | @UNREALENGINE
前半のまとめ ● ● ● プロパティはUE4のリフレクションの要素です クラスがリフレクション情報を持っています ネイティブコードのリフレクションはUHTがソースを解析し作り出します ● ● ● ● ● クラス 関数名 変数名 型情報 ブループリントクラスも同様にプロパティを使ってクラスの構造をもってい ます ● ブループリントノードが増えるごとにプロパティが増えて行く傾向があります #UE4 | @UNREALENGINE
目次 ・本日の主役紹介 ・[寄り道]リフレクション ・FProperty ・対応方法 ・改善点 ・まとめ #UE4 | @UNREALENGINE
背景 プロパティはブループリントクラスも含みクラスを作成すると大量に生成されま す。タイトルによっては数十万を超える数のUPropertyがあり、UObjectを継承 している事によって様々な面でオーバーヘッドを生み出します。 -余分なメモリスペース -UProperty構築/破壊コスト -ガベージコレクションパフォーマンス -などなどエンジン全般に幅広く影響 エンジンのコアチームはこの改善に取り組みました。 #UE4 | @UNREALENGINE
UPropertyからFPropertyへ 4.25からこのUPropertyがFPropertyに全面的に変更されました。 接頭詞”F”はUObjectを継承していない構造体の宣言です。 Note: UPropertyクラスの定義は残っていますが バージョンアップ時に旧バージョンで作られたアセットを FPropertyへの変換する前に一度読み込むためにあるだけです #UE4 | @UNREALENGINE
プロパティ関連クラス継承関係 4.24 UObject UField UProperty UObjectProperty UBoolProperty UStruct UEnum UClass UFunction UBlueprintGeneratedClass #UE4 | @UNREALENGINE
プロパティ関連クラス継承関係 4.25 UObject 依存関係が 切れている FField FProperty FObjectProperty FBoolProperty UField UStruct UEnum UClass UFunction UBlueprintGeneratedClass #UE4 | @UNREALENGINE
プロパティ関連クラス継承関係 4.25 FField FProperty FObjectProperty #UE4 | @UNREALENGINE FBoolProperty このFFieldがUObjectやUFieldが持っていた ほとんどの関数を同じ名前で持っているため、 エンジンを更新するとなってもUxxPropertyか らFxxPropertyにリネームするだけで ほぼ動作するようになっています。
プロパティ関連クラス継承関係 4.25 FField FProperty FObjectProperty #UE4 | @UNREALENGINE FBoolProperty 実際にFPropertyの宣言や実装をコードレベルで 見比べてみると、 UPropertyとあまり変わっていな いことが判ります。
目次 ・本日の主役紹介 ・[寄り道]リフレクション ・FProperty ・対応方法 ・改善点 ・まとめ #UE4 | @UNREALENGINE
まずはブループリントに関して C++コード以外の例えばブループリントのプロパティなどは自動的に変換される ので作業は不要です。 #UE4 | @UNREALENGINE
#UE4 | @UNREALENGINE
UnrealEngine 4.25 Release Note # Uproperty マイナー リリースノート https://docs.unrealengine.com/ja/Support/Builds/ReleaseNotes/4_25/index.html#bookmarkuproperty #UE4 | @UNREALENGINE
C++の対応 ● ● ● ● ● ● ● ● アプリケーションコード中の U*Property を F*Property にリネーム プロパティのCastはCastFieldに メンバ変数のPropertyはTFieldPathで囲う TFieldIterator<UField>はプロパティを含まないので TFieldIterator<FProperty>を使う FPropertyを新たにアロケートするときはNewObjectではなくnewを使う UStruct::Childrenリンクリストはプロパティを含まないので UStruct::ChildPropertyを使う FPropertyの所有者を取得する場合はGetOuterではなく、GetOwnerを使う FPropertyとUFunctionやUEnumを統一的に扱いたいときは FFieldVariantを利用する #UE4 | @UNREALENGINE
U*PropertyをF*Propertyに置換 基本的にアプリケーションコード内にU*Propertyを書くことはありません。 UBoolProperty UFloatProperty UVectorProperty UObjectProperty UMapProperty などなど FBoolProperty FFloatProperty FVectorProperty FObjectProperty FMapProperty などなど 古いコードがあった場合はこのような警告がでます。 warning C4996: UArrayProperty has been renamed to FArrayProperty Please update your code to the new API before upgrading to the next release, otherwise your project will no longer compile. #UE4 | @UNREALENGINE
FPropertyの生成は new で 他の構造体と同様に自分で生成する場合はc++標準のnewを使います。 newでアロケートされたメモリポインタはUEのGCで回収されないので、 アプリケーションのコードで削除する必要があります。 #UE4 | @UNREALENGINE
プロパティのCastはCastFieldに
void USkeletalMesh::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
...
UProperty* PropertyThatChanged = PropertyChangedEvent.Property;
...
if( GIsEditor &&
Cast<UObjectProperty>(PropertyThatChanged) &&
Cast<UObjectProperty>(PropertyThatChanged)->PropertyClass == UMorphTarget::StaticClass() )
{
// A morph target has changed, reinitialize morph target maps
InitMorphTargets();
void USkeletalMesh::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
...
FProperty* PropertyThatChanged = PropertyChangedEvent.Property;
...
if( GIsEditor &&
CastField<FObjectProperty>(PropertyThatChanged) &&
CastField<FObjectProperty>(PropertyThatChanged)->PropertyClass == UMorphTarget::StaticClass() )
{
// A morph target has changed, reinitialize morph target maps
InitMorphTargets();
#UE4 | @UNREALENGINE
メンバ変数プロパティはTFiledPathで囲う
以下のようにメンバ変数にFPropertyのポインタを持っているケースでは、名前
を置換しただけではコンパイルエラーになります。
class AMyCharacter : public ACharacter
{
...
UPROPERTY()
FStructProperty* ValueHandlerNodeProperty;
}
MyCharacter.h(75): error : Unrecognized type 'FStructProperty' - type must be a UCLASS, USTRUCT or UENUM
#UE4 | @UNREALENGINE
メンバ変数プロパティはTFiledPathで囲う この場合はTFieldPath<F*Property> に書き換えます。 UPropertyで宣言されたときと同じ挙動、サービスが提供されます。 USTRUCT() struct ENGINE_API FExposedValueHandler { ... UPROPERTY() UStructProperty* ValueHandlerNodeProperty; USTRUCT() struct ENGINE_API FExposedValueHandler { ... UPROPERTY() TFieldPath<FStructProperty> ValueHandlerNodeProperty; #UE4 | @UNREALENGINE
UStructのChildrenリンクリストが分離 UStruct::Children リンクリストにはプロパティは含まれません。 代わりに UStruct::ChildProperties に プロパティ専用のリンクリストがあります。 class COREUOBJECT_API UStruct : public UField #if USTRUCT_FAST_ISCHILDOF_IMPL == USTRUCT_ISCHILDOF_STRUCTARRAY , private FStructBaseChain #endif { DECLARE_CASTED_CLASS_INTRINSIC(UStruct, UField, CLASS_MatchedSerializers, TEXT("/Script/CoreUObject"), CASTCLASS_UStruct) // Variables. protected: friend struct Z_Construct_UClass_UStruct_Statics; private: /** Struct this inherits from, may be null */ UStruct * SuperStruct; public: /** Pointer to start of linked list of child fields */ UField* Children; /** Pointer to start of linked list of child fields */ FField* ChildProperties; #UE4 | @UNREALENGINE
4.24 Children プロパティが 含まれている #UE4 | @UNREALENGINE
4.25 Children プロパティ以外 ChildProperties プロパティは 全てこちら #UE4 | @UNREALENGINE
TFieldIterator<FProperty>を使う TFieldIterator<UField> はプロパティを含まなくなりました。 プロパティをイテレートする時は TFieldIterator<FProperty> を利用します。 #UE4 | @UNREALENGINE
FFieldVariant ヘルパー
FField 派生の FProperty と、UField 派生の UFunction や UEnum を統一的に扱
いたい場合は FFieldVariant を利用できます。
いくつかこのFFieldVariantを使ったFFieldとUFieldを統一的に扱うヘルパー関数
が用意されています。
FFieldVariant Field = FindUFieldOrFProperty (YourClass, FieldName );
FBoolProperty * BoolProperty = Field. Get<FBoolProperty >();
if( BoolProperty )
{
DoSomething ( BoolProperty );
}
UEnum* Enum = Field. Get<UEnum>();
if( Enum )
{
DoSomething ( Enum );
}
#UE4 | @UNREALENGINE
GetOuterはGetOwnerに変更します GetOuter() を利用していた所はGetOwner() を使います。 GetOuter()はUObjectのための定義なのでFPropetyには適しません。 Ownerは内部にFFieldVariantを使っているので 受け取る型が判っているときはテンプレート引数付きのGetOwner()を、 型が推測出来ない場合はGetOwnerVariant() を使うと良さそうです。 #UE4 | @UNREALENGINE
目次 ・本日の主役紹介 ・[寄り道]リフレクション ・FProperty ・対応方法 ・改善点 ・まとめ #UE4 | @UNREALENGINE
#UE4 | @UNREALENGINE
UObjectの数が減りました! アプリケーション全体で使っているUObjectの内、 Propertyは40%以上、場合によっては60%を超える程あると思います。 特に多数の関数呼び出しを含むブループリントがたくさんある場合に 多くなりがちです。 ThirdPersonTemplateでは UE4.24でUObjectは合計で 5万強ほどあり そのうち70%程がPropertyでした。 これがまるっと無くなります。 #UE4 | @UNREALENGINE
例えばこんなブループリントで改善を見てみます #UE4 | @UNREALENGINE
例えばこんなブループリントで改善を見てみます マクロをつかってBoolPropertyの戻り値が8個ある関数を 2304回呼び出します。 obj list forget → ブループリントのロード → obj list の手順でブループリントが持つObject数を計測しました。 #UE4 | @UNREALENGINE
Obj listによる計測 BoolProperty数 4.24 0回関数呼び出し 4.24 2304回関数呼び出し 4.25 0回関数呼び出し 4.25 2304回関数呼び出し #UE4 | @UNREALENGINE 32 TotalObject 数 5694 Total(MB) 61,005 13.117MB 0 5026 2.220MB 5643 1関数 呼び出し辺り(KB) 2.544MB 55,332 0 2304回 呼び出した時の 増加分(MB) 3.352MB 10.573MB 4.699KB 1.132MB 0.503KB
生成と破壊がシンプルに 前述の通りプロパティの生成と破壊もシンプルになり 素早く処理できるようになりました。 ● UProperty ○ ○ ● NewObject<> や AllocateObject GC FProperty ○ ○ C++ 標準の ‘new’ C++ 標準の ‘delete’ 4.25では様々なアセット読み込みに関する高速化が組み込まれましたが その一端はFPropety化が担っています。 同様にアプリケーションの起動時間の短縮効果も期待できます。 #UE4 | @UNREALENGINE
メモリ/ストレージ消費量が減りました UObjectを継承していたことによる、 1つのプロパティあたり余分な100バイト強のメモリが不要になりました。 一つ一つの容量は微々たるものですが、 数が多いため合計すると数10MByte以上メモリ使用量が削減される事もありま す。 #UE4 | @UNREALENGINE
MallocProfilerによる計測 MallocProfilerについては弊社鍬農の記事が詳しいです。 https://qiita.com/donbutsu17/items/a72a282587390f43d12d (または MallocProfiler ue4 でググる) #UE4 | @UNREALENGINE
MallocProfilerによる計測 呼び出し回数 合計メモリ使用量 4.24 2304回関数呼び出し 18,456 6.92MB 4.25 2304回関数呼び出し 18,440 2.25MB #UE4 | @UNREALENGINE
エクスポートテーブルに配置されなくなりました 4.24以前のプロパティはuassetファイルのエクスポートテーブルに配置されてい ましたがされなくなり、アセットの実コンテンツ側に配置されます。 この変更はeditor用uassetをクックした後に(cooked)uassetとubulkに分割され たもので顕著に観測できます。 (editor)uasset エディタで読み込まれるアセット Cook (cooked)uasset アセットがどのアセットと 関連しているのかを含むファイル #UE4 | @UNREALENGINE UProperty FProperty uexp ファイルの実コンテンツ
ファイルサイズ-ThirdPersonCharacter ThirdPersonCharacter uasset uexp total 4.24 16,682 Byte 7,643 Byte 24,325 Byte 4.25 10,552 Byte 10,552 Byte 21,104 Byte #UE4 | @UNREALENGINE
ファイルサイ ズ-ThirdPersonGameMode ThirdPersonGameMode uasset uexp total 4.24 2,936 Byte 490 Byte 3,426 Byte 4.25 2,732 Byte 504 Byte 3,236 Byte #UE4 | @UNREALENGINE
ファイルサイズ-テスト用ブループリント 2304回関数呼び出し uasset 4.24 2,143,821 Byte 4.25 uexp 837,607 Byte 2,981,428 Byte 3,493 Byte 1,685,099 Byte 1,688,592 Byte 43%減! #UE4 | @UNREALENGINE total
目次 ・本日の主役紹介 ・[寄り道]リフレクション ・FProperty ・対応方法 ・改善点 ・まとめ #UE4 | @UNREALENGINE
本日のまとめ ● 4.25でUPropertyがFPropertyに変わりました ● ● ● ● UObjectを継承していたことによるオーバーヘッドが無くなりました アプリケーションのネイティブコード内で利用していた場合はコードの修正が必要です ブループリントに書かれたコードは自動で変換されます 様々な部分でパフォーマンスが向上します ● ● ● ● ● ● 起動時間 プロパティを含むアセットの読み込み GC処理時間の改善 メモリ使用量の減少 パッケージ後のストレージサイズの減少 などなど横断的に改善の影響があります #UE4 | @UNREALENGINE
ご視聴ありがとうございました! ● エピック ゲームズ ジャパン による オンラインラーニング ● ● ● https://www.unrealengine.com/ja/onlinelearning-courses Unreal Engine 5 初公開 ● ● 「次世代機を見据えた新アセットローディングシステム」を近日公開予定 Unreal オンラインラーニング ● ● https://www.unrealengine.com/ja/blog/connect-with-the-unreal-engine-community-online https://www.unrealengine.com/ja/blog/a-first-look-at-unreal-engine-5 アンリアルエンジン Twitter ● @UnrealEngineJP #UE4 | @UNREALENGINE