1 异步加载资源
异步加载的执行过程主要在addImageAsync函数中: 判定当前路径对应的纹理是否已经加载,如果未加载,为该纹理创建一个AsyncStruct 数据结构存放该纹理数据,并加入至_requestMutex请求队列中。在创建的用于专门解析纹理的新线程LoadImage中,请求队列中的数据将被逐个解析至AsyncStruct .image中并加入已解析队列_responseQueue中。 在创建的计时器回调函数addImageAsyncCallBack中,将解析得到的image转换为最终的texture。
上述过程涉及两个线程:主线程GL Thread与子线程Load Thread,过程中两个重要的数据为AsyncStruct 与Image Data。其中AsyncStruct的创建与析构都在主线程中完成,Image Data在Load Thread中创建,在GL Thread中析构。
- void TextureCache::addImageAsync(const std::string &path,const std::function<void(Texture2D*)>& callback,const std::string& callbackKey)
- {
- Texture2D *texture = nullptr;
- // 确定图片纹理是否已经加载
- std::string fullpath = FileUtils::getInstance()->fullPathForFilename(path);
- auto it = _textures.find(fullpath);
- if (it != _textures.end())
- texture = it->second;
-
- // 已加载 直接执行回调函数
- if (texture != nullptr)
- {
- if (callback) callback(texture);
- return;
- }
-
- // 检查当前路径对应的文件是否存在
- if (fullpath.empty() || !FileUtils::getInstance()->isFileExist(fullpath)) {
- if (callback) callback(nullptr);
- return;
- }
-
- // lazy init
- if (_loadingThread == nullptr)
- {
- // 创建一个新的线程 用于加载纹理
- _loadingThread = new (std::nothrow) std::thread(&TextureCache::loadImage,this);
- _needQuit = false;//线程是否终止的标志
- }
-
- if (0 == _asyncRefCount)
- {
- // 创建定时器回调 用于将解析的image转为texture并执行回调函数
- Director::getInstance()->getScheduler()->schedule(CC_SCHEDULE_SELECTOR(TextureCache::addImageAsyncCallBack),this,0,false);
- }
- ++_asyncRefCount;
-
- // 生成async struct,并加入至请求队列
- AsyncStruct *data =
- new (std::nothrow) AsyncStruct(fullpath,callback,callbackKey);
-
- // add async struct into queue
- _asyncStructQueue.push_back(data); // 该队列在unbind函数中使用 在加载纹理过程中不使用
- _requestMutex.lock();
- _requestQueue.push_back(data);
- _requestMutex.unlock();
-
- _sleepCondition.notify_one();
- }
-
- // 解析资源的线程
- void TextureCache::loadImage()
- {
- AsyncStruct *asyncStruct = nullptr;
- std::mutex signalMutex;
- std::unique_lock<std::mutex> signal(signalMutex);
- while (!_needQuit)
- {
- // 从请求队列中取出待解析资源
- _requestMutex.lock();
- if (_requestQueue.empty())
- {
- asyncStruct = nullptr;
- }
- else
- {
- asyncStruct = _requestQueue.front();
- _requestQueue.pop_front();
- }
- _requestMutex.unlock();
-
- if (nullptr == asyncStruct) {
- _sleepCondition.wait(signal);
- continue;
- }
-
- // load image
- asyncStruct->loadSuccess = asyncStruct->image.initWithImageFileThreadSafe(asyncStruct->filename);
-
- // push the asyncStruct to response queue
- _responseMutex.lock();
- _responseQueue.push_back(asyncStruct);
- _responseMutex.unlock();
- }
- }
-
- void TextureCache::addImageAsyncCallBack(float /*dt*/)
- {
- Texture2D *texture = nullptr;
- AsyncStruct *asyncStruct = nullptr;
- while (true) // 轮询解析队列中的image
- {
- // pop an AsyncStruct from response queue
- _responseMutex.lock();
- if (_responseQueue.empty())
- {
- asyncStruct = nullptr;
- }
- else
- {
- asyncStruct = _responseQueue.front();
- _responseQueue.pop_front();
-
- // 保持_asyncStructQueue 与 _responseQueue的同步
- CC_ASSERT(asyncStruct == _asyncStructQueue.front());
- _asyncStructQueue.pop_front();
- }
- _responseMutex.unlock();
-
- if (nullptr == asyncStruct) {
- break;
- }
-
- // check the image has been convert to texture or not
- auto it = _textures.find(asyncStruct->filename);
- if (it != _textures.end()) // 检查当前纹理资源是否已被加载
- {
- texture = it->second;
- }
- else
- {
- // convert image to texture
- if (asyncStruct->loadSuccess)
- {
- Image* image = &(asyncStruct->image);
- // generate texture in render thread
- texture = new (std::nothrow) Texture2D();
-
- texture->initWithImage(image,asyncStruct->pixelFormat);
- //parse 9-patch info
- this->parseNinePatchImage(image,texture,asyncStruct->filename);
- #if CC_ENABLE_CACHE_TEXTURE_DATA
- // cache the texture file name
- VolatileTextureMgr::addImageTexture(texture,asyncStruct->filename);
- #endif
- // cache the texture. retain it,since it is added in the map
- _textures.emplace(asyncStruct->filename,texture);
- texture->retain(); // 为何要retain: 下一帧不自动释放该纹理,纹理的释放由程序手动控制
- texture->autorelease();
- }
- else {
- texture = nullptr;
- CCLOG("cocos2d: Failed to call TextureCache::addImageAsync(%s)",asyncStruct->filename.c_str());
- }
- }
-
- // 执行回调函数
- if (asyncStruct->callback)
- {
- (asyncStruct->callback)(texture);
- }
-
- // release the asyncStruct
- delete asyncStruct;
- --_asyncRefCount;
- }
-
- if (0 == _asyncRefCount) // 所有异步加载过程均已完成 销毁计时器回调
- {
- Director::getInstance()->getScheduler()->unschedule(CC_SCHEDULE_SELECTOR(TextureCache::addImageAsyncCallBack),this);
- }
- }
2 纹理中关键帧的检索
检索之前,首先需要将一整张纹理按照区域切割成各个关键帧,并建立帧名与帧之间的map映射表。addSpriteFramesWithFile完成了这一过程,该函数为已加载完成的纹理数据与对应的plist文件构建映射关系。
- void SpriteFrameCache::addSpriteFramesWithFile(const std::string& plist,Texture2D *texture)
- {
- if (_loadedFileNames->find(plist) != _loadedFileNames->end())
- {
- return; // We already added it
- }
-
- std::string fullPath = FileUtils::getInstance()->fullPathForFilename(plist);// plist全路径
- ValueMap dict = FileUtils::getInstance()->getValueMapFromFile(fullPath); // plist是XML文件,cocos将其解析为key-value的map结构
-
- addSpriteFramesWithDictionary(dict,texture);
- _loadedFileNames->insert(plist);
- }
-
- // 从texture中取出相应区域spriteframe
- void SpriteFrameCache::addSpriteFramesWithDictionary(ValueMap& dictionary,Texture2D* texture)
- {
- /* Supported Zwoptex Formats: ZWTCoordinatesFormatOptionXMLLegacy = 0,// Flash Version ZWTCoordinatesFormatOptionXML1_0 = 1,// Desktop Version 0.0 - 0.4b ZWTCoordinatesFormatOptionXML1_1 = 2,// Desktop Version 1.0.0 - 1.0.1 ZWTCoordinatesFormatOptionXML1_2 = 3,// Desktop Version 1.0.2+ */
-
- if (dictionary["frames"].getType() != cocos2d::Value::Type::MAP)
- return;
-
- ValueMap& framesDict = dictionary["frames"].asValueMap(); // 存放每一帧图片路径、大小、偏移、对应的纹理区域位置等属性
- int format = 0;
-
- Size textureSize;
-
- // get the format
- if (dictionary.find("Metadata") != dictionary.end())
- {
- ValueMap& MetadataDict = dictionary["Metadata"].asValueMap(); // Metadata中存放纹理格式、纹理大小、纹理名称等纹理属性
- format = MetadataDict["format"].asInt();
- if(MetadataDict.find("size") != MetadataDict.end())
- {
- textureSize = SizeFromString(MetadataDict["size"].asString());
- }
- }
-
- // check the format
- CCASSERT(format >=0 && format <= 3,"format is not supported for SpriteFrameCache addSpriteFramesWithDictionary:textureFilename:");
-
- auto textureFileName = Director::getInstance()->getTextureCache()->getTextureFilePath(texture);
- Image* image = nullptr;
- NinePatchImageParser parser;
- for (auto& iter : framesDict)
- {
- ValueMap& frameDict = iter.second.asValueMap();
- std::string spriteFrameName = iter.first;
- SpriteFrame* spriteFrame = _spriteFrames.at(spriteFrameName);
- if (spriteFrame)
- {
- continue; // 当前帧已加入至关键帧集合 无需处理
- }
-
- if(format == 0)
- {
- float x = frameDict["x"].asFloat();
- float y = frameDict["y"].asFloat();
- float w = frameDict["width"].asFloat();
- float h = frameDict["height"].asFloat();
- float ox = frameDict["offsetX"].asFloat();
- float oy = frameDict["offsetY"].asFloat();
- int ow = frameDict["originalWidth"].asInt();
- int oh = frameDict["originalHeight"].asInt();
- // check ow/oh
- if(!ow || !oh)
- {
- CCLOGWARN("cocos2d: WARNING: originalWidth/Height not found on the SpriteFrame. AnchorPoint won't work as expected. Regenerate the .plist");
- }
- // abs ow/oh
- ow = std::abs(ow);
- oh = std::abs(oh);
- // create frame
- spriteFrame = SpriteFrame::createWithTexture(texture,Rect(x,y,w,h),false,Vec2(ox,oy),Size((float)ow,(float)oh)
- );
- }
- else if(format == 1 || format == 2)
- {
- Rect frame = RectFromString(frameDict["frame"].asString());
- bool rotated = false;
-
- // rotation
- if (format == 2)
- {
- rotated = frameDict["rotated"].asBool();
- }
-
- Vec2 offset = PointFromString(frameDict["offset"].asString());
- Size sourceSize = SizeFromString(frameDict["sourceSize"].asString());
-
- // create frame
- spriteFrame = SpriteFrame::createWithTexture(texture,frame,rotated,offset,sourceSize
- );
- }
- else if (format == 3)
- {
- // get values
- Size spriteSize = SizeFromString(frameDict["spriteSize"].asString());
- Vec2 spriteOffset = PointFromString(frameDict["spriteOffset"].asString());
- Size spriteSourceSize = SizeFromString(frameDict["spriteSourceSize"].asString());
- Rect textureRect = RectFromString(frameDict["textureRect"].asString());
- bool textureRotated = frameDict["textureRotated"].asBool();
-
- // get aliases
- ValueVector& aliases = frameDict["aliases"].asValueVector();
-
- for(const auto &value : aliases) {
- std::string oneAlias = value.asString();
- if (_spriteFramesAliases.find(oneAlias) != _spriteFramesAliases.end())
- {
- CCLOGWARN("cocos2d: WARNING: an alias with name %s already exists",oneAlias.c_str());
- }
-
- _spriteFramesAliases[oneAlias] = Value(spriteFrameName);
- }
-
- // create frame
- spriteFrame = SpriteFrame::createWithTexture(texture,Rect(textureRect.origin.x,textureRect.origin.y,spriteSize.width,spriteSize.height),textureRotated,spriteOffset,spriteSourceSize);
-
- if(frameDict.find("vertices") != frameDict.end())
- {
- std::vector<int> vertices;
- parseIntegerList(frameDict["vertices"].asString(),vertices);
- std::vector<int> verticesUV;
- parseIntegerList(frameDict["verticesUV"].asString(),verticesUV);
- std::vector<int> indices;
- parseIntegerList(frameDict["triangles"].asString(),indices);
-
- PolygonInfo info;
- initializePolygonInfo(textureSize,spriteSourceSize,vertices,verticesUV,indices,info);
- spriteFrame->setPolygonInfo(info);
- }
- if (frameDict.find("anchor") != frameDict.end())
- {
- spriteFrame->setAnchorPoint(PointFromString(frameDict["anchor"].asString()));
- }
- }
-
- bool flag = NinePatchImageParser::isNinePatchImage(spriteFrameName);
- if(flag)
- {
- if (image == nullptr) {
- image = new (std::nothrow) Image();
- image->initWithImageFile(textureFileName);
- }
- parser.setSpriteFrameInfo(image,spriteFrame->getRectInPixels(),spriteFrame->isRotated());
- texture->addSpriteFrameCapInset(spriteFrame,parser.parseCapInset());
- }
- // add sprite frame
- _spriteFrames.insert(spriteFrameName,spriteFrame);
- }
- CC_SAFE_DELETE(image);
- }
-
- // 基于frameName检索得到SpriteFrame
- SpriteFrame* SpriteFrameCache::getSpriteFrameByName(const std::string& name)
- {
- SpriteFrame* frame = _spriteFrames.at(name);
- if (!frame)
- {
- // try alias dictionary
- if (_spriteFramesAliases.find(name) != _spriteFramesAliases.end())
- {
- std::string key = _spriteFramesAliases[name].asString();
- if (!key.empty())
- {
- frame = _spriteFrames.at(key);
- if (!frame)
- {
- CCLOG("cocos2d: SpriteFrameCache: Frame aliases '%s' isn't found",key.c_str());
- }
- }
- }
- else
- {
- CCLOG("cocos2d: SpriteFrameCache: Frame '%s' isn't found",name.c_str());
- }
- }
- return frame;
- }
3 lua层的接口调用
- local createAni = function ( texture )
- -- 回调函数时将传入已加载的纹理
- local testSp = cc.Sprite:createWithTexture(texture)
- self:addChild(testSp)
-
- local ani = cc.Animation:create()
- ani:setDelayPerUnit(0.12)
- ani:setRestoreOriginalFrame(true) -- 动画停止时显示为起始图片
- for i=1,num do
- local frameName = getFrameName(i)
- local frame = cc.SpriteFrameCache:getInstance():getSpriteFrameByName(frameName)
- if frame then
- ani:addSpriteFrame(frame)
- end
- -- 关键帧全部加载完毕 播放动画
- if i == num then
- self:stopAllActions()
- local action = cc.Animation:create(ani)
- self:runAction(action)
- end
- end
- end
- local textureName = getTextureName() or "" -- 禁止向cocos接口中传入nil 避免宕机
- cc.Director:getInstance():getTextureCache():addImageAsync(textureName,createAni)