cocos2dx-3 addImageAsync陷阱

addImageAsync异步加载
未响应回调前调用unbindImageAsync撤销消息回调
void TextureCache::unbindImageAsync(const std::string& filename)
{
    _imageInfoMutex.lock();
    if (_imageInfoQueue && !_imageInfoQueue->empty())
    {
        std::string fullpath = FileUtils::getInstance()->fullPathForFilename(filename);
        auto found = std::find_if(_imageInfoQueue->begin(), _imageInfoQueue->end(), [&fullpath](ImageInfo* ptr)->bool{ return ptr->asyncStruct->filename == fullpath; });
        if (found != _imageInfoQueue->end())
        {
           (*found)->asyncStruct->callback = nullptr;
        }
    }
    _imageInfoMutex.unlock();
}

但是在极端情况下,调用 addImageAsync后马上调用unbindImageAsync
此时loadImage线程还未将ImageInfo加入_imageInfoQueue
而是在unbindImageAsync之后添加,导致消息回调解绑失败

无法调用(*found)->asyncStruct->callback = nullptr;
从而在异步加载完成后调用callback时候出现问题

loadImage()线程
同时操作两个链表
_asyncStructQueue
_imageInfoQueue

请求从_asyncStructQueue弹出后生成ImageInfo添加到_imageInfoQueue

void TextureCache::loadImage()
{
_asyncStructQueueMutex.lock();
_asyncStructQueue
_asyncStructQueueMutex.unlock();

.....临界点,执行unbindImageAsync

_imageInfoMutex.lock();
 _imageInfoQueue->push_back(imageInfo);
_imageInfoMutex.unlock();
}

此处的Bug是两个链表是分别加锁的
请求从_asyncStructQueue弹出后,未即时插入_imageInfoQueue
在临界点又调用了unbindImageAsync撤销异步加载响应
跟昨天提到的Bug类似,同样会导致撤销失败
在回调时引起异常

=================================================================

以下为转帖

就以往的经验,异步加载图片是一个复杂的工作,往往容易出现bug。 
        那么,cocos2d-x提供的这个异步加载功能是否可靠?百度了一下,没发现什么重要的信息,于是自己分析之。 
        照cocos2d-x自身的注释来看,这个addImageAsync函数是从0.8版本就有了,而现在是3.1版本,怎么也该稳定了吧?可惜的是,里面的陷阱并不少。 
          
        陷阱1:_textures未加锁 
        在异步加载时,cocos2d-x主要用了两个队列,即_asyncStructQueue和_imageInfoQueue,在操作这两个队列的时候也都很小心的加锁了。但是对_textures的访问则没有加锁。因此,如果先用addImageAsync进行异步加载图片A,再用addImage同步加载图片B,则有几率导致_textures这个对象被损坏,进而导致程序不稳定。 
        修改:由于涉及到_textures的地方很多,逐一加锁实在麻烦,所以干脆不加锁,转为让异步线程不要访问_textures。大不了就是同一张图片被多次加载,浪费一些运算量罢了。上层代码小心控制的话,是不会真的有图片被重复加载的。 
          
        陷阱2:判断逻辑错误 
        在cocos2d-x 3.1.1版本中,异步加载的代码中有一句判断:if(imageInfo->asyncStruct->filename.compare(asyncStruct->filename)),这是有问题的。 
        作者的本意可能是想,如果队列中有多个请求都是加载同一幅图片,那么其实只需要加载一次即可。可惜这个判断写反了,下文又有一处判断写反,导致不知所云。 
        这个问题在cocos2d-x 3.2版本已经修复了。 
        P.S. 字符串比较,还是用==、!=这样的操作符比较好,可读性和运行性能都要优于compare函数。 
        修改:cocos官方已经修正。不过其实跟陷阱1类似,不必判断,大不了就是同一张图片被多次加载,浪费一些运算量罢了。 
          
        陷阱3:insert失败导致内存泄漏 
        在异步加载完毕之后,主线程有一句:_textures.insert( std::make_pair(filename, texture) );。 
        由于陷阱1、陷阱2中,我们并没有进行彻底的检查,所以有几率出现重复加载的情形。(实际上,除非全程加锁,否则很难彻底避免重复加载。然而,全程加锁的话,异步加载也就没有意义了)。当出现重复加载同一张图片时,这里的insert就会失败。于是,texture不会有被销毁的机会,于是造成内存泄漏。 
          
        陷阱4:创建线程时,需要的变量尚未初始化完毕 
        创建异步加载的线程时,原始代码是先创建线程,再设置_needQuit。 
        正常应该是先设置_needQuit为false(初始化值为true!),再创建线程。否则理论上有可能线程刚创建完毕就立即结束。 
          
        疑似陷阱 
        异步加载线程和主线程,都调用了Image::initWithImageFileThreadSafe,这个函数看名字似乎是线程安全的,实际上它调用了FileUtils::fullPathForFilename。这个函数除非参数是绝对路径,否则就会对一个名为_fullPathCache的哈希表进行读写,若不加锁就会出错。幸好在异步加载线程中,传入给FileUtils::fullPathForFilename的参数已经是绝对路径,所以没有上述的问题。

http://www.cocoachina.com/bbs/read.php?tid-312395.html

http://www.58player.com/blog-2479-108013.html

时间: 2024-10-20 01:45:35

cocos2dx-3 addImageAsync陷阱的相关文章

cocos2d-x addImageAsync()异步加载资源成功之后的场景跳转问题

http://blog.csdn.net/w20175357/article/details/23546985 1.先说说addImageAsync()异步加载图片的问题 做游戏的时候现在资源的比较大,所有我们必然会有一个loading界面,而我在找写loading界面的方法的时候,发现了2种方法.       一种是自己创建一个线程,再在这个线程里面加载资源,不过由于openGL的限制,只能在主线程里面绘制UI,不过有的人也想出了其他的方法,就是先只缓存,再在主线程里面绘制.这里有这方面的教程

cocos2d-x 3.0 Loading界面实现

这个世界每一天都在验证我们的渺小,但我们却在努力创造,不断的在这生活的画卷中留下自己的脚印,也许等到我们老去的那一天,老得不能动只能靠回忆的那一天,你躺在轮椅上,不断的回忆过去,相思的痛苦忘不了,相恋的甜蜜浮现在心头,嘴角不觉一笑,年少时的疯狂,热情,理想和抱负,都随着岁月的流去而化作人生的财富,或多或少,犹如那夕阳西下的余辉,在慢慢消失着不见.. (不文艺你会死?) 好吧,最近天天在忙着写游戏,天天写,而且效率还不高,光这两天想着怎么优化和控制敌人出现的逻辑和地图数据的存储,就前前后后墨迹了俩

cocos2dx 内存管理

cocos2dx的内存管理移植自Objective-C, 对于没有接触过OC的C++开发人员来说是挺迷惑的.不深入理解内存管理是无法写出好的C++程序的,我用OC和cocos2dx也有一段时间了,在此总结一下,希望对想用cocos2dx开发游戏的朋友有所帮助. C++的动态内存管理一般建议遵循谁申请谁释放的原则,即谁通过new操作符创建了对象,谁就负责通过delete来释放对象.如果对象的生命周期在一个函数内,这很容易做到,在函数返回前delete就行了.但一般我们在函数中new出来的对象的生命

cocos2d-x 图片资源加密,Lua文件加密 (转)

游戏开发中常遇到资源保护的问题. 目前游戏开发中常加密的文件类型有:图片,Lua文件,音频等文件,而其实加密也是一把双刃剑. 需要安全那就得耗费一定的资源去实现它.目前网上也有用TexturePacker工具来加密的,不过针对性还是不够强. 分析一下原理为: 1,转格式:将需要加密的文件转为流的方式: 2,加密:根据自己需要使用加密手段,MD5,AES,甚至可以直接改变位移,加一些自己的特殊字符也可以使文件简单加密,加密完后基本保证 图片类型基本用特殊软件预览不了也打不开,Lua文件加密后一片乱

cocos2d-x 如何制作一个类马里奥的横版平台动作游戏 1 献给所有对动作游戏有爱的朋友

本文翻译自国外著名IOS源码教学商业网站raywenderlich 的IOS Game Start Kits三件套之一的Platformer Game/平台动作游戏的前奏曲,另一个是Beat'Em up Game/横版格斗游戏,作者是国外著名游戏开发专家Jake Gundersen,曾参与开发过SFC时代的洛克人X系列. 原文网址: http://www.raywenderlich.com/15230/how-to-make-a-platform-game-like-super-mario-br

cocos2dx 之内存管理

 cocos2dx的内存管理移植自Objective-C, 对于没有接触过OC的C++开发人员来说是挺迷惑的.不深入理解内存管理是无法写出好的C++程序的,我用OC和cocos2dx也有一段时间了,在此总结一下,希望对想用cocos2dx开发游戏的朋友有所帮助. C++的动态内存管理一般建议遵循谁申请谁释放的原则,即谁通过new操作符创建了对象,谁就负责通过delete来释放对象.如果对象的生命周期在一个函数内,这很容易做到,在函数返回前delete就行了.但一般我们在函数中new出来的对象

cocos2d-x lua中实现异步加载纹理

原文地址:  http://www.cnblogs.com/linchaolong/p/4033118.html 前言   问题:最近项目中需要做一个loading个界面,界面中间有一个角色人物走动的动画,在显示这个loading界面的时候加载资源,项目是用cocos2d-x lua实现的,界面做出来后发现在加载资源的时候界面会卡住. 原因: 因为使用的不是异步加载,而且cocos2d-x没有绑定异步加载资源的api到lua中,其实在lua中实现不了异步. 想通过在lua中启动一个线程去加载资源

Cocos2d-x优化中纹理优化

1.纹理像素格式纹理优化工作的另一重要的指标是纹理像素格式,能够最大程度满足用户对保真度要求的情况下,选择合适的像素格式,可以大幅提高纹理的处理速度.而且纹理像素格式有与硬件有这密切的关系.下面我们先了解一下纹理像素的格式,主要的格式有:RGBA8888.32位色,它是默认的像素格式,每个通道8位(比特),每个像素4个字节.BGRA8888.32位色,每个通道8位(比特),每个像素4个字节.RGBA4444.16位色,每个通道4位(比特),每个像素2个字节.RGB888.24位色,没有Alpha

cocos2dx实例开发之flappybird(入门版)

cocos2dx社区里有个系列博客完整地复制原版flappybird的所有特性,不过那个代码写得比较复杂,新手学习起来有点捉摸不透,这里我写了个简单的版本.演示如下: 创建项目 VS2013+cocos2dx 3.2创建win32项目,由于只是学习,所以没有编译为安卓.ios或者WP平台的可执行文件. 最终的项目工程结构如下: 很简单,只有三个类,预加载类,游戏主场景类,应用代理类,新手刚入门喜欢将很多东西都写在尽量少的类里面. 游戏设计 游戏结构如下,游戏包含预加载场景和主场景,主场景中包含背