讨论下2.4.0的引用计数

1.之前用分模块管理的时候,无法完美解决模块间的复杂引用关系问题
2.现在用引用计数解决这个复杂引用关系问题,但是发现引擎没有很友好的支持分模块管理
问题:一般项目的资源管理很难以单资源作为管理粒度,表面上管理起来很简单,加减操作,soeasy。但是很多时候加和减是没法配套的,如果不理解这个配套问题,举例:我在任何时候要加载任何东西,但是我只在一个地方释放,那么我在这个地方如何优雅的找到之前加载的东西呢?自己缓存起来?当然这个时候分目录管理是最合适,我直接释放一个目录,爽!但是引用计数不能真实的提现一个资源是否在当前运行环境有用,盲目释放目录,可能造成引用计数错误的情况。那就只能配套API === 加载目录=>释放目录,但是有时候我真的不想盲目的加载一个目录,而是更想按需加载…

唉 怎么办呢?不管是以场景做为粒度,还是以bundle作为粒度,都避不开上诉问题

我测了下。引用数是 1,2,3,3

应该是正常的

如果真想做到无脑释放资源,我觉得必须要用到 “环境标记法”。每次释放都必须经过一次环境标记,未被标记的资源可以安全释放,但是由于环境标记法无法随时随地调用(因为环境标记一次需要遍历当前环境资源,随时调用存在效率问题)。
给开发者提供一个假释放接口,用来收集即将被释放的列表。当时机合适时 做一次环境标记,然后释放掉安全资源。

时机:1场景切换,2提供一个真实释放接口

这种环境标记法,我经过一年的线上测试,没有什么问题

我们讨论了一下,目前关联到场景中的动态资源,释放起来还是比较蛋疼的。是因为这个原因所以找不到之前加载的东西吗?如果不是跟场景有关的话,应该不会有问题?

可能你误解我说的“按模块释放”的意思了,我的意思是在一个特定的代码范围(模块)内重复使用某个资源时,为了不那么麻烦和中间的其他问题而只在代码范围开始前增加一次计数,结束前减少一次计数

有一张英雄图片,A 模块假设是包括匹配和选择英雄,加载中,游戏中,游戏结算这样的整个关卡流程(当然实际上不会这么分模块)

那可以在匹配刚开始把这张图片加载并计数,在游戏结算再减少计数,这样视情况而定就不至于每次用到都需要计数

还是很难用文字描述。简单说就是:2.4的引用计数已经比之前好的太多了,但是真实体验,没有预期的方便。嗯,应该就是这样总结的

有赞同的吗?我是一个人吗?

我觉得要开放引用计数的接口就应该弃用release相关的接口。最好的方式应该引擎内部通过引用计数来对资源进行管理,但是对于开发人员只需要操作资源加载和释放。

嗯,这些还差些最佳实践的方案

Prefab里拖了一个SpriteFrame,SpriteFrame引用了Texture,理论上Texture应该加一变成4吧

@869751569 说的 “自己缓存起来” 就是我觉得的,现在我能想到的,最适合手动引用计数的使用方式,虽然听起来很不必要,为什么要自己又缓存一遍,但现在看了这几种其他的方式之后我依然觉得是最合适的。

1.封装一个“模块资源管理类”,第一次加载进行 addRef,之后只读取缓存,当调用 destroy 时对所有缓存到的资源 decRef
2.有签到,抽奖,任务,收集,大厅,玩法等一些零散的东西,一般都会按这样的模块分给不同的人做,只需要告诉他创建模块资源管理实例,需要加载资源时用实例方法加载资源,在模块销毁时调用模块资源管理实例的 destroy 即可

这样其实开发人员就只需要做加载和释放操作,他可以不知道引用计数,只需要知道自己做的东西的生命周期即可。

对于签到,抽奖,任务弹框类的生命周期很简单的模块,打开即开始,关闭即销毁的非常适合,如果是其他的如全局公告跑马灯这种模块,还是可以用其他类似的办法去降低手动引用计数使用上的麻烦,但绝大多数的东西都是有简单的,明确的生命周期的

1赞

不会的,资源只会对其直接引用的资源增引用。不会管间接引用的资源。所以第四种情况,spriteFrame 的引用会增加,但是 texture 的不会

环境标记法,核心逻辑用的引擎内部方法,项目自测效率是可以的,遍历一次场景一般20ms左右,我觉得你们可以系统测试一下这个方法在场景节点差不多2000个,每个节点至少配套一个组件,一个资源的情况下,不同节点深度和广度下的效率情况,如果在可接受范围内,用环境标记法是最安全的

重新测试了下,直接拖动到编辑器的图片,引用计数是对对。但是有个疑问,在destroy一个精灵的时候,是否需要手动对精灵引用的spriteFrame进行释放?测试结果是销毁精灵后,它所使用的spf和tex2d的引用计数没变还是1, 这个按说在节点销毁时,应该可以自动把引用的资源释放掉吧?

start () {
        let spf: cc.SpriteFrame = this.testSp.spriteFrame;
        let tex2d: cc.Texture2D = spf.getTexture();
        cc.log('spfRef: ', spf.refCount);  // 1
        cc.log('texRef:', tex2d.refCount);  // 1

        // spf.decRef(); // 这里如果打开则下面都是0,0
        this.testSp.destroy();

        this.scheduleOnce(t=>{
            cc.log('spfRef: ', spf.refCount);  // 
            cc.log('texRef:', tex2d.refCount);  // 1 
        }, 0);
    }

WHAT?刚告诉我就是不加,你这又加了???:joy:

我是以场景作为当前运行环境,释放的时候校验手动加载持有和场景持有(动态资源+静态资源+自定义组件拖挂资源+所有资源的依赖资源) ;未被任何形式持有的 才能真实的释放掉

假释放接口提供了,释放单资源(uuid,cc.Asset, path),释放目录,释放节点
提供两个真实释放接口 releaseAllUnused 和 releaseManualUnused,内部进行了环境标记后的安全释放

测试结果是销毁精灵后,它所使用的spf和tex2d的引用计数没变还是1, 这个按说在节点销毁时,应该可以自动把引用的资源释放掉吧?

目前是不会的,场景中直接引用的资源不会与某个节点绑定,只会与场景绑定。所以你在场景切换的时候,如果勾选了自动释放的话,应该可以看到这个资源被释放掉。

在经过手动对spf调用decRef后,它引用的tex2d的引用计数却没变化,这个不应该自动减1嘛?

是应该减一的,不过这个操作不是立即执行的,会延迟到本帧的最后一个阶段执行,所以你执行之后打印出来的结果还是老的结果。你可以延迟个100ms来看,texture 应该被释放了

了解,这个真实释放接口可能比较适合中小型项目,大项目上万个节点都是有可能的,这么遍历一次,不太吃得消啊。而且跟组件复杂度有关,组件复杂度越高,消耗越高