In-depth UE - UObject (6) type system code generation reconstruction - UE4CodeGen_Private

introduction

In the previous chapters of "InsideUE4" UObject (4) type system code generation and "InsideUE4" UObject (5) type system collection , we introduced how UE4 generates reflection code based on our code and meta tags, and calls it in the Main function Previously, the initialization of static variables was used to collect type metadata information. After me procrastinating for such a long time...and Epic's version change for such a long time, UE has evolved from 4.15.1 to 4.18.3. Naturally, the CoreUObject module has also made some improvements. This article will first add an improvement on code generation: UObjectGlobals.h.cpp was refactored in UE4.17 (20170722) to optimize the content and organization of code generation.

Old version code generation

First, let’s take a look at the code metadata generation of the previous version:

Generation of UEnum:

//测试代码
#pragma once
#include "UObject/NoExportTypes.h"
#include "MyEnum.generated.h"
UENUM(BlueprintType)
enum class EMyEnum : uint8
{
    MY_Dance    UMETA(DisplayName = "Dance"),
    MY_Rain     UMETA(DisplayName = "Rain"),
    MY_Song     UMETA(DisplayName = "Song")
};

//生成代码节选(Hello.genrated.cpp):
ReturnEnum = new(EC_InternalUseOnlyConstructor, Outer, TEXT("EMyEnum"), RF_Public|RF_Transient|RF_MarkAsNative) UEnum(FObjectInitializer());//直接创建该UEnum对象
TArray<TPair<FName, uint8>> EnumNames;//设置枚举里的名字和值
EnumNames.Add(TPairInitializer<FName, uint8>(FName(TEXT("EMyEnum::MY_Dance")), 0));
EnumNames.Add(TPairInitializer<FName, uint8>(FName(TEXT("EMyEnum::MY_Rain")), 1));
EnumNames.Add(TPairInitializer<FName, uint8>(FName(TEXT("EMyEnum::MY_Song")), 2));
EnumNames.Add(TPairInitializer<FName, uint8>(FName(TEXT("EMyEnum::MY_MAX")), 3));   //添加一个默认的MAX字段
ReturnEnum->SetEnums(EnumNames, UEnum::ECppForm::EnumClass);
ReturnEnum->CppType = TEXT("EMyEnum");
#if WITH_METADATA   //设置元数据
UMetaData* MetaData = ReturnEnum->GetOutermost()->GetMetaData();
MetaData->SetValue(ReturnEnum, TEXT("BlueprintType"), TEXT("true"));
MetaData->SetValue(ReturnEnum, TEXT("ModuleRelativePath"), TEXT("MyEnum.h"));
MetaData->SetValue(ReturnEnum, TEXT("MY_Dance.DisplayName"), TEXT("Dance"));
MetaData->SetValue(ReturnEnum, TEXT("MY_Rain.DisplayName"), TEXT("Rain"));
MetaData->SetValue(ReturnEnum, TEXT("MY_Song.DisplayName"), TEXT("Song"));
#endif

Generation of UStruct:

//测试代码:
#pragma once
#include "UObject/NoExportTypes.h"
#include "MyStruct.generated.h"
USTRUCT(BlueprintType)
struct HELLO_API FMyStruct
{
    GENERATED_USTRUCT_BODY()
    UPROPERTY(BlueprintReadWrite)
    float Score;
};
//生成代码节选(Hello.genrated.cpp):
ReturnStruct = new(EC_InternalUseOnlyConstructor, Outer, TEXT("MyStruct"), RF_Public|RF_Transient|RF_MarkAsNative) UScriptStruct(FObjectInitializer(), NULL, new UScriptStruct::TCppStructOps<FMyStruct>, EStructFlags(0x00000201));//直接创建UScriptStruct对象
UProperty* NewProp_Score = new(EC_InternalUseOnlyConstructor, ReturnStruct, TEXT("Score"), RF_Public|RF_Transient|RF_MarkAsNative) UFloatProperty(CPP_PROPERTY_BASE(Score, FMyStruct), 0x0010000000000004);//直接关联相应的Property信息
ReturnStruct->StaticLink(); //链接
#if WITH_METADATA   //元数据
UMetaData* MetaData = ReturnStruct->GetOutermost()->GetMetaData();
MetaData->SetValue(ReturnStruct, TEXT("BlueprintType"), TEXT("true"));
MetaData->SetValue(ReturnStruct, TEXT("ModuleRelativePath"), TEXT("MyStruct.h"));
MetaData->SetValue(NewProp_Score, TEXT("Category"), TEXT("MyStruct"));
MetaData->SetValue(NewProp_Score, TEXT("ModuleRelativePath"), TEXT("MyStruct.h"));
#endif

Generation of UClass:

//测试代码:
#pragma once
#include "UObject/NoExportTypes.h"
#include "MyClass.generated.h"
UCLASS(BlueprintType)
class HELLO_API UMyClass : public UObject
{
    GENERATED_BODY()
public:
    UPROPERTY(BlueprintReadWrite)
    float Score;
public:
    UFUNCTION(BlueprintCallable, Category = "Hello")
    void CallableFunc();    //C++实现,蓝图调用
    UFUNCTION(BlueprintNativeEvent, Category = "Hello")
    void NativeFunc();  //C++实现默认版本,蓝图可重载实现
    UFUNCTION(BlueprintImplementableEvent, Category = "Hello")
    void ImplementableFunc();   //C++不实现,蓝图实现
};
//生成代码节选(Hello.genrated.cpp):
//添加子字段
OuterClass->LinkChild(Z_Construct_UFunction_UMyClass_CallableFunc());
OuterClass->LinkChild(Z_Construct_UFunction_UMyClass_ImplementableFunc());
OuterClass->LinkChild(Z_Construct_UFunction_UMyClass_NativeFunc());
PRAGMA_DISABLE_DEPRECATION_WARNINGS
UProperty* NewProp_Score = new(EC_InternalUseOnlyConstructor, OuterClass, TEXT("Score"), RF_Public|RF_Transient|RF_MarkAsNative) UFloatProperty(CPP_PROPERTY_BASE(Score, UMyClass), 0x0010000000000004);//添加属性
PRAGMA_ENABLE_DEPRECATION_WARNINGS
//添加函数名字映射
OuterClass->AddFunctionToFunctionMapWithOverriddenName(Z_Construct_UFunction_UMyClass_CallableFunc(), "CallableFunc"); // 774395847
OuterClass->AddFunctionToFunctionMapWithOverriddenName(Z_Construct_UFunction_UMyClass_ImplementableFunc(), "ImplementableFunc"); // 615168156
OuterClass->AddFunctionToFunctionMapWithOverriddenName(Z_Construct_UFunction_UMyClass_NativeFunc(), "NativeFunc"); // 3085959641
OuterClass->StaticLink();
#if WITH_METADATA   //元数据
UMetaData* MetaData = OuterClass->GetOutermost()->GetMetaData();
MetaData->SetValue(OuterClass, TEXT("BlueprintType"), TEXT("true"));
MetaData->SetValue(OuterClass, TEXT("IncludePath"), TEXT("MyClass.h"));
MetaData->SetValue(OuterClass, TEXT("ModuleRelativePath"), TEXT("MyClass.h"));
MetaData->SetValue(NewProp_Score, TEXT("Category"), TEXT("MyClass"));
MetaData->SetValue(NewProp_Score, TEXT("ModuleRelativePath"), TEXT("MyClass.h"));
#endif

As you can see, the previous method has a lot of "routine" SetValue and Add paragraphs in the generated code, which are used to add field attributes, functions and metadata information. Although these codes are also generated programmatically by UHT and do not require manual operation, they can only be said to be slightly flawed. However, from the perspective of excellence, the shortcomings are:

  1. In line with the DRY (Don't Repeat Yourself) principle, these patterned codes will be repeated N times in each reflection file, increasing the size of the code.
  2. The expansion of code files will naturally increase the time consumption of compilation.
  3. Even for program-generated code, it is sometimes necessary to read Debug. A large amount of pattern code noise obviously reduces the readability and debugability of key codes.
  4. When writing and maintaining UHT, more code is generated, which will naturally lead to an increase in the amount of UHT tool code written, increasing the cost of writing and maintaining it; the more code, the more bugs; UHT needs to output more code, which is naturally more efficient. will decrease, resulting in an increase in total compilation time consumption.

UE4CodeGen_Private

The ways to improve are obvious and easy to come by, don't do the same thing twice. Since these glue codes are everywhere, encapsulate these codes into functions; since these metadata information data are scattered everywhere, then encapsulate these data into structures as parameters of functions.
Therefore, in UE 4.17, a UE4CodeGen_Private namespace was added to UObjectGlobals.h.cpp, and some generation functions were added to it:

//UObjectGlobals.h
namespace UE4CodeGen_Private
{
    COREUOBJECT_API void ConstructUFunction(UFunction*& OutFunction, const FFunctionParams& Params);
    COREUOBJECT_API void ConstructUEnum(UEnum*& OutEnum, const FEnumParams& Params);
    COREUOBJECT_API void ConstructUScriptStruct(UScriptStruct*& OutStruct, const FStructParams& Params);
    COREUOBJECT_API void ConstructUPackage(UPackage*& OutPackage, const FPackageParams& Params);
    COREUOBJECT_API void ConstructUClass(UClass*& OutClass, const FClassParams& Params);
}

//UObjectGlobals.cpp
namespace UE4CodeGen_Private
{
    void ConstructUProperty(UObject* Outer, const FPropertyParamsBase* const*& PropertyArray, int32& NumProperties);
    void AddMetaData(UObject* Object, const FMetaDataPairParam* MetaDataArray, int32 NumMetaData);
}

The meaning of the function names is obvious. They are used to construct some metadata structures: UEnum, UFunction, UProperty, UScriptStruct, UClass, UPackage and add some metadata (these structures will be explained in detail later). The first parameter is a reference to a pointer, so it is used to construct an object and return it with a pointer; the key is the second parameter: they are all XXXParams parameters, used to pass in information.
So we continue to look at these parameter information:

Params and generation of UEnum:

//UObjectGlobals.h
namespace UE4CodeGen_Private
{
#if WITH_METADATA   //只有在编辑器模式下,才保留元数据信息
    struct FMetaDataPairParam   //元数据对
    {
        //例:MetaData->SetValue(ReturnEnum, TEXT("MY_Song.DisplayName"), TEXT("Song"));
        const char* NameUTF8;   //元数据的键值对信息
        const char* ValueUTF8;
    };
#endif
    struct FEnumeratorParam     //枚举项
    {
        const char*               NameUTF8; //枚举项的名字
        int64                     Value;    //枚举项的值
#if WITH_METADATA
        const FMetaDataPairParam* MetaDataArray;    //一个枚举项依然可以包含多个元数据键值对
        int32                     NumMetaData;
#endif
    };

    struct FEnumParams  //枚举参数
    {
        UObject*                  (*OuterFunc)();   //获取Outer对象的函数指针回调,用于获取所属于的Package
        EDynamicType                DynamicType;    //是否动态,一般是非动态的
        const char*                 NameUTF8;   //枚举的名字
        EObjectFlags                ObjectFlags;    //UEnum对象的标志
        FText                     (*DisplayNameFunc)(int32);    //获取自定义显示名字的回调,一般是nullptr,就是默认规则生成的名字
        uint8                       CppForm; 
        /*CppForm指定这个枚举是怎么定义的,用来在之后做更细的处理。
        enum class ECppForm
        {
            Regular,    //常规的enum MyEnum{}这样定义
            Namespaced, //MyEnum之外套一层namespace的定义
            EnumClass   //enum class定义的
        };
        */
        const char*                 CppTypeUTF8;    //C++里的类型名字,一般是等同于NameUTF8的,但有时定义名字和反射的名字可以不一样
        const FEnumeratorParam*     EnumeratorParams;   //枚举项数组
        int32                       NumEnumerators;
#if WITH_METADATA
        const FMetaDataPairParam*   MetaDataArray;  //元数据数组
        int32                       NumMetaData;
#endif
    };
}

//MyEnum.gen.cpp生成代码:
static const UE4CodeGen_Private::FEnumeratorParam Enumerators[] = { //所有的枚举项
            { "MyEnum::MY_Dance", (int64)MyEnum::MY_Dance },
            { "MyEnum::MY_Rain", (int64)MyEnum::MY_Rain },
            { "MyEnum::MY_Song", (int64)MyEnum::MY_Song },
        };
#if WITH_METADATA
static const UE4CodeGen_Private::FMetaDataPairParam Enum_MetaDataParams[] = {   //枚举的元数据
    { "BlueprintType", "true" },
    { "IsBlueprintBase", "true" },
    { "ModuleRelativePath", "MyEnum.h" },
    { "MY_Dance.DisplayName", "Dance" },
    { "MY_Rain.DisplayName", "Rain" },
    { "MY_Song.DisplayName", "Song" },
};
#endif
static const UE4CodeGen_Private::FEnumParams EnumParams = { //枚举的元数据参数信息
    (UObject*(*)())Z_Construct_UPackage__Script_Hello,
    UE4CodeGen_Private::EDynamicType::NotDynamic,
    "MyEnum",
    RF_Public|RF_Transient|RF_MarkAsNative,
    nullptr,
    (uint8)UEnum::ECppForm::EnumClass,
    "MyEnum",
    Enumerators, //枚举项数组
    ARRAY_COUNT(Enumerators),  
    METADATA_PARAMS(Enum_MetaDataParams, ARRAY_COUNT(Enum_MetaDataParams))//枚举元数据数组
};
UE4CodeGen_Private::ConstructUEnum(ReturnEnum, EnumParams); //利用枚举参数构造UEnum*对象到ReturnEnum

Pick the softest coconut first and start squeezing it. The structure of the enumeration is relatively simple, it just contains the enumeration items (string - integer), so you just need to add them in sequence. Metadata pairs refer to the content in macro tags such as UMETA, which can be used in many places to add additional information.

Params and generation of UStruct:

Because UStruct can only contain attributes, we focus here on how attribute information is generated.

//UObjectGlobals.h
//PS:为了阅读方便,与源码有一定的代码位置微调,但不影响功能正确性
namespace UE4CodeGen_Private
{
    enum class EPropertyClass   //属性的类型
    {
        Byte,
        Int8,
        Int16,
        Int,
        Int64,
        UInt16,
        UInt32,
        UInt64,
        UnsizedInt,
        UnsizedUInt,
        Float,
        Double,
        Bool,
        SoftClass,
        WeakObject,
        LazyObject,
        SoftObject,
        Class,
        Object,
        Interface,
        Name,
        Str,
        Array,
        Map,
        Set,
        Struct,
        Delegate,
        MulticastDelegate,
        Text,
        Enum,
    };

    // This is not a base class but is just a common initial sequence of all of the F*PropertyParams types below.
    // We don't want to use actual inheritance because we want to construct aggregated compile-time tables of these things.
    struct FPropertyParamsBase  //属性参数基类
    {
        EPropertyClass Type;    //属性的类型
        const char*    NameUTF8;     //属性的名字
        EObjectFlags   ObjectFlags;  //属性生成的UProperty对象标志,标识这个UProperty对象的特征,RF_XXX那些宏
        uint64         PropertyFlags;    //属性生成的UProperty属性标志,标识这个属性的特征,CPF_XXX那些宏
        int32          ArrayDim;        //属性有可能是个数组,数组的长度,默认是1
        const char*    RepNotifyFuncUTF8;   //属性的网络复制通知函数名字
    };

    struct FPropertyParamsBaseWithOffset // : FPropertyParamsBase
    {
        EPropertyClass Type;
        const char*    NameUTF8;
        EObjectFlags   ObjectFlags;
        uint64         PropertyFlags;
        int32          ArrayDim;
        const char*    RepNotifyFuncUTF8;
        int32          Offset;  //在结构或类中的内存偏移,可以理解为成员变量指针(成员变量指针其实本质上就是从对象内存起始位置的偏移)
    };
    //通用的属性参数
    struct FGenericPropertyParams // : FPropertyParamsBaseWithOffset
    {
        EPropertyClass   Type;
        const char*      NameUTF8;
        EObjectFlags     ObjectFlags;
        uint64           PropertyFlags;
        int32            ArrayDim;
        const char*      RepNotifyFuncUTF8;
        int32            Offset;
#if WITH_METADATA
        const FMetaDataPairParam*           MetaDataArray;
        int32                               NumMetaData;
#endif
    };

    //一些普通常用的数值类型就通过这个类型定义别名了
    // These property types don't add new any construction parameters to their base property
    typedef FGenericPropertyParams FInt8PropertyParams;
    typedef FGenericPropertyParams FInt16PropertyParams;

    //枚举类型属性参数
    struct FBytePropertyParams // : FPropertyParamsBaseWithOffset
    {
        EPropertyClass   Type;
        const char*      NameUTF8;
        EObjectFlags     ObjectFlags;
        uint64           PropertyFlags;
        int32            ArrayDim;
        const char*      RepNotifyFuncUTF8;
        int32            Offset;
        UEnum*         (*EnumFunc)();   //定义的枚举对象回调
#if WITH_METADATA
        const FMetaDataPairParam*           MetaDataArray;
        int32                               NumMetaData;
#endif
    };
    //...省略一些定义,可自行去UObjectGlobals.h查看
    //对象引用类型属性参数
    struct FObjectPropertyParams // : FPropertyParamsBaseWithOffset
    {
        EPropertyClass   Type;
        const char*      NameUTF8;
        EObjectFlags     ObjectFlags;
        uint64           PropertyFlags;
        int32            ArrayDim;
        const char*      RepNotifyFuncUTF8;
        int32            Offset;
        UClass*        (*ClassFunc)();  //用于获取该属性定义类型的函数指针回调
#if WITH_METADATA
        const FMetaDataPairParam*           MetaDataArray;
        int32                               NumMetaData;
#endif
    };

    struct FStructParams    //结构参数
    {
        UObject*                          (*OuterFunc)();   //所属于的Package
        UScriptStruct*                    (*SuperFunc)();   //该结构的基类,没有的话为nullptr
        void*                             (*StructOpsFunc)(); // really returns UScriptStruct::ICppStructOps*,结构的构造分配的辅助操作类
        const char*                         NameUTF8;   //结构名字
        EObjectFlags                        ObjectFlags;    //结构UScriptStruct*的对象特征
        uint32                              StructFlags; // EStructFlags该结构的本来特征
        SIZE_T                              SizeOf;     //结构的大小,就是sizeof(FMyStruct),用以后续分配内存时候用
        SIZE_T                              AlignOf;//结构的内存对齐,就是alignof(FMyStruct),用以后续分配内存时候用
        const FPropertyParamsBase* const*   PropertyArray;  //包含的属性数组
        int32                               NumProperties;
    #if WITH_METADATA
        const FMetaDataPairParam*           MetaDataArray;  //元数据数组
        int32                               NumMetaData;
    #endif
    };
}

//MyStruct.gen.cpp生成代码:
#if WITH_METADATA
static const UE4CodeGen_Private::FMetaDataPairParam Struct_MetaDataParams[] = { //结构的元数据
    { "BlueprintType", "true" },
    { "ModuleRelativePath", "MyStruct.h" },
};
#endif
auto NewStructOpsLambda = []() -> void* { return (UScriptStruct::ICppStructOps*)new UScriptStruct::TCppStructOps<FMyStruct>(); };   //一个获取操作类的回调
#if WITH_METADATA
//属性的元数据
static const UE4CodeGen_Private::FMetaDataPairParam NewProp_Score_MetaData[] = {
    { "Category", "MyStruct" },
    { "ModuleRelativePath", "MyStruct.h" },
};
#endif
static const UE4CodeGen_Private::FFloatPropertyParams NewProp_Score = { UE4CodeGen_Private::EPropertyClass::Float, "Score", RF_Public|RF_Transient|RF_MarkAsNative, 0x0010000000000004, 1, nullptr, STRUCT_OFFSET(FMyStruct, Score), METADATA_PARAMS(NewProp_Score_MetaData, ARRAY_COUNT(NewProp_Score_MetaData)) };//Score属性的信息
//属性的数组
static const UE4CodeGen_Private::FPropertyParamsBase* const PropPointers[] = {
    (const UE4CodeGen_Private::FPropertyParamsBase*)&NewProp_Score,
};
//结构的参数信息
static const UE4CodeGen_Private::FStructParams ReturnStructParams = {
    (UObject* (*)())Z_Construct_UPackage__Script_Hello,
    nullptr,
    &UE4CodeGen_Private::TNewCppStructOpsWrapper<decltype(NewStructOpsLambda)>::NewCppStructOps,
    "MyStruct",
    RF_Public|RF_Transient|RF_MarkAsNative,
    EStructFlags(0x00000201),
    sizeof(FMyStruct),
    alignof(FMyStruct),
    PropPointers, ARRAY_COUNT(PropPointers),
    METADATA_PARAMS(Struct_MetaDataParams, ARRAY_COUNT(Struct_MetaDataParams))
};
UE4CodeGen_Private::ConstructUScriptStruct(ReturnStruct, ReturnStructParams);//构造UScriptStruct*到ReturnStruct里去

The code is relatively simple, and you can roughly understand it by comparing it up and down and looking at the comments. It is to collect the information of each attribute into an array, then merge it into the structure parameters, and finally pass it to ConstructUScriptStruct for construction.

Thinking: Why don’t FPropertyParamsBaseWithOffset and subsequent ones inherit from FPropertyParamsBase?
If we look at the similarities behind the subsequent annotations such as FPropertyParamsBase and FPropertyParamsBaseWithOffset and the attribute members, it is easy to see that these F*PropertyParams actually use inheritance semantics. So why not inherit directly but laboriously write it again?
Although the official comments have been written on FPropertyParamsBase, some friends may still be confused. In fact, this involves an aggregate initialization rule of the C++ Aggregate type . Please study the specific C++ syntax rules by yourself. Simply put, an Aggregate is an array or a type that has no user-declared constructor, no private or protected type of non-static data members, and no superclass or virtual function.  The Aggregatel type can be   initialized with an initialization list of the form T object = {arg1, arg2, ...} . What we saw above:

static const UE4CodeGen_Private::FFloatPropertyParams NewProp_Score = { UE4CodeGen_Private::EPropertyClass::Float, "Score", RF_Public|RF_Transient|RF_MarkAsNative, 0x0010000000000004, 1, nullptr, STRUCT_OFFSET(FMyStruct, Score), METADATA_PARAMS(NewProp_Score_MetaData, ARRAY_COUNT(NewProp_Score_MetaData)) };

The following ={} is the initialization list. Of course, this is written for the purpose of simplicity, otherwise it would be too troublesome to set the parameter fields one by one.
So what happens if inheritance is used? We can do a test:

struct Point2
{
    float X;
    float Y;
};

struct Point3 :public Point2    //这不是个Aggregate类型,因为有父类
{
    float Z;
};

struct Point3_Aggregate //这是个Aggregate类型
{
    float X;
    float Y;
    float Z;
};
const static Point3 pos{ 1.f,2.f,3.f }; // error C2440:'initializing': cannot convert from 'initializer list' to 'Point3'
const static Point3_Aggregate pos2{ 1.f,2.f,3.f };

Therefore, UE chooses not to inherit, and would rather rewrite the field declaration each time, just so that it can simply use the {} initialization list to construct the object. But we also observed that in the PropPointers array, each element is still converted to FPropertyParamsBase*. Because according to the object memory model of C/C++, when inheriting, base class members are ranked at the memory address before derived class members. And because F*PropertyParams is such a POD, as long as the memory address and the order of the attribute members are consistent, it can be guaranteed that it can still be used correctly after being converted to another structure pointer.

Although it seems that this explanation makes sense, it still feels very troublesome. The semantics of inheritance should have been used, but it was compromised for the initialization list. It's still unbearable for perfectionists, so is there a solution that can use both inheritance and initialization lists?
In fact, just add a constructor. Instead of Aggregate types, relax restrictions and use POD types instead ( POD types are non-POD types (or arrays of these types) without non-static types and data members of reference types, and there are no user-defined assignment operators and destructors. type. ). like:

struct Point2
{
    float X;
    float Y;
    Point2(float x, float y) :X(x), Y(y) {} //构造函数
};

struct Point3_POD :public Point2
{
    float Z;
    Point3_POD(float x, float y, float z) :Point2(x, y), Z(z) {}//构造函数
};

struct Point3_Aggregate
{
    float X;
    float Y;
    float Z;
};
const static Point3_POD pos{ 1.f,2.f,3.f };     //works happy ^_^
const static Point3_Aggregate pos2{ 1.f,2.f,3.f };  //works happy ^_^

So just add F*PropertyParams to the constructor. As for why UE doesn’t do this? Ask someone from Epic, show your hands~

Params and generation of UFunction and UClass:

In order to test the function input and output parameters in UClass, an AddHP function is added.

//测试文件:
UCLASS()
class HELLO_API UMyClass :public UObject
{
    GENERATED_BODY()
public:
    UPROPERTY(BlueprintReadWrite)
    float Score;
public:
    UFUNCTION(BlueprintCallable, Category = "Hello")
    float AddHP(float HP);

    UFUNCTION(BlueprintCallable, Category = "Hello")
    void CallableFunc();    //C++实现,蓝图调用

    UFUNCTION(BlueprintNativeEvent, Category = "Hello")
    void NativeFunc();  //C++实现默认版本,蓝图可重载实现

    UFUNCTION(BlueprintImplementableEvent, Category = "Hello")
    void ImplementableFunc();   //C++不实现,蓝图实现
};

//Class.h
//类里的函数链接信息,一个函数名字对应一个UFunction对象
struct FClassFunctionLinkInfo 
{
    UFunction* (*CreateFuncPtr)();  //获得UFunction对象的函数指针回调
    const char* FuncNameUTF8;       //函数的名字
};
//类在Cpp里的类型信息,用一个结构是为了将来也许还会添加别的字段
struct FCppClassTypeInfoStatic
{
    bool bIsAbstract;   //是否抽象类
};

//UObjectGlobals.h
namespace UE4CodeGen_Private
{
    //函数参数
    struct FFunctionParams
    {
        UObject*                          (*OuterFunc)();   //所属于的外部对象,一般是外部的UClass*对象
        const char*                         NameUTF8;   //函数的名字
        EObjectFlags                        ObjectFlags;    //UFunction对象的特征
        UFunction*                        (*SuperFunc)();   //UFunction的基类,一般为nullptr
        EFunctionFlags                      FunctionFlags;  //函数本身的特征
        SIZE_T                              StructureSize;  //函数的参数返回值包结构的大小
        const FPropertyParamsBase* const*   PropertyArray;  //函数的参数和返回值字段数组
        int32                               NumProperties;  //函数的参数和返回值字段数组大小
        uint16                              RPCId;          //网络间的RPC Id
        uint16                              RPCResponseId;  //网络间的RPC Response Id
    #if WITH_METADATA
        const FMetaDataPairParam*           MetaDataArray;  //元数据数组
        int32                               NumMetaData;
    #endif
    };

    //实现的接口参数,篇幅所限,接口的内容可以自行分析
    struct FImplementedInterfaceParams
    {
        UClass* (*ClassFunc)();     //外部所属于的UInterface对象
        int32     Offset;           //在UMyClass里的实现的IMyInterface的虚函数表地址偏移
        bool      bImplementedByK2; //是否在蓝图中实现
    };

    //类参数
    struct FClassParams
    {
        UClass*                                   (*ClassNoRegisterFunc)(); //获得UClass*对象的函数指针
        UObject*                           (*const *DependencySingletonFuncArray)();    //获取依赖对象的函数指针数组,一般是需要前提构造的基类,模块UPackage对象
        int32                                       NumDependencySingletons;
        uint32                                      ClassFlags; // EClassFlags,类特征
        const FClassFunctionLinkInfo*               FunctionLinkArray;  //链接的函数数组
        int32                                       NumFunctions;
        const FPropertyParamsBase* const*           PropertyArray;  //类里定义的成员变量数组
        int32                                       NumProperties;
        const char*                                 ClassConfigNameUTF8;    //配置文件名字,有些类可以从配置文件从加载数据
        const FCppClassTypeInfoStatic*              CppClassInfo;   //Cpp里定义的信息
        const FImplementedInterfaceParams*          ImplementedInterfaceArray;  //实现的接口信息数组
        int32                                       NumImplementedInterfaces;
    #if WITH_METADATA           //类的元数据
        const FMetaDataPairParam*                   MetaDataArray;
        int32                                       NumMetaData;
    #endif
    };
}

//MyClass.gen.cpp
//构造AddHp函数的UFunction对象
UFunction* Z_Construct_UFunction_UMyClass_AddHP()
{
    struct MyClass_eventAddHP_Parms     //函数的参数和返回值包
    {
        float HP;
        float ReturnValue;
    };
    static UFunction* ReturnFunction = nullptr;
    if (!ReturnFunction)
    {
        //定义两个属性用来传递信息
        static const UE4CodeGen_Private::FFloatPropertyParams NewProp_ReturnValue = { UE4CodeGen_Private::EPropertyClass::Float, "ReturnValue", RF_Public|RF_Transient|RF_MarkAsNative, 0x0010000000000580, 1, nullptr, STRUCT_OFFSET(MyClass_eventAddHP_Parms, ReturnValue), METADATA_PARAMS(nullptr, 0) };

        static const UE4CodeGen_Private::FFloatPropertyParams NewProp_HP = { UE4CodeGen_Private::EPropertyClass::Float, "HP", RF_Public|RF_Transient|RF_MarkAsNative, 0x0010000000000080, 1, nullptr, STRUCT_OFFSET(MyClass_eventAddHP_Parms, HP), METADATA_PARAMS(nullptr, 0) };

        static const UE4CodeGen_Private::FPropertyParamsBase* const PropPointers[] = {
            (const UE4CodeGen_Private::FPropertyParamsBase*)&NewProp_ReturnValue,
            (const UE4CodeGen_Private::FPropertyParamsBase*)&NewProp_HP,
        };

#if WITH_METADATA
        static const UE4CodeGen_Private::FMetaDataPairParam Function_MetaDataParams[] = {
            { "Category", "Hello" },
            { "ModuleRelativePath", "MyClass.h" },
        };
#endif
        static const UE4CodeGen_Private::FFunctionParams FuncParams = { (UObject*(*)())Z_Construct_UClass_UMyClass, "AddHP", RF_Public|RF_Transient|RF_MarkAsNative, nullptr, (EFunctionFlags)0x04020401, sizeof(MyClass_eventAddHP_Parms), PropPointers, ARRAY_COUNT(PropPointers), 0, 0, METADATA_PARAMS(Function_MetaDataParams, ARRAY_COUNT(Function_MetaDataParams)) };

        UE4CodeGen_Private::ConstructUFunction(ReturnFunction, FuncParams); //构造函数
    }
    return ReturnFunction;
}
//...构造其他的函数
//该类依赖的对象列表,用函数指针来获取。
static UObject* (*const DependentSingletons[])() = {
                (UObject* (*)())Z_Construct_UClass_UObject,
                (UObject* (*)())Z_Construct_UPackage__Script_Hello,
            };
//函数链接信息
static const FClassFunctionLinkInfo FuncInfo[] = {
    { &Z_Construct_UFunction_UMyClass_CallableFunc, "CallableFunc" }, // 1841300010
    { &Z_Construct_UFunction_UMyClass_ImplementableFunc, "ImplementableFunc" }, // 2010696670
    { &Z_Construct_UFunction_UMyClass_NativeFunc, "NativeFunc" }, // 2593520329
};
#if WITH_METADATA
static const UE4CodeGen_Private::FMetaDataPairParam Class_MetaDataParams[] = {
    { "IncludePath", "MyClass.h" },
    { "ModuleRelativePath", "MyClass.h" },
};
#endif
#if WITH_METADATA
static const UE4CodeGen_Private::FMetaDataPairParam NewProp_Score_MetaData[] = {
    { "Category", "MyClass" },
    { "ModuleRelativePath", "MyClass.h" },
};
#endif
static const UE4CodeGen_Private::FFloatPropertyParams NewProp_Score = { UE4CodeGen_Private::EPropertyClass::Float, "Score", RF_Public|RF_Transient|RF_MarkAsNative, 0x0010000000000004, 1, nullptr, STRUCT_OFFSET(UMyClass, Score), METADATA_PARAMS(NewProp_Score_MetaData, ARRAY_COUNT(NewProp_Score_MetaData)) };

static const UE4CodeGen_Private::FPropertyParamsBase* const PropPointers[] = {
    (const UE4CodeGen_Private::FPropertyParamsBase*)&NewProp_Score,
};
static const FCppClassTypeInfoStatic StaticCppClassTypeInfo = {
    TCppClassTypeTraits<UMyClass>::IsAbstract,
};
static const UE4CodeGen_Private::FClassParams ClassParams = {
    &UMyClass::StaticClass,
    DependentSingletons, ARRAY_COUNT(DependentSingletons),
    0x00100080u,
    FuncInfo, ARRAY_COUNT(FuncInfo),
    PropPointers, ARRAY_COUNT(PropPointers),
    nullptr,
    &StaticCppClassTypeInfo,
    nullptr, 0,
    METADATA_PARAMS(Class_MetaDataParams, ARRAY_COUNT(Class_MetaDataParams))
};
UE4CodeGen_Private::ConstructUClass(OuterClass, ClassParams);   //构造UClass对象

Note that for a function, parameters and return values ​​can be regarded as attributes defined within the function, but they have different characteristics and uses. Classes contain properties and functions, and functions contain properties. The construction of attributes is the same as the rules in Struct, so I won’t go into details. The difference is that because Class can contain Function, all UFunction must be constructed before constructing UClass. So to sort it out, in fact, the above structures are just structures within structures, plus some information collections integrated by arrays.

Thinking: Why does the generated code use a lot of function pointers to return objects?
For example, UClass* (*ClassNoRegisterFunc)() or UFunction* (*CreateFuncPtr)() both use function pointers to obtain the defined UClass* object and the prerequisite UFunction* object. Why not just use a UClass* or UFunction* pointer?
The answer is simple, because the construction order is uncertain.
In a type system, type dependency management is a troublesome but very important matter. You must ensure that all preceding types of the current type have been defined before you can start the construction of this type. To address this problem, of course you can carefully sort out the order of definitions and ensure that all orders are from bottom to top. But the ideal is beautiful, the reality is very skinny, this step is difficult to achieve, everyone will make mistakes, not to mention the current 1572 UClass, 1039 UStruct, 588 Enum in UE4... Do you really believe that someone can manage it well? These? Therefore, it is basically unrealistic to manually organize the dependency definition order of types in the type system. It is almost difficult for you to get the object of the preceding type exactly when constructing this type.
What to do? It’s also simple, just like the dependency order for processing static singleton objects in C++. If it can’t be processed, then don’t process it! Using the idea of ​​lazy evaluation, when a prefixed type is needed, first determine whether it has been constructed. If so, return immediately. If not, construct it and then return - a simple version of the singleton pattern. Because this routine is so common, these judgments and the logical encapsulation of construction become functions. In order to obtain those objects, it becomes first to obtain those function pointers. The generated code contains roughly this routine:

UClass* Z_Construct_UClass_UMyClass()   //用以获取UMyClass所对应的UClass的函数
{
    static UClass* OuterClass = nullptr;    //一个函数局部静态指针
    if (!OuterClass)    //反正都是单线程执行,所以不需要线程安全的保护
    {
        //...一些构造代码
        UE4CodeGen_Private::ConstructUClass(OuterClass, ClassParams);
    }
    return OuterClass;
}

Using this method, a pre-object acquisition chain that can be automatically traced back is formed in the code. Any time we want to get a UClass* object of a certain class, we don't need to worry about whether it has been constructed, nor do we need to worry about whether all its dependencies have been constructed, because the code mechanism ensures that the prerequisites are constructed on demand.

Params and generation of UPackage:

For the Hello module, according to UE4's Module rules, we need to define a Hello UPackage to store the types defined in the module.
The previous code form of 4.15 was:

UPackage* Z_Construct_UPackage__Script_Hello()
{
    static UPackage* ReturnPackage = NULL;
    if (!ReturnPackage)
    {
        ReturnPackage = CastChecked<UPackage>(StaticFindObjectFast(UPackage::StaticClass(), NULL, FName(TEXT("/Script/Hello")), false, false));
        ReturnPackage->SetPackageFlags(PKG_CompiledIn | 0x00000000);
        FGuid Guid;
        Guid.A = 0x79A097CD;
        Guid.B = 0xB58D8B48;
        Guid.C = 0x00000000;
        Guid.D = 0x00000000;
        ReturnPackage->SetGuid(Guid);
    }
    return ReturnPackage;
}

After 4.17 it is changed to:

//UObjectGlobals.h
namespace UE4CodeGen_Private
{
    //包参数
    struct FPackageParams
    {
        const char*                        NameUTF8;    //名字
        uint32                             PackageFlags; // EPackageFlags包的特征
        uint32                             BodyCRC; //内容的CRC,CRC的部分后续介绍
        uint32                             DeclarationsCRC; //声明部分的CRC
        UObject*                  (*const *SingletonFuncArray)();   //依赖的对象列表
        int32                              NumSingletons;
#if WITH_METADATA
        const FMetaDataPairParam*          MetaDataArray;//元数据数组
        int32                              NumMetaData;
#endif
    };
}
//Hello.init.gen.cpp
UPackage* Z_Construct_UPackage__Script_Hello()
{
    static UPackage* ReturnPackage = nullptr;
    if (!ReturnPackage)
    {
        static const UE4CodeGen_Private::FPackageParams PackageParams = {
            "/Script/Hello",    //包名字
            PKG_CompiledIn | 0x00000000,
            0xA1EAFF6A,
            0x41CF0543,
            nullptr, 0,
            METADATA_PARAMS(nullptr, 0)
        };
        UE4CodeGen_Private::ConstructUPackage(ReturnPackage, PackageParams);
    }
    return ReturnPackage;
}

The content of this block is relatively simple. Friends who can insist on seeing this should understand the above code at a glance. One thing you need to know is that the type data defined in the Hello module are all placed in the "/Script/Hello" Package, so the Hello Package is the first to be constructed because it is followed by other types. All depend on it.

Summarize

Comparing the two versions of code, we can easily see that after refactoring, the generated code is more compact, the grammatical noise is reduced a lot, and the information density of the code is greatly improved. But it should be noted that the type system stage that this article focuses on is a supplement to the previous "InsideUE4" UObject (4) type system code generation . The follow-up will still start collecting the content of the "InsideUE4" UObject (5) type system collection chapter, so the previous article Those static static collection mechanisms have not changed.
As for the specific implementation of the UE4CodeGen_Private::ConstructXXX structure, we will explain it in detail when we talk about the structural organization of the type system in subsequent chapters. I promise, that day will not be too long ago. At the current stage, you can simply understand that type objects are constructed one by one through parameters.

Thinking: Can the generated code be made clearer and more efficient?
Although through this refactoring, the readability of the code has improved a lot. But to be fair, today's code generation is still far from elegant. So what are the general methods to continue to improve when generating programmatic code?
Going after its essence, the means to make the code concise are actually to increase the information density. If you compare code to a file, refactoring is like compression software. It is the ultimate to compress the amount of information in the code to nothing. But of course, issues such as platform portability (otherwise just save a binary file directly), readability, compilation efficiency and other issues must be weighed. There is only one way to increase information density: don’t write the same information twice. So the way to bring it is encapsulation! As for encapsulation, when generating code, we can actually use:

  1. Macros, UE4 actually uses some macros to reduce code, such as ARRAY COUNT, VTABLE OFFSET, IMPLEMENT_CLASS, etc. However, there are still a large number of long names in the current generated code, and routine array splicing can be spliced ​​using macros. Excessive use of macros will certainly reduce readability and debuggability, but using them in the right places can optimize a huge amount of code like cheating. Macros have always been the most powerful tool for code splicing.
  2. Functions. Encapsulating similar logic into functions can optimize a large number of operations and only provide the simplest information input interface to the outside world. The UE4 optimization method introduced in this article uses functions to optimize. My personal preference is to define more convenient auxiliary functions in the core layer to receive a variety of inputs, rather than spelling out function entities one by one when generating code. This can greatly reduce the size of the generated code. Clever use of variable parameters, arrays and loops in function implementation can make your function's throughput amazing.
  3. Templates dig deeper into the information provided by the compiler, squeeze out the amount of information provided by each field, and use it to automatically deduce other information you need. For example, the type of an attribute can be automatically derived based on the C++ type of the field using a template, without the need for manual analysis and injection. Templates are not used much in UE4's generated code because templates also have major disadvantages: slow compilation and difficulty in understanding. On the basis of already having the UHT analysis code, using the template to deduce it again does not seem to be that meaningful, so the compilation consumption can still be saved a little. As for the difficulty of understanding the template, an engine that is open source for the public should not show off excessive skills in technology selection and implementation, because the technical level of employees is mostly junior and intermediate. Taking into account audience issues and promotion, sometimes it is better to use some simple and unpretentious implementations to be more widely accepted, and at the same time have a greater probability of attracting refactoring maintainers. Otherwise, the code you write is very powerful, but only you can change it and understand it, so how can people in the community contribute to maintenance and upgrades for you.
  4. For scalability, it is also recommended to put the construction logic of the type system in Runtime as much as possible instead of in the generated code (previously, UE4 directly created new UXXX type objects in the generated code. Put it in Runtime and provide external function API interfaces. , the advantage of this is that testability is greatly increased, and you can manually construct the desired types for testing without relying on UHT. In addition, for some needs that require dynamic Emit to create types at runtime, it is possible to break away from UHT and maintain its own functions. Completeness is also required.

The above discussion is not limited to the UE4 engine, but for those who are interested in implementing a type system, there are actually many technical choices at each stage, but design is the art of trade-offs, clearly understand your audience, and clearly understand your design purpose. Vision, using all available means at your fingertips, and finally putting together an elegant design.

postscript

When we read the UE4 source code, we must always realize that the UE4 source code is not perfect. Many times, when reading a specific piece of code, we may try our best to guess the purpose of this piece of code, wondering what the original design concept was but cannot get it. In fact, the reality is that these codes are often just temporary fixes, and not all code writers are so skilled. He would make a mistake, another one he would fix, then another mistake and another fix, and the cycle begins again. Just like the evolution of living things, after repeated reconstruction and optimization, what is finally obtained is often not the optimal solution. Just like the blind spots of human eyes and human wisdom teeth are not the most correct design, but will leave traces of evolution. But despite this, we don't need to feel frustrated, because accepting imperfection, accepting the last acceptable solution, and properly knowing how to compromise and accept flaws may be one of the signs of maturity as a technical person.

There is an interesting phenomenon. For large-scale projects (such as UE4), the lower-level modules lack the impetus to refactor, and the lower-level code has greater resistance to changes. A single move affects the whole body. Sometimes, refactoring the underlying modules is actually the time when it can produce huge benefits, because its impact will be magnified to the top layer. But code is written by people after all. In a company or a team, the development atmosphere is often such that as long as the underlying code can work, no one will change it, and no one will dare to change it. Take the CoreUObject module of UE4 as an example. It is the object system module of UE4. It can be said to be the lowest core module. However, according to my study for a long time, the code is full of various historical traces and minor repairs. , some code structures are also frustrating, but it works, so this code came from UE3 to UE4. I believe it will continue to follow UE5 in my lifetime. The CoreUObject code module can currently work. Although there are sometimes bugs, small fixes will be enough. The elegant pursuit of those codes and the design of the structure, the benefits are not obvious after the changes. Are there any bugs after the changes? Is it yours? Therefore, it is precisely because of this benefit and responsibility that the modules that need the most attention are often the least upgraded. But the law of history also shows that when the code has accumulated too many small defects, the developers have accumulated enough grievances, and when there is a functional demand that cannot be started without moving the bottom layer, only then can they make up their minds to make major changes, or simply Started anew and redesigned. I say this in the hope that friends who are reading the UE4 source code, when encountering inexplicable designs in the code and scratching their heads and thinking hard, can relax their mind, be calm, take a rest, and let's move on.

Guess you like

Origin blog.csdn.net/ttod/article/details/133254469