cocos2dx-TextureCache::addImageAsync函数详解

这几天使用了cocos2dx后,对异步加载很感兴趣,所谓过异步加载,就是开启多线程进行资源的加载,同时因为cocos2dx的PoolManager(内存池)不是线程安全的,所以在工作线程中就得避免使用release() retain() autorelease()等函数。

因为现在的cocos2dx已经是3.x时代了,好些内容已经进行了改变,就比如异步加载资源,cocos2dx在 2.x使用的是pthread,是线程的POSIX标准。而在3.x就全部使用了c++11标准的<thread> <mutex>等标准库

先看看代码吧

void TextureCache::addImageAsync(const std::string &path, const std::function<void(Texture2D*)>& callback)
{
    Texture2D *texture = nullptr;
    //根据当前路径获取绝对路径
    std::string fullpath = FileUtils::getInstance()->fullPathForFilename(path);
    //当前是否存在该绝对路径所对应的texture
    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;
    }

    /*滞后赋值 创建一个工作线程,是c++11新增的,相对比较大的优点就是可以传递类的成员函数,第二个参数传递对象的指针*/
      if (_loadingThread == nullptr)
    {
        // 创建一个新的线程来不停尝试加载图片资源
        _loadingThread = new std::thread(&TextureCache::loadImage, this);
        _needQuit = false;
    }
    //开始定时调度器,会在主线程中一直尝试作加载资源的收尾阶段,比如创建Texture2D,回调函数等
    if (0 == _asyncRefCount)
    {
        Director::getInstance()->getScheduler()->schedule(CC_SCHEDULE_SELECTOR(TextureCache::addImageAsyncCallBack), this, 0, false);
    }
    //引用计数器加一
    ++_asyncRefCount;
    // 创建异步加载结构体,这个结构体内保存着资源路径,回调函数,异步加载完成的Image,以及loadSuccess,表示是否加载成功
    AsyncStruct *data = new (std::nothrow) AsyncStruct(fullpath, callback);
    
    // 增加这个异步结构体到队列中
    _asyncStructQueue.push_back(data);
    //std::mutex 互斥体,避免多线程同时访问
    _requestMutex.lock();
    _requestQueue.push_back(data);
    _requestMutex.unlock();
    //唤醒等待进程
    _sleepCondition.notify_one();
}

cocos2dx很巧妙地实现了异步加载。即cocos2dx维护了

_requestQueue 请求加载队列 由addImageAsync添加 在loadImage中进行移除

_responseQueue 加载完成队列 资源在loadImage中加载完成后,就会添加到此队列中 然后在addImageAsyncCallback函数中进行一些收尾工作

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();
        //当前请求队列已经全部加载完毕,此线程阻塞直至被唤醒,这个在addImageAsync中会被唤醒
        if (nullptr == asyncStruct) {
            _sleepCondition.wait(signal);
            continue;
        }
        
        // 加载image
        asyncStruct->loadSuccess = asyncStruct->image.initWithImageFileThreadSafe(asyncStruct->filename);

        // 把加载完成的结构体放在加载完成队列中
        _responseMutex.lock();
        _responseQueue.push_back(asyncStruct);
        _responseMutex.unlock();
    }
}
这个函数主要负责异步加载资源,至于那个条件变量_sleepCondition,在http://blog.youkuaiyun.com/watson2016/article/details/52861094中这样说到,"当 std::condition_variable 对象的某个 wait 函数被调用的时候,它使用 std::unique_lock(通过 std::mutex) 来锁住当前线程。当前线程会一直被阻塞,直到另外一个线程在相同的 std::condition_variable 对象上调用了 notification 函数来唤醒当前线程。"

void TextureCache::addImageAsyncCallBack(float dt)
{
    Texture2D *texture = nullptr;
    AsyncStruct *asyncStruct = nullptr;
    while (true)
    {
        // 从加载完成队列中移除一个结构体指针
        _responseMutex.lock();
        if(_responseQueue.empty())
        {
            asyncStruct = nullptr;
        }else
        {
            asyncStruct = _responseQueue.front();
            _responseQueue.pop_front();
            
            // 必须保证资源是按序加载
            CC_ASSERT(asyncStruct == _asyncStructQueue.front());
            _asyncStructQueue.pop_front();
        }
        _responseMutex.unlock();
        //当前加载完成队列为空,直接退出这次循环
        if (nullptr == asyncStruct) {
            break;
        }
        
        // 再次进行一个检测
        auto it = _textures.find(asyncStruct->filename);
        if(it != _textures.end())
        {
            texture = it->second;
        }
        else
        {
            // 转换成Texture2D
            if (asyncStruct->loadSuccess)
            {
                Image* image = &(asyncStruct->image);
                // generate texture in render thread
                texture = new (std::nothrow) Texture2D();
                
                texture->initWithImage(image);
                //parse 9-patch info
                //...略
        }
        
        // 尝试回调函数
        if (asyncStruct->callback)
        {
            (asyncStruct->callback)(texture);
        }

        // 释放资源
        delete asyncStruct;
        --_asyncRefCount;
    }
    //当前没有请求加载的资源,取消这个函数的调用
    if (0 == _asyncRefCount)
    {
        Director::getInstance()->getScheduler()->unschedule(CC_SCHEDULE_SELECTOR(TextureCache::addImageAsyncCallBack), this);
    }
}
如果加载完成队列非空,则一次性进行资源加载的后续处理。

使用异步加载也会对帧率造成影响,因为在addImageAsync内部需要获取互斥体_requestMutex,如果此时工作线程正好拥有此互斥体的话,主线程就会堵塞,直到获取到,不过这一点时间相对于主线程加载图片的时间就小巫见大巫了




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值