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

cocos2dx资源加载机制(同步异步),第1张

概述首先cocos2dx里的资源,有png,plist(pvr),exportjson(json)大致这三类,我们也从这3类去研究相应的加载代码。 本次代码分析基于: cocos2dx3.2 1、png png格式的资源,从sprite作为一个切入口来分析,一般Sprite的创建如下 Sprite* Sprite::create(const std::string& filename) 参数filen

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


本次代码分析基于:

cocos2dx3.2


1、png

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

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

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

内部调用的initWithfile

    Sprite *sprite = new (std::nothrow) Sprite();    if (sprite && sprite->initWithfile(filename))    {        sprite->autorelease();        return sprite;    }

initWithfile方法里

    Texture2D *texture = Director::getInstance()->getTextureCache()->addImage(filename);    if (texture)    {        Rect rect = Rect::ZERO;        rect.size = texture->getContentSize();        return initWithTexture(texture,rect);    }

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

    // 将相对路径转换成绝对路径    std::string fullpath = fileUtils::getInstance()->fullPathForfilename(path);  if (fullpath.size() == 0)  {    return nullptr;  }    // 查找是否已经载入过,找到老资源,直接返回  auto it = _textures.find(fullpath);  if( it != _textures.end() )    texture = it->second;

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

voID fileUtils::setSearchPaths(const std::vector<std::string>& searchPaths)

           bool bRet = image->initWithImagefile(fullpath);            CC_BREAK_IF(!bRet);            texture = new Texture2D();            if( texture && texture->initWithImage(image) )            {#if CC_ENABLE_CACHE_TEXTURE_DATA                // cache the texture file name                VolatileTextureMgr::addImageTexture(texture,fullpath);#endif                // texture already retained,no need to re-retain it                _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,提高效率    if (_loadedfilenames->find(pList) == _loadedfilenames->end())    {	// 转换成全路径,同理会在搜索路径里搜索        std::string fullPath = fileUtils::getInstance()->fullPathForfilename(pList);        // 解析pList,返回ValueMap		ValueMap dict = fileUtils::getInstance()->getValueMapFromfile(fullPath);        string texturePath("");	// 图片资源在pList里的Metadata/texturefilename        if (dict.find("Metadata") != dict.end())        {            ValueMap& MetadataDict = dict["Metadata"].asValueMap();            // try to read  texture file name from Meta data            texturePath = MetadataDict["texturefilename"].asstring();        }	// 因为pList里的图片资源都是文件名,而pList一般是一个相对路径,拼接一下        if (!texturePath.empty())        {            // build texture path relative to pList file            texturePath = fileUtils::getInstance()->fullPathFromrelativefile(texturePath.c_str(),pList);        }        else        {			// 要是pList里没有找到Metadata/texturefilename,直接就是pList去后缀,该成pList的路径+.png            // build texture path by replacing file extension            texturePath = pList;            // remove .xxx            size_t startPos = texturePath.find_last_of(".");             texturePath = texturePath.erase(startPos);            // append .png            texturePath = texturePath.append(".png");            cclOG("cocos2d: SpriteFrameCache: Trying to use file %s as texture",texturePath.c_str());        }	// 熟悉的方法又来了,参考png格式资源载入的分析吧        Texture2D *texture = Director::getInstance()->getTextureCache()->addImage(texturePath.c_str());        if (texture)        {		// 做一下善后的初始化工作            addSpriteFramesWithDictionary(dict,texture);	 <span >	</span>// 开头怎么cached检查的,最后把自己也加入吧            _loadedfilenames->insert(pList);        }        else        {            cclOG("cocos2d: SpriteFrameCache: Couldn't load texture");        }    }

基本分都写在代码注释里了,其实pList格式资源,图片相关资源还是最后调用的

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


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

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

中处理的,


	// 解析frames块	ValueMap& framesDict = dictionary["frames"].asValueMap();    int format = 0;	// 主要获取format数据,用来判断图块参数格式    // get the format    if (dictionary.find("Metadata") != dictionary.end())    {        ValueMap& MetadataDict = dictionary["Metadata"].asValueMap();        format = MetadataDict["format"].asInt();    }    // check the format    CCASSERT(format >=0 && format <= 3,"format is not supported for SpriteFrameCache addSpriteFramesWithDictionary:texturefilename:");	// 遍历每一个frame    for (auto iter = framesDict.begin(); iter != framesDict.end(); ++iter)    {        ValueMap& frameDict = iter->second.asValueMap();        // pList每一个frame的key字段,其实就是这个块的原始独立文件名		std::string spriteFramename = iter->first;        SpriteFrame* spriteFrame = _spriteFrames.at(spriteFramename);        if (spriteFrame)        {            continue;        }		...				// 关键是这里,这里以每个图块的文件名作为key来索引该图块SpriteFrame,// 所以经常会原点资源冲突的问题,也源于此,		// 虽然你的pList不冲突,但是里面冲突也不行,所以资源的命名最好定好相应规则		 _spriteFrames.insert(spriteFramename,spriteFrame);	}

SpriteFrameCache是资源冲突比较高发的地方,由于pList是很多小资源打包在一起的,所以在制作图片资源的时候,命名的规则很重要,否则就是一个坑。


3.ExportJson格式资源载入分析

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

voID ArmatureDataManager::addArmaturefileInfo(const std::string& configfilePath)


   // 生成一个以configfilePath为key的relativeData,在remove的时候会用得着,     // 相当于是一个cache,里面有armature里有的一些东西     addrelativeData(configfilePath);     // 资源在解析的时候就载入  _autoLoadSpritefile = true;  DataReaderHelper::getInstance()->addDataFromfile(configfilePath);

一下是voID DataReaderHelper::addDataFromfile(const std::string& filePath) 的分析:


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

    for(unsigned int i = 0; i < _configfileList.size(); i++)    {        if (_configfileList[i] == filePath)        {            return;        }    }    _configfileList.push_back(filePath);

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

	// 这里在读入文件时,加锁了,由于读写文件不是线程安全的,所以这里加锁,但是这个函数有在非主线程调用过吗?	_dataReaderHelper->_getfileMutex.lock();    unsigned char *pBytes = fileUtils::getInstance()->getfileData(filePath,filemode.c_str(),&filesize);    std::string contentStr((const char*)pBytes,filesize);    _dataReaderHelper->_getfileMutex.unlock();        DataInfo dataInfo;	// 参数的文件路径    dataInfo.filename = filePathStr;    dataInfo.asyncStruct = nullptr;	// 参数的目录路径    dataInfo.basefilePath = basefilePath;    if (str == ".xml")    {        DataReaderHelper::addDataFromCache(contentStr,&dataInfo);    }    else if(str == ".Json" || str == ".ExportJson")    {		// 本次只分析该载入方式        DataReaderHelper::addDataFromJsonCache(contentStr,&dataInfo);    }    else if(isbinaryfilesrc)    {        DataReaderHelper::addDataFromBinaryCache(contentStr.c_str(),&dataInfo);    }

在voID DataReaderHelper::addDataFromJsonCache(const std::string& fileContent,DataInfo *dataInfo)中,开始解析ExportJson里的东西。过滤utf bom,解析Json

紧接着是几板斧,

1)解析armatures

2)解析animations

3)解析textures

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


    // ExportJson 文件中texture_data字段下纹理个数    length = DICTOol->getArrayCount_Json(Json,TEXTURE_DATA);     for (int i = 0; i < length; i++)    {        const rAPIdJson::Value &textureDic =  DICTOol->getSubDictionary_Json(Json,TEXTURE_DATA,i);        // 解析texture_data,看看下面关于texture_data的格式示例	TextureData *textureData = decodeTexture(textureDic);	// 在同步加载方式时,这里为空,后面分析异步在分析        if (dataInfo->asyncStruct)        {            _dataReaderHelper->_addDataMutex.lock();        }	// 载入当前这个texture_data的图片资源	// 这样的第一个参数是 图块的名称, 第三个参数为exportJson的路径        ArmatureDataManager::getInstance()->addTextureData(textureData->name.c_str(),textureData,dataInfo->filename.c_str());        	// textureData创建时1,addTextureData是加入Map结构retain了一次,变成了2,这里release一下,变成1.	textureData->release();        if (dataInfo->asyncStruct)        {            _dataReaderHelper->_addDataMutex.unlock();        }    }


关于texture_data的Json格式有哪些内容:

    {      "name": "png/shitouren01_R_xiabi","wIDth": 83.0,"height": 88.0,"pX": 0.0,"pY": 1.0,"pListfile": ""    },

基本对应着类TextureData

voID ArmatureDataManager::addTextureData(const std::string& ID,TextureData *textureData,const std::string& configfilePath)

	// 还记得最开始的时候,就为本exportJson创建了一个relativeData,if (relativeData *data = getrelativeData(configfilePath))    {		// 纹理资源放入对应的容器里,这里放入的子块的名称        data->textures.push_back(ID);    }	// 对字块名称与其对应的texturedata建立一种映射,方便查找    _textureDatas.insert(ID,textureData);

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

// 根据前面的分析,ArmatureDataManager::getInstance()->isautoLoadSpritefile() 返回为true	bool autoLoad = dataInfo->asyncStruct == nullptr ? ArmatureDataManager::getInstance()->isautoLoadSpritefile() : dataInfo->asyncStruct->autoLoadSpritefile;    if (autoLoad)    {		// 分析config_file_path字段        length =  DICTOol->getArrayCount_Json(Json,CONfig_file_PATH); // Json[CONfig_file_PATH].IsNull() ? 0 : Json[CONfig_file_PATH].Size();        for (int i = 0; i < length; i++)        {			const char *path = DICTOol->getStringValueFromArray_Json(Json,CONfig_file_PATH,i); // Json[CONfig_file_PATH][i].IsNull() ? nullptr : Json[CONfig_file_PATH][i].GetString();            if (path == nullptr)            {                cclOG("load CONfig_file_PATH error.");                return;            }            std::string filePath = path;            filePath = filePath.erase(filePath.find_last_of("."));			// 异步加载方式            if (dataInfo->asyncStruct)            {                dataInfo->configfileQueue.push(filePath);            }            else // 同步加载            {				// 这里直接写死了,一个png,一个pList,				// 实际在exportJson导出的格式,是有config_png_path与config_file_path                std::string pListPath = filePath + ".pList";                std::string pngPath =  filePath + ".png";				// 这里开始加入图片资源了                ArmatureDataManager::getInstance()->addSpriteFrameFromfile((dataInfo->basefilePath + pListPath).c_str(),(dataInfo->basefilePath + pngPath).c_str(),dataInfo->filename.c_str());            }        }    }

exprotJson里资源配置示例如下:

  "config_file_path": [    "020.pList"  ],"config_png_path": [    "020.png"  ]

资源载入方法voID ArmatureDataManager::addSpriteFrameFromfile(const std::string& pListPath,const std::string& imagePath,const std::string& configfilePath)里


	// 将pList信息保存至relativeData	if (relativeData *data = getrelativeData(configfilePath))    {        data->pListfiles.push_back(pListPath);    }		// SpriteFrameCacheHelper 只是SpriteFrameCache 的简单包装,实际就是调用的SpriteFrameCache::addSpriteFrameFromfile	// pListPath 是exportJson的路径改后缀为pList,同理imagePath    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)


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

<span >	</span>Texture2D *texture = nullptr;		// 将路径转换成全路径    std::string fullpath = fileUtils::getInstance()->fullPathForfilename(path);	// 先从cache查找一下,有直接返回    auto it = _textures.find(fullpath);    if( it != _textures.end() )        texture = it->second;    if (texture != nullptr)    {		// 找到了,调用一下回调方法        callback(texture);        return;    }	// 异步加载需要用到的一些结构    // lazy init    if (_asyncStructQueue == nullptr)    {                     _asyncStructQueue = new queue<AsyncStruct*>();        _imageInfoQueue   = new deque<ImageInfo*>();                // create a new thread to load images				// 开辟新的线程来处理本次加载任务,主要是防止重复加载,并实际加载图片资源,加载完之后放入_imageInfoqueue队列,		// 等待TextureCache::addImageAsyncCallBack 来处理        _loadingThread = new std::thread(&TextureCache::loadImage,this);        _needQuit = false;    }    if (0 == _asyncRefCount)    {		// 每帧调用,主要处理<span >_imageInfoQueue,构造Texture2D,</span>        Director::getInstance()->getScheduler()->schedule(schedule_selector(TextureCache::addImageAsyncCallBack),this,false);    }    ++_asyncRefCount;	// 开始构造异步加载的一些数据结构,给加载线程TextureCache::loadImage使用	// 放入的是资源的全路径以及加载完成时的回调	// 这个数据结构是new出来的,在TextureCache::addImageAsyncCallBack里释放    // generate async struct    AsyncStruct *data = new AsyncStruct(fullpath,callback);	// 这里产生了任务,等待工作线程来处理    // add async struct into queue    _asyncStructQueueMutex.lock();    _asyncStructQueue->push(data);    _asyncStructQueueMutex.unlock();	// 告诉一下工作线程,有任务了    _sleepCondition.notify_one();

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


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

voID ArmatureDataManager::addArmaturefileInfoAsync(const std::string& configfilePath,SEL_SCHEDulE selector){	// 同同步加载,建立一个以configfilePath为key的relativeData,用于remove    addrelativeData(configfilePath);	//    _autoLoadSpritefile = true;		//    DataReaderHelper::getInstance()->addDataFromfileAsync("","",configfilePath,target,selector);}

for(unsigned int i = 0; i < _configfileList.size(); i++)    {        if (_configfileList[i] == filePath)        {            if (target && selector)            {                if (_asyncRefTotalCount == 0 && _asyncRefCount == 0)                {                    (target->*selector)(1);                }                else                {                    (target->*selector)((_asyncRefTotalCount - _asyncRefCount) / (float)_asyncRefTotalCount);                }            }            return;        }    }    _configfileList.push_back(filePath);

一开始,依旧是从cache里查找一下,看是不是已经加载了,加载了,则调用下回调函数,这里的统计任务个数,后面会讲到,将本加载配置文件加入cache中。
    // 准备异步加载需要的数据结构    // lazy init    if (_asyncStructQueue == nullptr)    {        _asyncStructQueue = new std::queue<AsyncStruct *>();        _dataQueue = new std::queue<DataInfo *>();	// 开辟工作线程,用来解析exportJson	// 基本同图片资源异步加载方式,只不过这里调用的只是解析,DataReaderHelper::addDataFromJsonCache	// 完成解析后,构造DataInfo数据,交给DataReaderHelper::addDataAsyncCallBack来处理	// create a new thread to load images	_loadingThread = new std::thread(&DataReaderHelper::loadData,this);        need_quit = false;    }    if (0 == _asyncRefCount)    {	// 用来加载DataInfo中的configQueue,还记得DataReaderHelper::addDataFromJsonCache里异步加载部分吧,就是在哪里push进去的	// 最后调用ArmatureDataManager::addSpriteFrameFromfile来加载pList,png资源,你没看错,所以Armature异步加载是不完整的        Director::getInstance()->getScheduler()->schedule(schedule_selector(DataReaderHelper::addDataAsyncCallBack),false);    }     // 回调时告诉回调函数的进度    ++_asyncRefCount;    ++_asyncRefTotalCount;     // 由于回调是成员方法,方式其宿主提前释放    if (target)    {        target->retain();    }

voID DataReaderHelper::addDataAsyncCallBack(float dt){	...			// 取任务        DataInfo *pDataInfo = dataQueue->front();        dataQueue->pop();        _dataInfoMutex.unlock();        AsyncStruct *pAsyncStruct = pDataInfo->asyncStruct;	// 当调用voID ArmatureDataManager::addArmaturefileInfoAsync(	//	const std::string& imagePath,const std::string& pListPath,const std::string& configfilePath,...	// 时调用        if (pAsyncStruct->imagePath != "" && pAsyncStruct->pListPath != "")        {            _getfileMutex.lock();            ArmatureDataManager::getInstance()->addSpriteFrameFromfile(pAsyncStruct->pListPath.c_str(),pAsyncStruct->imagePath.c_str(),pDataInfo->filename.c_str());            _getfileMutex.unlock();        }	// 这个就是在DataReaderHelper::addDataFromJsonCache产生的        while (!pDataInfo->configfileQueue.empty())        {            std::string configPath = pDataInfo->configfileQueue.front();            _getfileMutex.lock();				<span >	</span>// 这里是正规的加载SpriteFrame,所以你的先自己吧pList资源加载进来,通过cache来加速            ArmatureDataManager::getInstance()->addSpriteFrameFromfile((pAsyncStruct->basefilePath + configPath + ".pList").c_str(),(pAsyncStruct->basefilePath + configPath + ".png").c_str(),pDataInfo->filename.c_str());            _getfileMutex.unlock();            pDataInfo->configfileQueue.pop();        }		        Ref* target = pAsyncStruct->target;        SEL_SCHEDulE selector = pAsyncStruct->selector;	// 本次任务结束        --_asyncRefCount;	// 调用回调        if (target && selector)        {	    // 回调参数完成百分比            (target->*selector)((_asyncRefTotalCount - _asyncRefCount) / (float)_asyncRefTotalCount);            // 还记得之前retain过吧	     target->release();        }	// 销毁辅助结构        delete pAsyncStruct;        delete pDataInfo;	// 没有任务,就取消每帧调用        if (0 == _asyncRefCount)        {            _asyncRefTotalCount = 0;            Director::getInstance()->getScheduler()->unschedule(schedule_selector(DataReaderHelper::addDataAsyncCallBack),this);        }


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

总结

以上是内存溢出为你收集整理的cocos2dx资源加载机制(同步/异步)全部内容,希望文章能够帮你解决cocos2dx资源加载机制(同步/异步)所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/1048596.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-25
下一篇 2022-05-25

发表评论

登录后才能评论

评论列表(0条)

保存