Cocos2d-x CCSprite Memory Usage ( releasing CCSprite) - c++

I'm developing a game in cocos2dx v2.2.3. I did a test and I found some problem as below:
Problem: (In windows) When I run my game and checking TaskManager, I found my game took ~20MB memory (in average).
But After Adding these lines of code in Init() of my game Layer and Then it increases the memory usage up to 300MB.
CCSprite *t;
t = CCSprite::create("Character/foo1.png"); //Each picture is about 50MB
t->release();
t = CCSprite::create("Character/foo2.png");
t->release();
t = CCSprite::create("Character/foo3.png");
t->release();
t = CCSprite::create("Character/foo4.png");
t->release();
( I myself create these picture huge for this test)
I also checked m_uReference when t->release(); is calling. and there m_uReference is becoming 0 and so It should be deleted! But why memory use is that high?
Question: I wonder How should I delete/remove/release a CCSprite after some time?
Additional Info/Test:
I found something that may help. After calling t->release(); It somewhere reach :
CCSprite::~CCSprite(void) {
CC_SAFE_RELEASE(m_pobTexture);
}
But in CC_SAFE_RELEASE(m_pobTexture);, m_uReference of texture decrease from 2 to 1 and therefor the texture don't get deleted.
I did a test and make this change :
CCSprite::~CCSprite(void) {
CC_SAFE_RELEASE(m_pobTexture);
CC_SAFE_RELEASE(m_pobTexture);
}
And RAM returned to ~20MB. ( Also I know This's not the correct way and I missing something else)

First, your code is likely to crash somewhere down the line (unless you left out some code): CCSprite::create returns an autoreleased CCSprite, which means you don't need to .release() it (and if you don't .retain() it, it's going to be deleted shortly after).
For your question: CCSprite needs a CCTexture to works (your image is loaded in memory). But CCSprite don't own the CCTexture; the CCTexture is managed by CCTextureCache.
CCTextureCache don't release a CCTexture directly when no CCSprite use it anymore: instead, it keeps the CCTexture for future usage (since loading a CCTexture is expensive) and only release CCTexture when there is not enough memory or you force it to do so, with
CCTextureCache::sharedTextureCache()->removeUnusedTextures();
The short answer is: you shouldn't worry about memory used unless you have a real memory problem (game crashing because of lack of memory). Games are supposed to use a lot of memory.

Related

QGraphicsItem is not visible after adding to scene

I'm working on a diagram visualisation tool and I ran into an issue where my QGraphicsScene does not display a shared_ptr<DiagramItem> when a raw pointer obtained via .get() is passed to scene->addItem().
Subsequent check via scene->items() shows that my DiagramItem is not a part of the scene. My guess is that it got freed as the refcounter on the shared_ptr will be zero after leaving the scope of the testing function...
But that was the testing case. In my actual code I'm using a shared_ptr that I got from elsewhere and is definitely present in memory with a non-zero refcounter. I get the raw pointer of that and pass it to scene->addItem(). It is also not displayed, but this time it is present in scene->items(). So why is it not being drawn?
If I switch from using shared_ptr<DiagramItem> to DiagramItem* then the issue disappears and everything is displayed properly. But due to limitations from the rest of the project, I cannot easily abandon smart pointers here, nor do I want to.
Did I run into some kind of memory limitation or am I doing something wrong?
I already tried calling show() and update() on the item and increasing the scene size in case the item doesn't fit (it does). I also tried breakpointing the paint() method, but that one doesn't get called at all.
I found a possibly related question here where similar behaviour occurs due to the object going out of scope and being deallocated, but that doesn't seem to be the case with my actual DiagramItem.
class DiagramItem : public QGraphicsItem
{
...
}
//Create scene
auto scene = new QGraphicsScene(nullptr);
//Item is created OR obtained from elsewhere
auto item1 = std::make_shared<DiagramItem>(nullptr, QString("aaa"), true);
auto item2 = GetDiagramItem(...);
//Raw pointers get passed to addItem
scene->addItem(item1.get());
scene->addItem(item2.get());
//Item1 is not present at all (directly created DiagramItem)
//Item2 is present but invisible (DiagramItem passed from elsewhere)
//myItem gets Item2
auto myItem = scene->items()[0];
...

Crash after QGraphicsScene::removeItem() with custom item class

I am populating a QGraphicsScene with instances of a custom item class (inherting QGraphicsPathItem). At some point during runtime, I try to remove an item (plus its children) from the scene by calling:
delete pItem;
This automatically calls QGraphicsScene::removeItem(), however it also leads to a crash in the class QGraphicsSceneFindItemBspTreeVisitor during the next repaint.
TL;DR: The solution is to ensure that QGraphicsItem::prepareGeometryChange() gets called before the item's removal from the scene.
The problem is that during the item removal from the scene, the scene internal index was not properly updated, resulting in the crash upon the next attempt of drawing the scene.
Since in my case, I use a custom subclass from QGraphicsPathItem, I simply put the call to QGraphicsItem::prepareGeometryChange() into its destructor since I am not manually removing the item from the scene (via QGraphicsScene::removeItem()), but instead I simply call delete pItem; which in return triggers the item's destructor as well as removeItem() later on.
I ran into the same issue using PySide2.
Disabling BSP indexing (as mentioned here) does work for me and is most likely the actual solution to the problem. But is a sub-optimal one, because the scene that I am working with can get arbitrarily large. I also tried to call prepareGeometryChange before removing the item, and while that did seem to work for a while, the error re-appeared just a few weeks later.
What worked for me (so far) is manually removing all child items before removing the item itself...
To that end, I am overwriting the QGraphicsScene::removeItem method in Python:
class GraphicsScene(QtWidgets.QGraphicsScene):
def removeItem(self, item: QtWidgets.QGraphicsItem) -> None:
for child_item in item.childItems():
super().removeItem(child_item)
super().removeItem(item)
Note that this will not quite work the same in C++ because QGraphicsScene::removeItem is not a virtual method, so you will probably have to add your own method removeItemSafely or whatever.
Disclaimer: Other methods have worked for me as well ... until they didn't. I have not seen a crash in QGraphicsSceneFindItemBspTreeVisitor::visit since introducing this workaround, but that does not mean that this is actually the solution. Use at your own risk.
I had this issue and it was a real pain to fix it. Besides the crash, I was also having "guost" items appearing on the screen.
I was changing the boundingRect size 2x inside a custom updateGeometry() method that updates the boundingbox and shape caches of the item.
I was initializing the boundig rectangle as QRectf():
boundingBox = QRectF();
... then doing some processing (and taking the opportunity to do some clean ups in unneeded objects from the scene).
And finally setting the value of the boundingRect to its new size:
boundingBox = polygon.boundingRect();
Calling prepareGeometryChange() in the beggining, alone, didn't solve the issue since I was changing it's size twice.
The solution was to remove the first attribution.
It seems the issue lasting for long time today and there are open bugs also.
But it seems to have a workaround, which I could find it useful and after hours of debugging and reading and investigations I have found it here:
https://forum.qt.io/topic/71316/qgraphicsscenefinditembsptreevisitor-visit-crashes-due-to-an-obsolete-paintevent-after-qgraphicsscene-removeitem/17
Some other tips and tricks regarding Graphics Scene here:
https://tech-artists.org/t/qt-properly-removing-qgraphicitems/3063/6

SDL2 Memory Leaks C++

So when I run the app, at the biginning every thing runs smooth, but the more it goes, the slower it is. I looked at the memory it was using and when it reaches 400 mb it completely stops for 30 secs and then drop back to 200.
I am pretty new to SDL2, and I assume it is because each frame I call:
optionsTS = TTF_RenderText_Blended(font, "Options.", blanc);
optionsT = SDL_CreateTextureFromSurface(renderer, optionsTS);
for example and I have plenty of them.
The problem is that I don't know how to delete properly the object each frame, because if I do a SDL_FreeSurface I get an error.
I won't publish my whole code because it's a mess, but if you want it, feel free to ask.
Do you know how to fix that?
Just thought I would turn my comment into an answer.
In your code you call
optionsTS = TTF_RenderText_Blended(font, "Options.", blanc);
optionsT = SDL_CreateTextureFromSurface(renderer, optionsTS);
every frame, I suspect that if you remove them from there, initialise them outwith of the render loop and simply pass them in as arguments, you should lose the memory leak: the reason being that you will create only one in-memory instance of each and then you can repeatedly use them as needed. On looking at it again, I suspect that you could destroy optionTS once you have made optionT, that way you will save even more memory. (not tested yet as my main machine just crashed this weekend, and I am still re-installing drivers and VS2010)
As a general rule, try and not create/destroy any objects in the render loop, tends to get big and messy fast.
Consider taking advantage of RAII in C++ if possible.
For example, create a class that wraps an SDL_Surface and calls SDL_FreeSurface in the destructor.
class MySurface
{
public:
MySurface(SDL_Surface & surface) : m_surface(surface) {}
~MySurface() {SDL_FreeSurface(m_surface);}
SDL_Surface & GetSDLSurface() {return m_surface;}
private:
SDL_Surface & m_surface;
};
You would then create an instance of MySurface every time you grabbed an SDL_Surface from the SDL API, and you won't have to worry about when or whether to free that surface. The surface will be freed as soon as your instance of MySurface goes out of scope.
I'm certain better implementations can be written and tailored to your needs, but at a minimum something similar to this may prevent you from having leaks in the future.

Child objects are (seemingly) randomly set to NULL or an 'illegal object'; how to debug that?

I use Cocos2d-x for a game which I am porting from Cocos2d-iphone. The original programmer seems to have used the 'feature' of Objective-C to not crash on calls to nil objects as a way to do a lot of sloppy things.
If this is related to that I don't know, however, in my code I never call release() manually and certainly not delete or anything like that. I don't even call ->removeObject() at all (although that would not result in the same issue as I have).
Now the problem: when the game is running, at random moments (they won't be random but they seem that way now obviously) child nodes get set to NULL. And this does not only affect my code but als the Cocos2d internals. Example:
CCLog("----------------");
for(int j = 0; j < this->getChildren()->count(); j++)
{
CCObject *child = this->getChildren()->objectAtIndex(j);
EnemySprite *enemy = dynamic_cast<EnemySprite*>(child);
if (enemy != NULL) {
CCLog("Enemy with tag %d found", enemy->getTag());
}
}
EnemySprite *enemy = dynamic_cast<EnemySprite*>(this->getChildByTag(i));
if (enemy == NULL) {
CCLog("Now enemy with %d is NULL :(", i);
}
In the getChildren() look, all enemies with the tags are there and print this;
Enemy with tag 1000 found
Enemy with tag 1001 found
Enemy with tag 1002 found
During the game it'll show this a lot, until it shows this;
Enemy with tag 1000 found
Enemy with tag 1001 found
Enemy with tag 1002 found
Now enemy with 1001 is NULL :(
and crashes.
In my mind, this should be impossible with the above code as I just checked, verified and printed exactly that object...
But even more interesting (maybe only to me, maybe it's some stupid mistake), this
this->getChildByTag(i)
randomly goes wrong internally as well; traversing the children, it'll find a NULL and conk out on Cocos2d internal code:
if(pNode && pNode->m_nTag == aTag)
return pNode;
The pNode is then not NULL (that's why the asserts do not trigger) but looks like this:
http://o7.no/137JXC4 (screenshot)
The cocos2d::CCCopying thing is already stuff of nightmares for me in this project; every time I see it I know something is wrong and I have no clue how to find what it is.
I already added a breakpoint at the release() delete line; it's not being called. And I, like I said, am not doing anything like that manually.
I use Xcode / iOS to debug, but the behavior is the same on Android (but on my computer Eclipse is slower that Xcode, especially during debugging).
I understand it would be difficult to give me a solution / cause, however; I would be really happy if someone can tell me how to attack this issue. It happens randomly throughout the (quite large) codebase and I'm at a loss how to find this issue...
I hope someone can help!
At times dynamic_cast returns 0 even tough its argument was not 0. This happens for example when you're casting a super class to a subclass (so called "down casting"). Check this tutorial for more information: http://www.cplusplus.com/doc/tutorial/typecasting/
I can imagine that if the elements in your list have a generic (unrelated) super type, this can be the problem in your case.
As you say it's hard to tell, but here are two ideas.
You might try turning on guard malloc.
Alternatively, you might gain something from putting a static int counter in your suspect class's (like EnemySprite's) deconstructor/constructor to decrement/increment, and break/log when it falls below zero.
I only see the definition of j what is i?
I believe it crashes after CCLog("Now enemy with %d is NULL :(", i);
since this line already been logged, it definitely not crashing here.
CCObjects are subject to the AutoReleasePool by default, which means Cocos2D-x will manage when to release the object. If you use the static constructor for these objects, you can call object->retain() and object->release() so that you can manage the memory yourself.
Source: http://www.cocos2d-x.org/projects/cocos2d-x/wiki/Reference_Count_and_AutoReleasePool_in_Cocos2d-x
If something changes the enemy object to NULL, what I would do is setting a data breakpoint at address of enemy(for 1001). Than,
If breakpoint is hit, that might be a memory corruption.
If breakpoint is not hit and you get NULL, dig into getChildByTag(). What I would do then is replacing this->getChildByTag() with dynamic_cast<EnemySprite*>(this->getChildren()->objectAtIndex()) check if the is any difference.
In Build Settings->Other C Flags->Debug and add -o0 flag and try debugging.

Cocos2d and SpriteBatchNode: cannot identify which sprite frame is causing an Assertion to fail

I have already asked something similar but I can't figure out properly how to debug this. That's the question.
I added some Exceptions handler (catches all Objective-C) exceptions and that's the result of what I see:
The problem is with the setTexture method and it fails at the assertion verifying whether the texture name that needs to be displayed is the same as the one in the current sprite batch node.
This happens when trying to replace one scene with another but doesn't happen all the times. It has to do with the new scene as I tried to "isolate" the problem by calling the replace from a different part of the game and it still does give trouble.
In the game scene I have a couple of sprite sheets and sprite batch nodes but as I don't manage to isolate a sprite sheet id I cannot understand which sprite frame is giving me the problem, as well as I don't understand why this happens only sometimes.
I would like to:
understand which sprite frame name gives me the AssertionFailure
understand to which sprite sheet it belongs
This should help me to understand if it is a naming problem or if this has to do with something else.
Hoping not to be too lame with this question..
EDIT: I tried the answer but I am not able to read the 'fileName' information, here is what the debugger says "Summary unavailable":
That's how I create the filename property:
/** TMP: Bug solving filename */
#property (copy) NSString *fileName;
-(id) initWithTexture:(CCTexture2D*)texture rectInPixels:(CGRect)rect rotated:(BOOL)rotated offset:(CGPoint)offset originalSize:(CGSize)originalSize
{
if( (self=[super init]) )
{
self.fileName = [NSString stringWithFormat:#"GLUINT texture name: %i", texture.name];
self.texture = texture;
rectInPixels_ = rect;
rect_ = CC_RECT_PIXELS_TO_POINTS( rect );
offsetInPixels_ = offset;
offset_ = CC_POINT_PIXELS_TO_POINTS( offsetInPixels_ );
originalSizeInPixels_ = originalSize;
originalSize_ = CC_SIZE_PIXELS_TO_POINTS( originalSizeInPixels_ );
rotated_ = rotated;
}
return self;
}
-(id) initWithTextureFilename:(NSString *)filename rectInPixels:(CGRect)rect rotated:(BOOL)rotated offset:(CGPoint)offset originalSize:(CGSize)originalSize
{
if( (self=[super init]) )
{
self.fileName = fileName; //TMP
texture_ = nil;
textureFilename_ = [filename copy];
rectInPixels_ = rect;
rect_ = CC_RECT_PIXELS_TO_POINTS( rect );
offsetInPixels_ = offset;
offset_ = CC_POINT_PIXELS_TO_POINTS( offsetInPixels_ );
originalSizeInPixels_ = originalSize;
originalSize_ = CC_SIZE_PIXELS_TO_POINTS( originalSizeInPixels_ );
rotated_ = rotated;
}
return self;
}
In such cases logging is your friend. Every time you create a CCAnimate action (or CCAnimation) you should log something like this:
// during 'create sprite frames from sprite frame names' loop
NSLog(#"adding sprite frame name for CCAnimate: %#", spriteFrameName);
// after CCAnimate was created
NSLog(#"creating CCAnimate %# with sprite frames: %#, animate, arrayOfSpriteFrames);
You will probably want to add more detail, like which sprite frame names were added to that CCAnimate. You may also have to add additional logging if you cache CCAnimations and reuse them later (log each CCAnimation when reused).
Now when you receive that error you should select the [CCSprite setDisplayFrame:] method in the call stack. The debugger will then show you the value for the CCSpriteFrame it wants to set. Look for the pointer value, it will read something like 0x254fb22e.
Search for that value in your log, this will bring you back to one of the "creating CCAnimate.." logs. From the log lines above you can see the sprite frame names it contains. Since you also logged the 'arrayOfSpriteFrames' you can get their pointer value, compare it with the pointer value of the CCSpriteFrame that caused the assertion.
When you have a match, and it's the fourth item in the sprite frame array, just look up the name of the fourth sprite frame name added to the CCAnimate.
There may be a quicker way to do this depending on which information is available in the debugger (and how well versed you are in debugging), but this is one approach that will definitely lead you to the offending sprite frame name in relatively short time.
Note that the pointer values are not unique - it's possible that a different CCAnimate was created with the same pointer value. Especially if there's a high frequence of CCAnimate playing and stopping it can happen that another CCAnimate is allocated at the exact same memory location of a previous one. So be wary if the results don't seem to match up. A quick way to find out is to test on different devices or Simulator vs. Device, as pointer values and allocation strategies vary.
You don't need to log which sprite frame name belongs to which texture atlas. Simply open the plist of each atlas and search for the sprite frame name. The plist is an XML file.
Tip: common cause for this problem can be having the same sprite frame name in two different texture atlases - cocos2d may use either texture when you request a sprite frame with a duplicate name.
Another Tip: if the logging seems daunting, I'd simply do the following:
open source code for CCSpriteFrame class
add a NSString* property 'filename' with 'copy' attribute
every time you create a CCSpriteFrame object, assign the filename to the CCSpriteFrame
Now whenever you see a CCSpriteFrame in the debugger, it'll show you the associated filename in the debugger view.
ok , looks like you have been there before ... Comment the first NSAssert, uncomment the if block. Then put a break point on the newly uncommented NSAssert. Execution will pause BEFORE the exception, and you should be to inspect the iVars of each class instance in the call stack (at least i can with AppCode, hope xCode allows that). Maybe you will get enough of a hint to figure out which texture/animation/batchnode is giving you a hard time.
Also ... fyi. Whenever i start a project with cocos2d (any version) , i apply some 'standard' fixes to it so that my life will be easier for debugging. A standard 'patch' is to add a name to CCNode, which i set to some meaningful value : always. Also i override the descpription method to return MY name. Assigning meaningful names has helped me many times, especially when walking down (or up) the node hierarchy to figure out a bug. I also nuke many of the NSAsserts and whenever possible (especially in ctors), return nil and log an error message. As per Stefen's suggestion, if a cocos2d ctor returns me a nil, i spew out an error log message. I can then break on that log statement and drill-down the issue.