Unreal Engine リフレクション

# はじめに
この記事は, Unreal Engineでどうしても必要なことがあり, 調べたことについてまとめます.

## リフレクション
リフレクション, リフレクティブプログラミングは, プログラムの構造や振る舞いを取得する機能やそのような動作のことをいいます.
主に次のような用途に使われます. 組み合わせ爆発に対応するためであったり, 構造が事前にわからないときに使用します.

- 単体テスト
- シリアライゼーション
- エディタ
- 依存性の注入

フレームワークやライブラリの一部として使われます. アプリケーションプログラムで不用意に使うと, プログラムの構造や意味を変えてしまうため危険です.

# Unreal Engineのリフレクション
Unreal Engineでは, 特定のタグを`Unreal Header Tool`が解析し, リフレクションやガベージコレクションに必要なコードを生成します. 次のようなコードになります.

```cpp:AActorA.h
#pragma once
#include <CoreMinimal.h>
#include <Containers/Array.h>

#include "ActorA.generated.h"

UENUM()
enum class EEnumA : uint8
{
    A,
    B,
    C,
};

USTRUCT()
struct REFLECTION_API FStructA
{
    GENERATED_BODY()
    UPROPERTY()
    int32 Int_;
};

UCLASS()
class REFLECTION_API AActorA : public AActor
{
    GENERATED_BODY()
public:
    virtual void BeginPlay() override{}
    virtual void Tick(float DeltaTime) override{}

    UFUNCTION()
    int32 Get() const;
    UFUNCTION()
    void Set(int32 Int);

    //UFUNCTION()
    //const int32& operator[](int32 Index) const; // 複雑なものはだめ
    int32& operator[](int32 Index);
private:
    UPROPERTY()
    int32 Int_;
    UPROPERTY()
    TArray<int32> Array_;
};
```

```cpp:AActorA.cpp
#include "ActorA.h"

int32 AActorA::Get() const
{
    return Int_;
}

void AActorA::Set(int32 Int)
{
    Int_ = Int;
}

int32& AActorA::operator[](int32 Index)
{
    return Array_[Index];
}
```

`Unreal Header Tool`は, `UCLASS`などを解析し, `(プロジェクトディレクトリ)\Intermediate\Build\(プラットフォーム名)\UnrealEditor\Inc\(モジュール名)`などにコードを生成します. などというのはバージョンに依って変更があるからです. 
Unreal Engineでは対応するクラスがあり, `UCLASS`は`UClass`が対応します, `UClass`を見れば大体何ができるかがわかります.
例として, [FProperty](https://docs.unrealengine.com/4.27/en-US/API/Runtime/CoreUObject/UObject/FProperty/)をみると, 子孫に`FMapProperty`があります. これは, `Unreal Header Tool`が認識できる, `TMap`以外の連想配列は対応できないということです.

## 余談１：コーディング規約
基本はEpic社のコーディング規約に則っていますが, ~~私はUnreal Engineが大嫌いなので~~アレンジ部分があります.

規約ではメンバ変数はPascalケースですが, 私は`_`を接尾語として付けます. これは, 次のようにメンバ名と実引数名が同じとき, `In`を仕方なく付けるというプログラムが間抜けに思うからです.

```cpp
void Hoge::Set(int32 InVar)
{
    Var = InVar;
}
```

## メンバ変数
`AActorA`について, 次のようなテストをしてみます. 関数呼び出しはWIPで上手くいっていませんが, プロパティについては読み書きができます.

オブジェクトのメタデータのロードは, `Class'/Script/(モジュール名)/(接頭辞を除いたオブジェクト名)'`となります.

```cpp:TestReflection.cpp
#include <Misc/AutomationTest.h>

#include "ActorA.h"

IMPLEMENT_SIMPLE_AUTOMATION_TEST(FTestReflection, "Reflection.TestReflection", EAutomationTestFlags::EditorContext | EAutomationTestFlags::ProductFilter);

bool FTestReflection::RunTest(const FString& Parameters)
{
    UStruct* StructA = Cast<UStruct>(StaticLoadObject(UObject::StaticClass(), nullptr, TEXT("Class'/Script/Reflection.StructA'"), nullptr, LOAD_None, nullptr, true));
    {
        UE_LOG(LogTemp, Log, TEXT("FStructA:"));
        FStructA InstanceA = {123};
        for (TFieldIterator<FProperty> It(StructA); It; ++It) {
            FProperty* Property = *It;
            UE_LOG(LogTemp, Log, TEXT("    %s"), *Property->GetName());
        }
        FProperty* PropertyInt = StructA->FindPropertyByName(TEXT("Int_"));
        int32* Int = PropertyInt->ContainerPtrToValuePtr<int32>(&InstanceA);
        UE_LOG(LogTemp, Log, TEXT("    %d"), *Int);
    }

    UClass* ClassActorA = StaticLoadClass(UObject::StaticClass(), nullptr, TEXT("Class'/Script/Reflection.ActorA'"), nullptr, LOAD_None, nullptr);
    {
        UE_LOG(LogTemp, Log, TEXT("AActorA:"));
        AActor* InstanceA = GWorld->SpawnActor(ClassActorA);
        for (TFieldIterator<FProperty> It(ClassActorA); It; ++It) {
            FProperty* Property = *It;
            UE_LOG(LogTemp, Log, TEXT("    %s"), *Property->GetName());
        }
        FIntProperty* PropertyInt = CastField<FIntProperty>(ClassActorA->FindPropertyByName(TEXT("Int_")));
        int32* Int = PropertyInt->ContainerPtrToValuePtr<int32>(InstanceA);
        PropertyInt->SetPropertyValue(Int, 123);
        UE_LOG(LogTemp, Log, TEXT("    Get %d"), *Int);

        UFunction* Setter = ClassActorA->FindFunctionByName(TEXT("Set"));
        {
            int32 Value = 1;
            InstanceA->ProcessEvent(Setter, &Value);
        }
        InstanceA->Destroy();
        UE_LOG(LogTemp, Log, TEXT("    Get %d"), *Int);
    }
    return true;
}
```

# ~~悪用して~~別モジュールのクラスをインスタンシング
やりたかったことはこれです.
Unreal Engineでは, モジュールと呼ばれる仕組みによって, プログラムの機能単位を分けることができます. 実体は動的リンクライブラリです.
例えば次のように, プロジェクトを複数のモジュール（動的リンクライブラリ）に分けて管理することができます. モジュールには依存関係があり, 依存先から依存元のプログラムを参照することはできません. 循環参照を許した場合の動作は保証されません.

![Reflection.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27065/220feb15-d87e-1a77-dc6d-57fe516fdb2d.jpeg)

さてここで, `ReflectionLib`から`Reflection`のクラスをインスタンシングしたいという, ~~悪巧み~~切なる希望があるとします.

こんなことをすると普通に動作します. OpenGLやVulkanのAPIをロードしている感じですね. ただの動的リンクライブラリですからね.

```cpp:TestReflectionLib.cpp
#include <Misc/AutomationTest.h>

IMPLEMENT_SIMPLE_AUTOMATION_TEST(FTestReflectionLib, "Reflection.TestReflectionLib", EAutomationTestFlags::EditorContext | EAutomationTestFlags::ProductFilter);

bool FTestReflectionLib::RunTest(const FString& Parameters)
{
    UStruct* StructA = Cast<UStruct>(StaticLoadObject(UObject::StaticClass(), nullptr, TEXT("Class'/Script/Reflection.StructA'"), nullptr, LOAD_None, nullptr, true));
    {
        UE_LOG(LogTemp, Log, TEXT("FStructA:"));
        for (TFieldIterator<FProperty> It(StructA); It; ++It) {
            FProperty* Property = *It;
            UE_LOG(LogTemp, Log, TEXT("    %s"), *Property->GetName());
        }
    }

    UClass* ClassActorA = StaticLoadClass(AActor::StaticClass(), nullptr, TEXT("Class'/Script/Reflection.ActorA'"), nullptr, LOAD_None, nullptr);
    {
        UE_LOG(LogTemp, Log, TEXT("AActorA:"));
        AActor* InstanceA = GWorld->SpawnActor(ClassActorA);

        FIntProperty* PropertyInt = CastField<FIntProperty>(ClassActorA->FindPropertyByName(TEXT("Int_")));
        int32* Int = PropertyInt->ContainerPtrToValuePtr<int32>(InstanceA);
        int32 Value0 = *Int;
        PropertyInt->SetPropertyValue(Int, 123);
        int32 Value1 = PropertyInt->GetPropertyValue(Int);
        UE_LOG(LogTemp, Log, TEXT("    AActorA::_Int %d => %d"), Value0, Value1);
        InstanceA->Destroy();
    }
    return true;
}
```

# まとめ
メソッドについては調査中ですが, プロパティアクセスは可能なことがわかりました. 内部については, C++の未定義ばかりなのでどんな環境でも動く保証はありません.
悪用については, 真似しないでください.