讨论下2.4.0的引用计数

@jare 场景放置一个精灵,打印它引用的spriteframe的refCount为1,当销毁这个精灵的时候,该精灵引用的spriteframe的refcount并没有变化啊,按说不应该自动-1嘛?

你这种思路跟引擎给你提供引用计数接口期望用法没什么关系了,完全可以在我开始的时候记录用了什么资源,最后干掉这个模块的时候疯狂的调用delRef()一万次就行了,跟实际剩几个引用一点关系都没有。
如果这样,引擎提供的这对接口还有什么意义?

而且实际开发的时候有几个会跟你的游戏完全一致,能做到按模块释放的?我游戏资源消耗多的时候,我得按粒度小得多的UI去释放。

要是有模块 A 和 B 都引用了同一个资源,A 模块随意去 decRef ,这个资源不就可能被提前释放吗?

引擎封装我现在能感觉到的有两个点:

1.对于新手或小项目,提供基于场景粒度的自动释放,使资源管理更简单易上手
2.对于需要以更小粒度释放的项目能更快更简单地封装自己的资源管理器,用旧引擎接口和新引擎接口封装一个就知道了

有多少游戏能做到我不太清楚,我们讨论的是:

1.引擎对这块的设计
我的观点是设计没有问题,降低了资源释放的门槛,但还是手动引用计数,所以在需要更小粒度去做资源释放的时候没有减少多少困难。

2.多少粒度适合
我的观点是按模块释放的粒度应该适合大部分游戏,而不是“有几个会一致”,一个 UI 界面也可以认为是一个模块,那看项目的不同。并且我认为绝大部分游戏都不需要以每个资源这种粒度去释放资源,但这不是选择题,我们的项目针对某些资源也会以单个资源的粒度去释放,都是视实际情况而定

我想着啊,加载资源遵循以下原则
谁加载谁释放。
加载的资源手动addRef,引用资源靠引擎addRef。

例如加载prefabA,prefabA引用了textureB,游戏逻辑中prefabA调用addRef,
之后又单独加载textureB,游戏逻辑中textureB调用addRef。

这样prefabA的ref为1 textureB的ref为2;
释放一律使用decRef自动释放,不使用release.
这样加载prefabA的模块释放时prefabA.refCount–,为0,释放。textureB.refCount–,为1不释放

在场景拖放一个精灵a,引用了texture b, 此时你去打印b的引用计数却是0,而不是1, 只有a的spriteframe的引用计数才是1,有点不理解了,按说a的精灵帧和精灵帧使用的贴图资源的引用计数不应该都是1嘛?

如果每个项目都能以模块为单位释放,那使用旧版本的资源释放方式就行了,根本不用引用计数。

能给个demo么,我这边简单测试了下 spriteFrame 和 texture 都是1

还真是,我试了下

测试步骤是这样
加载一个texture2d对象A并调用A.addRef();
A的refCount 加一;

加载A的spriteFrame对象资源A1,并调用A1.addRef();
A的refCount 加一;

加载一个绑定“包含一个@property(cc.Texture2D)private texture: cc.Texture2D = null;,并将A拖到该属性”的脚本的prefabB并调用B.addRef();
A的refCount 加一;

加载一个拖入spriteFrameA1的prefabC并调用C.addRef()
A的refCount 不变;

等我拆个DEMO

为什么以模块为单位释放时就不需要引用计数了?

既然你都知道哪个模块要用,哪个模块不用,那就直接像旧版本那样无脑释放就好了。引用计数是用来解决模块之间有资源复用的情况的。

DEMO.zip (842.7 KB)

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

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

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

应该是正常的

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

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

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

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

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

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

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

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

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