上QQ阅读APP看书,第一时间看更新
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); } } }