Cocos Creator 通用框架设计 —— 资源管理

楼主很强啊。
不过我想,如果cc.loader.getDependsRecursively可以找到指定资源的所有依赖,cc.loader._cache可以找到当前已加载的所有资源,那么不就可以在每次cc.loader.loadRes的完成回调中调用getDependsRecursively获得当前加载资源的引用,然后去_cache里查找到这个资源的uuid的对象给它的引用计数器累加1(js动态添加这个属性);在释放资源时,依旧通过getDependsRecursively找到其依赖,再去_cache中查找到对应属性,通过检查前面添加的引用计数器来确定是否可以释放该依赖。
这样是不是也可以达到同样目的?
然而这只是个设想,可能在_cache很大且某个资源的引用特别多时会有效率问题。
如果我错了还请指教。

这是一个基础的想法,但getDependsRecursively的性能频繁调用的话可能会比较差,它做的事情是递归遍历并且生成一个数组,资源之间的互相依赖,可能有很多层,最好只了解一层的依赖,而不要递归到所有的,现在GitHub上最新的实现就是只记录一层的依赖关系,每层维护好自己的,每个资源记录我被谁引用了,然后当没有人引用我的时候,我就可以去解除我依赖的那些资源的记录。

1赞

简单说就是尽量用最少的空间和时间来完成这个事情

1赞

mark

感谢分享

了解~

请问一下像是类似cocos图集类的该怎么加载呢?

宝爷,用的时候发现,loadArray 支持数组type

// 释放一个资源
    private _release(item, itemUrl) {
        if (!item) {
            return;
        }
        let cacheInfo = this.getCacheInfo(item.url);
        // 解除自身对自己的引用
        cacheInfo.refs.delete(itemUrl);

        if (cacheInfo.uses.size == 0 && cacheInfo.refs.size == 0) { // ①
            // 解除引用
            let delDependKey = (item, refKey) => {
                if (item && item.dependKeys && Array.isArray(item.dependKeys)) {
                    for (let depKey of item.dependKeys) {
                        let ccloader: any = cc.loader;
                        let depItem = ccloader._cache[depKey]
                        this._release(depItem, refKey);
                    }
                }
            }
            delDependKey(item, itemUrl);
            //如果没有uuid,就直接释放url
            if (item.uuid) {
                cc.loader.release(item.uuid);
                cc.log("resloader release item by uuid :" + item.url);
            } else {
                cc.loader.release(item.url);
                cc.log("resloader release item by url:" + item.url);
            }
        }
    }

这一段代码,有泄漏问题。

比如

A 引用了 B ,B引用C 

那么加载A的时候,B 与 C 都打上了标记上了  A的标志。

同时又有

D 引用了B, B引用C 

那么加载D的时候,B 与 C 都打上了标记上了  D的标志。

好了,此时,对A进行释放。当递归到释放B的时候,在①处的条件肯定不成立。(因为cacheInfo.refs.size == 1,他被D引用着),所以就返回了。 
那么问题来了,当我们进行D的释放的时候,C依旧无法得到释放(因为C 身上永远就挂着A的引用了。而A已经释放完毕了)。

github上的最新代码换了一种实现方式,不存在泄露问题

楼主写个啥书

楼主,这个这个还是会存在问题,在两个节点之间,快速切换,不停的生成和销毁,如果有共有的资源,就会出现加载错误
类似这种,这个没有什么好办法解决吗,目前就卡在这里, 难道只能把共用资源 永不销毁吗

还有一个是有的资源,如果快速生成然后销毁,然后再快速生成, 会出现黑块的现象

可以写一个简短的示例代码看看

用法很简单, 重点还是资源多,而且预制体之间重复使用也多, 一个预制体可能有 100 -200个资源,而且我们使用了合图,有可能合图里某一张图, 会用在两个预制体。

这个流程我想大概是这样的:

  • 资源A 和 B 都依赖了 C
  • 同时加载资源A和B(异步流程自动加载C)
  • 释放资源A和B
  • A加载完,释放A和C
  • B还没加载完,所以释放的时候不会释放B,但底层检测到C已经加载,此时也不会再去加载C,而是直接加载B
  • B加载完了,但是C被释放了,出现黑块(假设C是图片)

这里,如果你用cc.loader来加载和卸载呢,是不会出问题的,因为它不会释放资源的依赖。好的修改方法应该要在cc.loader内部规避这个情况,但我觉得可以看看2.4的新资源机制有没有解决这样的问题。

另外,这是个什么应用场景???

我用的是2个单独的预制体,是一般常见的弹出框这种,点击就会弹出。 问题和你解释的原因差不多。

但是即使使用 cc.loader, 我们在测试的时候,如果以极快的速度打开和关闭预制体,等于对这个预制体的资源疯狂进行cc.loader.loadRes 和 cc.loader.release,也会出现一些问题,报错资源材质丢失,黑块等等,这个已经是底层机制的问题了。 我们增加了打开和关闭的间隔, 会降低一些风险。 使用你的脚本也是同样的。

手机和网页调试,都可以出现, 手机相对不易触发。 这个问题,我们其实在想是不是我们测试方法有问题,不应该这样极限去测试。 因为正常用户不会这样非常快的手速来回点。 正常情况下,使用你的脚本, 或者使用 cc.loader。loaderres 极小概率会出现问题。

但是这样来看,我单个预制体反复点, 极小概率出现材质丢失或者黑块,已经和你的脚本没有关系了,因为说到底你的加载和销毁资源最终还是使用了cc.loader.loadRes 和 cc.loader.release 。 我们使用查找资源的方法还是getDependsRecursively去获取的,相对你的方法效率应该差距不大甚至比你还差一点。

所以目前的问题就是:单个预制体快速load 和release, 多个预制体来回快速切换,小概率会出现问题, 前提都是快速切换。

图片黑块的问题,可能是精灵纹理有异步的load行为函数。最好规避方式,可以通过director注册相关事件来延后释放。

我们尝试一下看看效果,谢谢啦:joy: 重点还是会报资源丢失引起的错误

给你看一下我们本来使用的资源加载和删除的方法:

使用我们自己的方法,主要问题就是快速点击可能会导致资源加载丢失。 我同时使用过你的方法和我们的方法,做过对比,基本上面临的情况是一样的。

我也大概知道 问题就是:
1.我加载a 然后销毁a,然后再加载a,在速度很快的情况下,因为销毁是异步且延迟的, 那么我在第二次加载的时候,就可能出现资源没有加载成功的问题, 因为a在第二次加载的时候,它以为自己加载成功了,结果又被a的第一次销毁给销毁掉了。

2.我加载a,然后销毁a,然后加载b,然后销毁b,然后加载a这种来回切换,出现问题的原因是两者可能共用了资源,报错和黑块的原因像你解释的那样, 如果没有共用资源像我说的1那样。

现在问题就是这两个问题,不论是你的方法,或者我们的方法,都存在这两个问题。 还有一些问题,也体现在快速加载的时候,我们一些代码不严谨缺少判断也会导致一些问题,不过那是我们应该解决的。 而这两个资源相关的问题,真的是想破头都没什么头绪。我们只能增加点击间隔,来延缓预制体的生成和销毁。 这样会大大的降低风险