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);
            }
          }
      }