Jump to content

How To - Cleaning Up All Pixi Sprites and Textures


JAA
 Share

Recommended Posts

Simple question. How to cleanup? I want to do this well as I want to destroy every sprite and texture without exiting the window. I then want to create a load more textures and sprites, but some of the textures will be ones that were previously destroyed, so I want to avoid the 'Resource already exists' problem.

I have created loads of sprites, and since I need to access them quickly I have them in a quick lookup, so I clean these up by going through each one and using the function destroy.

However, I also want to clean up all the textures.They are in PIXI.utils.TextureCache I think. How do I clean these up?

Is there any other references to these textures that need cleaning?

Are there any other issue I might encounter?

 

Link to comment
Share on other sites

You need to call the `destroy` method of sprites and/or textures you are done with. Since you created them, it is your responsibility to destroy them. Simply call `.destroy()` on each of the objects you created.

`.destroy(true)` on a Sprite for example will destroy the sprite, the texture it has, and the base texture that texture uses. Where is gets complex is that destroying an object twice is an error, so if you have two sprites sharing a texture and you call `.destroy(true)` on both of them, you may get exceptions. This is why it is generally a good idea to track any resources you create and destroy them manually each.

Link to comment
Share on other sites

Thanks for these replies. It looks a little more complicated than I thought.

I create my sprite from a style sheet. So I think this means I cannot just do destroy(true) as several sprites will share the same base texture and get the exception @xerver is talking about.

In the sprite's destroy function it says I can pass an object.

Since I am creating my sprite from a frame using the following:

texture = PIXI.utils.TextureCache["textureId"]);
rect = new PIXI.Rectangle(x, y, w, h);
textureWithFrame = new PIXI.Texture(texture, rect);

Option 1:

I am guessing my SPRITE's destroy call needs to look like this:

spr.destroy({children: true, texture: true, baseTexture: false});
   

Is this correct? The 'texture' is the one from the frame (textureWithFrame) so that needs to be destroyed as it is only used in one sprite. baseTexture is the entire spritesheet so cannot be destroyed as several sprites share this.

Q: Will all the textureWithFrame be removed from PIXI.utils.TextureCache? How do I cycle through PIXI.utils.TextureCache destroying each element?

Option 2:

Since all the textures (including the ones like textureWithFrame) are stored in PIXI.utils.TextureCache, then I should destroy my sprite with:

spr.destroy(false);

Then I need to cycle through PIXI.utils.TextureCache and destroy every texture without worrying at all where it was used.

Q: How do I cycle through PIXI.utils.TextureCache destroying each element?

So which option is best, Option 1 or Option 2? Or am I completely getting this wrong?

 
       
 
Link to comment
Share on other sites

19 hours ago, yahiko said:

Simply removing those sprites from their containers is not enough?

No, Textures will continue to have references in certain internal caches until they are cleaned up (via .destroy usually). Additionally, WebGL memory is not managed. It is up to the programmer to manage the WebGL memory, and therefore you need to tell Pixi when to remove it from GPU mem.

6 hours ago, JAA said:

I am guessing my SPRITE's destroy call needs to look like this:


spr.destroy({children: true, texture: true, baseTexture: false});

Yes, that will destroy the sprite, it's texture, all children and all their textures; but leave all the base textures alive.

7 hours ago, JAA said:

Q: Will all the textureWithFrame be removed from PIXI.utils.TextureCache? How do I cycle through PIXI.utils.TextureCache destroying each element?

If it is used by a sprite you called the destroy on above, then yes, otherwise no. You should not be using the TextureCache or the BaseTextureCache. These are private structures and are not meant to be used or interacted with by users (see here and here). Not all objects are in this cache. For example, when constructing a texture with `new` (new PIXI.Texture()), that texture is not in the cache. Looping through the internal private cache to clean up is not a valid resource management strategy. We will remove things from that cache when you call destroy.

Instead you should be managing your own resources. When you create a texture, store it somewhere. When you are done with it, call destroy. Same for other pixi objects you create. I generally abstract this concept into a resource manager (which allows me to properly track object lifetimes and implement pooling as necessary). This is why I mentioned it is often best (IMO) to simply call .destroy() with no params so that only this object is destroyed, no chance of destroying twice and only what you want is destroyed.

In games I've done in the past I've used a key-based resource storage system where different maps or levels of my game have a key and they use that key to fill the resource manager with objects and then I am done with a level I can clear them all by just saying "mymanager.destroyAll('some_key')".

A naive, example of something like this (that I wrote in about 30 seconds) could be:

class ResourceContainer {
    constructor() {
        this.objects = [[], [], []];
    }
 
    addResource(type, object) {
        this.objects[type].push(object);
    }

    destroyResources(type) {
        this._destroy(this.objects[type]);
    }

    _destroy(arr) {
        for (let i = 0; i < arr.length; ++i) {
            arr[i].destroy();
        }

        arr.length = 0;
    }
}

class ResourceManager {
    constructor() {
        this.containers = {};
    }

    addResource(contianerKey, type, object) {
        if (!this.containers[contianerKey]) {
            this.containers[contianerKey] = new ResourceContainer();
        }

        this.containers[contianerKey].addResource(type, object);
    }

    //.. and similar createX() methods
    createSprite(contianerKey, texture) {
        const sprite = new PIXI.Sprite(texture);

        this.addResource(contianerKey, ResourceManager.TYPE.SPRITE, sprite);
    }

    destroyAll(contianerKey) {
        const container = this.containers[contianerKey];

        for (const key in ResourceManager.TYPE) {
            container.destroyResources(ResourceManager.TYPE[key]);
        }
    }
}

ResourceManager.TYPE = {
    SPRITE: 0,
    TEXTURE: 1,
    BASETEXTURE: 2,
}

Hopefully you can see how this can be expanded into something robust with many different features such as pooling and ref counting, but also should illustrate how you can track all your object creations, assign them an application "scope" (containerKey) and then cleanup scopes as needed. Obviously more work needs to be done to share objects between scopes, etc.

I think this is a common enough task that we may include a plugin in v5. But right now you need to think of it like resource allocation in other languages. If you have the ability to create something, it is your responsibility to also clean it up.

One final piece of advice: Don't mix Sprite.fromImage() and PIXI.loader. Use the loader or use fromImage, don't do both. You'll just end up confusing yourself and leaking.

Good luck!

Link to comment
Share on other sites

Thanks for your hints and tips xerver. I had something similar to your ResourceContainer, but I really do like the idea of your ResourceManager and will be using that from now on.

One thing I am still confused about, namely ResourceManager.BASETEXTURE in your code. I load these with PIXI.loader. I then fill my own array with these (like your ResourceContainer) ready to destroy.

However, how should I destroy these? Should I destroy these with in the loader? Can I just cycle through the array and call destroy on them.

If I do destroy using the latter (cycling through my array) will my PIXI.loader be able to reload these, or do I need to call destroy on the PIXI.loader?

 

Link to comment
Share on other sites

I generally use the loader, pull out the resources I want and insert them with the "addResource" method above, then call loader.reset() so it drops references (and can be reused to load more stuff later). Then their lifetime is again managed by the resource manager. In fact I usually use my resource manager to load the assets necessary for a given key based on a manifest I bake at build time, so I know what resources each level requires and can just say .loadLevel(key, callback) and then .destroyLevel(key) and move on.

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...