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

原创 2015年03月18日 16:00:35

首先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 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() 返回为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 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里的资源,有png,plist(pvr),exportjson(json)大致这三类,我们也从这3类去研究相应的加载代码。 本次代码分析基于: cocos2...

cocos2d-x Loading界面实现资源加载

有时候场景中的资源加载过多的话就会引起游戏进入的时候很卡,因为那是边加载边显示。在tests例子里面有一个很好的例子叫做TextureCacheTest,里面讲解了如何写loading。 ...

cocos2d-x Loading界面实现资源加载

有时候场景中的资源加载过多的话就会引起游戏进入的时候很卡,因为那是边加载边显示。在tests例子里面有一个很好的例子叫做TextureCacheTest,里面讲解了如何写loading。 #inc...

Cocos Creator 获取和加载资源(摘自官方文档)

获取和加载资源 Cocos Creator 有一套统一的资源管理机制 ,在本篇教程,我们将介绍 资源的分类如何在 属性检查器 里设置资源动态加载 Asset动态加载 Raw Asset 资源的分...

Cocos2dx v3.2骨骼动画加载学习

一、使用Cocos Studio之前需要导入GUI、Extensions、CocosStudio的类库 右击解决方案选择添加现有项目添加这三个库文件导入成功效果如下 二、选择工程右...

Cocos2dx之进度条使用,异步加载资源进缓存

在游戏前通常都会加载好游戏中需要用到的资源,游戏的时候绘图才不会让内存飙升,不那么卡顿, 加载资源存到缓存时有一点点需要等待的时间,那么就设置一个场景显示一下进度条,加载完成就切换场景进入主游戏 进度...

Cocos2dx教程-多线程与异步加载

1.为什么要使用多线程 Cocos2d-x是一个单线程循环的引擎,引擎通过每一帧之间更新游戏中各元素的状态,以保证它们之间互不干扰,这个过程中尽管看起来成程序像是在并行运行,但实际上却是一个串行...

cocos2dx3.2 异步加载和动态加载

cocos2dx3.2 异步动态加载

Cocos2d-x教程(36)-多线程与异步加载

Cocos2d-x 多线程与异步加载

cocos2d-x游戏开发(十)执行单元场景CCScene

欢迎转载:http://blog.csdn.net/fylz1125/article/details/8524081 游戏逻辑其实就像拍摄一部电影,整个过程都由导演来驱动。 游戏中的一个一...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:cocos2dx资源加载机制(同步/异步)
举报原因:
原因补充:

(最多只允许输入30个字)