之前我们对源码的项目结构有了简单的了解,这篇文章我们来看看Piccolo里是怎么实现反射的
我们先看一下生成的反射文件和源码,这里拿engine/source/runtime/function/framework.component/animation/animation_component.h举例。
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 #pragma once #include "runtime/function/animation/skeleton.h" #include "runtime/function/framework/component/component.h" #include "runtime/resource/res_type/components/animation.h" namespace Piccolo { REFLECTION_TYPE (AnimationComponent) CLASS (AnimationComponent : public Component, WhiteListFields) { REFLECTION_BODY (AnimationComponent) public : AnimationComponent () = default ; void postLoadResource (std::weak_ptr<GObject> parent_object) override ; void tick (float delta_time) override ; const AnimationResult& getResult () const ; protected : META (Enable) AnimationComponentRes m_animation_res; Skeleton m_skeleton; }; }
为了实现反射,类的声明用到了宏定义,其中WhiteListFields表示白名单,只有被META(Enable)
的字段会被注册到反射中。
REFLECTION_TYPE的宏定义在engine/source/runtime/core/meta/reflection/reflection.h中
1 2 3 4 5 6 7 8 #define REFLECTION_TYPE(class_name) \ namespace Reflection \ { \ namespace TypeFieldReflectionOparator \ { \ class Type##class_name##Operator; \ } \ };
就是简单声明了一个命名空间中的类名,注意这里类名和实际的class_name有了变化,##表示符号的拼接,也就是声明了一个TypeAnimationComponentOperator
类。
REFLECTION_BODY则是声明了一系列友元类
1 2 3 #define REFLECTION_BODY(class_name) \ friend class Reflection::TypeFieldReflectionOparator::Type##class_name##Operator; \ friend class PSerializer;
这里就有REFLECTION_TYPE里声明的类。
接下来CLASS和META的定义有个小的trick,容易走歪。
1 2 3 4 5 6 7 8 9 10 11 #if defined(__REFLECTION_PARSER__) #define META(...) __attribute__((annotate(#__VA_ARGS__))) #define CLASS(class_name, ...) class __attribute__((annotate(#__VA_ARGS__))) class_name #define STRUCT(struct_name, ...) struct __attribute__((annotate(#__VA_ARGS__))) struct_name #else #define META(...) #define CLASS(class_name, ...) class class_name #define STRUCT(struct_name, ...) struct struct_name #endif
这里我们的IDE高亮的是#else这部分,但实际上我们编译的时候走的是上面的#if分支。上一篇我们提到,reflection的生成是把所有runtime和editor的include喂给llvm
clang,生成AST,而在喂给clang时,我们的MetaParser通过成员变量arguments传递了一部分参数
1 2 3 4 5 6 7 8 9 10 11 std::vector<const char *> arguments = {{"-x" , "c++" , "-std=c++11" , "-D__REFLECTION_PARSER__" , "-DNDEBUG" , "-D__clang__" , "-w" , "-MG" , "-M" , "-ferror-limit=0" , "-o clangLog.txt" }};
可以看到,-D__REFLECTION_PARSER__
就定义了这个宏。那么#else这个分支是用来干嘛的呢,以我的理解来看,这是为了使得我们的IDE不报错而采用的一种trick,因为annotate是一个clang-specific的语法,通过添加一个宏的前提,使得IDE在解析时直接走了#else分支,避开了非所有编译器支持的特性。这也是为什么在#else分支里CLASS和META的宏展开非常简单
1 2 3 4 #define CLASS(class_name, ...) class class_name CLASS (AnimationComponent : public Component, WhiteListFields)class AnimationComponent : public Component
1 2 3 4 #define META(...) META (Enable)
现在让我们回到正经生成AST时会走的#if分支。
1 2 3 #define META(...) __attribute__((annotate(#__VA_ARGS__))) #define CLASS(class_name, ...) class __attribute__((annotate(#__VA_ARGS__))) class_name #define STRUCT(struct_name, ...) struct __attribute__((annotate(#__VA_ARGS__))) struct_name
__attribute__((annotate()))
会添加一个annotation,而annotations是可以在clang生成AST后进行访问的,于是我们的generator就可以根据这个annotation判断需要做什么,#表示转化成字符串,展开后如下
1 2 3 4 class __attribute__ ((annotate ("WhiteListFields" ))) AnimationComponent : public Component __attribute__((annotate ("Enable" )))
接下来我们具体看看这个annotation是怎么被使用的,我们首先找到程序解析clang生成的AST的地方
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 void MetaParser::buildClassAST (const Cursor& cursor, Namespace& current_namespace) { for (auto & child : cursor.getChildren ()) { auto kind = child.getKind (); if (child.isDefinition () && (kind == CXCursor_ClassDecl || kind == CXCursor_StructDecl)) { auto class_ptr = std::make_shared <Class>(child, current_namespace); TRY_ADD_LANGUAGE_TYPE (class_ptr, classes); } else { RECURSE_NAMESPACES (kind, child, buildClassAST, current_namespace); } } }
这里生成了一个Class类的对象。Class类的构造函数如下
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 Class::Class (const Cursor& cursor, const Namespace& current_namespace) : TypeInfo (cursor, current_namespace), m_name (cursor.getDisplayName ()), m_qualified_name (Utils::getTypeNameWithoutNamespace (cursor.getType ())), m_display_name (Utils::getNameWithoutFirstM (m_qualified_name)) { Utils::replaceAll (m_name, " " , "" ); Utils::replaceAll (m_name, "Piccolo::" , "" ); for (auto & child : cursor.getChildren ()) { switch (child.getKind ()) { case CXCursor_CXXBaseSpecifier: { auto base_class = new BaseClass (child); m_base_classes.emplace_back (base_class); } break ; case CXCursor_FieldDecl: m_fields.emplace_back (new Field (child, current_namespace, this )); break ; default : break ; } } }
没直接看到annotation相关的内容,很可能是在成员变量或父类的初始化中,很自然地,我们就能锁定TypeInfo。看一下代码,里面确实是有我们关注的Enable这个annotation。
1 2 3 4 5 TypeInfo::TypeInfo (const Cursor& cursor, const Namespace& current_namespace) : m_meta_data (cursor), m_enabled (m_meta_data.getFlag (NativeProperty::Enable)), m_root_cursor (cursor), m_namespace (current_namespace) {}
那么现在的关键就是m_meta_data,也就是MetaInfo类。它在初始化时,读取了所有含annotate的child,将它们的属性保存起来。
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 MetaInfo::MetaInfo (const Cursor& cursor) { for (auto & child : cursor.getChildren ()) { if (child.getKind () != CXCursor_AnnotateAttr) continue ; for (auto & prop : extractProperties (child)) m_properties[prop.first] = prop.second; } }std::vector<MetaInfo::Property> MetaInfo::extractProperties (const Cursor& cursor) const { std::vector<Property> ret_list; auto propertyList = cursor.getDisplayName (); auto && properties = Utils::split (propertyList, "," ); static const std::string white_space_string = " \t\r\n" ; for (auto & property_item : properties) { auto && item_details = Utils::split (property_item, ":" ); auto && temp_string = Utils::trim (item_details[0 ], white_space_string); if (temp_string.empty ()) { continue ; } ret_list.emplace_back (temp_string, item_details.size () > 1 ? Utils::trim (item_details[1 ], white_space_string) : "" ); } return ret_list; }
捋一下思路,现在我们会把所有annotation放入m_properties字典中(注意是annotation而不是annotation对应的field,prop.first
是Enable之类的内容),但是这并没有将field和annotation关联起来。
那generator是怎么根据Meta(Enable)
来生成的呢?
先看看reflection_generator
的generate函数
1 2 3 4 std::string render_string = TemplateManager::getInstance ()->renderByTemplate ("commonReflectionFile" , mustache_data); Utils::saveFile (render_string, file_path);
可以看到它通过模板生成了一个反射文件。搜一下commonReflectionFile
,可以找到engine\template\commonReflectionFile.mustache
,这个文件就是最终生成的反射文件的模板,它里面使用到了{{#class_field_defines}}
,我们可以在代码里找一下哪里设置了class_field_defines
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 void GeneratorInterface::genClassFieldRenderData (std::shared_ptr<Class> class_temp, Mustache::data& feild_defs) { static const std::string vector_prefix = "std::vector<" ; for (auto & field : class_temp->m_fields) { if (!field->shouldCompile ()) continue ; Mustache::data filed_define; filed_define.set ("class_field_name" , field->m_name); filed_define.set ("class_field_type" , field->m_type); filed_define.set ("class_field_display_name" , field->m_display_name); bool is_vector = field->m_type.find (vector_prefix) == 0 ; filed_define.set ("class_field_is_vector" , is_vector); feild_defs.push_back (filed_define); } }
其实很简单,就是直接读的Class的变量m_fields
。m_fields
是在Class初始化的时候赋值的。
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 Class::Class (const Cursor& cursor, const Namespace& current_namespace) : TypeInfo (cursor, current_namespace), m_name (cursor.getDisplayName ()), m_qualified_name (Utils::getTypeNameWithoutNamespace (cursor.getType ())), m_display_name (Utils::getNameWithoutFirstM (m_qualified_name)) { Utils::replaceAll (m_name, " " , "" ); Utils::replaceAll (m_name, "Piccolo::" , "" ); for (auto & child : cursor.getChildren ()) { switch (child.getKind ()) { case CXCursor_CXXBaseSpecifier: { auto base_class = new BaseClass (child); m_base_classes.emplace_back (base_class); } break ; case CXCursor_FieldDecl: m_fields.emplace_back (new Field (child, current_namespace, this )); break ; default : break ; } } }
再看一眼Field类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 Field::Field (const Cursor& cursor, const Namespace& current_namespace, Class* parent) : TypeInfo (cursor, current_namespace), m_is_const (cursor.getType ().IsConst ()), m_parent (parent), m_name (cursor.getSpelling ()), m_display_name (Utils::getNameWithoutFirstM (m_name)), m_type (Utils::getTypeNameWithoutNamespace (cursor.getType ())) { Utils::replaceAll (m_type, " " , "" ); Utils::replaceAll (m_type, "Piccolo::" , "" ); auto ret_string = Utils::getStringWithoutQuot (m_meta_data.getProperty ("default" )); m_default = ret_string; }bool Field::shouldCompile (void ) const { return isAccessible (); }bool Field::isAccessible (void ) const { return ((m_parent->m_meta_data.getFlag (NativeProperty::Fields) || m_parent->m_meta_data.getFlag (NativeProperty::All)) && !m_meta_data.getFlag (NativeProperty::Disable)) || (m_parent->m_meta_data.getFlag (NativeProperty::WhiteListFields) && m_meta_data.getFlag (NativeProperty::Enable)); }
这里就可以看出端倪了。Field
和Class
一样都继承自TypeInfo
,TypeInfo
又继承自MetaInfo
,所以Field
里其实包含了m_properties
,对于一个Field
,它只需要查看它的m_properties
就可以知道自身是否有对应的annotation,于是就可以知道它相应的访问权限了。
现在我们已经知道了如何使用annotation来控制field的访问权限,我们最后还需要解决的问题就是这个模板文件的结构。
class Type{{class_name}}Operator
和class Array{{vector_useful_name}}Operator
是为我们要实现的类的反射提供访问接口的工具类,它的方法都是静态的。但是很明显,现在每一个反射类都对应一个或两个(非vector的话则不会有class Array{{vector_useful_name}}Operator
)工具类,但我们最终想要实现的其实是一个统一的接口,即给定一个类名,就能得到它的一系列函数。所以还需要进一步处理。
TypeWrapperRegister_{{class_name}}
这个函数就是将这一系列反射函数组合起来,然后加入到反射的字典当中。
这里以Animation_Clip里的AnimNodeMap为例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 void TypeWrapperRegister_AnimNodeMap () { FieldFunctionTuple* f_field_function_tuple_convert=new FieldFunctionTuple ( &TypeFieldReflectionOparator::TypeAnimNodeMapOperator::set_convert, &TypeFieldReflectionOparator::TypeAnimNodeMapOperator::get_convert, &TypeFieldReflectionOparator::TypeAnimNodeMapOperator::getClassName, &TypeFieldReflectionOparator::TypeAnimNodeMapOperator::getFieldName_convert, &TypeFieldReflectionOparator::TypeAnimNodeMapOperator::getFieldTypeName_convert, &TypeFieldReflectionOparator::TypeAnimNodeMapOperator::isArray_convert); REGISTER_FIELD_TO_MAP ("AnimNodeMap" , f_field_function_tuple_convert); ArrayFunctionTuple* f_array_tuple_stdSSvectorLstdSSstringR = new ArrayFunctionTuple ( &ArrayReflectionOperator::ArraystdSSvectorLstdSSstringROperator::set, &ArrayReflectionOperator::ArraystdSSvectorLstdSSstringROperator::get, &ArrayReflectionOperator::ArraystdSSvectorLstdSSstringROperator::getSize, &ArrayReflectionOperator::ArraystdSSvectorLstdSSstringROperator::getArrayTypeName, &ArrayReflectionOperator::ArraystdSSvectorLstdSSstringROperator::getElementTypeName); REGISTER_ARRAY_TO_MAP ("std::vector<std::string>" , f_array_tuple_stdSSvectorLstdSSstringR); ClassFunctionTuple* f_class_function_tuple_AnimNodeMap=new ClassFunctionTuple ( &TypeFieldReflectionOparator::TypeAnimNodeMapOperator::getAnimNodeMapBaseClassReflectionInstanceList, &TypeFieldReflectionOparator::TypeAnimNodeMapOperator::constructorWithJson, &TypeFieldReflectionOparator::TypeAnimNodeMapOperator::writeByName); REGISTER_BASE_CLASS_TO_MAP ("AnimNodeMap" , f_class_function_tuple_AnimNodeMap); }
上面涉及到的一些类型及宏的定义如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 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;
1 2 #define REGISTER_FIELD_TO_MAP(name, value) TypeMetaRegisterinterface::registerToFieldMap(name, value);
通过实例化一个包含一系列函数指针的tuple,再将tuple放入字典中,就完成了我们上述的目的。而这个字典呢,再进一步跟踪就可以发现是reflection.cpp中的静态全局变量。
1 2 3 4 5 6 7 8 9 10 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 () ; };
1 2 3 4 5 6 7 8 9 10 namespace Piccolo { namespace Reflection { 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; } }
至此,Piccolo的反射机制就算清楚了。接下来是最后的收尾工作,TypeWrapperRegister_AnimNodeMap
是什么时候被调用的呢?在文件最后,类的注册函数又被重命名为了类名。
1 2 3 4 5 6 7 namespace TypeWrappersRegister{ void AnimNodeMap () { TypeWrapperRegister_AnimNodeMap ();} void AnimationChannel () { TypeWrapperRegister_AnimationChannel ();} void AnimationClip () { TypeWrapperRegister_AnimationClip ();} void AnimationAsset () { TypeWrapperRegister_AnimationAsset ();} }
在all_reflection里将所有反射类的注册函数组合成了一个函数Register。
1 2 3 4 5 6 7 8 9 10 namespace Piccolo{namespace Reflection{ void TypeMetaRegister::Register () { ... TypeWrappersRegister::AnimationComponent (); ... } } }
这个Register函数会在引擎启动的时候被调用,完成反射的初始化。
1 2 3 4 5 6 7 8 9 void PiccoloEngine::startEngine (const std::string& config_file_path) { Reflection::TypeMetaRegister::Register (); g_runtime_global_context.startSystems (config_file_path); LOG_INFO ("engine start" ); }
反射系统完结撒花~
序列化系统和反射系统的思路基本一致,就是模板不太一样。因为序列化只需要把所有的类的读写统一放在一个静态类PSerializer中,特化读写模板函数即可,就不需要和Reflection一样在启动时初始化,所以Serializer反而会简单许多。
8月11写的前一半文章,然后就忙保研考研去了,没想到再一次写就已经是9.24了。因为是边看代码边写的文章,所以可能有些地方读起来有些割裂,希望不会影响到阅读和理解