cocos引擎并没有提供一套专门资源管理解决方案,仅仅只有一个TextureCache提供了针对Texture2D的(异步)加载、缓存、清理等管理工作,我们可以参照TextureCache的思路设计一个针对常见资源的可扩展的资源管理器ResourceCache,使用引用计数原理来控制每个资源的生命周期。
UML
使用示例
#include <iostream>#include "ResourceMgr.h"using namespace std;// 假设已经扩展了以下几种资源类型:// JsonData// Mp3Data// Mp4Datavoid main(){// 考虑以下情景:// 首先启动游戏,进入到场景1,然后从场景1切换到场景2,场景1、2之间共用了部分资源// 场景1的资源列表vector<pair<string, string>> data_scene_1{{ "jsondata_1", JsonData::TYPE },{ "1.mp3", Mp3Data::TYPE },{ "1.mp4", Mp4Data::TYPE },},// 场景2的资源列表data_scene2{{ "jsondata_1", JsonData::TYPE }, // 和场景1共享的资源{ "2.mp3", Mp3Data::TYPE },{ "2.mp4", Mp4Data::TYPE },}ResourceMgr::getInstance()->addResRef(data_scene_1);// 当前资源引用计数表:实际还没有加载场景1资源// {// "jsondata_1" : 1,// "1.mp3" : 1,// "1.mp4" : 1,// }// 加载当前资源引用计数表中的资源,即:jsondata_1、1.mp3、1.mp4// callback: 加载完成后的回调ResourceMgr::getInstance()->updateResRefAsync(callback);ResourceMgr::getInstance()->addResRef(data_scene_2); // 加载场景2的资源// 当前资源引用计数表:实际还没有加载场景2资源// {// "jsondata_1" : 2,// "1.mp3" : 1,// "1.mp4" : 1,// "2.mp3" : 1,// "2.mp4" : 1,//}ResourceMgr::getInstance()->removeResRef(data_scene_1); // 清除上一个场景的资源// 当前资源引用计数表:// {// "jsondata_1" : 1,// "1.mp3" : 0,// "1.mp4" : 0,// "2.mp3" : 1,// "2.mp4" : 1,// }ResourceMgr::getInstance()->updateResRefAsync(data_scene_2);//1、卸载引用计数=0的资源,即1.mp3、1.mp4//2、加载引用计数!=0且还未加载的资源,即2.mp3、2.mp4//// 当前缓存的资源如下://{// "jsondata_1" : ...,// "2.mp3" : ...,// "2.mp4" : ...,//}}
代码设计
ResourceMgr
ResourceMgr.h
#ifndef __RESOURCEMGR_H__#define __RESOURCEMGR_H__#include "cocos2d.h"#include "ResourceCache.h"/*** 资源管理的统一(唯一)入口*/class ResourceMgr : public cocos2d::Ref {public:static ResourceMgr* getInstance();void addResProtocol(ResourceProtocol* data);/*** 资源引用计数管理API** 注意,有两种引用计数:* 1、Ref引用计数* 2、资源引用计数:ResourceMgr内部定义的引用计数,资源的引用计数+1表示该资源将在下一个场景使用,* 资源并不会马上加载,而是在updateResourceReferenceAsync时,才真正加载。**//*** 告知资源管理器将要使用哪些资源(对应资源引用计数+1)* 新增加的资源将在下一次调用updateResourceReferenceAsync的时候异步载入*/void addResRef(const std::vector<std::pair<std::string,std::string>>& path_type);/*** 告知资源管理器哪些资源不再被使用(对应资源引用计数-1)* 当资源引用计数为0时将在下次调用updateResourceAsync的时候从内存中释放*/void removeResRef(const std::vector<std::string>&);void removeUnusedResRef();/** 查询某个资源的引用计数 */int getResRef(const std::string& filename);/*** 资源引用计数 > 0,且资源未加载,则加载资源* 资源引用计数 = 0,则清楚该资源缓存*/void updateResRefAsync(const std::function<void()>& callback);void updateResRefAsyncHandler(int handler);void unupdateResRefAsyncHandler();private:ResourceMgr();virtual ~ResourceMgr();private:ResourceCache* _resourceCache; // 自定义资源缓存管理,实现原理参考TextureCache/*** 资源引用计数表** key : resource full path* value : resource reference count*/std::unordered_map<std::string, int> _resRefMap;/*** 待加载的资源** key : resource full path* value : resource type*/std::vector<std::pair<std::string, std::string>> _resToAdd;int _addHandler;int _updateHandler;bool _isLoading;};#endif /* defined(__Resource__) */
ResourceMgr.cpp
#include "ResourceMgr.h"#include "JsonData.h"USING_NS_CC;using namespace std;static ResourceMgr* _resourceInstance = nullptr;ResourceMgr* ResourceMgr::getInstance(){if (_resourceInstance == nullptr) {_resourceInstance = new ResourceMgr();}return _resourceInstance;}ResourceMgr::ResourceMgr(): _resourceCache(nullptr), _updateHandler(0), _addHandler(0), _isLoading(false){_resourceCache = new ResourceCache();}ResourceMgr::~ResourceMgr(){CCLOG("~ResourceMgr");unupdateResRefAsyncHandler();}void ResourceMgr::addResProtocol(ResourceProtocol* data){_resourceCache->addResourceProtocol(data);}void ResourceMgr::addResRef(const std::vector<std::pair<std::string, std::string>>& path_type){if (path_type.empty()) {return;}for (auto iter = path_type.cbegin(); iter != path_type.cend(); iter++) {auto fullpath = cocos2d::FileUtils::getInstance()->fullPathForFilename(iter->first);if (!fullpath.empty()) {auto find = _resRefMap.find(fullpath);if (find != _resRefMap.end()) {find->second++;}else {_resRefMap.insert(std::make_pair(fullpath, 1));_resToAdd.push_back(make_pair(fullpath, iter->second));}}}}void ResourceMgr::removeResRef(const std::vector<std::string>& files){for (auto iter = files.cbegin(); iter != files.cend(); iter++) {auto fullpath = cocos2d::FileUtils::getInstance()->fullPathForFilename(*iter);if (!fullpath.empty()) {auto find = _resRefMap.find(fullpath);if (find != _resRefMap.end() && find->second > 0) {find->second--;}}}}void ResourceMgr::removeUnusedResRef(){for (auto iter = _resRefMap.begin(); iter != _resRefMap.end();) {if (iter->second == 0) {iter = _resRefMap.erase(iter);}else {iter++;}}}int ResourceMgr::getResRef(const std::string& file){auto fullpath = cocos2d::FileUtils::getInstance()->fullPathForFilename(file);auto find = _resRefMap.find(fullpath);if (find != _resRefMap.end()) {return find->second;}return 0;}void ResourceMgr::unupdateResRefAsyncHandler(){if (0 != _updateHandler) {ScriptEngineManager::getInstance()->getScriptEngine()->removeScriptHandler(_updateHandler);_updateHandler = 0;}}void ResourceMgr::updateResRefAsyncHandler(int handler){unupdateResRefAsyncHandler();_updateHandler = handler;auto callback = [this] {#if CC_ENABLE_SCRIPT_BINDINGCommonScriptData data(_updateHandler, "", this);ScriptEvent event(kCommonEvent, (void*)&data);CCLOG("updateResourceReferenceAsyncHandler callback lua.");ScriptEngineManager::getInstance()->getScriptEngine()->sendEvent(&event);return;#endif};updateResRefAsync(callback);}void ResourceMgr::updateResRefAsync(const std::function<void()>& callback){// 第一步:清除资源,资源引用计数=0的资源。for (auto iter = _resRefMap.begin(); iter != _resRefMap.end(); iter++) {if (iter->second == 0) {_resourceCache->removeResourceForKey(iter->first);}}// 第二步:移除资源引用计数,=0的removeUnusedResRef();// 第三步:异步加载新的资源(引用计数为1的)int* reference = new int();*reference = _resToAdd.size();#if COCOS2D_DEBUGtime_t* t = new time_t();time(t);int count = _resToAdd.size();#endif//资源全部加载完毕auto completed = [reference, callback, this#if COCOS2D_DEBUG, t, count#endif]() {if ((*reference) == 0) {delete reference;#if COCOS2D_DEBUGtime_t now;time(&now);CCLOG("we spend %f seconds to load {%d} files.", (float)difftime(now, *t), count);delete t;#endif_resToAdd.clear();_isLoading = true;callback();}};CCLOG("we need to load %d files", (int)_resToAdd.size());if (_resToAdd.size() == 0) {completed();return;}for (auto iter = _resToAdd.begin(); iter != _resToAdd.end(); iter++) {_resourceCache->addResourceAsync(iter->first, [&](void* data) {(*reference)--;if (*reference < 1) {completed();}}, iter->second);}}
ResourceCache
ResourceCache.h
#ifndef __RESOURCECACHE_H__#define __RESOURCECACHE_H__#include <thread>#include <unordered_map>#include "cocos2d.h"#include "ResourceProtocol.h"class ResourceMgr;/*** 自定义资源缓存** 不对外公开,全部private,由友元ResourceMgr代理*/class ResourceCache : public cocos2d::Ref {private:ResourceCache();virtual ~ResourceCache();/*** 同步加载资源到缓存*/void* addResource(const std::string& filename, const std::string& type = "Texture2D");/*** 异步加载资源到缓存*/void addResourceAsync(const std::string& filename, std::function<void(void*)> callback, const std::string& type="Texture2D");/*** 清除所有自定义资源缓存,类似TextureCache::removeAllTextures*/void removeAllResources();/*** 清除所有当前未被使用的资源,即Ref引用计数为1,类似TextureCache::removeUnusedTexture*/void removeUnusedResources();/*** 删除指定资源*/bool removeResource(void* data);bool removeResourceForKey(const std::string& key);/*** 注册一个自定义数据类型及解析器*/void addResourceProtocol(ResourceProtocol* data);private:/*** 自定义资源加载完成的回调,schedule update每帧执行,也就是说是在GL thread中执行*/void addResourceAsyncCallback(float dt);/*** 在子线程中加载自定义资源*/void loadData();private:struct AsyncStruct {AsyncStruct(const std::string& fn, std::function<void(ResourceProtocol*)> cb, const std::string& type):filename(fn), callback(cb), resourceType(type){}std::string filename;std::string resourceType;std::function<void(ResourceProtocol*)> callback;};typedef struct _DataInfo {AsyncStruct* asyncStruct;ResourceProtocol* data;} DataInfo;std::thread* _loadingThread;std::queue<AsyncStruct*>* _asyncStructQueue;std::deque<DataInfo*>* _dataInfoQueue;std::mutex _asyncStructMutex;std::mutex _dataInfoMutex;std::mutex _sleepMutex;std::condition_variable _sleepCondition;bool _needQuit;int _asyncRefCount;private:cocos2d::TextureCache *_textureCache;/** 所有缓存数据 */std::unordered_map<std::string, ResourceProtocol*> _resources;/*** 自定义资源类型模板*/std::map<std::string, ResourceProtocol*> _resourceTemplate;// 此类的代理friend ResourceMgr;};#endif /* defined(__ResourceCache__) */
ResourceCache.cpp
#include "ResourceCache.h"USING_NS_CC;ResourceCache::ResourceCache(): _loadingThread(nullptr), _asyncStructQueue(nullptr), _dataInfoQueue(nullptr), _needQuit(false), _asyncRefCount(0), _textureCache(Director::getInstance()->getTextureCache()){}ResourceCache::~ResourceCache(){for (auto iter = _resources.begin(); iter != _resources.end(); ++iter) {iter->second->release();}for (auto iter = _resourceTemplate.begin(); iter != _resourceTemplate.end(); iter++) {iter->second->release();}}void* ResourceCache::addResource(const std::string& filename, const std::string& type){CCASSERT(!filename.empty(), "filename cann't be nil.");void* resource = nullptr;if (type == "Texture2D") {resource = _textureCache->addImage(filename);}else {//全路径keystd::string fullpath = cocos2d::FileUtils::getInstance()->fullPathForFilename(filename);if (fullpath.empty()) {return nullptr;}//是否已加载auto iter = _resources.find(fullpath);if (iter != _resources.end()) {resource = iter->second;;}if (!resource) {do {CCLOG("add resource date type: %s", type.c_str());auto iter1 = _resourceTemplate.find(type);if (iter1 != _resourceTemplate.end()) {ResourceProtocol* data = iter1->second->cloneEmpty();if (!data->loadWithFile(filename)) {CC_SAFE_RELEASE(data);CCLOG("ResouceDataDelegate is error: %s", iter1->first.c_str());break;}_resources.insert(std::make_pair(fullpath, data));resource = data;}}while (0);}}return resource;}void ResourceCache::addResourceAsync(const std::string& filename, std::function<void(void*)> callback, const std::string& type){CCASSERT(!filename.empty(), "filename cann't be nil.");if (type == "Texture2D") {auto func = [this, filename, callback, type](cocos2d::Texture2D* tex) {callback(tex);};_textureCache->addImageAsync(filename, func);}else {ResourceProtocol* data = nullptr;std::string fullpath = FileUtils::getInstance()->fullPathForFilename(filename);auto iter = _resources.find(fullpath);if (iter != _resources.end()) {data = iter->second;}if (data != nullptr) {callback(data);return;}//lazy initif (_asyncStructQueue == nullptr) {_asyncStructQueue = new std::queue<AsyncStruct*>();_dataInfoQueue = new std::deque<DataInfo*>();_loadingThread = new std::thread(&ResourceCache::loadData, this);_needQuit = false;}if (_asyncRefCount == 0) {Director::getInstance()->getScheduler()->schedule(schedule_selector(ResourceCache::addResourceAsyncCallback), this, 0, false);}++_asyncRefCount;AsyncStruct* async = new AsyncStruct(fullpath, callback, type);_asyncStructMutex.lock();_asyncStructQueue->push(async);_asyncStructMutex.unlock();_sleepCondition.notify_one();}}void ResourceCache::addResourceAsyncCallback(float dt){std::deque<DataInfo*>* datasQueue = _dataInfoQueue;_dataInfoMutex.lock();if (_dataInfoQueue->empty()) {_dataInfoMutex.unlock();}else {DataInfo* dataInfo = datasQueue->front();datasQueue->pop_front();_dataInfoMutex.unlock();AsyncStruct* asyncStruct = dataInfo->asyncStruct;ResourceProtocol* data = dataInfo->data;const std::string& filename = asyncStruct->filename;if (data) {_resources.insert(std::make_pair(filename, data));data->retain();data->autorelease();}else {auto iter = _resources.find(asyncStruct->filename);if (iter != _resources.end()) {data = iter->second;}}asyncStruct->callback(data);delete asyncStruct;delete dataInfo;--_asyncRefCount;if (0 == _asyncRefCount) {Director::getInstance()->getScheduler()->unschedule(schedule_selector(ResourceCache::addResourceAsyncCallback), this);}}}void ResourceCache::loadData(){AsyncStruct* asyncStruct = nullptr;while (true) {std::queue<AsyncStruct*>* pQueue = _asyncStructQueue;_asyncStructMutex.lock();if (pQueue->empty()) {_asyncStructMutex.unlock();if (_needQuit) {break;}else {std::unique_lock<std::mutex> lk(_sleepMutex);_sleepCondition.wait(lk);continue;}}else {asyncStruct = pQueue->front();pQueue->pop();_asyncStructMutex.unlock();}ResourceProtocol* data = nullptr;bool loaded = false;auto iter = _resources.find(asyncStruct->filename);if (iter == _resources.end()) {_dataInfoMutex.lock();DataInfo* dataInfo;size_t pos = 0;size_t infoSize = _dataInfoQueue->size();for (; pos < infoSize; pos++) {dataInfo = (*_dataInfoQueue)[pos];if (dataInfo->asyncStruct->filename.compare(asyncStruct->filename)) {break;}}_dataInfoMutex.unlock();if (infoSize == 0 || pos < infoSize) {loaded = true;}}if (loaded) {const std::string & filename = asyncStruct->filename;auto cloneData = _resourceTemplate.find(asyncStruct->resourceType);if (cloneData != _resourceTemplate.end()) {data = cloneData->second->cloneEmpty();if (!data->loadWithFile(filename)) {delete data;CCLOG("load file: %s fail.", filename.c_str());continue;}CCLOG("load file: %s success.", filename.c_str());}else {CCLOG("load file: %s fail. no parser.", filename.c_str());continue;}}DataInfo* dataInfo = new DataInfo();dataInfo->asyncStruct = asyncStruct;dataInfo->data = data;_dataInfoMutex.lock();_dataInfoQueue->push_back(dataInfo);_dataInfoMutex.unlock();}}void ResourceCache::removeAllResources(){for (auto it = _resources.begin(); it != _resources.end(); ++it) {(it->second)->release();}_resources.clear();_textureCache->removeAllTextures();}void ResourceCache::removeUnusedResources(){for (auto it = _resources.cbegin(); it != _resources.cend(); /* nothing */) {ResourceProtocol *tex = it->second;if (tex->getReferenceCount() == 1) {CCLOG("GX: ResourceCache: removing unused data: %s", it->first.c_str());tex->release();_resources.erase(it++);}else {++it;}}_textureCache->removeUnusedTextures();}bool ResourceCache::removeResource(void* data){return false;}bool ResourceCache::removeResourceForKey(const std::string& key){auto it = _resources.find(key);if (it == _resources.end()) {std::string fullpath = FileUtils::getInstance()->fullPathForFilename(key);it = _resources.find(fullpath);}if (it == _resources.end()) {_textureCache->removeTextureForKey(key);return true;}else {if (!_resources.empty()) {(it->second)->release();_resources.erase(it);return true;}}return false;}void ResourceCache::addResourceProtocol(ResourceProtocol* data){if (_resourceTemplate.find(data->getType()) == _resourceTemplate.end()) {_resourceTemplate.insert(std::make_pair(data->getType(), data));}}
ResourceProtocol
ResourceProtocol.h
#ifndef __RESOURCEPROTOCOL_H__#define __RESOURCEPROTOCOL_H__#include "cocos2d.h"/*** 自定义数据类型*/class ResourceProtocol : public cocos2d::Ref {public:ResourceProtocol() {};virtual ~ResourceProtocol() {};public:virtual const std::string& getType() const = 0;//返回一个空数据的实例,用于创建数据实例virtual ResourceProtocol* cloneEmpty() const = 0;//必须实现加载数据到内存方式virtual bool loadWithFile(const std::string& filename) = 0;};#endif
JsonData
JsonData.h
#ifndef __JSONDATA_H__#define __JSONDATA_H__#include "ResourceProtocol.h"#include "json\rapidjson.h"class JsonData : public ResourceProtocol {public:JsonData();~JsonData();const std::string& getType() const override{return TYPE;};ResourceProtocol* cloneEmpty() const override;bool loadWithFile(const std::string& filename) override;public:static std::string TYPE;rapidjson::Document data;};#endif /* defined(__JsonData__) */
JsonData.cpp
#include "JsonData.h"using namespace std;USING_NS_CC;std::string JsonData::TYPE = "JsonData";JsonData::JsonData(){}JsonData::~JsonData(){}ResourceProtocol* JsonData::cloneEmpty() const{return new JsonData();}bool JsonData::loadWithFile(const std::string& filename){bool bRet = false;do {std::string jsonpath = cocos2d::FileUtils::getInstance()->fullPathForFilename(filename);std::string contentStr = cocos2d::FileUtils::getInstance()->getStringFromFile(jsonpath);data.Parse<0>(contentStr.c_str());CC_BREAK_IF(data.HasParseError());bRet = true;}while (0);return bRet;}
