creator动态引用设计思考2:最小的实现雏形

256 阅读3分钟

深入理解内存释放

内存释放到底在释放什么?

  1. 内存/显存销毁是调用gl.deleteTexture(this._glID);image.close()等相关函数

  2. 解除引用关系,本质上是要回收js object占用的内存,creator文档上也有写明需要这么做

    sprite.SpriteFrame = null
    

相对纹理的内存占用,CC.Assetsjs object占用的内存量是很小的。

实现思路

addRef/decRef 的时机

Sprite组件为切入点,对sprite.spriteFrame=xxxget/set进行修改,增加addRef/decRef的操作

核心思路就是监控SpriteFrame相关的赋值操作

  • 对旧的texture.decRef
  • 对新的texture.addRef
  • Spritedestroy的时候decRef

事实上cocos2dx也是这么玩的

需要注意的是在资源被释放之后,再次使用的时候需要去走load相关接口进行加载

在cocos2dx上是每次都从cache中获取,cache如果没有会加载,因为是native,所以会重新从磁盘上读取纹理文件

更智能的内存动态释放

当某个纹理超过一定帧数之后,仍旧没有被再次使用,那么我们就认为这个纹理可能需要释放,这种设计更加符合使用的直觉。

在实现的过程中,没有被使用并不是没有被渲染

比如某个Sprite暂时隐藏了,此时Sprite.texture是没有被渲染的,如果期间texture被释放了,当Sprite显示时就会发生异常。

所以在实现没有被使用的逻辑时,还需要关注这个情况。

设置纹理的渲染帧数据

要在Texture2D上挂latestFrame,不能在CC_Texture2D上,因为

_setPassProperty (name, value, pass, directly) {
    let properties = pass._properties;

    if (!properties.hasOwnProperty(name)) {
        this._createPassProp(name, pass);
    }

    let prop = properties[name];

    let compareValue = value;
    if (prop.type === enums.PARAM_TEXTURE_2D) {
        compareValue = value && value.getImpl(); // 看这里: getImpl的缘故
    }
}

如果挂在CC_Texture2D上,也就是cc.Assets上,要改动的地方比较大,因为在渲染时,提交的类型是Texture2D,

image.png

  • 一种做法是将Texture2D增加一个属性指向CC_Texture2D,然后在渲染时,更新CC_Texture2D的渲染帧,这么做需要考虑引用带来的js object内存释放问题
  • 另外一种做法就是顺势而为,也就是上边提到的,只需要不同的cc.Asssets提供getLatestRenderFrame即可,不同Assets实现去Texture2D查找latestFrame即可,这其实也很容易办到,因为SpriteFrame很容易知道当前使用的纹理数据

至于获取当前游戏第几帧率,creator已经提供了这样的接口

const total = cc.director.getTotalFrames();

潜在的问题

在 C 与 C++ 等语言中,开发人员可以直接控制内存的申请和回收,而 JavaScript 所有对象的内存都由垃圾回收机制来管理,会周期性对那些我们不再使用的变量、对象所占用的内存进行释放,这就导致 JS 层逻辑永远不知道一个对象会在什么时候被释放

想象一种情况,当你释放了 AssetManager 对某个资源的引用之后,由于考虑不周的原因,游戏逻辑再次请求了这个资源,这时垃圾回收还没有开始(垃圾回收的时机不可控)

当出现这个情况时,意味着这个资源还存在内存中,但是 AssetManager 已经访问不到了,所以会重新加载它,就会造成这个资源在内存中有两份同样的拷贝,一份为刚刚请求的,另一份为已经释放但未被回收的,形成资源在内存中 暂时性 的 冗余

之所以说暂时性,是因为在下个 GC 周期时,该资源依然会被回收,释放对应的内存

如果只是一个资源还好,但是如果类似的资源很多,甚至不止一次被重复加载,就会造成当前时间内存飙升,而且频繁GC也会影响游戏的流畅性

因此我们释放资源时,应该 避免频繁释放,同时 避免释放近期内将要复用的资源