cocos2dx资源加载机制(同步/异步)
来源:互联网 发布:手机订餐软件 编辑:程序博客网 时间:2024/06/06 04:27
首先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,返回ValueMapValueMap 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 style="white-space:pre"></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() 返回为truebool 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信息保存至RelativeDataif (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 style="white-space:pre"></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 style="font-family: Arial, Helvetica, sans-serif;">_imageInfoQueue,构造Texture2D,</span> Director::getInstance()->getScheduler()->schedule(schedule_selector(TextureCache::addImageAsyncCallBack), this, 0, 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, Ref *target, 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), this, 0, 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 style="white-space:pre"></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资源加载机制(同步/异步)
- cocos2dx 资源 异步加载
- cocos2dx异步加载资源制作Loading界面
- Unity资源加载的选择(同步/异步)
- Unity资源加载的选择(同步/异步)
- 异步实现顺序同步加载资源
- Cocos2dx之进度条使用,异步加载资源进缓存
- Cocos2dx 异步加载纹理
- 【cocos2dx 加载资源文件夹】
- 同步转异步机制
- javascript同步异步机制
- cocos2dx 3.x 异步加载
- Cocos2dx多线程与异步加载
- UE4 异步资源加载
- UE4 异步资源加载
- ue4-异步加载资源
- UE4异步加载资源
- tomcat发布项目以后修改项目名称
- 巴什博弈 HDU2188
- elasticsearch 配置详解
- kickstart配置文件详解
- IOS修改UINavigationBar高度
- cocos2dx资源加载机制(同步/异步)
- Unable to instantiate default tuplizer
- 同时使用NavigationController,TabBarController,模态页面造成的显示混乱
- 网上热传10个健身牛人六块腹肌训练教程
- 企业版证书($299)In-House方式发布指南
- Linux在本地使用yum安装软件 2
- UML用例图
- PO VO DAO DTO BO TO概念与区别
- Freeswitch中文用户手册(第四章 SIP)----2