Hi all,
I've been playing with Instruments and trying to debug why some of my AtlasSprite objects are not being released when tearing down my game scene. By slowly and meticulosity removing things from my game until the problem went away, I've managed to narrow it down to my running a RepeatForever Action on an AtlasAnimation and an AtlasSprite. In my search to find a solution, I found a thread (http://www.cocos2d-iphone.org/forum/topic/130) that makes it sound as if other people are having my problem, but no one has replied with an answer / suggestion as what to do.
To try and solve this (and to make sure that the problem doesn't lie with my code), I created a trimmed down test case that reproduces the issue. Basically, the code below adds a scene to the director, then adds a layer containing an AtlasSprite running a RepeatForever AtlasAnimation (based off of the 'grossini_dance_atlas.png' from the test cases). When the layer detects a double click, it calls removeAllChildrenWithCleanup:YES on itself and, unfortunately dealloc is not always called in the AtlasSprite (thus it is leaked). The strange thing is that dealloc sometimes does get called on the AtlasSprite object... If someone could have a look, I'd really appreciate it.
I am using 0.8.1 beta.
Here is the code:
"RunnerAppDelegate.h":
// DLog is almost a drop-in replacement for DLog
// DLog();
// DLog(@"here");
// DLog(@"value: %d", x);
// Unfortunately this doesn't work DLog(aStringVariable); you have to do this instead DLog(@"%@", aStringVariable);
#ifdef DEBUG
# define DLog(fmt, ...) NSLog((@"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__);
#else
# define DLog(...)
#endif
// ALog always displays output regardless of the DEBUG setting
#define ALog(fmt, ...) NSLog((@"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__);
#import <UIKit/UIKit.h>
#import "cocos2d.h"
@class TestScene;
@class TestLayer;
@interface RunnerAppDelegate : NSObject <UIApplicationDelegate> {
UIWindow *window;
TestScene *testScene;
}
@end
@interface TestLayer : Layer {
AtlasSpriteManager *mainASManager;
BOOL gameOn;
}
- (void) makeSprites;
@property (readwrite) BOOL gameOn;
@end
@interface TestScene : Scene {
}
@end
@interface TestAtlasSprite : AtlasSprite {
}
@end
"RunnerAppDelegate.m":
#import "RunnerAppDelegate.h"
@implementation RunnerAppDelegate
- (void) applicationDidFinishLaunching: (UIApplication *) application {
[UIApplication sharedApplication].isIdleTimerDisabled = YES;
window = [[UIWindow alloc] initWithFrame: [[UIScreen mainScreen] bounds]];
[window setUserInteractionEnabled: YES];
[window setMultipleTouchEnabled: YES];
[[Director sharedDirector] setPixelFormat: kRGBA8];
[[Director sharedDirector] attachInWindow: window];
[[Director sharedDirector] setDeviceOrientation: CCDeviceOrientationLandscapeLeft];
[[Director sharedDirector] setDisplayFPS: YES];
[[Director sharedDirector] setAnimationInterval: (1.0/30.0)];
[window makeKeyAndVisible];
testScene = [TestScene node];
[[Director sharedDirector] runWithScene: testScene];
}
- (void) dealloc {
[window release];
[super dealloc];
}
@end
@implementation TestLayer
@synthesize gameOn;
- (BOOL) ccTouchesEnded: (NSSet *) touches withEvent: (UIEvent *) event {
NSSet *allTouches = [event allTouches];
UITouch *touch = [[allTouches allObjects] objectAtIndex: 0];
if ([touch tapCount] == 2) {
DLog(@"Double click...");
if (gameOn) {
[self removeAllChildrenWithCleanup: YES];
[self setGameOn: NO];
}
else {
[self makeSprites];
[self setGameOn: YES];
}
return kEventHandled;
}
return kEventIgnored;
}
- (void) makeSprites {
mainASManager = [AtlasSpriteManager spriteManagerWithFile: @"grossini_dance_atlas.png" capacity: 50];
[mainASManager.textureAtlas.texture setAntiAliasTexParameters];
[self addChild: mainASManager z: 0];
TestAtlasSprite *testAtlasSprite = [TestAtlasSprite spriteWithRect: CGRectMake(0, 0, 85, 121) spriteManager: mainASManager];
[testAtlasSprite setPosition: CGPointMake(100,100)];
[mainASManager addChild: testAtlasSprite];
AtlasAnimation *testAnimation = [AtlasAnimation animationWithName: @"test" delay: 0.05];
for (int i = 0; i < 14; i++) {
int x = i % 5;
int y = i / 5;
[testAnimation addFrameWithRect: CGRectMake(x * 85, y * 121, 85, 121)];
}
// Dealloc on testAtlasSprite is not called on removeAllChildrenWithCleanup when using a RepeatForever to animate:
[[ActionManager sharedManager] addAction: [RepeatForever actionWithAction: [Animate actionWithAnimation: testAnimation restoreOriginalFrame: NO]] target: testAtlasSprite paused: NO];
// But it does release properly when the animation only runs once:
// [[ActionManager sharedManager] addAction: [Animate actionWithAnimation: testAnimation restoreOriginalFrame: NO] target: testAtlasSprite paused: NO];
}
@end
@implementation TestScene
- (id) init {
if (self = [super init]) {
TestLayer *mainLayer = [TestLayer node];
[mainLayer makeSprites];
[mainLayer setIsTouchEnabled: YES];
[mainLayer setGameOn: YES];
[self addChild: mainLayer z: 0];
}
return self;
}
@end
@implementation TestAtlasSprite
- (void) dealloc {
DLog(@"Dealloc in TestAtlasSprite! YAY!");
[super dealloc];
}
@end
Could someone please try this and let me know that I am not alone in this? If anyone knows what is happening, that would be great. I hope this is not a leak, and only some silly mistake on my part... Any help would be greatly appreciated.
Mike