动态节点使用对象池的释放问题@引擎开发团队

  • Creator 版本: 2.4.2

  • 目标平台:谷歌浏览器

  • 问题:

在某一场景内使用nodePool,当nodePool内对象不够时,会用到new Node 或者 cc.instantiate动态添加节点。
当然在切换到另一场景的时候,会手动将所有的new Node 或者 cc.instantiate动态添加的节点收回到nodePool。然后利用场景的自动释放功能释放。
问题就在于,是否要在onDestroy的时候,遍历nodePool并且distroy它们。

那我测试到的结果是:如果不遍历并销毁它们,内存会泄露;
如果遍历并销毁它们,内存不会泄露,但是会报警告,提示对象已经销毁。

代码如下:

整个封装脚本如下
/**

  • @module 要用到节点池的脚本可继承此脚本
  • */
    import data_control from “…/lib/data_control”;

let {ccclass, property} = cc._decorator;

//来自节点池的属性
export class Pool_property {
id: string;
pos: number;
other: any;

//ID位置
constructor(id: string, pos: number) {
    this.id = id;
    this.pos = pos;
}

}

export class PoolNode extends cc.Node {
pool_property: Pool_property = null;
}

/*采用节点池复用节点/
@ccclass
class node_pool extends cc.Component {

@property({type: cc.Prefab, displayName: '复用预制', tooltip: ''})
prefab: cc.Prefab = null;

static Pool_property = typeof Pool_property;

//成员
private Pool: cc.NodePool = new cc.NodePool();
protected recordArray: Array<PoolNode> = [];

/**
 * @returns {PoolNode}
 */
newPoolNode(parent: cc.Node = null): PoolNode {
    let pool = null;
    let created = false;
    if (this.Pool.size() > 0) { // 通过 size 接口判断对象池中是否有空闲的对象
        pool = this.Pool.get();
        created = true;
    } else { // 如果没有空闲对象,也就是对象池中备用对象不够时,我们就用 cc.instantiate 重新创建
        pool = cc.instantiate(this.prefab);
    }
    const id = data_control.getRandId();
    // const id = Symbol();
    const pos = this.recordArray.length;
    pool.pool_property = new Pool_property(id, pos);
    this.recordArray.push(pool);
    if (parent) {
        parent.addChild(pool);
        if (created && pool.getComponent) {
            let com = pool.getComponent(cc.Component);
            com.onLoad && com.onLoad();
            com.start && com.start();
        }
    }
    return pool;
}

/**
 * @return PoolNode
 * */
getNodeOfId(id: string): PoolNode {
    for (let node of this.getAllNodes()) {
        if (node.pool_property.id == id) {
            return node;
        }
    }
    return null;
}

getNodeOfPos(pos: number): PoolNode {
    return this.recordArray[pos];
}

getAllNodes(): Array<PoolNode> {
    return this.recordArray;
}

deletePoolNode(node: PoolNode | cc.Node) {
    //
    if (node['pool_property']) {
        //坐标左移
        for (let i = node['pool_property'].pos + 1; i < this.recordArray.length; i++) {
            this.recordArray[i]['pool_property'].pos--;
        }
        this.recordArray.splice(node['pool_property'].pos, 1);
        node['pool_property'] = null;
        this.Pool.put(node);
    } else {
        console.error('删除出错', node);
    }
}

//此函数在切场景前会调用,从之前的记录里把所有节点收回到节点池
deleteAllPool() {
    while (this.recordArray.length > 0) {
        this.deletePoolNode(this.recordArray[0]);
    }
}

//将所有节点收回后再销毁池内所有对象
destroyAll() {
    //这里调不调都无所谓,切换场景前已经调用过
    this.deleteAllPool();
    //
    while (this.Pool.size()) {
        let node = this.Pool.get();
        node.destroy();
    }
}

onDestroy() {
    //是否销毁 ?
    this.destroyAll();
}

}

export default node_pool;

destroy
销毁该对象,并释放所有它对其它对象的引用。
实际销毁操作会延迟到当前帧渲染前执行。从下一帧开始,该对象将不再可用。

会不会是这的问题

他不是立即触发,是在下一帧触发。 有可能你已经删掉了,之后这个destroy下一帧触发了

这种怎么解决呢 我也遇到了destroy带来的困扰

当前版本,正确做法就是只要是动态节点,都要自行Destory,不管有没有用节点池。如果非要用不需要释放的节点池,只能把节点池放在全局了。

这几天也遇到类似问题了,不过我不是用的节点池,我是loadRes一些预制体放到了一个数组A中,onload里A = [],勾选自动释放资源。
我以为重新加载这个场景的时候需要destroy数组里的元素,但是给我提示说already destroy了。。

你有查看内存泄露吗?

一样的,虽然提示你already destroy了,但是如果你不destory 内存一样泄露,而且你用loadRes预制体你还是要cc.instantiate
因为cocos在new节点的时候相对会非常卡,用节点池可以避免复用大量节点不会卡。

等3.0吧 新世代的引擎 保留了低时代的封装与bug 让我们拭目以待