想要做一款完整的游戏,应该从配置文件读取开始。cocos2d-x本身提供了UserDefault来操作xml格式的配置文件,准确的说配置这模块引擎开发者已经考虑到了.但是xml格式包含大量无关的格式信息,对于开发者直接使用编辑器操作也不是很友好.我期望的配置文件应该具备两个特点,第一:格式简单.这是我一贯的*nix风格的要求,我喜欢只管的#注释以及key-value的配置方式.可能唯一需要考虑的是这样简单的配置方式会不会因为业务扩展而显得功能不足.这一点是可以放心的,那些都是业务逻辑相关的,选择就会很多.至少现在不是应该考虑的范畴.第二:对开发者友好.什么叫做友好? 说白了,按照我的理解就是很容易可以定位配置信息的位置,容易修改,对编辑器依赖不高.我们随便用个notepad++/subl之类的文本编辑器就可以直接阅读和修改.
好了,基于上面的考虑,ini格式无疑是比较好的选择.我最终选用了ini格式作为配置文件首选.所以就写了一个c++类来解析和操作ini配置.代码如下:
1 #ifndef __xy_INICache_INCLUDE__
2 #define __xy_INICache_INCLUDE__
3 #include <map>
4 #include <vector>
5 #include <string>
6 namespace xy
7 {
8 class INICache
9 {
10 public:
11 INICache();
12 virtual ~INICache();
13 static INICache* createFromFile(const std::string& filePath);
14 static INICache* createFromStream(const std::stringstream& sstream);
15 bool loadFromFile(const std::string& filePath);
16 bool loadFromStream(const std::stringstream& sstream);
17 void flush(const std::string& filePath);
18 std::string getFilePath() const { return this->M_filePath_; }
19 bool isSectionExist(const std::string& section) const;
20 bool deleteSection(const std::string& section);
21 void getSectionNames(std::vector<std::string>& sectionVector);
22 void setString(const std::string& section, const std::string& key, const std::string& value);
23 std::string getString(const std::string& section, const std::string& key, const std::string& defaultValue);
24 void setInteger(const std::string& section, const std::string& key, int value);
25 int getInteger(const std::string& section, const std::string& key, int defaultValue);
26 void setDouble(const std::string& section, const std::string& key, double value, int pre);
27 double getDouble(const std::string& section, const std::string& key, double defaultValue);
28 void setBoolean(const std::string& section, const std::string& key, bool value);
29 bool getBoolean(const std::string& section, const std::string& key, bool defalutValue);
30 protected:
31 void trimString(std::string& buffer, const std::string& trim, bool isAll);
32 void parseLine(const std::string& buffer);
33 std::string parseSection(const std::string& buffer, const std::string& leftTag, const std::string& rightTag);
34 bool stringToBoolean(const std::string& buffer, bool defaultValue);
35
36 typedef std::map<std::string, std::string> KVtype;
37 typedef std::map<std::string, KVtype> SKVtype;
38
39 std::string M_filePath_;
40 std::string M_currentSection_;
41 SKVtype propsMap_;
42 };
43 }
44 #endif//__xy_INICache_INCLUDE__
实现部分的代码如下,如果需要看的,可以自行查看.
1 #include "INICache.h"
2 #include <fstream>
3 #include <sstream>
4 namespace xy
5 {
6 INICache::INICache()
7 {
8 }
9 INICache::~INICache()
10 {
11 if(!this->propsMap_.empty()) { this->propsMap_.clear(); }
12 }
13 INICache* INICache::createFromFile(const std::string& filePath)
14 {
15 INICache* pINICache = new INICache;
16 if(!pINICache->loadFromFile(filePath))
17 {
18 delete pINICache;
19 return NULL;
20 }
21 return pINICache;
22 }
23 bool INICache::loadFromFile(const std::string& filePath)
24 {
25 std::ifstream fin(filePath.c_str());
26 if(!fin.is_open()) { return false; }
27
28 std::string lineBuffer;
29 while(!fin.eof())
30 {
31 std::getline(fin, lineBuffer);
32 this->trimString(lineBuffer, std::string(" "), true);
33 this->parseLine(lineBuffer);
34 }
35 fin.close();
36 this->M_filePath_ = filePath;
37 return true;
38 }
39 INICache* INICache::createFromStream(const std::stringstream& sstream)
40 {
41 INICache* pINICache = new INICache;
42 if(!pINICache->loadFromStream(sstream))
43 {
44 delete pINICache;
45 return NULL;
46 }
47 return pINICache;
48 }
49 bool INICache::loadFromStream(const std::stringstream& sstream)
50 {
51 std::string lineBuffer;
52 std::istringstream isstream(sstream.str());
53 while(!isstream.eof())
54 {
55 std::getline(isstream, lineBuffer);
56 this->trimString(lineBuffer, std::string(" "), true);
57 this->parseLine(lineBuffer);
58 }
59 return true;
60 }
61 void INICache::flush(const std::string& filePath)
62 {
63 std::ofstream fout(filePath.c_str());
64 if(!fout.is_open()) { return; }
65 std::string buffer;
66 SKVtype::iterator skv_iter;
67 KVtype::iterator kv_iter;
68 for(skv_iter = this->propsMap_.begin(); skv_iter != this->propsMap_.end(); ++skv_iter)
69 {
70 fout << std::endl;
71 fout << std::string("[") << skv_iter->first << std::string("]") << std::endl;
72 for(kv_iter = skv_iter->second.begin(); kv_iter != skv_iter->second.end(); ++kv_iter)
73 {
74 fout << kv_iter->first << std::string("=") << kv_iter->second << std::endl;
75 }
76 }
77 fout.close();
78 }
79 bool INICache::isSectionExist(const std::string& section) const
80 { return (this->propsMap_.find(section) != this->propsMap_.end()); }
81 bool INICache::deleteSection(const std::string& section)
82 {
83 if(!section.empty()) { return false; }
84 SKVtype::iterator skv_iter = this->propsMap_.find(section);
85 if(skv_iter != this->propsMap_.end())
86 {
87 this->propsMap_.erase(skv_iter);
88 return true;
89 }
90 return false;
91 }
92 void INICache::getSectionNames(std::vector<std::string>& sectionVector)
93 {
94 if(!sectionVector.empty()) { sectionVector.clear(); }
95 SKVtype::iterator skv_iter = this->propsMap_.begin();
96 for(; skv_iter != this->propsMap_.end(); ++skv_iter)
97 { sectionVector.push_back(skv_iter->first); }
98 }
99 void INICache::setString(const std::string& section, const std::string& key, const std::string& value)
100 {
101 if(!section.empty() && !key.empty())
102 { this->propsMap_[section][key] = value; }
103 }
104 std::string INICache::getString(const std::string& section, const std::string& key, const std::string& defaultValue)
105 {
106 if(!section.empty() && !key.empty())
107 {
108 SKVtype::iterator skv_iter = this->propsMap_.find(section);
109 if(skv_iter != this->propsMap_.end())
110 {
111 KVtype::iterator kv_iter = skv_iter->second.find(key);
112 if(kv_iter != skv_iter->second.end())
113 { return kv_iter->second; }
114 }
115 }
116 return defaultValue;
117 }
118 void INICache::setInteger(const std::string& section, const std::string& key, int value)
119 {
120 std::stringstream sstream;
121 sstream << value;
122 this->setString(section, key, sstream.str());
123 }
124 int INICache::getInteger(const std::string& section, const std::string& key, int defaultValue)
125 {
126 std::string tmp = this->getString(section, key, std::string(""));
127 std::stringstream sstream;
128 sstream << tmp;
129 sstream >> defaultValue;
130 return defaultValue;
131 }
132 void INICache::setDouble(const std::string& section, const std::string& key, double value, int pre)
133 {
134 std::stringstream sstream;
135 if(pre) { sstream.precision(pre); }
136 sstream << value;
137 this->setString(section, key, sstream.str());
138 }
139 double INICache::getDouble(const std::string& section, const std::string& key, double defaultValue)
140 {
141 std::string tmp = this->getString(section, key, std::string(""));
142 std::stringstream sstream;
143 if(!tmp.empty())
144 {
145 sstream << tmp;
146 sstream >> defaultValue;
147 }
148 return defaultValue;
149 }
150 void INICache::setBoolean(const std::string& section, const std::string& key, bool value)
151 { this->setInteger(section, key, value ? 1:0); }
152 bool INICache::getBoolean(const std::string& section, const std::string& key, bool defaultValue)
153 {
154 std::string tmp = this->getString(section, key, std::string(""));
155 if(!tmp.empty()) { return this->stringToBoolean(tmp, defaultValue); }
156 return defaultValue;
157 }
158 void INICache::trimString(std::string& buffer, const std::string& trim, bool isAll)
159 {
160 if(buffer.empty()) { return; }
161 while(buffer.find(trim) == 0)
162 {
163 buffer.erase(0, trim.length());
164 if(!isAll) { break; }
165 }
166 while(!buffer.empty() && (buffer.rfind(trim) == (buffer.length() - trim.length())))
167 {
168 buffer.erase(buffer.length() - trim.length(), trim.length());
169 if(!isAll) { break; }
170 }
171 }
172 void INICache::parseLine(const std::string& buffer)
173 {
174 if(buffer.empty()) { return; }
175 switch (buffer[0])
176 {
177 case '#':
178 case '%':
179 return;
180 case '[':
181 {
182 std::string section = this->parseSection(buffer, std::string("["), std::string("]"));
183 this->trimString(section, std::string(" "), true);
184 if(!section.empty()) { this->M_currentSection_ = section; }
185 }
186 return;
187 default:
188 {
189 if(buffer.find(std::string("=")) != std::string::npos && !this->M_currentSection_.empty())
190 {
191 std::string key = this->parseSection(buffer, std::string(""), std::string("="));
192 this->trimString(key, std::string(" "), true);
193 std::string value = this->parseSection(buffer, std::string("="), std::string(""));
194 this->trimString(value, std::string(" "), true);
195 if(!key.empty()) { this->propsMap_[this->M_currentSection_][key] = value; }
196 }
197 }
198 return;
199 }
200 }
201 std::string INICache::parseSection(const std::string& buffer, const std::string& leftTag, const std::string& rightTag)
202 {
203 std::string ret;
204 if(!buffer.empty())
205 {
206 std::string::size_type pos_begin = 0, pos_end = 0;
207 if(!leftTag.empty())
208 {
209 pos_begin = buffer.find(leftTag);
210 if(pos_begin == std::string::npos) { return ret; }
211 pos_begin += leftTag.size();
212 } else { pos_begin = 0; }
213 if(!rightTag.empty())
214 {
215 pos_end = buffer.find(rightTag, pos_begin);
216 if(pos_end == std::string::npos) { return ret; }
217 ret = buffer.substr(pos_begin, pos_end - pos_begin);
218 } else { ret = buffer.substr(pos_begin, std::string::npos); }
219 }
220 return ret;
221 }
222 bool INICache::stringToBoolean(const std::string& buffer, bool defaultValue)
223 {
224 if(buffer.empty()) { return defaultValue; }
225 std::stringstream sstream;
226 sstream << buffer;
227 int tmp = 0;
228 sstream >> tmp;
229 return (buffer.compare(std::string("true"))==0 || tmp > 0);
230 }
231 }
这里主要是说一下我做的这些接口的思路. 读取涉及到的get/set这些方法,是基本需求.提供了两个读取Ini buffer的方法,一个是loadFromFile(const std::string& filePath),这个接口主要是应对客户端从本地读取配置文件信息,而loadFromStream(const std::stringstream& sstream)接口则是作为预留,从buffer直接读取Ini配置,为什么会做这样一个接口的预留呢? 可以一起看一下getSectionNames这个接口方法,返回的是key值的集合.
我是这样考虑的,c++这部分的接口尽量的简单,实现基本的功能,尽量避开业务逻辑的操作.要什么,我就提供基本接口,除非是在lua那边实现效率不高,或者是复杂,才会考虑提供完整的功能.因为紧接着下面会做更新模块的功能,这部分至少会依赖读取配置中一条关于资源版本的信息.而从远程Http服务器下载资源的时候也还是需要做很多版本的检测处理,如果是增量更新的话,需要按照版本的先后顺序依次下载.这就是要对key值进行按照需求排序. 本来在c++这部分做很简单的,我只要用std::sort函数,提供一个compare函数就好了。可是这部分是和具体的更新业务逻辑关联的,所以我不应该在c++这部分实现,所以就只是提供给你获取集合的方法.然后自己去处理.
绑定这部分就没什么好说的了. getSectionNames loadFromStream createFromStream这三个函数接口需要skip掉,结合前面的更新需求,需要的话,另外绑定接口,传入lua callback,在C++这边调用getSectionNames接口就好了,具体细节在后面写到更新的时候自然就会写出来了.我写了一个测试:
1 ---------------------------------------------------------------------
2 -- @Author 小岩
3 -- @Created on 2014-12-26 21:29
4 -- @Brief INI解析测试
5 ---------------------------------------------------------------------
6 INICacheTestCase = class("INICacheTestCase", TestsCase)
7 -----------------------------------------------------------
8 -- 测试解析INI文件
9 function INICacheTestCase:run()
10 local iniCache = xy.INICache:createFromFile("src/Tests/INICache/INICache_conf.ini")
11 if iniCache == nil then
12 Logger.Error(" cannot get local conf file! ")
13 end
14 Logger.Info("load file succ!")
15 Logger.Info("%s", iniCache:getString("Section", "Key", "Failed"))
16 Logger.Info("%d", iniCache:getInteger("Section", "Integer", "-1"))
17 Logger.Info("%f", iniCache:getDouble("Section", "Double", "-1"))
18 local boolean = iniCache:getBoolean("Section", "Boolean", false)
19 if boolean == true then
20 Logger.Info("%s", "boolean == true")
21 else
22 Logger.Info("%s", "boolean == false")
23 end
24 end
25 ---------------------------------------------------------------------
26 -- End Of Lua File
27 ---------------------------------------------------------------------
需要注意的问题是,INICache读取文件后,将配置文件信息一直都是保存在map中的,所以不要在不同的地方对同一份配置文件做多次读取操作,这样的话,将配置再次持久化到设备的时候,配置信息就会错掉.所以最好是提供配置的单例操作方式.