Cocos2dx 异步加载纹理

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)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值