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

宝爷,用的时候发现,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那样。

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

你这预制体被回调时,如果预制体里面有sprite组件,那么sprite组件上的纹理是异步load 完成的,此时如果你直接释放,那么load回调时就会报错。

我先试一试延后释放,你们使用的时候,如果快速的去加载和销毁单个预制体会出现问题吗。 而且这个预制体,不算特别大,只能是中等大小,大概有100-200来个资源 还有一个因素是,我们所有的图片资源都使用了 cocos的自动图集

我没用贴主的这套,自己实现了一套不过内核都差不多。 引用计数。不过我加载和销毁 都放到了,director的行为事件中延后处理,并且保证释放时不存在任何异步加载的行为。所以不会出现贴图丢失错误。

不存在任何异步加载行为? 你怎么做到的,求指教:joy:,我们的程序理论上来讲做不到, 加载这个页面他销毁后,很有可能又去点击加载下一个页面,然后又立马关闭销毁下一个页面,然后又回来加载这个,如果速度快,就能出现问题。 或者需要对资源加载底层做一些修改了。

肯定可以实现啊,你都能知道用户触发异步的行为了。修改下释放资源的时机。保证释放时用户没有发起任何异步加载请求。。你们只要保证在加载下一个异步资源时,当前关联引用的资源已经被释放了,那么cc.loader就会重新加载的。

mark!

类似这样的问题,我在UIManager里面解决,把UI标记为cache