cocos2d-x開發: 一切應該從配置文件讀取開始


想要做一款完整的游戲,應該從配置文件讀取開始。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 }
INICache.cc

 

 這里主要是說一下我做的這些接口的思路. 讀取涉及到的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中的,所以不要在不同的地方對同一份配置文件做多次讀取操作,這樣的話,將配置再次持久化到設備的時候,配置信息就會錯掉.所以最好是提供配置的單例操作方式.

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM