creator动态引用设计思考3:prefab、dragonBones的动态引用实现

205 阅读4分钟

动态加载龙骨资源

image.png

你会注意到,对应纹理的dynamicRef会变很多,是因为骨骼多导致的。

dragonBones的动态引用实现思路如下:

动态加载时,tex/ske的static正常都是0,在Armature.dragonAsset和Armature.dragonAtlasAset赋值时,对assets进行引用管理,目前这样的实现逻辑dynamicRef符合预期。

注意到texture.staticRef=1,是因为tex引用导致的,staticRef=1在gc时会排除(因为gc只关注staticRef=0的情况),问题不大

当龙骨所在的节点销毁后,目前sketex能够恢复dynamicRef,真正的texture的动态引用没有恢复。

不过这个问题不大,因为在释放db.tex时,会把真正的texture也释放掉,因为会查找db.tex的依赖

ske这类非纹理资源的渲染帧问题

ske的renderTime始终为0,gc在遇到0的时候就认为一直没有使用,事实上目前renderTime只能检索到纹理,非纹理类的资源是没有效的

但是因为dynamicRef是正常的,所以他并不会被释放,当dynamicRef真正为0时,会被立刻释放,可能存在和tex释放不同步的问题

解决办法也有,取了个巧:

this.dragonAsset.getLatestRenderFrame = () => {
    const dragonAtlasAsset = this.dragonAtlasAsset;
    if (dragonAtlasAsset === null) {
        // prefab模式加载龙骨,销毁龙骨时,dragonAtlasAsset=null
        return 0;
    } else {
        // 重写函数:使用dragonAtlasAsset的renderTime
        return dragonAtlasAsset.getLatestRenderFrame();
    }
};

通过prefab加载龙骨,销毁后ske是索引不到tex的,所以导致这里为0

image.png

对于非纹理类的资源,这种借用的做法很明显存在缺陷,一旦依赖的纹理和自身失去了关联,就导致无法检索到renderTime,所以还是得assets增加一个renderTime属性,后续开发过程中也证明这样做很有必要。

在update中,纹理和自身同步即可

Armature.update(){
    const total = cc.director.getTotalFrames();
    this.dragonAsset.setLatestRenderFrame(total);
}

db需要加载2个资源带来的问题

因为它是需要动态加载2个资源,需要2次load的过程

function createDB(asset_tex, asset_ske){
     // 异步创建dragonBones
     setTimeout(()=>{
        const db = new cc.Node("db");
        const comp = db.addComponent(dragonBones.ArmatureDisplay);
        comp.dragonAsset = asset_ske;
        comp.dragonAtlasAsset = asset_tex;
        
        // 一定要记得取消gc的保护,否则gc将无法自动释放相关资源
        gc.protectAsset(asset_tex, false); 
        gc.protectAsset(asset_ske, false); 
     }, 1000);
}

cc.loader.loadRes(tex,(error, asset_tex)=>{
    // 此时asset_tex并不会被释放,调用这个api的原因就是为了防止gc释放掉
    gc.protectAsset(asset_tex, true);
    cc.loader.loadRes(ske, (error, asset_ske)=>{
        // 如果createDB中创建dragonBones没有异步逻辑,可以取消对asset_ske的保护
        gc.protectAsset(asset_ske, true);
        // 因为load ske需要时间,如果这段时间刚好触发gc,就会将之前的asset_tex释放掉
        createDB(asset_tex, asset_ske);
    })
})

prefab方式加载龙骨

prefab静态引用

prefab在释放的时候会连带所依赖资源也一起释放

var depends = dependUtil.getDeps(asset._uuid);
for (let i = 0, l = depends.length; i < l; i++) {
    var dependAsset = assets.get(depends[i]);
    if (dependAsset) {
        dependAsset.decRef(false);
        releaseManager._free(dependAsset, false);
    }
}

image.png

prefab是能够自己感知到所依赖的资源的,staticRef这些都是静态引用。

实现prefab的动态引用管理

一般在使用dragonBones的时候,都是搞到一个prefab中,这样的话prefab就会自动统计所依赖的静态资源。

我也考虑到了你可能在prefab里面添加龙骨,其实本质上还是对prefab的管理

image.png

测试发现:加载前,释放后,ref并没有任何变化

image.png

实现思路应该从prefab.dynamicRef下手,调试下顶层的加载逻辑

const ins = cc.instantiate(prefab);// 实例化prefab

追踪代码,发现最终都走到了base-node,所以可以在base-node里面添加对prefab资源的引用操作。

node._instantiate(){
    if (cloned._prefab && cloned._prefab.asset) {
      cloned._prefab.asset.addDynamicRef();
    }
}
node.destroy () {
    if (this._prefab && this._prefab.asset) {
      this._prefab.asset.decDynamicRef();
    }
}  

接下来就是更新prefab.renderTime,最开始我想到的是update函数,但是这个函数是针对component的,node并没有update

还是得从渲染逻辑下手,换个思路,因为prefab资源是和node关联的,所以只要node被渲染了,对应的prefab也算是被渲染了。

如何知道node被渲染了?是的!RenderFlow

我们可以在RenderFlow里面的children阶段,增加node.visit的机制

RenderFlow.prototype._children = function (node) {
    node.visit && node.visit();
}
node.visit() {
  if (this._prefab && this._prefab.asset) {
    const total = cc.director.getTotalFrames();
    this._prefab.asset.setLatestRenderFrame(total); // 更新prefab的渲染帧
  } 
}

至此,我们的prefab已经完全可以正常被gc管理了。

因为prefab在释放时,会连带释放依赖的资源,我们也恰巧是利用了这点,达到了prefab完美的释放。