xresloader转表工具链增加了一些新功能(map,oneof支持,输出矩阵,基于模板引擎的加载代码生成等)
xresloader 是一组用于把Excel数据结构化并导出为程序可读的数据文件的导表工具集。它包含了一系列跨平台的工具、协议描述和数据读取代码。支持把Excel配置输出成 protobuf二进制、xml、json、lua、javascript、nodejs、msgpack、UE的Json格式及支持蓝图的代码、UE的Csv格式及支持蓝图的代码。
最近一段时间有一些其他项目组也用了 xresloader 工具链来执行转表。提了一些需求,我并且针对我们自己的项目需要耶新增了一系列功能。这里总结介绍一下吧。
Github: https://github.com/xresloader
文档: https://xresloader.atframe.work/
主要项目 |
说明 |
状态 |
---|---|---|
xresloader |
转表引擎 |
|
xresconv-gui |
GUI客户端 |
|
xresconv-cli |
命令行客户端 |
|
xresloader-docs |
文档 |
|
xresconv-conf |
转表配置规范 |
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-Json
和 UE-Csv
输出的蓝图代码中,增加指示oneof分支的字段,便于对 oneof 输出的分支判断和反射使用。
更多详情见: 《文档 - Oneof/Union支持(需要 xresloader 2.8.0及以上)》
Map类型支持
从 xresloader 2.9.0 版本开始,我们支持使用 protobuf 内置的map类型。map类型的数据输入配置和数组类似,与其不同的是,我们增加了内置的 key
和 value
字段用于通过标准模式指定元素的 key
和 value
。 当然我们也可以使用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-Csv
和 UE-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++ 、 lua 和 C# 版本。支持 多索引 、 多级索引 、 多版本支持 、 支持嵌入客户端引擎 、 支持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("%sn", 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("tt%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
- 在Orchard中使用Image Gallery模块
- CentOS7设置IP地址
- 服务器端Javascript
- ASP连接数据库
- 彻底隐藏你HTML网页的源代码
- java学习:JMM(java memory model)、volatile、synchronized、AtomicXXX理解
- 使用CSS制作文字环绕图片效果(文字内容包含<li>标签)
- ClojureScript魔法堂:搭建开发环境
- PHP error_reporting() 错误控制函数功能详解
- centos上安装elasticsearch 5.5.1 遇到的各种坑
- 概率论08 随机变量的函数
- @Transactional导致AbstractRoutingDataSource动态数据源无法切换的解决办法
- 防F12扒代码:按下F12关闭当前页面
- TCP/IP(七)之玩转HTTP协议
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Android实现点击缩略图放大效果
- Android 应用签名的两种方法
- Android 关闭多个Activity的实现方法
- 我从Vue源码中学到的一些JS编程技巧
- 组复制升级 | 全方位认识 MySQL 8.0 Group Replication
- 浅谈关于Android WebView上传文件的解决方案
- Android对图片Drawable实现变色示例代码
- 排序|优先队列不知道,先看看堆排序吧
- Android关于FTP文件上传和下载功能详解
- Android中封装RecyclerView实现添加头部和底部示例代码
- Python 十六进制hex-bytes-str之间的转换和Bcc码的生成
- android中实现手机号码的校验的示例代码
- Android ListView实现下拉加载功能
- Android 截图功能源码的分析
- Docker下搭建禅道管理系统