creator动态引用设计思考4:实现过程中需要注意的几种情况

205 阅读5分钟

在实现动态引用的过程中,需要注意的几种情况

节点隐藏

active=false也会导致纹理不会被使用,但因为dynamicRef没有减少,所以不会被释放,虽然latestFrame没有更新

这里存在一个极限情况,remove的时候,dynamicRef会减少,会在当帧结束后立刻被释放掉,因为节点隐藏latestFrame没有更新的原因。暂时感觉没有必要优化这个极限。

// Render
this.emit(cc.Director.EVENT_BEFORE_DRAW, deltaTime);
renderer.render(this._scene, deltaTime);

// After draw
this.emit(cc.Director.EVENT_AFTER_DRAW, deltaTime);

EVENT_AFTER_DRAW事件是在render之后派发的,render的是时候就会更新latestFrame。

预加载

为了保证预加载也不会被释放掉,可以给资源本身标记protect,在gc时,会自动跳过protect的资源,主要是考虑到预加载的强需求。

cc.loader.loadRes(
    "1.png", 
    cc.SpriteFrame,
    (error: Error, spriteFrame: cc.SpriteFrame) => {
            if (error) {
            } else {
                spriteFrame.dynamicRefCount; // 此时为0,
            }
    }
)

因为没人使用,如果超过一定帧数没人用,就会被回收,所以gc.frameLife不建议设置为0

removeFromParent

先了解下removedestroy的区别

destroy

凡是调用destroy的node,都会放进objectsToDestroy数组里面


function loop(){
    // Destroy entities that have been removed recently
    Obj._deferredDestroy();// 在帧的最开始清理之前要销毁的对象,也就是解除引用,也就是destryct()
}
function destruct(){
    o._name="";
    o._parent=null;
    o._children=null;
    o._components=null;
    o._prefab=null;
    o._color=null;
    o._contentSize=null;
    o._anchorPoint=null;
    o._trs=null;
    o._eulerAngles=null;
    o.__eventTargets=null;
    o._widget=null;
    o._renderComponent=null;
    o._capturingListeners=null;
    o._bubblingListeners=null;
    o._touchListener=null;
    o._mouseListener=null;
    o._spaceInfo=null;
    o._matrix=null;
    o._worldMatrix=null;
    o._position=undefined;
    o._scale=undefined;
    o._zIndex=undefined;
}
                
// 里面会牵扯节点树的操作                
_onPreDestroy () {
    if (!destroyByParent) {
        // remove from parent
        if (parent) {
            var childIndex = parent._children.indexOf(this);
            parent._children.splice(childIndex, 1); // 从节点树移除
            parent.emit && parent.emit('child-removed', this);
        }
    }

    return destroyByParent;
}

remove

仅仅是从节点树移除了,但是仍旧可以渲染,后续可以随时在添加的渲染树里面

prefab

资源类型是cc_Prefab类型: class Prefab extends Asset image.png

加载prefab的逻辑,加载完毕后静态引用是0,动态引用也是0会导致释放,这里不应该释放,解决办法参考这里

cc.loader.loadRes("prefab1", cc.Prefab, (error: Error, prefab: cc.Prefab) => {
    if (error) {
    } else {
      this.bgNode.addChild(cc.instantiate(prefab));
    }
});

实例化的细节逻辑

function instantiate (original, internal_force) {
    // assets也是object类型
    if (original instanceof CCObject) {
            if (original._instantiate) {
            cc.game._isCloning = true;
            clone = original._instantiate(null, true);
            cc.game._isCloning = false;
            return clone;
        }
    }
}

SpriteAtlas

也和prefab存在同样的问题

image.png

因为这个plist一共有17张散图,没有name的那个assets就是对应的texture,而plist的ref都是0,要想办法优化下,

怎么产生plist对应的SpriteFrame?

image.png

直接赋值导致atlas更新frame没有触发dynamicRef没有增加

plist文件转换的json只有自己的信息

{
  "__type__": "cc.SpriteAtlas",
  "_name": "yu.plist",
  "_objFlags": 0,
  "_native": "",
  "_spriteFrames": {
    "yu1": {
      "__uuid__": "695f3d07-f9cc-480d-ba60-c4b03581ade2"
    }
}    

下载完毕plist文件后,实例化的SpriteAtlas._spriteFrame都是空的

image.png

接着去下载对应的SpriteFrame的json配置文件,里面有详细的数据信息

{
  "__type__": "cc.SpriteFrame",
  "content": {
    "name": "yu1",
    "texture": "c54d45f1-9567-4ddd-bf5a-e618cc91b1b1",
    "atlas": "4ce62492-3003-4a49-b081-e03a1886700c",
    "rect": [202, 69, 102, 70],
    "offset": [0, 0],
    "originalSize": [102, 70],
    "capInsets": [0, 0, 0, 0]
  }
}

序列化cc.SpriteFrame后,再去加载依赖texture.json,最后才是是真正的Texture.png

然后就这样一层层剥洋葱一样向上赋值

在给SpriteAtlas的_frames赋值时

depend.owner[depend.prop] = dependAsset.addRef();

因为无法区分SpiritFrame是来自Atals,还是普通的png,其实这2个没啥区别,普通的png创建的SpriteFrame可以认为是,只有1个散图的Atlas

因为SpriteAtlas/SpriteFrame/CCTexture的索引关系链,如果被渲染了,还是能够知道SpriteAtlas的使用情况

特殊情况

  • 情况1:所有的SpriteFrame没人使用:

    Atlas所有的Frame都没人用、或者隐藏了,plist.renderTime来自SpriteFrame,time超过了规定时间后,即使SpriteFrame的动态引用不为0,Atlas仍旧会被回收,造成问题

    解决办法:SpriteAtals在获取renderTime的时候,如果SpriteFrame的动态引用不为0,就返回当前的渲染帧,只要在使用,可不是意味着必须要引擎去渲染。

    spriteFrame也要自己记录latestFrame,也就是在visit里面去更新(为了保证节点隐藏也能更新到),这样atlas才能拿到真正最后的latestFrame,不能使用atlas.texture.latestFrame,

  • 情况2(已验证没问题):部分frame没人用:

    Atlas的部分Frame没人用,过了规定时间,frame不会被回收,因为判断依据是frame.texture.time,虽然这个frame没人用,但是其他frame有人用,这个atlas就不会被释放。

    后续发现这个方案会被动态图集干扰,原因是图集的纹理被合并到了一个大的renderTexture中,如果renderTexture还有其他正在被渲染使用的纹理,会导致这个atlas.texture.time一直被更新,导致atlas无法释放。

    即使plist被release了,但是SpriteFrame的texture仍旧存在于那张大的renderTexture中,engine并没有将纹理数据从renderTexture中擦除,也没有意义

底层用到了Function

  setProperties (uuid, asset, assetsMap) {
  
  // spriteFrame._textureSetter=cc_Texture2D
    depend.owner[depend.prop] = dependAsset.addRef();
  }
return Function('s', 'o', 'd', 'k', sources.join(''));

sources对应的字符串代码

(function anonymous(
    s, // _Deserializer x序列化实例
    o, // 外边new cc.SpirteAtlas()
    d, // 对应配置文件的数据,也就是library/imports/xxx里面的json数据 
    k  // SpirteAtlas的构造函数
    ) {
    var prop;
    prop = d._name;
    if (typeof prop !== "undefined") {
      o._name = prop;
    }
    prop = d._objFlags;
    if (typeof prop !== "undefined") {
      o._objFlags = prop;
    }
    prop = d._native;
    if (typeof prop !== "undefined") {
      o._native = prop;
    }
    prop = d._spriteFrames;
    if (typeof prop !== "undefined") {
      if (prop) {
        s._deserializeObjField(o, prop, "_spriteFrames");
      } else o._spriteFrames = null;
    }
})

proxy查到了这个

image.png

prototype._deserializeObject = function (serialized, owner, propName) {
    if ( !Array.isArray(serialized) ) {
        // embedded primitive javascript object
        obj = {}; // 序列化的对象数据来源,也就是this._spriteFrames的来源
        this._deserializePrimitiveObject(obj, serialized);
    }
}

web平台注意事项

如果从缓存中销毁,对于web来说,会重新发起请求进行下载的逻辑,消耗cdn的流量,项目不介意,那就不用管了

建议在web平台拉长gc时间,控制assets上限超过多少再进行释放