01 反射
什么是反射
反射是程序在运行时检查自身,将参与反射的数据(变量,类,函数等等)暴露给蓝图,允许运行时去调用。
反射的使用
宏就是用来帮助建立反射的,Unreal Header Tool(UHT)是建立反射的工具,它会扫描头文件。
例如:UE4中只要在一个函数的前面加上UFUNCTION()宏,然后在括号里面加上BlueprintCallable就可以在编辑器里面调用。
-
为了标记一个头文件包含反射类型,需要添加一个头文件
"XXX.generate.h"
,必须放在头文件的尾部,该文件一般位于“Project\工程名\Intermediate\Build\Win64\ Inc\工程名\类名.generate.h”#include "XXX.generate.h"
-
可以使用UENUM()、UCLASS()、USTRUCT()、UFUNCTION()、以及UPROPERTY()来在头文件中注解不同的类型以及成员变量。每一个宏都会出现在类型或者成员变量的前面,并且可以包含额外的修饰符关键字。
UCLASS() //标识类参与反射 UPROPERTY()//标识属性参与反射 UFUNCTION()//标识函数参与反射 GENERATED_CODE//反射后的结果放置位置
-
UHT对代码文件内容进行处理,生成 .generated.h/.gen.cpp 文件,在生成的代码中加入反射信息。头文件中包一些函数实现声明的补全,对Current FileId的赋值,将一些函数注册到蓝图的虚拟中的操作。cpp文件(全局唯一)中会生成一个静态全局变量,在静态全局变量的构造函数中进行信息的注册,并将结果保存在UClass对象中(存放真实信息)。
反射的类型层次
![img](https://i-blog.csdnimg.cn/blog_migrate/fe2ecbabba83ce951167d00ea591334e.png)
UStruct
是所有 聚合结构体的基础类型(包含其它成员的类型,比如一个C++类、结构体、或者函数),不应该跟C++中的结构体(struct)混为一谈(那是UScriptStruct)。UClass
可以包含函数、属性以及它们的孩子,而UFunction
和UStriptStruct
只能包含属性。
![img](https://i-blog.csdnimg.cn/blog_migrate/1ac4379bf4fff71fa17958dde96d3836.jpeg)
反射实现机制
Unreal Build Tool(UBT)
和Unreal Header Tool (UHT)
两个协同工作来生成运行时反射需要的数据。UBT属性通过扫描头文件,记录任何至少有一个反射类型的头文件的模块。如果其中任意一个头文件从上一次编译起发生了变化,那么 UHT就会被调用来利用和更新反射数据。UHT分析头文件,创建一系列反射数据,并且生成包含反射数据的C++代码(放到每一个模块的moulde.generated.inl
中。注:最新版会生成到moudle.generated.cpp
中),还有各种帮助函数以及thunk函数(每一个 头文件.generated.h
)
用生成的C++代码来存储反射数据的一个最大好处就是,它可以保证跟二进制做到同步。你永远也不会加载陈旧或者过时的反射数据,因为它是跟引擎的其它代码同时编译的,并且它会在程序启动的时候使用C++表达式来计算成员偏移等,而不是通过针对特定平台/编译器/优化的组合中进行逆向工程。
生成的诸如StaticClass()、StaticStruct()
函数是为了让当前类型更好的获取反射数据,以及那此转换函数(thunks)用来在蓝图或者网络复制中调用C++函数。这些必须声明为类或者结构体的一部分,这也就解释了为什么GENERATED_UCLASS_BODY() or GENERATED_USTRUCT_BODY()
宏会包含在你的反射系统的类型中,而#include "TypeName.generated.h"
的头文件中定义了这些宏。
反射实例
#include "MyObject.generated.h"//虚幻引擎将生成所有反射数据并将放入此文件
UCLASS(Blueprintable)//Blueprintable:此类可由蓝图延展
class UMyObject : public UObject
{
GENERATED_BODY()
public:
MyUObject();
UPROPERTY(BlueprintReadOnly, EditAnywhere)//BlueprintReadOnly:此属性只可从蓝图读取,不可写入
float ExampleProperty;//EditAnywhere:说明此属性可通过属性窗口在原型和实例上进行编辑
UFUNCTION(BlueprintCallable)//BlueprintCallable:公开属性在蓝图代码中调用
void ExampleFunction();
};
02 垃圾回收机制(GC)
UE4中的GC是追踪式、非实时、精确式,非渐近、增量回收(时间片),先标记后回收的过程,为了提高效率和减少回收过程中的卡顿,可以做到并行标记和增量回收以及通过簇来提高回收的效率等。详解链接
UObject和垃圾回收
UE4的垃圾收集比较自动化,通过垃圾回收便无需手动删除 UObjects,只需维持对它们的有效引用即可。类须派生自 UObject,才能启用垃圾回收。
UCLASS()
class MyGCType : public UObject
{
GENERATED_BODY()
};
在垃圾回收器中存在称为根集的概念,此根集是一个对象列表,回收器不会对这些对象进行垃圾回收。只要根集中的对象到讨论中的对象之间存在引用路径,对象便不会被垃圾回收。如对象到根集的此路径不存在,它便会被识别为无法达到,垃圾回收器下次运行时便会将其收集(删除),引擎以特定间隔运行垃圾回收器。
怎样才算是一个引用?存储在一个UPROPERTY属性中的UObject 对象指针。让我们看看简单的例子:
void CreateDoomedObject()
{
MyGCType* DoomedObject = NewObject<MyGCType>();
}
当我们调用上面的方法时,我们实例化一个新的UObject对象,但我们没有存储对象指针到任何UPROPERTY的属性中,所以它不是根集的一部分。事实上,垃圾回收器会检测这个对象是否无法访问,若是,则回收它。
UE4采用了标记-清扫垃圾回收方式,是一种经典的垃圾回收方式。一次垃圾回收分为两个阶段。第一阶段从一个根集合出发,遍历所有可达对象,遍历完成后就能标记出可达对象和不可达对象了,这个阶段会在一帧内完成。第二阶段会渐进式的清理这些不可达对象,因为不可达的对象将永远不能被访问到,所以可以分帧清理它们,避免一下子清理很多UObject,比如map卸载时,发生明显的卡顿。
Actor和垃圾回收
Actors 通常不会被垃圾回收。Actors 生成后,必须手动调用 Destroy()。它们不会被立即删除,而会在下个垃圾回收阶段被清理。
UCLASS()
class AMyActor : public AActor
{
GENERATED_BODY()
public:
UPROPERTY()
MyGCType* SafeObject;
MyGCType* DoomedObject;
AMyActor(const FObjectInitializer& ObjectInitializer)
:Super(ObjectInitializer)
{
SafeObject = NewObject<MyGCType>();
DoomedObject = NewObject<MyGCType>();
}
};
void SpawnMyActor(UWorld* World, FVector Location, FRotator Rotation)
{
World->SpawnActor<AMyActor>(Location, Rotation);
}
调用上述函数时,将在世界场景中生成一个actor
。Actor
的构建函数创建两个对象。一个指定到 UPROPERTY
,另一个指定到裸指针。Actors
自动成为根集的一部分,SafeObject
将不会被垃圾回收,因为它从根集对象出到达。然而 DoomedObject
的进展不是十分顺利。我们未将其标为UPROPERTY
,因此回收器并不知道其正在被引用,而会将它逐渐销毁。