0%

【Piccolo代码解读】整体框架与反射机制

本节主要了解 Piccolo 游戏引擎的代码框架、对象序列化的实现以及反射机制的实现。

1 引擎运行结果

image-20220619142030660

2 代码整体框架

Pilot 引擎除了第三方库之外,主要包含四部分,分别是编辑器、预编译、引擎运行时核心和 Shader 编译,如下图:

image-20220617091342823

其中预编译部分负责预先生成一些文件,比如用来实现反射的文件,会在后文中提到,Shader 编译部分在之后的渲染系统代码中也会说到。编辑器负责整个引擎的界面、工具等的实现,需要引擎核心部分作为成员,是引擎核心功能对使用者的接口;引擎核心部分按照【游戏引擎】(一)游戏引擎架构中的分层结构实现,分别是核心层、功能层、平台层、资源层:

image-20220617092113728

而 engine.cpp 负责将这些系统整合起来构成完整的运行时引擎。

整个引擎的主函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <iostream>
#include <string>
#include <thread>
#include <unordered_map>

#include "runtime/engine.h"

#include "editor/include/editor.h"

// https://gcc.gnu.org/onlinedocs/cpp/Stringizing.html
#define PILOT_XSTR(s) PILOT_STR(s)
#define PILOT_STR(s) #s

int main(int argc, char** argv)
{
std::filesystem::path pilot_root_folder = std::filesystem::path(PILOT_XSTR(PILOT_ROOT_DIR));

Pilot::EngineInitParams params;
params.m_root_folder = pilot_root_folder;
params.m_config_file_path = pilot_root_folder / "PilotEditor.ini";

Pilot::PilotEngine* engine = new Pilot::PilotEngine();

engine->startEngine(params);
engine->initialize();

Pilot::PilotEditor* editor = new Pilot::PilotEditor();
editor->initialize(engine);

editor->run();

editor->clear();

engine->clear();
engine->shutdownEngine();

return 0;
}

从主函数出发可以大概了解引擎运行时的整体架构。主函数中首先初始化了运行时引擎,然后启动和初始化引擎,之后用运行时引擎初始化编辑器,再让编辑器运行,编辑器运行结束后先清理编辑器内容再清理引擎内容。

其中关键的函数就是 editor->run() 了,函数内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void PilotEditor::run()
{
assert(m_engine_runtime);
assert(m_editor_ui);
float delta_time;
while (true)
{
delta_time = m_engine_runtime->calculateDeltaTime();
g_editor_global_context.m_scene_manager->tick(delta_time);
g_editor_global_context.m_input_manager->tick(delta_time);
if (!m_engine_runtime->tickOneFrame(delta_time))
return;
}
}

首先判断了运行时引擎和引擎 UI 是否初始化成功,然后就是运行时主循环了,循环内就是各种 tick() 函数,其中最重要的是运行时引擎的 tickOneFrame 函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
bool PilotEngine::tickOneFrame(float delta_time)
{
logicalTick(delta_time);
calculateFPS(delta_time);

// single thread
// exchange data between logic and render contexts
g_runtime_global_context.m_render_system->swapLogicRenderData();

rendererTick();

g_runtime_global_context.m_window_system->pollEvents();

g_runtime_global_context.m_window_system->setTile(
std::string("Pilot - " + std::to_string(getFPS()) + " FPS").c_str());

const bool should_window_close = g_runtime_global_context.m_window_system->shouldClose();
return !should_window_close;
}

可以看到其中包含 logicalTick()rendererTick() 两部分,和我们之前笔记中描述的流程完全一致,当窗口关闭时函数返回 false,于是引擎主循环就会退出。

3 代码反射

代码反射是指允许在运行时检查已知数据类型。 反射允许枚举给定程序集中的数据类型,并且可以发现给定类或值类型的成员。 无论类型在编译时是已知的还是引用的,都是如此。 这使得反射成为开发和代码管理工具的有用功能。

定义很抽象,通过一个实际场景来理解,比如引擎中对于不同对象要显示出它的组件,但是不同对象的属性不同,我们不可能为每个类都编写一个组件面板,这时通过反射,就可以快速方便的知道一个类中有哪些成员变量,他们都是什么类型。

反射的另一个作用是在对象序列化时使用,所谓序列化就是存储到磁盘上,将对象变成一定格式的二进制文件或者自定义格式的文件(如json),然后要用的时候再将保存在磁盘上的文件转化成一个内存中的对象,这个过程中总是需要有一个指示来告诉编译器要生成什么样的对象,最简单的方式当然就是类名了,例如:将一个 ClassXXX 对象存储到磁盘上,再从磁盘读取的时候让编译器根据 “ClassXXX” 名称来 new 一个对象。但问题是,C++ 语言本身不支持反射,也就是说不能通过类的名称字符串来生成一个对象,像下面这样:

1
ClassXXX object = new “ClassXXX”;

于是就需要反射机制来实现这个功能。

反射的实现方法是,提前处理需要反射的类型,生成一个类似配置文件的东西,在 Pilot 引擎中也是一个 C++ 文件,在预编译阶段生成,文件中包含一个类有哪些属性变量、父类。这些属性变量的 get 、set、getFieldName 获取变量的名字、getFieldTypeName 获取变量的类型、isArray_id 是不是数组等方法,都被封装成一个结构体,与类型的名字(字符串类型)做一个映射(map)。
在运行的时候加载这些配置表,这样我们其实通过一个类型的名字就可以知道他有什么变量,以及这些变量的具体属性和方法,然后就可以把这个类型当做一个方法集,与类型的实例挂钩就可以方便的处理这个实例的各个属性。

接下来我们以常用的 Vector3 类为例,来看看 Pilot 中的反射实现。首先是类的声明:

1
2
3
4
5
6
7
8
9
10
11
12
13
REFLECTION_TYPE(Vector3)
CLASS(Vector3, Fields)
{
REFLECTION_BODY(Vector3);

public:
float x {0.f};
float y {0.f};
float z {0.f};

public:
...
}

这里用到了三个宏定义,他们都在 reflection.h 中定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#define CLASS(class_name, ...) class class_name
#define REFLECTION_BODY(class_name) \
friend class Reflection::TypeFieldReflectionOparator::Type##class_name##Operator; \
friend class PSerializer;
// public: virtual std::string getTypeName() override {return #class_name;}

#define REFLECTION_TYPE(class_name) \
namespace Reflection \
{ \
namespace TypeFieldReflectionOparator \
{ \
class Type##class_name##Operator; \
} \
};

可以看到首先声明了与类名称有关的 Type##class_name##Operator 类,## 是连接符,将两个字符串连接起来,这里生成的类就是 TypeVector3Operator,然后将该类和序列化类 PSerializer 作为了 Vector3 类的友元。

TypeVector3Operator 类在 vector3.reflection.gen.h 文件中定义,这个文件是预编译阶段生成的,其中定义的就是对该类的各种回调函数,包括返回类名称,序列化类对象,通过序列化的 json 文件构建该类的对象,获取该类的基类,以及上面说到的对成员变量的各种方法,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
#pragma once
#include "D:/TechStack/GamesEngine/PilotEngine/Pilot-main/engine/source/runtime/core/math/vector3.h"
namespace Pilot{
class Vector3;
namespace Reflection{
namespace TypeFieldReflectionOparator{
class TypeVector3Operator{
public:
static const char* getClassName(){ return "Vector3";}
static void* constructorWithJson(const PJson& json_context){
Vector3* ret_instance= new Vector3;
PSerializer::read(json_context, *ret_instance);
return ret_instance;
}
static PJson writeByName(void* instance){
return PSerializer::write(*(Vector3*)instance);
}
// base class
static int getVector3BaseClassReflectionInstanceList(ReflectionInstance* &out_list, void* instance){
int count = 0;
return count;
}
// fields
static const char* getFieldName_x(){ return "x";}
static const char* getFieldTypeName_x(){ return "float";}
static void set_x(void* instance, void* field_value){ static_cast<Vector3*>(instance)->x = *static_cast<float*>(field_value);}
static void* get_x(void* instance){ return static_cast<void*>(&(static_cast<Vector3*>(instance)->x));}
static bool isArray_x(){ return 0;}
static const char* getFieldName_y(){ return "y";}
static const char* getFieldTypeName_y(){ return "float";}
static void set_y(void* instance, void* field_value){ static_cast<Vector3*>(instance)->y = *static_cast<float*>(field_value);}
static void* get_y(void* instance){ return static_cast<void*>(&(static_cast<Vector3*>(instance)->y));}
static bool isArray_y(){ return 0;}
static const char* getFieldName_z(){ return "z";}
static const char* getFieldTypeName_z(){ return "float";}
static void set_z(void* instance, void* field_value){ static_cast<Vector3*>(instance)->z = *static_cast<float*>(field_value);}
static void* get_z(void* instance){ return static_cast<void*>(&(static_cast<Vector3*>(instance)->z));}
static bool isArray_z(){ return 0;}
};
}//namespace TypeFieldReflectionOparator
void TypeWrapperRegister_Vector3(){
FieldFunctionTuple* f_field_function_tuple_x=new FieldFunctionTuple(
&TypeFieldReflectionOparator::TypeVector3Operator::set_x,
&TypeFieldReflectionOparator::TypeVector3Operator::get_x,
&TypeFieldReflectionOparator::TypeVector3Operator::getClassName,
&TypeFieldReflectionOparator::TypeVector3Operator::getFieldName_x,
&TypeFieldReflectionOparator::TypeVector3Operator::getFieldTypeName_x,
&TypeFieldReflectionOparator::TypeVector3Operator::isArray_x);
REGISTER_FIELD_TO_MAP("Vector3", f_field_function_tuple_x);
FieldFunctionTuple* f_field_function_tuple_y=new FieldFunctionTuple(
&TypeFieldReflectionOparator::TypeVector3Operator::set_y,
&TypeFieldReflectionOparator::TypeVector3Operator::get_y,
&TypeFieldReflectionOparator::TypeVector3Operator::getClassName,
&TypeFieldReflectionOparator::TypeVector3Operator::getFieldName_y,
&TypeFieldReflectionOparator::TypeVector3Operator::getFieldTypeName_y,
&TypeFieldReflectionOparator::TypeVector3Operator::isArray_y);
REGISTER_FIELD_TO_MAP("Vector3", f_field_function_tuple_y);
FieldFunctionTuple* f_field_function_tuple_z=new FieldFunctionTuple(
&TypeFieldReflectionOparator::TypeVector3Operator::set_z,
&TypeFieldReflectionOparator::TypeVector3Operator::get_z,
&TypeFieldReflectionOparator::TypeVector3Operator::getClassName,
&TypeFieldReflectionOparator::TypeVector3Operator::getFieldName_z,
&TypeFieldReflectionOparator::TypeVector3Operator::getFieldTypeName_z,
&TypeFieldReflectionOparator::TypeVector3Operator::isArray_z);
REGISTER_FIELD_TO_MAP("Vector3", f_field_function_tuple_z);
ClassFunctionTuple* f_class_function_tuple_Vector3=new ClassFunctionTuple(
&TypeFieldReflectionOparator::TypeVector3Operator::getVector3BaseClassReflectionInstanceList,
&TypeFieldReflectionOparator::TypeVector3Operator::constructorWithJson,
&TypeFieldReflectionOparator::TypeVector3Operator::writeByName);
REGISTER_BASE_CLASS_TO_MAP("Vector3", f_class_function_tuple_Vector3);
}
namespace TypeWrappersRegister{
void Vector3(){ TypeWrapperRegister_Vector3();}
}//namespace TypeWrappersRegister
}//namespace Reflection
}//namespace Pilot

上面的代码中还有注册这些回调函数的函数 TypeWrapperRegister_Vector3,函数中就是对所有成员的方法进行了注册,所谓注册就是将这些函数和类型名称对应起来,放到哈希表中,实现方法是用变量的各种方法初始化一个 FieldFunctionTuple 元组,这个元组的声明同样在 reflection.h 中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
typedef std::function<void(void*, void*)>      SetFuncion;
typedef std::function<void*(void*)> GetFuncion;
typedef std::function<const char*()> GetNameFuncion;
typedef std::function<void(int, void*, void*)> SetArrayFunc;
typedef std::function<void*(int, void*)> GetArrayFunc;
typedef std::function<int(void*)> GetSizeFunc;
typedef std::function<bool()> GetBoolFunc;

typedef std::function<void*(const PJson&)> ConstructorWithPJson;
typedef std::function<PJson(void*)> WritePJsonByName;
typedef std::function<int(Reflection::ReflectionInstance*&, void*)> GetBaseClassReflectionInstanceListFunc;

typedef std::tuple<SetFuncion, GetFuncion, GetNameFuncion, GetNameFuncion, GetNameFuncion, GetBoolFunc>
FieldFunctionTuple;
typedef std::tuple<GetBaseClassReflectionInstanceListFunc, ConstructorWithPJson, WritePJsonByName>
ClassFunctionTuple;
typedef std::tuple<SetArrayFunc, GetArrayFunc, GetSizeFunc, GetNameFuncion, GetNameFuncion> ArrayFunctionTuple;

元组中存放的就是各类函数的指针,然后通过一个宏定义 REGISTER_FIELD_TO_MAP 注册这些函数,该宏定义如下:

1
2
3
4
#define REGISTER_FIELD_TO_MAP(name, value) TypeMetaRegisterinterface::registerToFieldMap(name, value);
#define REGISTER_BASE_CLASS_TO_MAP(name, value) TypeMetaRegisterinterface::registerToClassMap(name, value);
#define REGISTER_ARRAY_TO_MAP(name, value) TypeMetaRegisterinterface::registerToArrayMap(name, value);
#define UNREGISTER_ALL TypeMetaRegisterinterface::unregisterAll();

实际上宏定义就是调用了 TypeMetaRegisterinterface 类中的方法,该类是用来注册各种回调函数的接口,定义如下:

1
2
3
4
5
6
7
8
9
class TypeMetaRegisterinterface
{
public:
static void registerToClassMap(const char* name, ClassFunctionTuple* value);
static void registerToFieldMap(const char* name, FieldFunctionTuple* value);
static void registerToArrayMap(const char* name, ArrayFunctionTuple* value);

static void unregisterAll();
};

类中的各种方法就是用来将给定的函数指针元组和类名称关联起来,存放到全局定义的哈希表中,以类名为键,这样之后使用时通过类名就可以找到类中各种成员变量的名称、类型、方法了,这部分在 reflection.cpp 中实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
namespace Reflection
{
const char* k_unknown_type = "UnknownType";
const char* k_unknown = "Unknown";

static std::map<std::string, ClassFunctionTuple*> m_class_map;
static std::multimap<std::string, FieldFunctionTuple*> m_field_map;
static std::map<std::string, ArrayFunctionTuple*> m_array_map;

void TypeMetaRegisterinterface::registerToFieldMap(const char* name, FieldFunctionTuple* value)
{
m_field_map.insert(std::make_pair(name, value));
}

void TypeMetaRegisterinterface::registerToArrayMap(const char* name, ArrayFunctionTuple* value)
{
if (m_array_map.find(name) == m_array_map.end())
{
m_array_map.insert(std::make_pair(name, value));
}
else
{
delete value;
}
}

void TypeMetaRegisterinterface::registerToClassMap(const char* name, ClassFunctionTuple* value)
{
if (m_class_map.find(name) == m_class_map.end())
{
m_class_map.insert(std::make_pair(name, value));
}
else
{
delete value;
}
}

void TypeMetaRegisterinterface::unregisterAll()
{
for (const auto& itr : m_field_map)
{
delete itr.second;
}
m_field_map.clear();
for (const auto& itr : m_class_map)
{
delete itr.second;
}
m_class_map.clear();
for (const auto& itr : m_array_map)
{
delete itr.second;
}
m_array_map.clear();
}

...

}

所有需要反射的类都会在运行时引擎启动时进行注册,也就是之前主函数中的 engine->startEngine() 函数,该函数定义如下:

1
2
3
4
5
6
7
8
9
10
void PilotEngine::startEngine(const EngineInitParams& param)
{
m_init_params = param;

Reflection::TypeMetaRegister::Register();

g_runtime_global_context.startSystems(param);

LOG_INFO("engine start");
}

其中 TypeMetaRegister 类就是用来注册函数的类:

1
2
3
4
5
6
7
8
9
10
11
12
13
#pragma once
namespace Pilot
{
namespace Reflection
{
class TypeMetaRegister
{
public:
static void Register();
static void Unregister();
};
} // namespace Reflection
} // namespace Pilot

Register() 函数就是调用了各个类的注册函数:

1
2
3
4
5
6
void TypeMetaRegister::Register(){
TypeWrappersRegister::Transform();
TypeWrappersRegister::Quaternion();
TypeWrappersRegister::Vector3();
...
}

关于对象序列化和反射的使用示例,Pilot 也提供了一个样例代码,在 meta/meta_example.cpp 中。

---- 本文结束 知识又增加了亿点点!----

文章版权声明 1、博客名称:LycTechStack
2、博客网址:https://lz328.github.io/LycTechStack.github.io/
3、本博客的文章部分内容可能来源于网络,仅供大家学习与参考,如有侵权,请联系博主进行删除处理。
4、本博客所有文章版权归博主所有,如需转载请标明出处。