本篇文章的内容主要是对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")