Entity.destroy() not working always?

On an application I am working that does a lot of remote loading and creating new entities that replace the previous on runtime I noticed a memory leak. The JS heap is continuously increasing.

I isolated the issue on the code that creates the new batch of entities. I made sure that I remove all created entities before I initiate a new creative cycle using something like this:

    for (i = this.holderEntity.children.length - 1; i >= 0; --i) {
        if( this.holderEntity.children[i] instanceof pc.Entity ){
            this.holderEntity.children[i].destroy();
        }
    }

I check that the holderEntity is empty before adding new ones and it is. But the memory leak is still in place, like additional entities are added and previous aren’t completely destroyed.

I created a separate array and pushing in there the all entities added and - just to be sure - I destroy all entities in there as well:

    for (i = this.deleteArray.length - 1; i >= 0; --i) {
        this.deleteArray[i].destroy();
    }

When inspecting this array I see that entities aren’t destroyed on first block code, but only removed from holder entity. And still, the destroy() method doesn’t completely destroy them, entities are still there referenced by the deleteArray.

I tried event using the delete keyword:

    for (i = this.deleteArray.length - 1; i >= 0; --i) {
        delete this.deleteArray[i];
    }

I am feeling stuck here, any ideas? I reproduced the case in this project:

https://playcanvas.com/editor/scene/490286

Code on third script that clones entities on intervals and removing them:

var Test = pc.createScript('test');

// initialize code called once per entity
Test.prototype.initialize = function() {

    this.test = Array();
    this.bank = this.app.root.findByName('Test');
    this.children = this.app.root.findByName('Children');
    
    window.setInterval(function(){

        for (i = 0; i < 1000; i++) {

            var child = this.bank.clone();
            child.name = 'Name'+this.test.length;
            
            this.children.addChild(child);
            this.test.push(child);
            
        }
        
        
        for (i = this.children.children.length - 1; i >= 0; --i) {
            if( this.children.children[i] instanceof pc.Entity ){
                this.children.children[i].destroy();
            }
        }
        
        console.log(this.test);
        
    }.bind(this), 3000);

};

Well I notice that in your second listing you aren’t actually calling “destroy” just getting a reference to it. So not actually destroying anything. (it’s missing the brackets)

In your test code you are adding the items to your test array. That keeps a reference to the item so that isn’t letting them be collected.

Thirdly you are using console.log - if you do that in Chrome Developer Tools it keeps a reference to the array and contents so that you can expand it in the console! (That caught me out more than once).

So I removed the log, cleared the array and ran CDT memory timeline and pressed the “Garbage collect” icon every now and again. That showed this profile which appears to have no leak.

1 Like

Hi @whydoidoit thanks for the reply. It was a missing copy and paste regarding “destroy” and not “destroy()”, added that above.

I used the additional array and indeed I keep references of the entities even after calling destroy() because the leak happened even without it. Tried using explicitly the delete this.deleteArray[i]; to remove the reference after I do that but probably that doesn’t work in this case.

What troubles me is that … I remove all children entities from holder entity, then run a lot of code preparing 10 entities each time and at the end i attach it to the parent holder entity:

...
...
 this.entity.addChild(child)

The holder entity always holds 10 entities. But there is always an increasing memory leak. If I comment out the above line, entities are created and there is no memory leak.

Feeling stuck, but I will debug some more using CDT, thanks again.

1 Like

Well my CDT memory chart shows no leak when I run your code with the modifications I mentioned and I hit Garbage Collect every now and again…

https://playcanvas.com/project/453112/overview/test-mem

Hi @Leonidas

I’m faced with the same problem.

It disappears on canvas, but it seems to remain on memory.

I use like this, but memory still increasing

const length = this.product.children.length;
    for (let i = 0; i < length; i++) {
      let child = this.product.children.pop();
      child.destroy();
    }

Can you get some help on how you solved it?

Hi @Jinwoo_Choi,

Yes, sure, my issue back then was that even though I was destroying the entities, somewhere in my codebase I was keeping a reference to the entities.

That meant that even though for the Playcanvas engine the entities were removed from the scene, for the browser JavaScript context the garbage collector wouldn’t remove the data from memory. Because somewhere a variable was referencing entity objects.

The garbage collector in JavaScript will de-allocate memory only when all references to the object have been removed.

For example, if I create an entity and reference it on a global context like this, destroying it won’t release the memory allocated:

window.myEntity = new pc.Entity();
window.myEntity.destroy();
1 Like

Thank you for your answer! @Leonidas

I am using a gltf format for model load.

I think something problem with gltf object destroy…

I’ll check more.

Hi @Jinwoo_Choi, if you are using gltf models using the playcanvas-gltf importer, there is work being done right now in the Playcanvas engine to suport gltf models natively in the engine.

Do take a look at that if you think that’s where the issue is, when it’s released.

1 Like

@Leonidas
I’m just coding with the engine and really back on the basics.(return json model)

my test code is so simple

    app.assets.loadFromUrl('assets/somefile.json', 'model', (err, asset) => {

      entity.addComponent('model');
      entity.model.model = asset.resource;
      const node = app.root.findByName('parent');
      node.addChild(entity);
    });


    setTimeout(() => {
      entity.destroy();
      console.log(entity);
    }, 9000);
  }

My prediction is that heap memory become low after 9 second but… It is still maintained. Is this the right approach?
heap

Yes, at some point heap memory will be released. When that happens can’t be predicted fully since the garbage collector will decide when it’s the right time to free the memory.

How many entities are you creating and testing with? Also make sure that you aren’t retaining any reference to the entity var outside of your runtime scope.

Last, note, in case you aren’t aware, the loaded asset isn’t removed or unloaded from memory when destroying the entity. It has to be explicitly removed from the asset registry.

I use only 1 model above test case and there is any reference to the ‘entity’…

even if I explicitly remove the asset, the heap memory doesn’t change.

I made a simple example.


heap2

You can see that the heap memory is still not change after you delete model

If the model is turned on and off in the my actual project, the heap memory will continue to increase and eventually the browser will fail

Hi @Jinwoo_Choi,

I think your code is working and has the expected outcome. The memory heap allocation eventually drops from around 110-120mb to half of that when you remove the asset from the registry.

It’s just that the browser decides when that happens: it can happen immediately or it can take several seconds. That’s the nature of the garbage collector.

If you think you are still experiencing some form of memory leak, do raise an issue on the engine repo as it maybe something related with the engine’s internals.

1 Like

Thank you @Leonidas

I’ll take a little more time to study it.

Thanks

1 Like