cocos2dx资源加载机制(同步/异步)

前端之家收集整理的这篇文章主要介绍了cocos2dx资源加载机制(同步/异步)前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

首先cocos2dx里的资源,有png,plist(pvr),exportjson(json)大致这三类,我们也从这3类去研究相应的加载代码


本次代码分析基于:

cocos2dx3.2

1、png

png格式的资源,从sprite作为一个切入口来分析,一般Sprite的创建如下

Sprite* Sprite::create(const std::string& filename)

参数filename,是图片资源的路径。

内部调用的initWithFile

  1. Sprite*sprite=new(std::nothrow)Sprite();
  2. if(sprite&&sprite->initWithFile(filename))
  3. {
  4. sprite->autorelease();
  5. returnsprite;
  6. }

initWithFile方法

    Texture2D*texture=Director::getInstance()->getTextureCache()->addImage(filename);
  1. if(texture)
  2. Rectrect=Rect::ZERO;
  3. rect.size=texture->getContentSize();
  4. returninitWithTexture(texture,rect);
  5. }

在Texture2D * TextureCache::addImage(const std::string &path)方法是实际的载入资源的实现

    //将相对路径转换成绝对路径
  1. std::stringfullpath=FileUtils::getInstance()->fullPathForFilename(path);
  2. if(fullpath.size()==0)
  3. {
  4. returnnullptr;
  5. }
  6. //查找是否已经载入过,找到老资源,直接返回
  7. autoit=_textures.find(fullpath);
  8. if(it!=_textures.end())
  9. texture=it->second;

有传入的相对路径换成了绝对路径,其在找资源时,会搜索以下函数设置的搜索路径

void FileUtils::setSearchPaths(const std::vector<std::string>& searchPaths)

    boolbRet=image->initWithImageFile(fullpath);
  1. CC_BREAK_IF(!bRet);
  2. texture=newTexture2D();
  3. if(texture&&texture->initWithImage(image))
  4. #ifCC_ENABLE_CACHE_TEXTURE_DATA
  5. //cachethetexturefilename
  6. VolatileTextureMgr::addImageTexture(texture,fullpath);
  7. #endif
  8. //texturealreadyretained,noneedtore-retainit
  9. _textures.insert(std::make_pair(fullpath,texture));

没有找到,构造出Texture,然后按<fullpath,texture>放入_textures。以备下次下次资源载入时查找使用,

结论是:png这种资源是 资源的完全路径用来查找相应资源的。


2、plist 格式资源的载入方式

a.最原始的调用方式

void addSpriteFramesWithFile(const std::string& plist);

b.重载方式

void addSpriteFramesWithFile(const std::string&plist,Texture2D *texture);

void addSpriteFramesWithFile(const std::string& plist,const std::string& textureFileName);

void addSpriteFramesWithFile(const std::string& plist)分析如下,

    //这里做了一下cached,提高效率
  1. if(_loadedFileNames->find(plist)==_loadedFileNames->end())
  2. //转换成全路径,同理会在搜索路径里搜索
  3. std::stringfullPath=FileUtils::getInstance()->fullPathForFilename(plist);
  4. //解析plist,返回ValueMap
  5. ValueMapdict=FileUtils::getInstance()->getValueMapFromFile(fullPath);
  6. stringtexturePath("");
  7. //图片资源在plist里的Metadata/textureFileName
  8. if(dict.find("Metadata")!=dict.end())
  9. ValueMap&MetadataDict=dict["Metadata"].asValueMap();
  10. //trytoreadtexturefilenamefromMetadata
  11. texturePath=MetadataDict["textureFileName"].asString();
  12. }
  13. //因为plist里的图片资源都是文件名,而plist一般是一个相对路径,拼接一下
  14. if(!texturePath.empty())
  15. //buildtexturepathrelativetoplistfile
  16. texturePath=FileUtils::getInstance()->fullPathFromRelativeFile(texturePath.c_str(),plist);
  17. else
  18. //要是plist里没有找到Metadata/textureFileName,直接就是plist去后缀,该成plist的路径+.png
  19. //buildtexturepathbyreplacingfileextension
  20. texturePath=plist;
  21. //remove.xxx
  22. size_tstartPos=texturePath.find_last_of(".");
  23. texturePath=texturePath.erase(startPos);
  24. //append.png
  25. texturePath=texturePath.append(".png");
  26. CCLOG("cocos2d:SpriteFrameCache:Tryingtousefile%sastexture",texturePath.c_str());
  27. //熟悉的方法又来了,参考png格式资源载入的分析吧
  28. Texture2D*texture=Director::getInstance()->getTextureCache()->addImage(texturePath.c_str());
  29. //做一下善后的初始化工作
  30. addSpriteFramesWithDictionary(dict,texture);
  31. <spanstyle="white-space:pre"></span>//开头怎么cached检查的,最后把自己也加入吧
  32. _loadedFileNames->insert(plist);
  33. CCLOG("cocos2d:SpriteFrameCache:Couldn'tloadtexture");
  34. 基本分都写在代码注释里了,其实plist格式资源,图片相关资源还是最后调用

    Texture2D *texture = Director::getInstance()->getTextureCache()->addImage(texturePath.c_str());

    也是plist的图片资源,被便签为:plist的全路径改后缀为.png,但是plist里有很多子块SpriteFrame,那么这些小图块是怎么组织安排的,这些小SpriteFrame是在

    void SpriteFrameCache::addSpriteFramesWithDictionary(ValueMap& dictionary,Texture2D* texture)

    中处理的,

      //解析frames块
    1. ValueMap&framesDict=dictionary["frames"].asValueMap();
    2. intformat=0;
    3. //主要获取format数据,用来判断图块参数格式
    4. //gettheformat
    5. if(dictionary.find("Metadata")!=dictionary.end())
    6. ValueMap&MetadataDict=dictionary["Metadata"].asValueMap();
    7. format=MetadataDict["format"].asInt();
    8. //checktheformat
    9. CCASSERT(format>=0&&format<=3,"formatisnotsupportedforSpriteFrameCacheaddSpriteFramesWithDictionary:textureFilename:");
    10. //遍历每一个frame
    11. for(autoiter=framesDict.begin();iter!=framesDict.end();++iter)
    12. ValueMap&frameDict=iter->second.asValueMap();
    13. //plist每一个frame的key字段,其实就是这个块的原始独立文件
    14. std::stringspriteFrameName=iter->first;
    15. SpriteFrame*spriteFrame=_spriteFrames.at(spriteFrameName);
    16. if(spriteFrame)
    17. continue;
    18. ...
    19. //关键是这里,这里以每个图块的文件名作为key来索引该图块SpriteFrame,
    20. //所以经常会原点资源冲突的问题,也源于此,
    21. //虽然你的plist不冲突,但是里面冲突也不行,所以资源的命名最好定好相应规则
    22. _spriteFrames.insert(spriteFrameName,spriteFrame);
    23. SpriteFrameCache是资源冲突比较高发的地方,由于plist是很多小资源打包在一起的,所以在制作图片资源的时候,命名的规则很重要,否则就是一个坑。

      3.ExportJson格式资源载入分析

      ExportJson是cocostudio导出的格式,是一种json格式,可读性的导出方式。其载入的入口是

      void ArmatureDataManager::addArmatureFileInfo(const std::string& configFilePath)

        //生成一个以configFilePath为key的RelativeData,在remove的时候会用得着,
      1. //相当于是一个cache,里面有armature里有的一些东西
      2. addRelativeData(configFilePath);
      3. //资源在解析的时候就载入
      4. _autoLoadSpriteFile=true;
      5. DataReaderHelper::getInstance()->addDataFromFile(configFilePath);

      一下是void DataReaderHelper::addDataFromFile(const std::string& filePath) 的分析:

      a.首先依旧是从cache机制里找一找,找到的就是已经载入过,直接放回

        for(unsignedinti=0;i<_configFileList.size();i++)
      1. if(_configFileList[i]==filePath)
      2. return;
      3. _configFileList.push_back(filePath);

      b.接下来,就是判断参数的后缀是.csb二进制格式,还是文本格式,打开文件的模式不一样。

        //这里在读入文件时,加锁了,由于读写文件不是线程安全的,所以这里加锁,但是这个函数有在非主线程调用过吗?
      1. _dataReaderHelper->_getFileMutex.lock();
      2. unsignedchar*pBytes=FileUtils::getInstance()->getFileData(filePath,filemode.c_str(),&filesize);
      3. std::stringcontentStr((constchar*)pBytes,filesize);
      4. _dataReaderHelper->_getFileMutex.unlock();
      5. DataInfodataInfo;
      6. //参数的文件路径
      7. dataInfo.filename=filePathStr;
      8. dataInfo.asyncStruct=nullptr;
      9. //参数的目录路径
      10. dataInfo.baseFilePath=basefilePath;
      11. if(str==".xml")
      12. DataReaderHelper::addDataFromCache(contentStr,&dataInfo);
      13. elseif(str==".json"||str==".ExportJson")
      14. //本次只分析该载入方式
      15. DataReaderHelper::addDataFromJsonCache(contentStr,&dataInfo);
      16. elseif(isbinaryfilesrc)
      17. DataReaderHelper::addDataFromBinaryCache(contentStr.c_str(),43); font-size:13.63636302948px; font-family:Arial; line-height:26px"> 在void DataReaderHelper::addDataFromJsonCache(const std::string& fileContent,DataInfo *dataInfo)中,开始解析ExportJson里的东西。过滤utf bom,解析json

        紧接着是几板斧,

        1)解析armatures

        2)解析animations

        3)解析textures

        我们关注图片资源的载入方式,前2种在此略过。

          //ExportJson文件中texture_data字段下纹理个数
        1. length=DICTOOL->getArrayCount_json(json,TEXTURE_DATA);
        2. for(inti=0;i<length;i++)
        3. constrapidjson::Value&textureDic=DICTOOL->getSubDictionary_json(json,TEXTURE_DATA,i);
        4. //解析texture_data,看看下面关于texture_data的格式示例
        5. TextureData*textureData=decodeTexture(textureDic);
        6. //在同步加载方式时,这里为空,后面分析异步在分析
        7. if(dataInfo->asyncStruct)
        8. _dataReaderHelper->_addDataMutex.lock();
        9. //载入当前这个texture_data的图片资源
        10. //这样的第一个参数是图块的名称,第三个参数为exportJson的路径
        11. ArmatureDataManager::getInstance()->addTextureData(textureData->name.c_str(),textureData,dataInfo->filename.c_str());
        12. //textureData创建时1,addTextureData是加入Map结构retain了一次,变成了2,这里release一下,变成1.
        13. textureData->release();
        14. if(dataInfo->asyncStruct)
        15. _dataReaderHelper->_addDataMutex.unlock();

        16. 关于texture_data的json格式有哪些内容

          [plain]
            {
          1. "name":"png/shitouren01_R_xiabi",
          2. "width":83.0,
          3. "height":88.0,226)"> "pX":0.0,226)"> "pY":1.0,226)"> "plistFile":""
          4. },

          基本对应着类TextureData

          void ArmatureDataManager::addTextureData(const std::string& id,TextureData *textureData,const std::string& configFilePath)

            //还记得最开始的时候,就为本exportjson创建了一个RelativeData,226)"> if(RelativeData*data=getRelativeData(configFilePath))
          1. //纹理资源放入对应的容器里,这里放入的子块的名称
          2. data->textures.push_back(id);
          3. //对字块名称与其对应的texturedata建立一种映射,方便查找
          4. _textureDatas.insert(id,textureData);

          最后解析的最后,开始解析资源配置字段了,

            //根据前面的分析,ArmatureDataManager::getInstance()->isAutoLoadSpriteFile()返回为true
          1. boolautoLoad=dataInfo->asyncStruct==nullptr?ArmatureDataManager::getInstance()->isAutoLoadSpriteFile():dataInfo->asyncStruct->autoLoadSpriteFile;
          2. if(autoLoad)
          3. //分析config_file_path字段
          4. //json[CONFIG_FILE_PATH].IsNull()?0:json[CONFIG_FILE_PATH].Size();
          5. constchar*path=DICTOOL->getStringValueFromArray_json(json,CONFIG_FILE_PATH,i);//json[CONFIG_FILE_PATH][i].IsNull()?nullptr:json[CONFIG_FILE_PATH][i].GetString();
          6. if(path==nullptr)
          7. CCLOG("loadCONFIG_FILE_PATHerror.");
          8. std::stringfilePath=path;
          9. filePath=filePath.erase(filePath.find_last_of("."));
          10. //异步加载方式
          11. dataInfo->configFileQueue.push(filePath);
          12. else//同步加载
          13. //这里直接写死了,一个png,一个plist,
          14. //实际在exportJson导出的格式,是有config_png_path与config_file_path
          15. std::stringplistPath=filePath+".plist";
          16. std::stringpngPath=filePath+".png";
          17. //这里开始加入图片资源了
          18. ArmatureDataManager::getInstance()->addSpriteFrameFromFile((dataInfo->baseFilePath+plistPath).c_str(),(dataInfo->baseFilePath+pngPath).c_str(),43); font-size:13.63636302948px; font-family:Arial; line-height:26px"> exprotJson里资源配置示例如下:

            [html]
              "config_file_path":[
            1. "020.plist"
            2. ],226)"> "config_png_path":[
            3. "020.png"
            4. ]

            资源载入方法void ArmatureDataManager::addSpriteFrameFromFile(const std::string& plistPath,const std::string& imagePath,const std::string& configFilePath)里

              //将plist信息保存至RelativeData
            1. data->plistFiles.push_back(plistPath);
            2. //SpriteFrameCacheHelper只是SpriteFrameCache的简单包装,实际就是调用的SpriteFrameCache::addSpriteFrameFromFile
            3. //plistPath是exportJson的路径改后缀为plist,同理imagePath
            4. SpriteFrameCacheHelper::getInstance()->addSpriteFrameFromFile(plistPath,imagePath);

            至此,armature资源载入流程分析完毕,总结下armature:

            在texturedata中,是子块的名称为key的,我们通过分析SpriteFrameCache知道,其内部资源也是以字块为key的,在cocostudio里我们设计动作或者ui的时候,都是子块的名称

            综合来分析:

            单个png资源,是以该资源的全路径为key的,由TextureCache来维持

            plist资源集式的资源,其依赖的png,依然是上述方式,不过在其基础上,通过SpriteFrameCache做了一层二级的缓存机制,是以里面每个子块名称作为key映射相关rect信息的SpriteFrame,

            异步载入分析:

            从了解的情况来看,有cocos2dx提供2种资源异步加载方式,一个原始图片资源的异步加载

            void TextureCache::addImageAsync(const std::string &path,const std::function<void(Texture2D*)>& callback)

            另一个就是上面我们接触到的Armature的异步加载方式,

            void ArmatureDataManager::addArmatureFileInfoAsync(const std::string& configFilePath,Ref *target,SEL_SCHEDULE selector)

            下面我逐一分析,先从原始图片资源异步加载方式开刀:

              <spanstyle="white-space:pre"></span>Texture2D*texture=nullptr;
            1. //将路径转换成全路径
            2. //先从cache查找一下,有直接返回
            3. autoit=_textures.find(fullpath);
            4. if(it!=_textures.end())
            5. texture=it->second;
            6. if(texture!=nullptr)
            7. //找到了,调用一下回调方法
            8. callback(texture);
            9. //异步加载需要用到的一些结构
            10. //lazyinit
            11. if(_asyncStructQueue==nullptr)
            12. _asyncStructQueue=newqueue<AsyncStruct*>();
            13. _imageInfoQueue=newdeque<ImageInfo*>();
            14. //createanewthreadtoloadimages
            15. //开辟新的线程来处理本次加载任务,主要是防止重复加载,并实际加载图片资源,加载完之后放入_imageInfoQueue队列,
            16. //等待TextureCache::addImageAsyncCallBack来处理
            17. _loadingThread=newstd::thread(&TextureCache::loadImage,this);
            18. _needQuit=false;
            19. if(0==_asyncRefCount)
            20. //每帧调用,主要处理<spanstyle="font-family:Arial,sans-serif;">_imageInfoQueue,构造Texture2D,</span>
            21. Director::getInstance()->getScheduler()->schedule(schedule_selector(TextureCache::addImageAsyncCallBack),this,false);
            22. ++_asyncRefCount;
            23. //开始构造异步加载的一些数据结构,给加载线程TextureCache::loadImage使用
            24. //放入的是资源的全路径以及加载完成时的回调
            25. //这个数据结构是new出来的,在TextureCache::addImageAsyncCallBack里释放
            26. //generateasyncstruct
            27. AsyncStruct*data=newAsyncStruct(fullpath,callback);
            28. //这里产生了任务,等待工作线程来处理
            29. //addasyncstructintoqueue
            30. _asyncStructQueueMutex.lock();
            31. _asyncStructQueue->push(data);
            32. _asyncStructQueueMutex.unlock();
            33. //告诉一下工作线程,有任务了
            34. _sleepCondition.notify_one();

            结合上述的注释,基本上可以理解图片资源异步加载的基本原理了。

            下面是Armature的异步加载分析:

              voidArmatureDataManager::addArmatureFileInfoAsync(conststd::string&configFilePath,Ref*target,SEL_SCHEDULEselector)
            1. //同同步加载,建立一个以configFilePath为key的RelativeData,用于remove
            2. addRelativeData(configFilePath);
            3. //
            4. _autoLoadSpriteFile=true;
            5. //
            6. DataReaderHelper::getInstance()->addDataFromFileAsync("","",configFilePath,target,selector);
              1. if(target&&selector)
              2. if(_asyncRefTotalCount==0&&_asyncRefCount==0)
              3. (target->*selector)(1);
              4. (target->*selector)((_asyncRefTotalCount-_asyncRefCount)/(float)_asyncRefTotalCount);
              5. return;
              6. 一开始,依旧是从cache里查找一下,看是不是已经加载了,加载了,则调用下回调函数,这里的统计任务个数,后面会讲到,将本加载配置文件加入cache中。

                  //准备异步加载需要的数据结构
                1. //lazyinit
                2. if(_asyncStructQueue==nullptr)
                3. _asyncStructQueue=newstd::queue<AsyncStruct*>();
                4. _dataQueue=newstd::queue<DataInfo*>();
                5. //开辟工作线程,用来解析exportJson
                6. //基本同图片资源异步加载方式,只不过这里调用的只是解析,DataReaderHelper::addDataFromJsonCache
                7. //完成解析后,构造DataInfo数据,交给DataReaderHelper::addDataAsyncCallBack来处理
                8. _loadingThread=newstd::thread(&DataReaderHelper::loadData,this);
                9. need_quit=false;
                10. if(0==_asyncRefCount)
                11. //用来加载DataInfo中的configQueue,还记得DataReaderHelper::addDataFromJsonCache里异步加载部分吧,就是在哪里push进去的
                12. //最后调用ArmatureDataManager::addSpriteFrameFromFile来加载plist,png资源,你没看错,所以Armature异步加载是不完整的
                13. Director::getInstance()->getScheduler()->schedule(schedule_selector(DataReaderHelper::addDataAsyncCallBack),0); background-color:inherit">//回调时告诉回调函数的进度
                14. ++_asyncRefCount;
                15. ++_asyncRefTotalCount;
                16. //由于回调是成员方法,方式其宿主提前释放
                17. if(target)
                18. target->retain();
                  1. voidDataReaderHelper::addDataAsyncCallBack(floatdt)
                  2. //取任务
                  3. DataInfo*pDataInfo=dataQueue->front();
                  4. dataQueue->pop();
                  5. _dataInfoMutex.unlock();
                  6. AsyncStruct*pAsyncStruct=pDataInfo->asyncStruct;
                  7. //当调用voidArmatureDataManager::addArmatureFileInfoAsync(
                  8. //conststd::string&imagePath,conststd::string&plistPath,conststd::string&configFilePath,...
                  9. //时调用
                  10. if(pAsyncStruct->imagePath!=""&&pAsyncStruct->plistPath!="")
                  11. _getFileMutex.lock();
                  12. ArmatureDataManager::getInstance()->addSpriteFrameFromFile(pAsyncStruct->plistPath.c_str(),pAsyncStruct->imagePath.c_str(),pDataInfo->filename.c_str());
                  13. _getFileMutex.unlock();
                  14. //这个就是在DataReaderHelper::addDataFromJsonCache产生的
                  15. while(!pDataInfo->configFileQueue.empty())
                  16. std::stringconfigPath=pDataInfo->configFileQueue.front();
                  17. _getFileMutex.lock();
                  18. //这里是正规的加载SpriteFrame,所以你的先自己吧plist资源加载进来,通过cache来加速
                  19. ArmatureDataManager::getInstance()->addSpriteFrameFromFile((pAsyncStruct->baseFilePath+configPath+".plist").c_str(),(pAsyncStruct->baseFilePath+configPath+".png").c_str(),pDataInfo->filename.c_str());
                  20. _getFileMutex.unlock();
                  21. pDataInfo->configFileQueue.pop();
                  22. Ref*target=pAsyncStruct->target;
                  23. SEL_SCHEDULEselector=pAsyncStruct->selector;
                  24. //本次任务结束
                  25. --_asyncRefCount;
                  26. //调用回调
                  27. if(target&&selector)
                  28. //回调参数完成百分比
                  29. //还记得之前retain过吧
                  30. target->release();
                  31. //销毁辅助结构
                  32. deletepAsyncStruct;
                  33. deletepDataInfo;
                  34. //没有任务,就取消每帧调用
                  35. _asyncRefTotalCount=0;
                  36. Director::getInstance()->getScheduler()->unschedule(schedule_selector(DataReaderHelper::addDataAsyncCallBack),this);

                  37. 从上面的分析,我们可以看出Armature的异步加载,只是部分,而不是全部,只是把解析部分交给了线程,图片资源还是需要自己通过图片资源异步加载方式加载。


                    原文链接:https://www.f2er.com/cocos2dx/343896.html

                    猜你在找的Cocos2d-x相关文章