UE C++リフレクション

# 目的
この記事は
* 各種リフレクションのページがわかりにくかったり、資料が分散されていたのでわかりやすく簡単に理解できるように記述しました
* 利用ケースを想定してみました
* その他情報補完

# 実行環境
* Windows11
* UnrealEngine 5.0.3
:::note info
スペックはいらない
:::

# リフレクションとは
　``情報工学においてリフレクション (reflection) とは、プログラムの実行過程でプログラム自身の構造を読み取ったり書き換えたりする技術のことを指す。``
UnrealEngineの場合、class, struct内の構造を読み書きすることにあたると考えられる
読み取りに際して``UCLASS()`` ``USTRUCT()`` ``UENUM()`` ``UPROPERTY()``等を付与することでUnrealC++上で認識される

# 使用用途
以下のようなものがあると思われます
・APIのレスポンスデータ(JSON/CSV等)からStruct/Classのデータに値を入れる
・APIのレスポンスデータ(JSON/CSV等)とテンプレートのStruct/ClassファイルからStruct/Classのデータクラスをコードで生成する
・データを独自形式でエクスポート/インポートできる

:::note info
UnrealではDataTableをエクスポートするときにDataTable(Struct)からJSON/CSVに
インポートするときにJSON/CSVからDataTable(Struct)にするときにリフレクションが使われている
:::

# 読み書きコード
## 前提
* 今回はStructのみの説明を記述
* わかりやすいように少しずつ説明
### リフレクションに使用するデータ
* DataTableを想定してFTableRowBaseを追加
```c++:.h
USTRUCT(BlueprintType)
struct FDemoDataStruct
{
	GENERATED_BODY()

public:
	UPROPERTY(EditAnywhere)
	int IntParam;

	UPROPERTY(EditAnywhere)
	float FloatParam;

	UPROPERTY(EditAnywhere)
	bool BoolParam;

	UPROPERTY(EditAnywhere)
	FString StringParam;
};
```

## 【基礎】Struct内の特定のFPropertyを取得(メンバ名固定Ver)
* 以下コードではstruct領域の``IntParam``メンバーを直接指定して取得しようとしています
```c++:.cpp
FProperty* _Property = FindField<FProperty>(FDemoDataStruct::StaticStruct(), TEXT("IntParam"));
UE_LOG(LogTemp, Log, TEXT("%s"), *_Property->GetName());
```
```text:結果
LogTemp: IntParam
```

## 【基礎】Struct内のFPropertyをすべて取得
* 今回のデータだとstruct内にある``UPROPERTY()``がついているものをすべて取得します
```c++:.cpp
// Struct型内の変数群を取得
for (TFieldIterator<FProperty> It(FDemoDataStruct::StaticStruct()); It; ++It)
{
	FProperty* _Property = *It; // FPropertyが取れて
	UE_LOG(LogTemp, Log, TEXT("%s"), *_Property->GetName()); // どの変数か判明
}
```

```text:結果
LogTemp: IntParam
LogTemp: FloatParam
LogTemp: BoolParam
LogTemp: StringParam
```

## 【基礎】Struct内のメンバがどの型か調べる
* 以下の例ではfloat型が条件分岐に含まれていないので出力されないです
* 条件分岐に入るFPropertyはUnrealType.hに記載されているので取得したい値によってStruct拡張、コード追加するとよいでしょう
```c++:.cpp
for (TFieldIterator<FProperty> It(FDemoDataStruct::StaticStruct()); It; ++It)
{
	FProperty* _Property = *It;
	if (_Property->IsA<FIntProperty>()) // Int系
	{
		UE_LOG(LogTemp, Log, TEXT("%s is Int"), *_Property->GetName());
	}
	else if (_Property->IsA<FStrProperty>()) // String
	{
		UE_LOG(LogTemp, Log, TEXT("%s is FString"), *_Property->GetName());
	}
	else if (_Property->IsA<FBoolProperty>()) // bool
	{
		UE_LOG(LogTemp, Log, TEXT("%s is bool"), *_Property->GetName());
	}
}
```

```text:結果
LogTemp: IntParam is Int
LogTemp: BoolParam is bool
LogTemp: StringParam is FString
```

## 【応用】structに対して読み書きしたい
 全体像
```c++:.cpp
FDemoDataStruct* Struct = new FDemoDataStruct();

UE_LOG(LogTemp, Log, TEXT("IntParam : %d"), Struct->IntParam); // 変更前確認
for (TFieldIterator<FProperty> It(FDemoDataStruct::StaticStruct()); It; ++It)
{
	FProperty* _Property = *It;
	if (_Property->IsA<FIntProperty>()) // Int系
	{
		FIntProperty* IntProperty = CastField<FIntProperty>(_Property);
		void* IntPointer = IntProperty->ContainerPtrToValuePtr<uint8>(Struct);
		IntProperty->SetPropertyValue(IntPointer, 1);
		int Value = IntProperty->GetPropertyValue(IntPointer);
		UE_LOG(LogTemp, Log, TEXT("%s is IntPointerParam : %d"), *_Property->GetName(), Value); // 変更後確認(ポインタ)
	}
    //...以下条件文略
}
UE_LOG(LogTemp, Log, TEXT("IntParam : %d"), Struct->IntParam); // 変更後確認
```

```text:結果
LogTemp: IntParam : 0
LogTemp: IntParam is IntPointerParam : 1
LogTemp: IntParam : 1
```

### 書き込み処理説明
書き込み先のデータ用意
```c++:.cpp
FDemoDataStruct* Struct = new FDemoDataStruct();
```

1. Int処理ができるようFIntPropertyに変換
2. 対象のメンバが所属するStructポインタを入れて対象メンバのデータが存在するポインタを取得
3. 対象メンバのポインタに対してint型数値をセットする
```c++:.cpp 
1|FIntProperty* IntProperty = CastField<FIntProperty>(_Property);
2|void* IntPointer = IntProperty->ContainerPtrToValuePtr<uint8>(Struct);
3|IntProperty->SetPropertyValue(IntPointer, 1);
```

### 読み込み処理説明
基本は書き込み時と同様で取得するときは ``GetPropertyValue``を使用して値を取得する
```c++:.cpp
1|FIntProperty* IntProperty = CastField<FIntProperty>(_Property);
2|void* IntPointer = IntProperty->ContainerPtrToValuePtr<uint8>(Struct);
3|int Value = IntProperty->GetPropertyValue(IntPointer);
4|UE_LOG(LogTemp, Log, TEXT("%s is IntPointerParam : %d"), *_Property->GetName(), Value); // 変更後確認(ポインタ)
```

```text:結果
LogTemp: IntParam is IntPointerParam : 1
```

### その他
* Classの書き方についてもStruct部分がClassになったりと似たような書き方になります。

# 最後に
　Advent Calendarに初心者歓迎！！とコメント書かれていたのでなるべくわかりやすいように書いたつもりです。
しっかり書こうとすると量がすごくなるので軽く読む程度の感覚でとどめて頂ければと思います。