なぜなにFProperty
対応方法と改善点
Epic games Japan
Software Engineer, Developer Relations
鈴木孝司
#UE4 | @UNREALENGINE
はじめに
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
本日の主役たちの紹介
UPropertyUObject UClass
#UE4 | @UNREALENGINE
UObject
UE4で接頭詞UがつくクラスはUObject派生クラスです。
UObjectはクラス(UClass)を持ちます。
アンリアルエンジンのコアとなる部分でこれによってメモリ管理や各種機能の基
盤が作られています。
UObject
GetClass() UClass
UMyObject::StaticClass() UClass
#UE4 | @UNREALENGINE
クラス UClass
UClassはそのオブジェクトの構造を保持しています。
内部にUFunction,UEnum,UPropertyなどの要素のリストを持ち、
これらが組み合わさって「リフレクション」を提供します
UClass
UFunction 生成されたオブジェクト
UProperty
NewObject()
このUClassを参考に
新しいオブジェクトを
作ってください!
#UE4 | @UNREALENGINE
プロパティ UProperty
プロパティはクラスの構成要素として変数を定義します。
ピチピチの代役が設定されて降板が決まった今回の主役です。
UClass
UFunction
UVectorProperty
名前 : Velocity
UMapProperty
名前 : ConvertionMap
UFloatProperty
名前 : Health
UFunction
#UE4 | @UNREALENGINE
プロパティ - ブループリント
ネイティブクラスと同様にブループリントクラスにも
クラスの構造などの定義のためにプロパティが利用されています。
C++ネイティブクラス
UClass
関数
UProperty(変数)
列挙子
ブループリントクラス
UBlueprintGeneratedClass
関数
UProperty(変数)
列挙子
#UE4 | @UNREALENGINE
プロパティ - ブループリント
ブループリントの変数やノード、実行ノードの戻り値などもプロパティです。
#UE4 | @UNREALENGINE
目次
・本日の主役紹介
・[寄り道]リフレクション
・FProperty
・対応方法
・改善点
・まとめ
#UE4 | @UNREALENGINE
リフレクション
通常c++では関数名や変数名などのソースファイルが持っていた情報は
コンパイル時にその本来の名前は省略された形で機械語に翻訳されます。
例えば関数 HogeHogeFunc を定義したとしてもプログラム中から
HoegeHogeFunc という名前や定義を使ってなにか処理することは出来ません。
リフレクションはこれらの情報を静的または動的に構築しランタイム時に利用す
ることです。
UE4は静的にリフレクションを作成します。
#UE4 | @UNREALENGINE
素のC++
class FYourClass
{
static void HogeHogeFunc();
FVector FooLocation;
}
コンパイル
#UE4 | @UNREALENGINE
class FYourClass
{
static void HogeHogeFunc();
FVector FooLocation;
}
素のC++
コンパイル
“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;
}
コンパイル
解析
.cpp
.h
#UE4 | @UNREALENGINE
UnrealC++
UCLASS()
Class UYourClass
{
UFUNCTION(BlueprintCallable)
Static void HogeHogeFunc();
UPROPEPTY(BlueprintReadOnly)
FVector FooLocation;
}
コンパイル
解析
.cpp
.h
“UYourClass”の
“HogeHogeFunc”
はありますか?
#UE4 | @UNREALENGINE
UnrealC++
UCLASS()
Class UYourClass
{
UFUNCTION(BlueprintCallable)
Static void HogeHogeFunc();
UPROPEPTY(BlueprintReadOnly)
FVector FooLocation;
}
知っていますとも!
コンパイル
解析
.cpp
.h
“UYourClass”の
“HogeHogeFunc”
はありますか?
知りません
#UE4 | @UNREALENGINE
リフレクションの使用例 - FindObject
クラスはFindObjectを通じて名前で獲得することが可能です。
次のコードはコマンドレットクラスを取得する例です。
次のコードはSkeletalMeshComponentを名前から生成する例です。
//注 : Token はコマンドレットオプション(“run=xxx” )で与えられたクラス名が入っている
if (!bHasEditorToken)
{
UClass* CommandletClass = nullptr;
if (!bIsRegularClient)
{
CommandletClass = FindObject<UClass>(ANY_PACKAGE,*Token,false);
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」について【★★☆】
- キンアジのブログ 様
● http://kinnaji.com/2019/11/20/cekeevent/
● UE4 Unreal C++のリフレクションを使って文字列で関数を呼び出す方法に
ついて
- Let’s Enjoy Unreal Engine 様
● 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
UStruct
UClass UFunction
UBlueprintGeneratedClass
UEnumUProperty
UObjectProperty UBoolProperty
#UE4 | @UNREALENGINE
プロパティ関連クラス継承関係 4.25
UObject
UField
UStruct
UClass UFunction
UBlueprintGeneratedClass
UEnumFProperty
FObjectProperty FBoolProperty
FField
依存関係が
切れている
#UE4 | @UNREALENGINE
プロパティ関連クラス継承関係 4.25
FProperty
FObjectProperty FBoolProperty
FField
このFFieldがUObjectやUFieldが持っていた
ほとんどの関数を同じ名前で持っているため、
エンジンを更新するとなってもUxxPropertyか
らFxxPropertyにリネームするだけで
ほぼ動作するようになっています。
#UE4 | @UNREALENGINE
プロパティ関連クラス継承関係 4.25
FProperty
FObjectProperty FBoolProperty
FField
実際にFPropertyの宣言や実装をコードレベルで
見比べてみると、UPropertyとあまり変わっていな
いことが判ります。
#UE4 | @UNREALENGINE
目次
・本日の主役紹介
・[寄り道]リフレクション
・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
Children
プロパティが
含まれている
4.24
#UE4 | @UNREALENGINE
Children
プロパティ以外
ChildProperties
プロパティは
全てこちら
4.25
#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数
TotalObject
数
Total(MB)
2304回
呼び出した時の
増加分(MB)
1関数
呼び出し辺り(KB)
4.24
0回関数呼び出し 32 5694 2.544MB
10.573MB 4.699KB
4.24
2304回関数呼び出し 55,332 61,005 13.117MB
4.25
0回関数呼び出し 0 5026 2.220MB
1.132MB 0.503KB4.25
2304回関数呼び出し 0 5643 3.352MB
#UE4 | @UNREALENGINE
生成と破壊がシンプルに
前述の通りプロパティの生成と破壊もシンプルになり
素早く処理できるようになりました。
● 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に分割され
たもので顕著に観測できます。
(cooked)uasset
アセットがどのアセットと
関連しているのかを含むファイル
uexp
ファイルの実コンテンツ
(editor)uasset
エディタで読み込まれるアセット
Cook
FPropertyUProperty
#UE4 | @UNREALENGINE
ファイルサイズ-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 uexp total
4.24 2,143,821 Byte 837,607 Byte 2,981,428 Byte
4.25 3,493 Byte 1,685,099 Byte 1,688,592 Byte
43%減!
#UE4 | @UNREALENGINE
目次
・本日の主役紹介
・[寄り道]リフレクション
・FProperty
・対応方法
・改善点
・まとめ
#UE4 | @UNREALENGINE
本日のまとめ
● 4.25でUPropertyがFPropertyに変わりました
● UObjectを継承していたことによるオーバーヘッドが無くなりました
● アプリケーションのネイティブコード内で利用していた場合はコードの修正が必要です
● ブループリントに書かれたコードは自動で変換されます
● 様々な部分でパフォーマンスが向上します
● 起動時間
● プロパティを含むアセットの読み込み
● GC処理時間の改善
● メモリ使用量の減少
● パッケージ後のストレージサイズの減少
● などなど横断的に改善の影響があります
#UE4 | @UNREALENGINE
ご視聴ありがとうございました!
● エピック ゲームズ ジャパン による オンラインラーニング
● https://www.unrealengine.com/ja/blog/connect-with-the-unreal-engine-community-online
● 「次世代機を見据えた新アセットローディングシステム」を近日公開予定
● Unreal オンラインラーニング
● https://www.unrealengine.com/ja/onlinelearning-courses
● Unreal Engine 5 初公開
● https://www.unrealengine.com/ja/blog/a-first-look-at-unreal-engine-5
● アンリアルエンジン Twitter
● @UnrealEngineJP

なぜなにFProperty - 対応方法と改善点 -