xresloader转表工具链增加了一些新功能(map,oneof支持,输出矩阵,基于模板引擎的加载代码生成等)

xresloader 是一组用于把Excel数据结构化并导出为程序可读的数据文件的导表工具集。它包含了一系列跨平台的工具、协议描述和数据读取代码。支持把Excel配置输出成 protobuf二进制、xml、json、lua、javascript、nodejs、msgpack、[UE][10]的Json格式及支持蓝图的代码、[UE][10]的Csv格式及支持蓝图的代码。

最近一段时间有一些其他项目组也用了 xresloader 工具链来执行转表。提了一些需求,我并且针对我们自己的项目需要耶新增了一系列功能。这里总结介绍一下吧。

Github: https://github.com/xresloader

文档: https://xresloader.atframe.work/

主要项目

说明

状态

转表引擎

GitHub release (latest by date) GitHub Actions status GitHub code size in bytes GitHub repo size GitHub All Releases GitHub forks GitHub stars

GUI客户端

GitHub release (latest by date) GitHub Actions status GitHub code size in bytes GitHub repo size GitHub All Releases GitHub forks GitHub stars

命令行客户端

GitHub release (latest by date) GitHub code size in bytes GitHub repo size GitHub forks GitHub stars

转表配置规范

GitHub repo size

plain模式

为了方便某些特殊场景使用,从 xresloader 2.7.0版本开始,我们开支支持Plain模式。 Plain模式的配置方式允许把数字和字符串数组和整个message配置在一个单元格里,多个元素或者多个字段按分隔符分割。分隔符支持多个候选项,实际执行会采用按输入的字符串中,第一个找到的候选项。 默认的分隔符候选项是 ,;|

Plain模式不需要额外配置,当数组元素没有配置下标或者配置的映射字段直接指向一个message时,将自动使用Plain模式解析。

比如对于以下协议:

message cfg {
    int32 id = 1;
    plain_message plain_msg = 2;
    repeated int32 plain_arr = 3;
}

message plain_message {
    int32 id = 1;
    repeated int32 param = 2;
}

如果Excel配置是如下形式:

配置ID

Plain结构

Plain数组

id

plain_msg

plain_arr

101

101|1,2,3

7;8;9

那么对于 plain_msg 字段输入的字符串是 101|1,2,3 ,第一个 | 会作为 plain_msg 的字段分隔符, , 会作为 plain_msg.param 的数组分隔符。 而对于 plain_arr 字段输入的字符串是 7;8;9 , ; 会作为数组分隔符。

如果想要指定自定义分隔符,特别是对 repeated message 要区分message的分隔符和数组的分隔符,可以使用使用 org.xresloader.field_separator 插件和 org.xresloader.msg_separator 插件。 需要注意的是,对于数组(repeated)的字段,字段分隔符仅接受通过 org.xresloader.field_separator 指定,而非数组的复杂数据结构(非repeated message) org.xresloader.field_separator 插件和 org.xresloader.msg_separator 都可以用于指定分隔符。

更多详情见: 《文档 - Plain模式(需要 xresloader 2.7.0及以上)》

oneof支持

xresloader 对Oneof的支持和Plain模式类似,并且只能通过Plain模式一样的方法配置,可以使用 org.xresloader.oneof_separator 插件指定自定义分隔符。

Oneof/Union支持的配置方法是直接在Excel字段映射中配置oneof的名字。输入字符串中第一组为字段的名字、数字标识(field number)或别名,第二组为对应的类型的Plain模式输入。比如:

// 常量类型
enum cost_type {
    EN_CT_UNKNOWN              = 0;
    EN_CT_MONEY                = 10001 [(org.xresloader.enum_alias) = "金币"];
    EN_CT_DIAMOND              = 10101 [(org.xresloader.enum_alias) = "钻石"];
}
message cfg {
  int32 id = 1;
  oneof reward {
    plain_message msg       = 11 [ (org.xresloader.field_alias) = "嵌套结构" ];
    int64         user_exp  = 12 [ (org.xresloader.field_alias) = "数字类型" ];
    string        note      = 13 [ (org.xresloader.field_alias) = "描述文本" ];
    cost_type     enum_type = 14 [ (org.xresloader.field_alias) = "货币类型" ];
  }
}
message plain_message {
    int32 id = 1;
    repeated int32 param = 2;
}

以下输入都是允许的:

配置ID

Oneof结构

id

reward

1001

msg|101;1,2,3

1002

数字类型|100

1003

13|Hello World

1004

enum_type|金币

1005

货币类型|EN_CT_DIAMOND

需要特别注意的是,和Plain模式一样,message字段解析是严格按照配置的field number的顺序,如果message里有嵌套的oneof,那么oneof的输入位置是第一个相关字段的位置,并且该oneof里后续的字段不需要配置。

UE-JsonUE-Csv 输出的蓝图代码中,增加指示oneof分支的字段,便于对 oneof 输出的分支判断和反射使用。

更多详情见: 《文档 - Oneof/Union支持(需要 xresloader 2.8.0及以上)》

Map类型支持

xresloader 2.9.0 版本开始,我们支持使用 protobuf 内置的map类型。map类型的数据输入配置和数组类似,与其不同的是,我们增加了内置的 keyvalue 字段用于通过标准模式指定元素的 keyvalue。 当然我们也可以使用Plain模式的输入。比如以下的协议:

message dep2_cfg {
    uint32 id = 1;
    string level = 2;
}
message arr_in_arr_cfg {
    option (org.xresloader.ue.helper)       = "helper";
    option (org.xresloader.msg_description) = "Test arr_in_arr_cfg";

    uint32   id                       = 1 [ (org.xresloader.ue.key_tag) = 1, (org.xresloader.field_description) = "This is a Key" ];
    map<int32, string>    test_map_is = 7;
    map<string, dep2_cfg> test_map_sm = 8 [ (org.xresloader.field_separator) = "|" ]; 
}

我们接受如下的Excel输入:

配置ID

Map嵌套模式[0].key

Map嵌套模式[0].value

Map嵌套模式[1].key

Map嵌套模式[1].value

MapPlain模式

id

test_map_is[0].key

test_map_is[0].value

test_map_is[1].key

test_map_is[1].value

test_map_sm

1001

10

Map嵌套模式[0].value

11

Map嵌套模式[1].value

aa;111,112|特殊:字符;121,122

1002

20

Map嵌套模式[0].value

21

Map嵌套模式[1].value

ba;211,212|特殊.字符;221,222

1003

30

Map嵌套模式[0].value

31

Map嵌套模式[1].value

ca;311,312|cb;321,322

对于 UE-CsvUE-Json 模式的输出,我们会输入如下的代码:

USTRUCT(BlueprintType)
struct FArrInArrCfg : public FTableRowBase
{
    GENERATED_USTRUCT_BODY()

    // Start of fields
    /** Field Type: STRING, Name: Name, Index: 0. This field is generated for UE Editor compatible. **/
    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "XResConfig")
    FName Name;

    // This is a Key
    /** Field Type: INT, Name: Id, Index: 1 **/
    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "XResConfig")
    int32 Id;

    /** Field Type: MESSAGE, Name: TestMapIs, Index: 7 **/
    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "XResConfig")
    TMap< int32, FString > TestMapIs;

    /** Field Type: MESSAGE, Name: TestMapSm, Index: 8 **/
    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "XResConfig")
    TMap< FString, FDep2Cfg > TestMapSm;

};

特别的对于 xml 类型的输出,由于map中的key的数据可能会不符合 xml 的tag的规则,所以我们对于map输出的数据中 tagName 采用类型名, 即 string , int32 , int64 。 然后增加 key 属性用于指示map中key的内容,增加 type 属性指示类型名。

更多详情见: 《文档 - Map类型支持(需要 xresloader 2.9.0及以上)》

杂项功能和优化

  • 增加 --require-mapping-all 用于检查message中所有字段都被映射,用于检查配置遗漏

  • 增加protobuf插件 - org.xresloader.msg_require_mapping_all 可以设置某个message的所有字段必须被全部映射,用于检查配置遗漏

  • 增加protobuf插件 - org.xresloader.field_required 用于向proto3提供,proto2的 required 约束

  • 协议里直接配置 enum 类型也支持默认增加该类型的验证器

  • 文本输出的模式全部为有序输出,方便使用diff查看差异

  • 增加输出数据源和协议名,方便运行时自动化分析配置问题

  • 内置插件proto和官方proto。这样输入的pb文件不需要打包官方的proto描述和xresloader自带的插件和头的proto了。

基于模板引擎的加载代码生成

目前我们项目组开发了工具来自动生成加载代码,包括 C++luaC# 版本。支持 多索引多级索引多版本支持支持嵌入客户端引擎支持C++98 - C++20支持简单的分表分文件加载规则

比如:

syntax = "proto3";

import "xresloader.proto";
import "xresloader_ue.proto";

import "xrescode_extensions_v3.proto";

message role_upgrade_cfg {
    option (org.xresloader.ue.helper)       = "helper";
    option (org.xresloader.msg_description) = "Test role_upgrade_cfg with multi keys";

    option (xrescode.loader) = {
        file_path : "role_upgrade_cfg.bytes"
        indexes : {
            fields : "Id"
            index_type : EN_INDEX_KL // Key - List index: (Id) => list<role_upgrade_cfg>
        }
        indexes : {
            fields : "Id"
            fields : "Level"
            index_type : EN_INDEX_KV // Key - Value index: (Id, Level) => role_upgrade_cfg
        }
        tags : "client"
        tags : "server"
    };

    uint32 Id        = 1 [ (org.xresloader.ue.key_tag) = 1000 ];
    uint32 Level     = 2 [ (org.xresloader.ue.key_tag) = 1 ];
    uint32 CostType  = 3 [ (org.xresloader.verifier) = "cost_type", (org.xresloader.field_description) = "Refer to cost_type" ];
    int32  CostValue = 4;
    int32  ScoreAdd  = 5;
}

加载代码生成 - C++

生成的C++索引代码类似这样:

namespace excel {
    class config_set_role_upgrade_cfg {
    public:
        typedef const ::role_upgrade_cfg item_type;
        typedef ::role_upgrade_cfg proto_type;
        typedef std::shared_ptr<item_type> item_ptr_type;

        // 省略掉一些加载代码 ...

        // ------------------------- index: id -------------------------
    public:
        typedef std::vector<item_ptr_type> id_value_type;
        const id_value_type* get_list_by_id(uint32_t Id);
        item_ptr_type get_by_id(uint32_t Id, size_t index);
    private:
        const id_value_type* _get_list_by_id(uint32_t Id);
    public:
        typedef std::map<std::tuple<uint32_t>, id_value_type> id_container_type;
        const id_container_type& get_all_of_id() const;

    private:
        id_container_type id_data_;

        // ------------------------- index: id_level -------------------------
    public:
        typedef item_ptr_type id_level_value_type;
        id_level_value_type get_by_id_level(uint32_t Id, uint32_t Level);
        typedef std::map<std::tuple<uint32_t, uint32_t>, id_level_value_type> id_level_container_type;
        const id_level_container_type& get_all_of_id_level() const;

    private:
        id_level_container_type id_level_data_;
    };
}

可以用如下代码加载:

#include <cstdio>

#include "config_manager.h"
#include "config_easy_api.h"

int main() {
    // Initialize ....
    excel::config_manager::me()->init();

    // Call reload to generate a configure group
    excel::config_manager::me()->reload();

    // Now you can load data by easy api or config_manager's raw API
    auto cfg = excel::get_role_upgrade_cfg_by_id_level(10001, 3); // using the Key-Value index: id_level
    if (cfg) {
        printf("%s\n", cfg->DebugString().c_str());
    }
    return 0;
}

加载代码生成 - Lua

生成的lua索引代码类似这样:

module("DataTableCustomIndex", package.seeall)
--
-- generated by xrescode on 2020-05-07 18:12:18, please don't edit it
--

-- ======================================== role_upgrade_cfg ========================================
role_upgrade_cfg = {
    -- require("DataTableService").Get("role_upgrade_cfg"):GetByIndex("id", "Id")
    { indexName = "id", filePath = "role_upgrade_cfg.bytes", options = { isList = true, allowNotFound = true }, keys = { "Id" } },
    -- require("DataTableService").Get("role_upgrade_cfg"):GetByIndex("id_level", "Id", "Level")
    { indexName = "id_level", filePath = "role_upgrade_cfg.bytes", options = { isList = false, allowNotFound = true }, keys = { "Id", "Level" } },
}

然后可以用这样加载:

-- We will use require(...) to load DataTableService53,DataTableCustomIndex53 and custom data files, please ensure these can be load by require(FILE_PATH)
-- Assuming the generated lua files by xresloader is located at ../../../xresloader/sample/proto_v3
package.path = '../../../xresloader/sample/proto_v3/?.lua;' .. package.path
local excel_config_service = require('DataTableService53')

-- Set logger
-- excel_config_service:OnError = function (message, index, indexName, keys...) end

excel_config_service:ReloadTables()

local role_upgrade_cfg = excel_config_service:Get("role_upgrade_cfg")
local data = role_upgrade_cfg:GetByIndex("id_level", 10001, 3) -- using the Key-Value index: id_level
for k,v in pairs(data) do
    print(string.format("%s=%s", k, tostring(v)))
end

-- We can also use DataTableService.GetCurrentGroup(self) and DataTableService.GetByGroup(self, group, loader_name) to support multi-version loader
local current_group = excel_config_service:GetCurrentGroup()
local role_upgrade_cfg2 = excel_config_service:GetByGroup(current_group, "role_upgrade_cfg")
local data2 = role_upgrade_cfg:GetByIndex("id", 10001) -- using the Key-List index: id
print("=======================")
for _,v1 in ipairs(data2) do
    print(string.format("\tid: %s, level: %s", tostring(v1.Id), tostring(v1.Level)))
    for k,v2 in pairs(v1) do
        print(string.format("\t\t%s=%s", k, tostring(v2)))
    end
end

加载代码生成 - C#

同样,也支持生成C#代码,生成的代码就不贴了,加载代码如下:

using System;
using excel;
class Program {
    static void Main(string[] args) {
        ConfigSetManager.Instance.Reload();
        // The C# configSet now generated by Singleton Classes.
        // For the multi ConfigGroup management may be added when need.
        var table = config_set_role_upgrade_cfg.Instance.GetByIdLevel(10001, 3);
        if (table != null) {
            Console.WriteLine(table.ToString());
        }
    }
}

目前的C#的代码生成的版本还不支持多版本并存。

更多详情见工具的代码仓库: https://github.com/xresloader/xres-code-generator

[10]: https://www.unrealengine.com/

Last updated

Was this helpful?