分布式游戏服务器框架sframe(五)—— 配置管理
来源:互联网 发布:域名免费备案 编辑:程序博客网 时间:2024/05/16 19:17
开发游戏服务器,肯定会有大量的配置数据,什么等级配置、关卡配置等等。对于这些静态数据,我们一般都是采取文件的方式来存储的。策划事先配置好这些数据文件,服务器启动后便将其加载到内存,由配置管理模块统一管理。对于配置文件,有各种各样的格式,CSV、JSON、XML、LUA等等都很流行。但是对于程序来说,我们希望无论配置文件采用什么格式,数据加载到内存后都是一样的存在形式。
sframe提供了一套配置管理机制来实现配置文件的加载、配置数据管理的功能。它负责解析配置文件,并将配置数据映射到c++的对象。对于开发者,我们使用的数据都在这些对象中,而不用关心配置文件到底是什么格式的。目前只支持json、csv两种格式的文件的解析和加载。
sframe的配置管理相关的代码都在sframe/sfram/conf目录中。本篇文章着重讲解如何使用sframe的配置管理,至于它的实现,就是一系列c++模板使用技巧,至于其原理,和前面讲的序列化、消息映射都是差不多的。只要理解了前面所讲内容,再看配置管理的源码,应该就比较好理解了。所以这里就不讲实现了,感兴趣的同学看源代码足矣。
加载配置文件
sframe可解析json、csv格式的文件,然后将数据映射到c++对象。sframe加载配置数据的步骤如下:
◆ 读取配置文件。
◆解析配置文件。读取了文件后,要解析文件的格式。csv格式很简单,所以我自己写了解析算法,将文件内容,解析为一个sframe::Table对象。json文件的解析,我采用了github上的json11(https://github.com/dropbox/json11)。这个库很精简,只有一个头文件加一个源文件,而且对外接口也是我喜欢的风格,所以就直接用了。它解析文件内容后,输出一个json11::Json对象。
◆ 将数据映射到对象。解析了之后,就要将数据映射到对象。之后我们直接接触的就是这个对象了,再也与具体的文件无关了。
上面3步就完成了配置数据的加载。第1、2步都很好理解,这里就不多说了。接下来主要讲讲第3步——将数据映射到对象。sframe提供了比较方便的接口来完成映射功能。json和csv的接口以及需要引用的头文件都不一样,下面我分开来说明。
1. json数据映射到c++对象
需要引用头文件"conf/JsonReader.h"。然后调用sframe::ConfigLoader::Load<constjson11::Json>(json, obj),即可完成将json11::Json对象的数据映射到obj对象。obj可以是所有c++基础类型、std::string、std::unordered_map<T_Key, T_Val>、std::map<T_Key,T_Val>、std::vector<T>、std::list<T>、std::set<T>、std::unordered_set<T>、std::shared_ptr<T>、以及以上所有类型的数组。json自己就有几种数据类型,这些数据类型与c++的类型对应关系见下表:
json数据类型
C++数据类型
Bool、Number、String
bool、任意数值类型、std::string
ARRAY
数组、std::map、std::unordered_map、std::vector、std::list、std::set、std::unordered_set
OBJECT
std::map、std::unordered_map、自定义结构体
这里要补充说明的是,JSON的OBJECT映射到c++的自定义结构体对象,还需要给结构体定义一个Fill()成员函数,以完成填充。具体的后面讲解。
现比如说有JSON数据{“name”:”小明”,“address”:””},解析后我们得到一个json11::Json的对象json,欲将其映射到std::map<std::string, std::string> obj。直接调用sframe::ConfigLoader::Load<constjson11::Json>(json, obj)即可。
2. csv数据映射到c++对象
需要引用头文件"conf/TableReader.h"。调用的方法是sframe::ConfigLoader::Load<TableReader>(tbl,obj)。
不同的是,csv是典型的表格形式的格式。对于整张表,可以映射到std::unordered_map<T_Key, T_Val>、std::map<T_Key,T_Val>、std::vector<T>、std::list<T>、std::set<T>、std::unordered_set<T>、以及以上所有类型的std::shared_ptr。以上所有容器的value都必须是自定义结构体。这些结构体都必须提供Fill()方法来完成填充。
在Fill函数里面,要完成将表格里面单元格映射到对象。而单元格数据没有数据类型,全部都是string,要完成将单元格的数据映射到对象,就要定义好格式,然后解析字符串。sframe已经提供了默认的格式:
C++类型
格式
std::vecto、std::list、数组
形如:v1|v2|v3|v4
std::unordered_map、std::map
形如:k1#v1;k2#v2;k3#v3
数值、string
符合转换要求即可
3. 实例说明
(1) 将配置映射到单一自定义结构体对象
比如说开发游戏,往往我们需要有一个全局配置来配置各种杂七杂八的东西。在程序里它就表现为一个结构体对象。至于文件格式,此种情况只支持json。
JSON数据:
{ "num_field" : 10000, "str_field" : "xiaoming", "vec_field" : [1,2,3,4], "map_field" : { "1" : "str1", "2" : "str2", "3" : "str3", "4" : "str4" }}结构体应该是:
struct GlobalConfig{ int num_field; std::string str_field; std::vector<int> vec_field; std::map<int, std::string> map_field; void Fill(const json11::Json & reader) { JSON_FILLFIELD(num_field); JSON_FILLFIELD(str_field); JSON_FILLFIELD(vec_field); JSON_FILLFIELD(map_field); }};
Fill 函数的返回值也可以是bool类型的,用以表示加载是够成功。JSON_FILLFIELD是个宏,JSON_FILLFIELD(num_field)实际上等于sframe::Json_FillField(reader,”num_field” , this-> num_field);所以,要使用此宏,voidFill(const json11::Json & reader)函数的参数名必须是reader,且这个成员的变量名必须等于文件中的字段名。加载配置文件的代码:
bool LoadJson(){std::string content;if (!sframe::FileHelper::ReadFile("global.json", content)){return false;}std::string err;json11::Json json = json11::Json::parse(content, err);if (!err.empty()){return false;}// 加载GlobalConfig global_conf;if (!sframe::ConfigLoader::Load<const json11::Json>(json, global_conf)){return false;}return true;}(2) 将配置映射到map
除了全局配置这一类配置,大多数情况一个配置文件都分为若干条目,映射到c++中应该是map或者其他容器,这里只以map举例。对于这种多条目的,JSON和CSV都是支持的。比如我们需要一个英雄等级配置。
若用JSON,数据文件应该是:{[ { “level” : 1, //等级“attk” : 100, //攻击力“title” : [“小白”, “初级战士”] //称号},{ “level” : 2, //等级“attk” : 200, //攻击力“title” : [“一般”, “中级战士”] //称号},{ “level” : 3, //等级“attk” : 300, //攻击力“title” : [“大神”, “高级战士”] //称号}]}
若用CSV,数据文件应该是:
可以填一些标识之类的东西
level
attk
title
本行填注释
类型(仅仅便于观察)
1
100
小白|初级战士
2
200
一般|中级战士
3
300
大神|高级战士
结构体:
struct LevelConfig{ KEY_FIELD(int32_t,level); int level; int attk; std::vector< std::string > title; void Fill(const json11::Json & reader) { JSON_FILLFIELD(level); JSON_FILLFIELD(attk); JSON_FILLFIELD(title); } void Fill(sframe::TableReader & reader) { TBL_FILLFIELD (level); TBL_FILLFIELD (attk); TBL_FILLFIELD (title); }};
其中的KEY_FIELD主要用于指定这个结构体key的字段,用做map的key。展开就是定义了一个获取key的方法,int32_t GetKey() const {return level;}
加载配置的代码如下(以CSV格式为例):
bool LoadCsv(){ std::string content; if (!sframe::FileHelper::ReadFile("lv.csv", content)) { return false; } sframe::Table tbl; if (!sframe::CSV::Parse(content, tbl)) { return false; } if (tbl.GetRowCount() < 1) { return false; } // 设置列名 int32_t column_count = tbl.GetColumnCount(); for (int i = 0; i < column_count; i++) { tbl.GetColumn(i).SetName(tbl[0][i]); } // 删除第一行,因为第一行是列名,不是数据 tbl.RemoveRow(0); // TableReader sframe::TableReader reader(tbl); // 加载 std::map<int, LevelConfig> lv_conf; if (!sframe::ConfigLoader::Load<sframe::TableReader>(reader, lv_conf)) { return false; } return true;}
配置管理
在实际项目中,配置文件是很多的。所以,将所有配置集中分类管理是很有必要的。sframe提供了ConfigSet类,用以统一加载、管理所有的配置。那么如何使用呢?我们还是以前面的例子来做说明。现欲将上面的两种配置都使用ConfigSet来做统一的加载与管理,GlobalConfig使用JSON,LevelConfig使用CSV。首先,ConfigSet的管理单元为配置模块,我们需要定义配置模块,sframe已提供了宏来辅助我们声明配置模块。如下:
OBJ_CONFIG_MODULE(GlobalConfigModule, GlobalConfig, 1); MAP_CONFIG_MODULE(LevelConfigModule, int32_t, LevelConfig, 2);
OBJ_CONFIG_MODULE声明一个单一对象配置模块,模块名为GlobalConfigModule,配置对象为GlobalConfig的对象,配置ID是1。
MAP_CONFIG_MODULE声明一个map类型的配置模块,模块名为LevelConfigModule,配置对象为key为int,value为LevelConfig对象的map,配置ID是2。
使用的代码如下:sframe::ConfigSet conf_set;conf_set.RegistConfig< JsonLoader, GlobalConfigModule >("global.json"); // 参数为文件名conf_set.RegistConfig<TableLoader<CSV>, LevelConfigModule >("level.csv");std::vector<ConfigError> vec_err_info; // 错误信息conf_set->Load(“/data/conf”, &vec_err_info); //第一个参数指明配置文件路径// 查询全局配置模块std::shard_ptr<const GlobalConfigModule> global_conf = conf_set. GetConfigModule< GlobalConfigModule >();// 查询全局配置std::shard_ptr<const GlobalConfig > global_conf = conf_set. GetConfig< GlobalConfigModule >();// 查询等级配置模块std::shard_ptr<const LevelConfigModule> global_conf = conf_set. GetConfigModule< LevelConfigModule >();// 查询等级配置std::shard_ptr<const std::map<int, std::shard_ptr<LevelConfig>> map_lv_conf = conf_set. GetConfig< LevelConfigModule >();// 查询一条等级配置std::shard_ptr<const LevelConfig> lv_conf = conf_set. GetMapConfigItem<LevelConfigModule>(1);
除了以上基本功能以外,sframe的配置管理模块还有以下功能:
(1) 配置模块的初始化
在有些时候,我们在加载完一项配置之后,可能会需要做一些额外的处理。sframe提供了初始化机制来支持此项功能。此时我们需要自己声明配置模块struct GlobalConfigModule : public sframe::ObjectConfigModule<GlobalConfig, 1> { void Initialize(sframe::ConfigSet & conf_set) { //Obj()方法获取std::shared_ptr<GlobalConfig>对象 } }; struct LevelConfigModule : public sframe::MapConfigModule<int, LevelConfig, 2> { void Initialize(sframe::ConfigSet & conf_set) { //Obj()方法获取std::shared_ptr<std::map<T_Key, std::shared_ptr<LevelConfig>>对象 }};
Initialize函数也支持返回bool类型的形式,若需返回bool,直接将void改成bool即可。ConfigSet在加载完所有配置后调用初始化函数。
(2) 自定义将配置插入容器
些时候我们在加载了一条配置以后,可能不希望单纯的将对象插入容器。可能使需要将对象分裂成几个对象,然后将几个对象全部保存到容器;或者我们要进行一些判断,成功才将其插入容器。sframe允许自定义将对象插入容器的方法。使用起来也很简单,我们只需为结构体添加PutIn成员函数。比如LevelConfig:
struct LevelConfig{bool PutIn(std::map<int, std::shard_ptr<LevelConfig>> & m){m[level] = *this;return true;}}
本讲了大概,更多内容请参考源代码。
- 分布式游戏服务器框架sframe(五)—— 配置管理
- 分布式游戏服务器框架sframe(一)—— 整体思想
- 分布式游戏服务器框架sframe(二)—— 服务调度与服务间通信
- 分布式游戏服务器框架sframe(三)—— 序列化与反序列化
- 分布式游戏服务器框架sframe(四)—— 消息映射
- Unity游戏UI框架(五):配置管理
- 高性能分布式游戏服务器框架
- python︱apple开源机器学习框架turicreate中的SFrame——新形态pd.DataFrame
- SFrame
- 高性能服务器架构思路(五)——分布式缓存
- 高性能服务器架构思路(五)——分布式缓存
- 高性能服务器架构思路(五)——分布式缓存
- 【开源】golang高性能分布式游戏服务器框架-mqant
- Java Web学习总结(23)——Distributed Configuration Management Platform(分布式配置管理平台)
- Disconf —— 来自百度的分布式配置管理平台
- spring cloud系列一 搭建配置服务器(分布式配置管理)configserver
- Unity+ Photon服务器实时对战游戏——远程过程调用 (五)
- springCloud入门(一)分布式配置管理
- 干货来了,手把手教你Android自定义饼状图,带加载动画!
- yii2 rules & scenario
- C语言的可变参数表函数的设计
- Gson转化为对象
- React学习之高级DOM元素属性(二十六)
- 分布式游戏服务器框架sframe(五)—— 配置管理
- iOS下使用FFMPEG所需的Frameworks
- fl2440——添加DM9000网卡支持
- 烧录文件到开发板操作
- 微信指数链接
- 方便的决定前端会员功能-逐浪CMS后台增加会员菜单可配置项
- C语言实现类
- JavaScript 实现内排序算法
- leetcode题解Java | 210. Course Schedule II