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)