- 精通Cocos2d-x游戲開發(進階卷)
- 王永寶
- 940字
- 2020-11-28 22:37:03
6.2 異步加載CSB
即使使用了緩存的方案,首次加載CSB文件還是會阻塞一段時間,因為這里面還包含了紋理的加載,如果要加載的紋理比較大或者要加載多個紋理,或者要同時加載多個CSB文件,那么就會有比較明顯的卡頓。如果能夠將CSB文件進行異步加載,就可以很好地改善這個問題。CSLoader是不支持異步加載CSB的,如果將CSB文件的加載分為加載紋理和創建節點兩部分,那么創建節點這部分是無法做到線程安全的!因為各種節點在創建時操作了各種單例對象,如從TextureCache中獲取紋理,在EventDispatcher中注冊觸摸事件等。在子線程和主線程中同時操作這些資源,很可能導致程序崩潰或出現其他異常。
使用了緩存方案之后,主要的瓶頸在紋理加載這里,所以可以使用TextureCache的異步加載紋理的方法,將CSB所需的紋理進行異步加載,加載完之后再在主線程中執行創建節點的邏輯。接下來的問題就是如何知道每個CSB需要加載哪些紋理。可以通過一個簡單的方法解析CSB文件,得到所需的紋理,但這個方法的效率不高,所以最好是通過另外一個簡單的程序,生成一個配置表,在配置表中記錄每個CSB文件所需的紋理列表,然后直接使用這個配置表。使用下面的方法可以遞歸找出一個CSB文件加載所需的全部紋理。
//傳入CSB文件的Data,以及用于保存紋理文件名的set,查找單個CSB所引用的所有紋理 void CCsbLoader::searchTexturesByCsbFile(Data& data, set<string>& texSet) { auto csparsebinary = GetCSParseBinary(data.getBytes()); auto textures = csparsebinary->textures(); int textureSize = csparsebinary->textures()->size(); for (int i = 0; i < textureSize; ++i) { string plistFile = FileUtils::getInstance()->fullPathForFilename (textures->Get(i)->c_str()); if (m_LoadingPlists.find(plistFile) ! = m_LoadingPlists.end() || SpriteFrameCache::getInstance()->isSpriteFramesWithFileLoaded (plistFile)) { continue; } m_LoadingPlists.insert(plistFile); Data plistData = FileUtils::getInstance()->getDataFromFile (plistFile); if (plistData.isNull()) { continue; } string textureFile; ValueMap dict = FileUtils::getInstance()->getValueMapFromData( reinterpret_cast<const char*>(plistData.getBytes()), plistData. getSize()); if (dict.find("metadata") ! = dict.end()) { ValueMap& metadataDict = dict["metadata"].asValueMap(); textureFile = metadataDict["textureFileName"].asString(); } if (! textureFile.empty()) { //計算相對路徑,將紋理的文件名對應到plist的路徑下 textureFile = FileUtils::getInstance()->fullPathFromRelativeFile (textureFile, plistFile); } else { //如果plist文件中沒有紋理路徑名,則嘗試讀取plist對應的.png textureFile = plistFile; //將xxxx.plist結尾的.plist移除,替換成.png textureFile = textureFile.erase(textureFile.find_last_of(".")); textureFile = textureFile.append(".png"); } //該紋理未被加載且沒有在待加載列表中,則添加進texSet中 if (Director::getInstance()->getTextureCache()->getTextureForKey (textureFile) == nullptr && m_LoadingTextures.find(textureFile) == m_ LoadingTextures.end()) { m_LoadingTextures.insert(textureFile); texSet.insert(textureFile); } } }
searchTexturesByCsbNodeTree()方法可以遞歸查找一個CSB節點的所有嵌套CSB文件所引用到的紋理,傳入一個對象和一個set容器,CSB文件所引用到的紋理都會被存儲到容器中。
void CCsbLoader::searchTexturesByCsbNodeTree(const flatbuffers::NodeTree* tree, set<string>& texSet) { //對所有的子節點做相同的處理 auto children = tree->children(); int size = children->size(); for (int i = 0; i < size; ++i) { auto subNodeTree = children->Get(i); //對于CsbNode子節點,需要一并加載進來 auto options = subNodeTree->options(); std::string classname = subNodeTree->classname()->c_str(); if (classname == "ProjectNode") { auto projectNodeOptions = (ProjectNodeOptions*)options->data(); std::string filePath = FileUtils::getInstance()-> fullPathForFilename( projectNodeOptions->fileName()->c_str()); //有此文件且未加載過該文件 //如果已經搜索過,則沒必要再搜索 if (! filePath.empty() && m_CsbNodes.find(filePath) == m_CsbNodes.end() && m_CheckedCsb.find(filePath) == m_CheckedCsb.end()) { m_CheckedCsb.insert(filePath); Data data = FileUtils::getInstance()->getDataFromFile (filePath); if (! data.isNull()) { m_CsbFileCache[filePath] = data; //找到這個CSB所引用的Png searchTexturesByCsbFile(data, texSet); auto csparsebinary = GetCSParseBinary(data.getBytes()); //對該CSB進行遞歸 searchTexturesByCsbNodeTree(csparsebinary->nodeTree(), texSet); } } } else { searchTexturesByCsbNodeTree(subNodeTree, texSet); } } }