[√]cocos2dx 垃圾回收机制

简介: [√]cocos2dx 垃圾回收机制

最早的构想:有很多问题

preload:load(){
 if(!list.has()){
  list.add()
 }
}
preload:unload(){
 if(list.has()){
     list.remove
 }
}
if(totalMem > maxMem){
  cache:remve()// 这个不会通知preload,导致preload认为还有这个texture
}
sprite:create()
scene:add(sprite)
sprite:remove() // 此时没有人使用这个texture
setMem(10)//就会触发清理texture
preload:load(res)
sprite:create()
scene:add(sprite)
sprite:remove() // 此时没有人使用这个texture
setMem(10)//不会触发清理texture,因为preload还持有
preload:unload(res)// 真正的清理了
preload:load(res)
sprite:create()
scene:add(sprite)
sprite:remove() // 此时没有人使用这个texture
setMem(10)//不会触发清理texture,因为preload还持有
preload:unload(res)// 真正的清理了
sprite:create()
scene:add(sprite)
preload:load(res)// 发现是外人持有
sprite:remove() // 此时没有人使用这个texture
setMem(10)//不会触发清理texture,因为preload还持有
preload:unload(res)// 无法删除,因为是外人持有,应该交给外人管理
spirteFrameCaceh  frame1 texture

绕过SpriteFrameCache,导致SpriteFrameFrameCache不知道Frame的Texuter已经没了

delete texture -> check in SpriteFrameCache -> notify SpriteFrame update texture to null

垃圾回收机制

如何判定为垃圾?

  • 没有人使用: 预加载后2s再使用,中间没人使用,也会被判定为垃圾,所以需要增加预加载接口,对preload assets -> retain

大致实现

schedule(()=>{
    spriteFrameCache::gc();// 怎么知道frame没有人使用,因为addCache后为2?
    // 为2就是没人在使用,可以尝试clean
    textureCache::gc();
})

怎么从SpriteFrameCache中移除

让frame.ref=1

function SpriteFrameCache:gc(){
    /*
    怎么知道卸载哪个
    SpriteFrameCache
        frame1
        frame2
     **/   
    if(frame.shouldUnload()){
        frame1.release() 会导致一个plist创建的N个frame,并不会完全卸载,
        //只要spriteFrame还有在使用,对应的Texture就不能卸载,Texture对应的所有SpriteFrame都不能卸载
    }   
    // 卸载没有任何人在使用的plist,即plist的每个SpriteFrame都没人在用
    spriteFrameCache.allPlist.for(plist=>{
        if(plist.spriteFrames.isUnuse()){
            spriteFrameCache:removeSpriteFramesFromFile(plist) // 尝试卸载plist文件
        }
    })
    // 调用这个api意味着:
    spriteFrameCache:removeSpriteFrameByName
}

导致的问题

// 情况1
spriteFrameCache.add(plist)
frame1.ref=2; // create +1 insertSpriteFrameCache +1  没人使用,也会导致被gc
// 情况2
const csbNode=csb:load()
csbNode:remove() // 此时csb对应的plist也没人使用了,也会导致被gc
// 触发了垃圾回收
异步逻辑
const frame = spriteFrameCache:getSpriteFrameByName(name)  
//可能为null,原因是垃圾回收将没有人使用的spriteFrame清理了
// name是路径,所以不会出现撞车
// 但是被移除了,也不知道这个name来自哪个plist
// 解决办法是记录信息,然后重新加载这个plist
const sprite = Sprite::create(frame) // null

removeUnuse() 虽然从cache中被干掉了,没有被真正的delete,但是其他地方的Sprite还在使用,当这个Sprite不使用,就会调用一次release,对应的texture才会被真正释放了

  • 一个极端的流程
texttureCache:add(1.png)
let spr1=sprite:create(1.png)
scene.add(spri1)
textureCache:remove(1.png) //old 1.png没有被delete
textureCache:add(1.png) // 此时使用的new 1.png纹理
let spr2= sprite:create(1.png) // 使用的是new 1.png
scene.add(spr2)
spr1.remove() // old 1.png会被delete,实际肯定不会这么玩
  • 正常的容易维护的流程
// preload
textureCache:add(1.png)
let spr1=sprite:create(1.png)
scene.add(spri1)
spr1.remove()
// unload
textureCache:remove(1.png) // 1.png会被delete

因为Texture没有加入AutoRelease,所以只能定时gc,仅仅是让cache放弃持有

function gc(){
    if(texture.lastUseTime < 1s && texture.ref=1){
         texture.release()   
    }
}

spriteFrameCache的gc思路

因为spriteFrame的本质是Texture的某一部分,一个plist有多个SpriteFrame,但不是每个SpriteFrame都会被使用。

比如button有normal、press2个状态,切换状态时都会从SpriteFrame中取,但是button只会使用其中的某一个状态,press状态的SpriteFrame没有人在使用,但是不代表这个SpriteFrame是无用的。

只有plist对应的所有SpriteFrame.ref=1,才能说明这个plist没人在使用,才需要卸载对应的所有SpriteFrame,这样做才安全。

单个SpriteFrame需要知道和它在一个plist的所有SpriteFrames,一种做法是新建一个plistDict类,持有它对应的frames,这样也不太好,主要是它还有removeSpriteFrameByName这样的接口,可以删除某一个SpriteFrame, 所以还需要从_spriteFrames单元遍历,分析出来对应的plist引用情况

auto gc的思路

自动gc,甚至都不需要unload,如果后续有再使用,unload会导致新的重复纹理产生,old Texture因为无人使用,到达lastUseTime才会被delete,如果不unload,lastUseTime会被更新,这样就不会释放了,也无需重复生成新纹理。

基于这个原理,只需要检测出Texture的使用频率,让cache释放掉低使用频率的持有关系即可。

texture->getName()可以证明渲染使用频率,在提交渲染时,更新texture.lastUseTime

新的使用流程:不需要手动remove,只管load就行了,gc会自动处理

// 进入到场景1
textureCache:add(image1)
textureCache:add(image2)
let spr1 = sprite:create(image1)
scene:add(spr1)
spr1:remove()
//进入到场景2,为了提升create性能,把image1, image2重新加一下
textureCache:add(image1)
textureCache:add(image2)
let spr2 = sprite:create(image2)
scene:add(spr2)
// 到这里只用了image2, image1当到达gc时间后,会被gc自动清理

在渲染的时候更新纹理的渲染时间

大部分使用的是TrianglesCommand,也有QuadCommand

TrianglesCommand::useMaterial(){
    GL::bindTexture2D(_textureID);
}

如何统计纹理内存

在Texture2D的构造函数进行统计添加,在Texture.init的时候才能真正知道纹理的占用大小,在Texture2D的析构函数进行统计移除,这样是最准确的。不能依赖TextureCache的add remove,因为removeUnuse函数有坑,

在Texture构造函数统计,new Texture的纹理也会被统计进去,而且这个纹理不加入cache,所以texture.ref=1就需要释放不符合这类纹理,所以texture.ref=1只适合textureCache里面的纹理。

image.png

发现以上的逻辑的问题:如果_utf8Text为空字符串,也会创建一个空的纹理,不过它可以正常随着label释放。

预加载对gc的影响

  • button
const btnPressTexture = textureCache.load("button_press.png")// 此时press纹理的ref=1,只有cache在持有
button.setPressTexture(btnPressTexture) // 给某个状态设置了一个纹理

当button触发press状态时,就会使用press纹理,此时的这个press纹理不能被释放,否则会有问题。

如果是在press是在plist里面,还不会被gc掉,因为plist.normal在使用。

但是如果press在另外的一个plist里面,按照目前的gc规则,它的确是有可能被gc掉,追加一个gc保护的设计可以避免press纹理被释放。

  • preload
// 加载大量的图集,但是就不使用,而且在这个scene有可能还真用不到
enterScene()
preload(1.plit, 2.plit)
use(1.plit)
exitScene()

2.plist在没有exitScene有可能就会被释放掉

如果不追加gc保护,那么游戏在正常的运行过程中,也会发生2.plit因为无人使用被释放的问题,但是后续会再使用2.plist的某个SpriteFrame,所以plist的信息也需要保存起来,方式后续如果获取不到SpriteFrame的时候,再次reload

还有一种办法解决,就是texture的useFrame如果是0,表示创建后没有使用,而预加载的texture刚好是符合这种情况的,所以0是一个非常特殊的值,但是这样做会导致一个问题,如果一直不使用,texture.useFrame就一直是0,就无法被gc释放掉。

因为预加载后就会一直放在那里,可能很久一段时间之后才会突然的使用,

当然也可以检测哪段时间大量密集的加载纹理,证明这些纹理是预加载的,但是这些都是基于表现推理的结果,没有preload api准确

节点缓存对gc的影响

思考

nodePool里面的每一帧都会更新lastRenderTime,保证node对应的纹理不会被gc

因为nodePool里面的node不会被渲染,导致不能更新lastUseTime

如何知道node,及其子节点所使用的纹理?

答案

以上的顾虑是多余的,不需要更新lastRenderTime也没问题,虽然lastTime满足条件,但是texture.ref不满足。不需要for循环遍历,判断引用计数即可,就连缓存池都不需要。

Sprite.create(1.png)不管有没有添加到scene中,Texture对应的ref已经是2了

只有sprite.remove 或者 sprite.release才会让Texture.ref=1 1意味着只有cache持有,这时只需要cache对接下gc逻辑即可。

主动调用sprite.retain()实现了缓存的效果,此时sprite.ref=2, 而Texture.ref=2是不会受影响的。

对SpriteFrame也做一个lastTime机制

目录
相关文章
|
11月前
|
JavaScript C++
[√]cocos creator 热更新源码剖析(2)
[√]cocos creator 热更新源码剖析(2)
87 1
|
11月前
|
JavaScript C++
[√]cocos creator 热更新源码剖析(1)
[√]cocos creator 热更新源码剖析
142 1
|
11月前
|
JavaScript
[√]cocos creator 热更新源码剖析(3)
[√]cocos creator 热更新源码剖析
169 1
|
5月前
|
JavaScript 前端开发 Java
CocosCreator 面试题(十)Cocos Creator 内存管理
CocosCreator 面试题(十)Cocos Creator 内存管理
374 0
|
11月前
|
Android开发 iOS开发
[√]cocos2dx 内存泄露
[√]cocos2dx 内存泄露
70 0
|
11月前
|
C语言 C++
[√]luahelper为什么无法调试cocos2dx lua
[√]luahelper为什么无法调试cocos2dx lua
106 0
|
11月前
[√]cocos2dx vld检测lua websocket的内存泄露问题
[√]cocos2dx vld检测lua websocket的内存泄露问题
71 0
【Cocos2d实例教程一】xcode5下Cocos2d环境的搭建
(转载请注明出处:http://blog.csdn.net/buptgshengod)     第一步,现在要安装集成环境xcode5,安装xcode5需要系统至少是os x 10.8.5。     第二步,下载cocos2d-iphone到电脑文档或任何一个文件夹(这里以文档为例),    下载地址:http://code.google.com/p/cocos2d-iphone/d
936 0