Piccolo源码解读(二)——反射实现

之前我们对源码的项目结构有了简单的了解,这篇文章我们来看看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;
};
} // namespace Piccolo

为了实现反射,类的声明用到了宏定义,其中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
//#define CLASS(class_name,...) class __attribute__((annotate(#__VA_ARGS__))) class_name:public Reflection::object
#else
#define META(...)
#define CLASS(class_name, ...) class class_name
#define STRUCT(struct_name, ...) struct struct_name
//#define CLASS(class_name,...) class class_name:public Reflection::object
#endif // __REFLECTION_PARSER__

这里我们的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(AnimationComponent : public Component, WhiteListFields)
class __attribute__((annotate("WhiteListFields"))) AnimationComponent : public Component
// META(Enable)
__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
// engine\source\meta_parser\parser\parser\parser.cpp
void MetaParser::buildClassAST(const Cursor& cursor, Namespace& current_namespace)
{
for (auto& child : cursor.getChildren())
{
auto kind = child.getKind();

// actual definition and a class or struct
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;
// field
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
// engine\source\meta_parser\parser\language_types\type_info.cpp
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
// engine\source\meta_parser\parser\meta\meta_info.cpp
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
// engine\source\meta_parser\parser\generator\reflection_generator.cpp
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
// engine\source\meta_parser\parser\generator\generator.cpp
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_fieldsm_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
// engine\source\meta_parser\parser\language_types\class.cpp
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;
// field
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
// engine\source\meta_parser\parser\language_types\field.cpp
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));
}

这里就可以看出端倪了。FieldClass一样都继承自TypeInfoTypeInfo又继承自MetaInfo,所以Field里其实包含了m_properties,对于一个Field,它只需要查看它的m_properties就可以知道自身是否有对应的annotation,于是就可以知道它相应的访问权限了。


现在我们已经知道了如何使用annotation来控制field的访问权限,我们最后还需要解决的问题就是这个模板文件的结构。

class Type{{class_name}}Operatorclass 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
// engine\source\runtime\core\meta\reflection\reflection.h
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
// engine\source\runtime\core\meta\reflection\reflection.h
#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
// engine\source\runtime\core\meta\reflection\reflection.h
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
// engine\source\runtime\core\meta\reflection\reflection.cpp
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
// engine\source\_generated\reflection\animation_clip.reflection.gen.h
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
// engine\source\_generated\reflection\all_reflection.h
namespace Piccolo{
namespace Reflection{
void TypeMetaRegister::Register(){
...
TypeWrappersRegister::AnimationComponent();
...
}
}
}

这个Register函数会在引擎启动的时候被调用,完成反射的初始化。

1
2
3
4
5
6
7
8
9
// engine\source\runtime\engine.cpp
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了。因为是边看代码边写的文章,所以可能有些地方读起来有些割裂,希望不会影响到阅读和理解


Piccolo源码解读(二)——反射实现
https://jhex-git.github.io/posts/2930143108/
作者
JointHex
发布于
2023年9月24日
许可协议