在实现动态引用的过程中,需要注意的几种情况
节点隐藏
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
先了解下remove和destroy的区别
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
加载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存在同样的问题
因为这个plist一共有17张散图,没有name的那个assets就是对应的texture,而plist的ref都是0,要想办法优化下,
怎么产生plist对应的SpriteFrame?
直接赋值导致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都是空的
接着去下载对应的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查到了这个
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上限超过多少再进行释放