本节主要了解 Piccolo 游戏引擎的代码框架、对象序列化的实现以及反射机制的实现。
1 引擎运行结果
2 代码整体框架 Pilot 引擎除了第三方库之外,主要包含四部分,分别是编辑器、预编译、引擎运行时核心和 Shader 编译,如下图:
其中预编译部分负责预先生成一些文件,比如用来实现反射的文件,会在后文中提到,Shader 编译部分在之后的渲染系统代码中也会说到。编辑器负责整个引擎的界面、工具等的实现,需要引擎核心部分作为成员,是引擎核心功能对使用者的接口;引擎核心部分按照【游戏引擎】(一)游戏引擎架构 中的分层结构实现,分别是核心层、功能层、平台层、资源层:
而 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" #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); 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; #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); } static int getVector3BaseClassReflectionInstanceList (ReflectionInstance* &out_list, void * instance) { int count = 0 ; return count; } 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 ;} }; } 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 ();} } } }
上面的代码中还有注册这些回调函数的函数 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 () ; }; } }
Register()
函数就是调用了各个类的注册函数:
1 2 3 4 5 6 void TypeMetaRegister::Register () { TypeWrappersRegister::Transform (); TypeWrappersRegister::Quaternion (); TypeWrappersRegister::Vector3 (); ... }
关于对象序列化和反射的使用示例,Pilot 也提供了一个样例代码,在 meta/meta_example.cpp
中。