Memory management problems

Hi,

I’m having memory problems in all my projects and after reading several posts, I can’t fix them.

My game have this this structure:

img

The size legend is the memory size allocated while the game scene is running and after interact with all elements of the scene (all creates, particles etc.)

In the loader scene I preload the sounds(AudioEngine) and textures(TexturePacker pvr.ccz) needed in the MainMenu, so the memory size should be always around the same (126MB).

After going to the MainScene, with everything loaded and created all sprites, etc. The size of the memory in the MainMenu is 177MB (ok).

The problem start when I go from the MainMenu to any of the games. I’m removing the textures that I don’t need (the ones I preload in the LoaderScene to use in the MenuScene) in the destructor of the MenuScene. I also uncache all sounds before creating the new scene and I preload the textures and the sounds needed in the game selected. The memory increases to 196MB, having removed 2 big texture packs and preload only a small one…

Going back to the MainMenu, I remove all textures preloaded for this game and sounds, and preload again the same textures and sounds I preloaded at the begining in the LoaderScene. The size should be the same as the first time (177MB) but not, now is 198MB!

Going to the second game, if I start the game again, the second game is 187MB and going back to the menu is 193MB, it happen the same. But if I open both games and going back to the menu, the difference its summed, and I have 6 games!
After running all 6 games and going back to the MainMenu, the memory is 340MB vs the 177MB of the first time.

I want to provide as much info as I can, so the way I’m doing all is as follows:

I preload the audio files this way:

AudioEngine::preload("Sounds/sound1.mp3");

I preload the textures like this:

SpriteFrameCache::getInstance()->addSpriteFramesWithFile("menu1.plist", "menu1.pvr.ccz");

The size of this texture file, for example, is 1817x1845 (I’ve read that I should use 1024x1024 npot pvr.ccz but not sure about the improvements that it will have and I have to split the textures in more files, but in case that is very important I can do it with all my texture packs. What do you think?).

All sprites and particles I create them this way:

File.h:

public:
Node* Nodo1;
Sprite* Logo;
.
.
private:
dragonBones::CCArmatureDisplay* Spider; 
ParticleSystemQuad *rain;
.
.

File.cpp:

//Sprites
Logo = Sprite::createWithSpriteFrameName("Logo.png");
addChild(Logo,4);

//Particles
rain = ParticleRain::create();
rain->setTexture( Director::getInstance()->getTextureCache()->addImage("Particulas/fire.png") );
addChild(rain,12);

//Dragonbones
auto fac = dragonBones::CCFactory::getFactory();
fac->loadDragonBonesData("Anim1_ske.json","Anim1");
fac->loadTextureAtlasData("Anim1_tex.json","Anim1");

Spider = fac->buildArmatureDisplay("ArmatureSpider");
addChild(Spider,5);

From the MainMenu, If I choose a minigame, I preload the sounds and textures that I will need before creating the new scene, and release the sounds and textures needed in the MainMenu in the destructor:

MainMenu.cpp:

AudioEngine::uncacheAll();
AudioEngine::preload("Sounds/hit.mp3");
  ...
SpriteFrameCache::getInstance()->addSpriteFramesWithFile("game1.plist", "game1.pvr.ccz");

if (scene2)
{
   Director::getInstance()->replaceScene(TransitionFade::create(0.5f, scene2, Color3B::BLACK));
}

MainMenu::~MainMenu()
{
    //Dragonbones
    if (Spider){
        this->removeChild(Spider);
        Spider->dispose();
        
        dragonBones::BaseObject::clearPool();

        auto fac = dragonBones::CCFactory::getFactory();
        fac->removeDragonBonesData("Spider",true);
        fac->removeTextureAtlasData("Spider",true);

    }

    //Nodes (Layers, Sprites...)
    if(Logo){
        Logo->stopAllActions();
        Logo->removeFromParentAndCleanup(true);
    }

    //Particles
    if(rain){
        rain->stopSystem();
        rain->stopAllActions();
        rain->removeFromParentAndCleanup(true);
    }

    this->stopAllActions();
    this->unscheduleUpdate();

    this->removeAllChildrenWithCleanup(true);

    //Textures
    SpriteFrameCache::getInstance()->removeSpriteFramesFromFile("menu1.plist");
}

What I’m doing wrong or missing?

Please help me!

After you remove the textures you no longer need in the destructor of whatever scene, call this:

const auto cacheInfo = Director::getInstance()->getTextureCache()->getCachedTextureInfo();
CCLOG("%s", cacheInfo.c_str());

It’ll output what textures are still in memory. Chances are that they still exist in memory, because when you call Director::getInstance()->getTextureCache()->removeUnusedTextures();, there are likely to still be references to them from game objects.

The problem is that references still exist even though you’ve hit the destructor of a scene. I can’t quite recall why at the moment, but it was an issue I ran into. It may be because references to objects are cleared on the next loop, after the destructor is called (don’t quote me on that!).

The way to solve it turned out to be something along the lines of the following:

Create a class that inherits from cocos2d::Scene with a callback method. In this new scene init() method, do something like this:

    this->scheduleOnce([this](float) {
        Director::getInstance()->getTextureCache()->removeUnusedTextures();

#ifdef _DEBUG
        const auto cacheInfo = Director::getInstance()->getTextureCache()->getCachedTextureInfo();
        CCLOG("%s", cacheInfo.c_str());
#endif

        Director::getInstance()->popScene();
        if (_callback)
        {
            _callback();
        }
    }, 0.f, "NEW_SCENE"); // to ensure update gets called

Let’s assume that new scene you created is named “CleanScene”, and say you’re in your menu scene, and you want to switch to a scene named “Game1Scene”. You would do so as follows:

auto* cleanScene = CleanScene::create([]() {
      auto* newScene = Game1Scene::create();
      Director::getInstance()->pushScene(newScene);
});
Director::getInstance()->replaceScene(cleanScene);

So, what happens here is:

1 - You call Director::replaceScene() to replace the menu scene with the CleanScene.
2 - In CleanScene::init(), it will schedules a call for the next loop.
3 - The director replaces the menu scene with CleanScene. At this point, all your textures are still in memory.
4 - Menu scene destructor is called.
5 - Next loop, all references to the menu textures should no longer exist. The CleanScene scheduled method is now called, which calls Director::getInstance()->getTextureCache()->removeUnusedTextures();. Any menu texture that is no longer used will be deleted from memory.
6 - Director::getInstance()->popScene(); is called, which pops the CleanScene, effectively removing it.
7 - The callback is invoked which calls the following:

auto* newScene = Game1Scene::create();
Director::getInstance()->pushScene(newScene);

This pushes the new Game1Scene scene onto the stack, and runs it. At this point CleanScene is no longer in the stack, and all textures you wanted to delete from the menu scene should be gone.

If it doesn’t make a lot of scene, spend some time thinking about it, because this does work.

1 Like

@panor Do you have any vectors/arrays? try to release them as well

@smitpatel88 I do. I forgot to mention them, but I think I’m releasing them right:

if(vecPiezas.size()>0){
    for ( auto sprite : vecPiezas) {
        sprite->removeFromParentAndCleanup(true);
        sprite=NULL;
    }
vecPiezas.clear();

Thanks a ton for this detailed answer. What I was missing is
Director::getInstance()->getTextureCache()->removeUnusedTextures();
I thought it was enough with
SpriteFrameCache::getInstance()->removeSpriteFramesFromFile("menu1.plist");
Removing unused textures I just reduced the memory size from around 340MB (running all scenes and going back to the MainMenu) to 165MB!

By checking what is in memory using :

const auto cacheInfo = Director::getInstance()->getTextureCache()->getCachedTextureInfo();
CCLOG("%s", cacheInfo.c_str());

I see exactly what you mean! The textures are still cached in the next loop as you said, so the way to do it is having a “fake” scene in between…

The only problem is that I don’t understand the usage of the callback and I never used lambda functions before. I’m getting some errors by doing it the way you say.

I’m switching scenes doing:

auto* cleanScene = CleanScene::create([]() {  //ERROR: Too many arguments to function call, expected 0, have 1
      auto* newScene = DungeonMemory::createScene();
      Director::getInstance()->pushScene(newScene);
});
Director::getInstance()->replaceScene(cleanScene);

CleanScene.h

#ifndef CleanScene_h
#define CleanScene_h

#include "cocos2d.h"

using namespace cocos2d;

USING_NS_CC;

class CleanScene : public Scene
{
public:
    
    static Scene* createScene();
        
    virtual bool init();
    
    void _callback();//??
    
    CREATE_FUNC(CleanScene);
    
};

#endif /* CleanScene_h */

CleanScene.cpp

#include "CleanScene.h"

USING_NS_CC;

Scene* CleanScene::createScene()
{
    // 'scene' is an autorelease object
    auto scene = Scene::create();
    
    // 'layer' is an autorelease object
    auto layer = CleanScene::create();
    
    // add layer as a child to scene
    scene->addChild(layer);
    
    // return the scene
    return scene;
}

// on "init" you need to initialize your instance
bool CleanScene::init()
{
    //////////////////////////////
    // 1. super init first
    if ( !Scene::init() )
    {
        return false;
    }
 
    this->scheduleOnce([this](float) {
            Director::getInstance()->getTextureCache()->removeUnusedTextures();

    #ifdef _DEBUG
            const auto cacheInfo = Director::getInstance()->getTextureCache()->getCachedTextureInfo();
            CCLOG("%s", cacheInfo.c_str());
    #endif

            Director::getInstance()->popScene();
        
            if (_callback) //ERROR: Is not a boolean/ don't know what to do with it 
            {
                _callback();
            }
         
        }, 0.f, "NEW_SCENE"); // to ensure update gets called
    
    return true;
}

You can’t use CREATE_FUNC(CleanScene);, since it just creates a method based on the template in the CREATE_FUNC macro. It won’t have the std::function parameter that you need for the callback. So, delete the CREATE_FUNC(CleanScene);

Also, your createScene method contains code that isn’t required at all. Is there any specific reason you’re creating a Scene object, which you’re then adding the CleanScene into (the variable named layer)?

It should be as simple as this:

In ‘.h’:

CleanScene(std::function<void()> callback); // constructor
static CreateScene* create(std::function<void()> callback);

std::function<void()> _callback;

In `.cpp’, just implement it like this:

CleanScene::CleanScene(std::function<void()> callback)
    : _callback(std::move(callback))
{
}

CleanScene* CleanScene::create(std::function<void()> callback)
{
    auto pRet = new(std::nothrow) CleanScene(std::move(callback));
    if (pRet && pRet->init())
    {
        pRet->autorelease();
        return pRet;
    }

    delete pRet;
    pRet = nullptr;
    return nullptr;
}

That’s all it is.

Thanks @R101 I reduced the memory size a lot!