本篇文章的内容主要是对Piccolo源码反射系统解读这一视频的内容进行课后记录,可以观看原版视频讲解,此处仅用作个人笔记。
关于如何调用的反射生成,使用的是CMake来添加的自定义COMMAND,具体的内容在engine/source/precompile/precompile.cmake
这个文件中。这个COMMAND通过调用PiccoloParser来生成代码。而PiccoloParser的具体代码在engine/source/meta_parser
文件夹中。
运行时的后台输出如下所示:
*************************************************************
**** [Precompile] BEGIN
*************************************************************
Parsing meta data for target "Piccolo"
Parsing in D:/Program/CppCode/Piccolo/engine/source
Parsing project file: D:/Program/CppCode/Piccolo/engine/bin/precompile.json
Generating the Source Include file: D:/Program/CppCode/Piccolo/build/parser_header.h
Parsing the whole project...
Start generate runtime schemas(40)...
Completed in 1672ms
+++ Precompile finished +++
1. 代码生成流程分析
首先我们来看看parser的主函数:
int main(int argc, char* argv[])
{
auto start_time = std::chrono::system_clock::now();
int result = 0;
if (argv[1] != nullptr && argv[2] != nullptr && argv[3] != nullptr && argv[4] != nullptr && argv[5] != nullptr &&
argv[6] != nullptr)
{
MetaParser::prepare();
result = parse(argv[1], argv[2], argv[3], argv[4], argv[5], argv[6]);
auto duration_time = std::chrono::system_clock::now() - start_time;
std::cout << "Completed in " << std::chrono::duration_cast<std::chrono::milliseconds>(duration_time).count()
<< "ms" << std::endl;
return result;
}
else
{
std::cerr << "Arguments parse error!" << std::endl
<< "Please call the tool like this:" << std::endl
<< "meta_parser project_file_name include_file_name_to_generate project_base_directory "
"sys_include_directory module_name showErrors(0 or 1)"
<< std::endl
<< std::endl;
return -1;
}
return 0;
}
主函数很短,且主要内容是通过调用parse
函数来实现的:
int parse(std::string project_input_file_name,
std::string source_include_file_name,
std::string include_path,
std::string sys_include,
std::string module_name,
std::string show_errors)
{
std::cout << std::endl;
std::cout << "Parsing meta data for target \"" << module_name << "\"" << std::endl;
std::fstream input_file;
bool is_show_errors = "0" != show_errors;
MetaParser parser(
project_input_file_name, source_include_file_name, include_path, sys_include, module_name, is_show_errors);
std::cout << "Parsing in " << include_path << std::endl;
int result = parser.parse();
if (0 != result)
{
return result;
}
parser.generateFiles();
return 0;
}
在parse
函数中,主要的工作都由MetaParser来完成了,因此我们来看看MetaParser:
MetaParser::MetaParser(const std::string project_input_file,
const std::string include_file_path,
const std::string include_path,
const std::string sys_include,
const std::string module_name,
bool is_show_errors) :
m_project_input_file(project_input_file),
m_source_include_file_name(include_file_path), m_index(nullptr), m_translation_unit(nullptr),
m_sys_include(sys_include), m_module_name(module_name), m_is_show_errors(is_show_errors)
{
m_work_paths = Utils::split(include_path, ";");
m_generators.emplace_back(new Generator::SerializerGenerator(
m_work_paths[0], std::bind(&MetaParser::getIncludeFile, this, std::placeholders::_1)));
m_generators.emplace_back(new Generator::ReflectionGenerator(
m_work_paths[0], std::bind(&MetaParser::getIncludeFile, this, std::placeholders::_1)));
}
它的构造函数中主要注册了两个generator,分别是反射generator与序列化generator,用于分别生成对应的反射与序列化的代码。
第二个重要的函数是parse函数,用于解析输入的源文件,并生成AST树,来提取关键信息:
int MetaParser::parse(void)
{
bool parse_include_ = parseProject();
if (!parse_include_)
{
std::cerr << "Parsing project file error! " << std::endl;
return -1;
}
std::cerr << "Parsing the whole project..." << std::endl;
int is_show_errors = m_is_show_errors ? 1 : 0;
m_index = clang_createIndex(true, is_show_errors);
std::string pre_include = "-I";
std::string sys_include_temp;
if (!(m_sys_include == "*"))
{
sys_include_temp = pre_include + m_sys_include;
arguments.emplace_back(sys_include_temp.c_str());
}
auto paths = m_work_paths;
for (int index = 0; index < paths.size(); ++index)
{
paths[index] = pre_include + paths[index];
arguments.emplace_back(paths[index].c_str());
}
fs::path input_path(m_source_include_file_name);
if (!fs::exists(input_path))
{
std::cerr << input_path << " is not exist" << std::endl;
return -2;
}
m_translation_unit = clang_createTranslationUnitFromSourceFile(
m_index, m_source_include_file_name.c_str(), static_cast<int>(arguments.size()), arguments.data(), 0, nullptr);
auto cursor = clang_getTranslationUnitCursor(m_translation_unit);
Namespace temp_namespace;
buildClassAST(cursor, temp_namespace);
temp_namespace.clear();
return 0;
}
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);
}
}
}
最后就是代码生成部分,这个部分使用了mustache模板来生成代码,具体的mustache介绍可以看这里。
void MetaParser::generateFiles(void)
{
std::cerr << "Start generate runtime schemas(" << m_schema_modules.size() << ")..." << std::endl;
for (auto& schema : m_schema_modules)
{
for (auto& generator_iter : m_generators)
{
generator_iter->generate(schema.first, schema.second);
}
}
finish();
}
2. 添加函数反射支持
为了添加对函数的反射功能,首先我们需要修改mustache的模板文件,来添加反射函数的代码。需要修改commonReflectionFile.mustache
文件,添加内容如下:
#pragma once
{{#include_headfiles}}
#include "{{headfile_name}}"
{{/include_headfiles}}
namespace Piccolo{
{{#class_defines}}class {{class_name}};
{{/class_defines}}
namespace Reflection{
{{#class_defines}}namespace TypeFieldReflectionOparator{
class Type{{class_name}}Operator{
public:
...
// base class
...
// fields
...
// methods
{{#class_method_defines}}static const char* getMethodName_{{class_method_name}}(){ return "{{class_method_name}}";}
static void invoke_{{class_method_name}}(void* instance){ static_cast<{{class_name}}*>(instance)->{{class_method_name}}(); }
{{/class_method_defines}}
};
}//namespace TypeFieldReflectionOparator
...
void TypeWrapperRegister_{{class_name}}(){
...
{{#class_method_defines}}MethodFunctionTuple* method_function_tuple_{{class_method_name}}=new MethodFunctionTuple(
&TypeFieldReflectionOparator::Type{{class_name}}Operator::getMethodName_{{class_method_name}},
&TypeFieldReflectionOparator::Type{{class_name}}Operator::invoke_{{class_method_name}});
REGISTER_METHOD_TO_MAP("{{class_name}}", method_function_tuple_{{class_method_name}});
{{/class_method_defines}}
...
}{{/class_defines}}
...
}//namespace Reflection
}//namespace Piccolo
并在generator.cpp文件中添加对应的代码:
void GeneratorInterface::genClassRenderData(std::shared_ptr<Class> class_temp, Mustache::data& class_def)
{
...
Mustache::data class_method_defines = Mustache::data::type::list;
genClassMethodRenderData(class_temp, class_method_defines);
class_def.set("class_method_defines", class_method_defines);
}
void GeneratorInterface::genClassMethodRenderData(std::shared_ptr<Class> class_temp, Mustache::data& method_defs)
{
for (auto& method : class_temp->m_methods)
{
if (!method->shouldCompile())
continue;
Mustache::data method_define;
method_define.set("class_method_name", method->m_name);
method_defs.push_back(method_define);
}
}
还有在Reflection中添加对应方法:
static std::multimap<std::string, MethodFunctionTuple*> m_method_map;
void TypeMetaRegisterinterface::registerToMethodMap(const char* name, MethodFunctionTuple* value)
{
m_method_map.insert(std::make_pair(name, value));
}
TypeMeta::TypeMeta(std::string type_name) : m_type_name(type_name)
{
...
auto methods_iter = m_method_map.equal_range(type_name);
while (methods_iter.first != methods_iter.second)
{
MethodAccessor f_method(methods_iter.first->second);
m_methods.emplace_back(f_method);
m_is_valid = true;
++methods_iter.first;
}
}
TypeMeta::TypeMeta() : m_type_name(k_unknown_type), m_is_valid(false)
{
m_fields.clear();
m_methods.clear();
}
int TypeMeta::getMethodsList(MethodAccessor*& out_list)
{
int count = m_methods.size();
out_list = new MethodAccessor[count];
for (int i = 0; i < count; ++i)
{
out_list[i] = m_methods[i];
}
return count;
}
MethodAccessor TypeMeta::getMethodByName(const char* name)
{
const auto it = std::find_if(m_methods.begin(), m_methods.end(), [&](const auto& i) {
return std::strcmp(i.getMethodName(), name) == 0;
});
if (it != m_methods.end())
return *it;
return MethodAccessor(nullptr);
}
TypeMeta& TypeMeta::operator=(const TypeMeta& dest)
{
...
m_methods.clear();
m_methods = dest.m_methods;
...
}
为了方便用,添加了MethodAccessor
class MethodAccessor
{
friend class TypeMeta;
public:
MethodAccessor();
void invoke(void* instance);
TypeMeta getOwnerTypeMeta();
const char* getMethodName() const;
MethodAccessor& operator=(const MethodAccessor& dest);
private:
MethodAccessor(MethodFunctionTuple* functions);
private:
MethodFunctionTuple* m_functions;
const char* m_method_name;
};
MethodAccessor::MethodAccessor()
{
m_method_name = k_unknown;
m_functions = nullptr;
}
MethodAccessor::MethodAccessor(MethodFunctionTuple* functions) : m_functions(functions)
{
m_method_name = k_unknown;
if (m_functions == nullptr)
{
return;
}
m_method_name = (std::get<0>(*m_functions))();
}
TypeMeta MethodAccessor::getOwnerTypeMeta()
{
// todo: should check validation
TypeMeta f_type((std::get<0>(*m_functions))());
return f_type;
}
const char* MethodAccessor::getMethodName() const { return m_method_name; }
void MethodAccessor::invoke(void* instance) { (std::get<1>(*m_functions))(instance); }
MethodAccessor& MethodAccessor::operator=(const MethodAccessor& dest)
{
if (this == &dest)
{
return *this;
}
m_functions = dest.m_functions;
m_method_name = dest.m_method_name;
return *this;
}
为了使编译顺利通过添加的宏定义:
#define REGISTER_METHOD_TO_MAP(name, value) TypeMetaRegisterinterface::registerToMethodMap(name, value);
添加的编译器识别字段:
namespace NativeProperty
{
const auto All = "All";
const auto Fields = "Fields";
const auto Methods = "Methods";
const auto Enable = "Enable";
const auto Disable = "Disable";
const auto WhiteListFields = "WhiteListFields";
const auto WhiteListMethods = "WhiteListMethods";
}
以及补充的method类:
#pragma once
#include "type_info.h"
class Class;
class Method : public TypeInfo
{
public:
Method(const Cursor& cursor, const Namespace& current_namespace, Class* parent = nullptr);
virtual ~Method(void) {}
bool shouldCompile(void) const;
public:
Class* m_parent;
std::string m_name;
bool isAccessible(void) const;
};
#include "common/precompiled.h"
#include "class.h"
#include "method.h"
Method::Method(const Cursor& cursor, const Namespace& current_namespace, Class* parent) :
TypeInfo(cursor, current_namespace), m_parent(parent), m_name(cursor.getSpelling())
{}
bool Method::shouldCompile(void) const { return isAccessible(); }
bool Method::isAccessible(void) const
{
return ((m_parent->m_meta_data.getFlag(NativeProperty::Methods) ||
m_parent->m_meta_data.getFlag(NativeProperty::All)) &&
!m_meta_data.getFlag(NativeProperty::Disable)) ||
(m_parent->m_meta_data.getFlag(NativeProperty::WhiteListMethods) &&
m_meta_data.getFlag(NativeProperty::Enable));
}
通过上述代码,就可以添加函数反射的支持了。
3. 添加反射函数的调用
视频课程使用的是Lua Component来添加反射函数的调用,具体代码如下,其中set与get函数用于注册到Lua虚拟机中,供Lua脚本调用,invoke函数用于调用通过反射注册到系统的函数:
bool find_component_field(std::weak_ptr<GObject> game_object,
const char* field_name,
Reflection::FieldAccessor& field_accessor,
void*& target_instance)
{
auto components = game_object.lock()->getComponents();
std::istringstream iss(field_name);
std::string current_name;
std::getline(iss, current_name, '.');
auto component_iter = std::find_if(
components.begin(), components.end(), [current_name](auto c) { return c.getTypeName() == current_name; });
if (component_iter != components.end())
{
auto meta = Reflection::TypeMeta::newMetaFromName(current_name);
void* field_instance = component_iter->getPtr();
while (std::getline(iss, current_name, '.'))
{
Reflection::FieldAccessor* fields;
int fields_count = meta.getFieldsList(fields);
auto field_iter = std::find_if(
fields, fields + fields_count, [current_name](auto f) { return f.getFieldName() == current_name; });
if (field_iter == fields + fields_count)
{
delete[] fields;
return false;
}
field_accessor = *field_iter;
delete[] fields;
target_instance = field_instance;
// for next iteration
field_instance = field_accessor.get(target_instance);
field_accessor.getTypeMeta(meta);
}
return true;
}
return false;
}
template<typename T>
static void LuaComponent::set(std::weak_ptr<GObject> game_object, const char* name, T value)
{
LOG_INFO(name);
Reflection::FieldAccessor field_accessor;
void* target_instance;
if (find_component_field(game_object, name, field_accessor, target_instance))
{
field_accessor.set(target_instance, &value);
}
else
{
LOG_ERROR("cannot find target field");
}
}
template<typename T>
static T LuaComponent::get(std::weak_ptr<GObject> game_object, const char* name)
{
LOG_INFO(name);
Reflection::FieldAccessor field_accessor;
void* target_instance;
if (find_component_field(game_object, name, field_accessor, target_instance))
{
return *(T*)field_accessor.get(target_instance);
}
else
{
LOG_ERROR("cannot find target field");
}
}
void LuaComponent::invoke(std::weak_ptr<GObject> game_object, const char* name)
{
LOG_INFO(name);
Reflection::TypeMeta meta;
void* target_instance = nullptr;
std::string method_name;
// find target instance
std::string target_name(name);
size_t pos = target_name.find_last_of('.');
method_name = target_name.substr(pos + 1, target_name.size());
target_name = target_name.substr(0, pos);
if (target_name.find_first_of('.') == target_name.npos)
{
// target is a component
auto components = game_object.lock()->getComponents();
auto component_iter = std::find_if(
components.begin(), components.end(), [target_name](auto c) { return c.getTypeName() == target_name; });
if (component_iter != components.end())
{
meta = Reflection::TypeMeta::newMetaFromName(target_name);
target_instance = component_iter->getPtr();
}
else
{
LOG_ERROR("cannot find component: " + target_name);
return;
}
}
else
{
Reflection::FieldAccessor field_accessor;
if (find_component_field(game_object, name, field_accessor, target_instance))
{
target_instance = field_accessor.get(target_instance);
field_accessor.getTypeMeta(meta);
}
else
{
LOG_ERROR("cannot find target field");
}
}
// invoke function
Reflection::MethodAccessor* methods;
size_t method_count = meta.getMethodsList(methods);
auto method_iter = std::find_if(
methods, methods + method_count, [method_name](auto m) { return m.getMethodName() == method_name; });
if (method_iter != methods + method_count)
{
method_iter->invoke(target_instance);
}
else
{
LOG_ERROR("cannot find method");
}
delete[] methods;
}
Lua脚本如下,根据游戏中player的状态来更改跳跃高度,然后调用反射注册的函数,可以从后台日志看出来该方法是否成功反射:
if (get_bool(GameObject, "MotorComponent.m_is_moving")) then
set_float(GameObject, "MotorComponent.m_motor_res.m_jump_height", 10)
else
set_float(GameObject, "MotorComponent.m_motor_res.m_jump_height", 4)
end
invoke(GameObject, "MotorComponent.getOffStuckState")