How do I avoid unneccesary deallocation? I'm running this code:
CCSpriteFrameCache * cache = [CCSpriteFrameCache sharedSpriteFrameCache];
[cache addSpriteFramesWithFile:#"boosttexture.plist"];
CCAnimation * animation = [[CCAnimation alloc] initWithName:#"boosting" delay:1/24.0f];
[animation addFrame:[cache spriteFrameByName:#"ship.png"]];
[animation addFrame:[cache spriteFrameByName:#"ship_boost_l_r.png"]];
id action = [CCRepeatForever actionWithAction:[CCAnimate actionWithAnimation:animation]];
[spaceShipSprite runAction:action];
When the animaton is running (granted - its an ugly one), I get this in the console:
2010-04-14 13:40:16.311 Booster2K10Beta[521:20b] cocos2d: deallocing CCSpriteFrame = 00EBA620 | TextureName=4, Rect = (1.00,32.00,32.00,32.00)
2010-04-14 13:40:16.411 Booster2K10Beta[521:20b] cocos2d: deallocing CCSpriteFrame = 00EBA620 | TextureName=4, Rect = (1.00,32.00,32.00,32.00)
2010-04-14 13:40:16.496 Booster2K10Beta[521:20b] cocos2d: deallocing CCSpriteFrame = 00EBA620 | TextureName=4, Rect = (1.00,32.00,32.00,32.00)
It seems unneccesary that the same SpriteFrame gets dealloc'ed 24 times per second - how do I avoid that?
I'm assuming you have CCDEBUG turned up to 2. Put it back down to 1. That's CCLOGINFO stuff you're seeing. Still, use Xcode's profiling tools to see if memory is being drained away. I'm betting not. You're just seeing things you don't understand yet.
Related
I set off to transform an NSImageView. My initially attempt was
self.imageView.wantsLayer = YES;
self.imageView.layer.transform = CATransform3DMakeRotation(1,1,1,1);
Unfortunately I noticed that the transform only happens sometimes (maybe once every 5 runs). Adding an NSLog between confirmed that on some runs self.imageView.layer is null. State of the whole project is shown on the image below.
It's an incredibly simple 200x200 NSImageView with an outlet to a generated NSViewController. Some experimentation showed settings wantsDisplay doesn't fix the problem, but putting the transform on an NSTimer makes it work every-time. I'd love an explanation why this happens (I presume it's due to some race condition).
I'm using Xcode 8 on the macOS 10.12 but I doubt this is the cause of the problem.
Update
Removing wantsLayer and madly enabling Core Animation Layers in Interface Builder did not fix the problem.
Neither did attempts to animate it (I wasn't sure what I was hoping for)
// Sometimes works.. doesn't animate
[NSAnimationContext runAnimationGroup:^(NSAnimationContext *context) {
context.duration = 1;
self.imageView.animator.layer.transform = CATransform3DMakeRotation(1,1,1,1);
} completionHandler:^{
NSLog(#"Done");
}];
or
// Animates but only sometimes
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"transform"];
animation.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeRotation(1,1,1,1)];
animation.duration = 1;
[self.imageView.layer addAnimation:animation forKey:nil];
After experimenting with allowsImplicitAnimation I realised I might be trying to animate too early.
Moving the transform code into viewDidAppear made it work every time.
- (void)viewDidAppear {
[super viewDidAppear];
self.imageView.animator.layer.transform = CATransform3DMakeRotation(1,1,1,1);
}
I created a simple, 2 frame sprite animation based off a few tutorials that I found across the web. I took my 2 images, created a sprite sheet with plist and png file, and incorporated them into my code as seen below. This setup worked fine in Cocos2d V 1.0.1. I just upgraded my project to 2.0rc0a and now my app crashes with the following error at the point where it should switch from the first frame to the second: 'CCSprite: setTexture doesn't work when the sprite is rendered using a CCSpriteBatchNode'
I looked at this SO question, but I'm not sure if it's the same thing that I'm doing wrong, and because I'm still very new to Cocos2d, am unsure how to correctly adjust my code. Is this something that was changed in 2.0 that I didn't see in the notes, a bug that I should report, or just incorrect coding on my part? I still have a copy of the 1.0.1 project with identical code and the animation works correctly.
//CHAMELEON
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:#"front page/mainchameleon.plist"];
mainChameleon = [CCSpriteBatchNode batchNodeWithFile:#"front page/mainchameleon.png"];
[self addChild:mainChameleon z:7];
NSMutableArray *chameleonFrames = [NSMutableArray array];
for (int i = 1; i <= 2; ++i)
{
[chameleonFrames addObject:[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:[NSString stringWithFormat:#"chameleon%d.png", i]]];
}
CCAnimation *mouthAnim = [CCAnimation animationWithSpriteFrames:chameleonFrames delay:0.3f];
chameleon = [CCSprite spriteWithSpriteFrameName:#"chameleon1.png"];
CCAnimate *chameleonAction = [CCAnimate actionWithAnimation:mouthAnim];
CCDelayTime *chameleonDelay = [CCDelayTime actionWithDuration:10];
CCRepeatForever *chameleonRepeat = [CCRepeatForever actionWithAction:[CCSequence actions:chameleonDelay, chameleonAction, chameleonDelay, nil]];
[chameleon runAction:chameleonRepeat];
[mainChameleon addChild:chameleon];
If I comment out chameleon = [CCSprite spriteWithSpriteFrameName:#"chameleon1.png"]; then the app doesn't crash, but the chameleon doesn't appear at all, as would be expected with how the code is currently written.
Or, if I comment out [chameleon runAction:chameleonRepeat]; then the chameleon appears showing the frame, chameleon1.png, but obviously doesn't go through the animation.
OK, so to make me even more confused because I'm definitely missing something, I tried changing the bottom portion of the code to this and the animation goes from frame 1, to frame 2, then stays on 2 indefinitely. However, if I make the delay anything longer than 1.0, I receive the same error I was getting before. If I re-include chameleonDelay prior to the action without the repeat forever statement, I also get the same crash. It appears that the app crashes if it has to wait longer than 1 second to perform the switch. What I need is for frame 1 to sit for a while (10 seconds) then switch to frame 2 for 0.3 seconds, then switch back to frame 1 and sit for a while again.
Attempted code #2:
CCAnimation *mouthAnim = [CCAnimation animationWithSpriteFrames:chameleonFrames delay:0.3f]; //<--- maxes out at 1.0. Anything more causes crash
chameleon = [CCSprite spriteWithSpriteFrameName:#"chameleon1.png"];
CCAnimate *chameleonAction = [CCAnimate actionWithAnimation:mouthAnim];
[chameleon runAction:chameleonAction];
[mainChameleon addChild:chameleon];
YvesLeBorg suggested using the restoreOriginalFrame statement, but that is deprecated in ver 2.0. I tried using
CCAnimation *mouthAnim = [CCAnimation animationWithAnimationFrames:chameleonFrames delayPerUnit:0.3f loops:5];
and get the error '-[CCSpriteFrame delayUnits]: unrecognized selector sent to instance'. I'm not sure why that isn't working or what else to try from here.
EDIT: So Now It's Working...But not as efficiently coded as I'd like:
New Code:
//CHAMELEON
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:#"front page/mainchameleon.plist"];
mainChameleon = [CCSpriteBatchNode batchNodeWithFile:#"front page/mainchameleon.png"];
[self addChild:mainChameleon z:7];
NSMutableArray *chameleonFrames = [NSMutableArray array];
//Frame 1 - closed mouth
[chameleonFrames addObject:[CCSpriteFrame frameWithTexture:mainChameleon.texture rect:CGRectMake(0, 124, 149, 122)]];
//Frame 2 - Open Mouth
[chameleonFrames addObject:[CCSpriteFrame frameWithTexture:mainChameleon.texture rect:CGRectMake(0, 0, 149, 122)]];
//Frame 1 - closed mouth
[chameleonFrames addObject:[CCSpriteFrame frameWithTexture:mainChameleon.texture rect:CGRectMake(0, 124, 149, 122)]];
CCAnimation *mouthAnim = [CCAnimation animationWithSpriteFrames:chameleonFrames delay:0.9f];
chameleon = [CCSprite spriteWithTexture:mainChameleon.texture rect:CGRectMake(0,124,149,122)];
CCAnimate *chameleonAction = [CCAnimate actionWithAnimation:mouthAnim];
CCDelayTime *chameleonDelay = [CCDelayTime actionWithDuration:10];
CCRepeatForever *chameleonRepeat = [CCRepeatForever actionWithAction:[CCSequence actions:chameleonDelay, chameleonAction, nil]];
[chameleon runAction:chameleonRepeat];
[mainChameleon addChild:chameleon];
I really liked the way that I was doing it in 1.0.1 because if I had 2 frames or 100 frames, I only had to make a small adjustment to the if statement. This way requires coding each individual frame which seems counter-intuitive to using a plist. If no one is able to provide a better solution, or "real" answer in the next few days, I will post this as an answer and accept it to close out the question.
Here's the code I ended up with that works correctly. Not sure why I had to make these changes, but there it is. (Some of the names have changed as I started a new project to fix this on, but the differences between my original code and this are apparent). To anyone else finding this thread, my original code was based on Ray Wenderlich's sprite sheet tutorial.
CCSpriteBatchNode *chameleonBN = [CCSpriteBatchNode batchNodeWithFile:#"chameleonimages.png"];
[self addChild:chameleonBN];
//ADDED the texture part to resolve: 'CCSprite: setTexture doesn't work when the sprite is rendered using a CCSpriteBatchNode'
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:#"chameleonplist.plist" texture:chameleonBN.texture];
NSMutableArray *chameleonframes = [NSMutableArray array];
for (int i = 1; i <= 2 ; i++)
{
[chameleonframes addObject:[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:[NSString stringWithFormat:#"chameleon%d.png", i]]];
}
CCAnimation *mouthAnim = [CCAnimation animationWithSpriteFrames:chameleonframes delay:0.9f];
//ADDED this sprite frame to resolve texture id assert error
CCSpriteFrame *frame = [CCSpriteFrame frameWithTexture:chameleonBN.texture rect:CGRectMake(0, 0, 149, 122)];
CCSprite *chameleon = [CCSprite spriteWithSpriteFrame:frame];
chameleon.position = ccp(512,384);
CCAnimate *chameleonAnimate = [CCAnimate actionWithAnimation:mouthAnim];
CCDelayTime *chameleonDelay = [CCDelayTime actionWithDuration:10];
CCDelayTime *chameleonDelay2 = [CCDelayTime actionWithDuration:0.1];//Had to use this to ge tthe mouth to close. Using restore original frame doesn't work for me.
CCRepeatForever *chameleonRepeat = [CCRepeatForever actionWithAction:[CCSequence actions:chameleonDelay, chameleonAnimate, chameleonDelay2, nil]];
[chameleon runAction:chameleonRepeat];
[chameleonBN addChild:chameleon];
Not certain whether this is a 2.0 issue or not, but i notice you are using twice the chameleonDelay object in the same sequence. Is this really what you were trying to accomplish ie
delay,action,delay,delay,action,delay,delay,action .... etc.
try this to see if it works :
CCRepeatForever *chameleonRepeat = [CCRepeatForever actionWithAction:[CCSequence actions:chameleonDelay, chameleonAction, nil]];
I'm generating UIImages with a bit-bucket, creating them on the fly and swapping the UIImageView's image. Is there a way to edit the UIImageView's Image directly? (ie. change the color of a specific pixel, without removing the UIImage from the UIImageView, and get it to redraw.)
Currently, I'm flushing the UIImage and using imageWithCGIImage to make a new one, and assigning it to the UIImageView. This works. Shows no MemLeaks. But on the iPhone (3Gs) after about 100 image replacements, CRASHES. Cache'n issue? The memory summation seems to be hitting the phone's limit if cache not releasing, however, Simulator does not show memory consumption with each image swap. Stays flatlined without leaks.
Note: topologyImage array is the RGBA pixel-bucket. The REF variables are not released. Every attempt to do so, crashes next call. Without, Instruments reports no leaks.
=========
CGColorSpaceRef colorSpaceRef=CGColorSpaceCreateDeviceRGB();
CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault | kCGImageAlphaLast;
CGColorRenderingIntent renderingIntent=kCGRenderingIntentDefault;
CGDataProviderRef provider=CGDataProviderCreateWithData(NULL,topologyImage,(I*I*4),NULL);
CGImageRef imageRef=CGImageCreate(I,I,8,4*8,4*I,colorSpaceRef,bitmapInfo,provider,NULL,false,renderingIntent);
UIImage *img=[UIImage imageWithCGImage:imageRef];
if( IMG[NDXtopo].vw ) {
[IMG[NDXtopo].vw setImage:img];
}
else {
IMG[NDXtopo].vw=[[UIImageView alloc] initWithImage:img];
[master.view addSubview:IMG[NDXtopo].vw];
}
Basically you should release your references, especially the CGImageRef since the imageWithCGImage doesn't take ownership of the CGImage but rather seems to copy the data internally.
The docs on this are quite unclear, but from what I have found in my testing if I don't release CGImageRefs and CGDataProviderRefs it will eventually cause the application to get memory warnings... and then crash.
Not sure why you would have a crash, but in doing a quick test with:
UIImageView *view = [[UIImageView alloc] init];
int I = 128;
unsigned char *topologyImage = malloc(I*I*4*sizeof(unsigned char));
for(int i=0; i<I*I*4; i++)
{
topologyImage[i] = 100;
}
for(int i=0; i<1000; i++)
{
CGColorSpaceRef colorSpaceRef=CGColorSpaceCreateDeviceRGB();
CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault | kCGImageAlphaLast;
CGColorRenderingIntent renderingIntent=kCGRenderingIntentDefault;
CGDataProviderRef provider=CGDataProviderCreateWithData(NULL,topologyImage,(I*I*4),NULL);
CGImageRef imageRef=CGImageCreate(I,I,8,4*8,4*I,colorSpaceRef,bitmapInfo,provider,NULL,false,renderingIntent);
CGColorSpaceRelease(colorSpaceRef);
CGDataProviderRelease(provider);
UIImage *img=[UIImage imageWithCGImage:imageRef];
view.image = img;
CGImageRelease(imageRef);
}
free(topologyimage);
Seems to work just fine for me, so whatever is causing your crash seems to be because of something outside of your example, like for example how you got the image data into the topologyImage
This is my image resize code:
CALayer *newCALayer = [[CALayer layer] retain];
NSImage* image = [[NSImage alloc] initWithData:[NSData dataWithContentsOfFile:path]];
CGImageRef newCGImageFullResolution = [image CGImageForProposedRect:nil context:nil hints:nil];
CGContextRef context = CGBitmapContextCreate(NULL, drawRect.size.width, drawRect.size.height,
CGImageGetBitsPerComponent(newCGImageFullResolution),
CGImageGetBytesPerRow(newCGImageFullResolution),
CGImageGetColorSpace(newCGImageFullResolution),
CGImageGetAlphaInfo(newCGImageFullResolution));
CGContextDrawImage(context, CGRectMake(0, 0, drawRect.size.width, drawRect.size.height), newCGImageFullResolution);
CGImageRef scaledImage = CGBitmapContextCreateImage(context);
newCALayer.contents = (id)scaledImage;
CGImageRelease(scaledImage);
newCALayer.contentsGravity = kCAGravityResizeAspect;
newCALayer.opacity = 0.0;
newCALayer.anchorPoint = CGPointMake(0.0f,0.0f);
newCALayer.frame = CGRectMake( 0.0,
0.0,
[Singleton sharedSingleton].fullscreenRect.size.width,
[Singleton sharedSingleton].fullscreenRect.size.height);
[newCALayer setAutoresizingMask:kCALayerWidthSizable | kCALayerHeightSizable];
//CGImageRelease(cgImageFullResolution); (bonus points if you can explain why I can't release this! I mean, I can release the scaled image ok??)
CGContextRelease(context);
[image release];
I am doing all of this from a background thread in order to preload pictures so my GUI feels snappy. It took some work getting synchronization and what not set up so the CALayers ends up in view.
But I believe the term for describing how fast this is would be "it's a dog".
Comparing to IKImageView - that thing flings up thumbnails of images faster than I can scroll.
Does anybody have some suggestions for how to handle this better than I am doing it now?
In other words, my problem is that I want to have a super-fast UX. I believe the way to accomplish this is by preloading things to CALayers (this may be wrong? I tried NSImageView and some IK-stuff, but at least CALayer is better than that).
ImageKit is probably using CGImageSourceCreateThumbnailAtIndex() to quickly get an image appropriate to the destination, rather than reading in the entire image file.
Here:
NSImage *image = [[[NSImage alloc] initWithContentsOfFile:path] autorelease];
[image setScalesWhenResized:YES]; // *
[image setDataRetained:YES]; // *
[image setSize:desiredNewSize];
Then use the image as it is.
As for why your app is slow, run it under Instruments. That will tell you specifically where you are spending the majority of the processor time you use—it may not be in your scaling code after all.
*Since 10.6, these messages do nothing useful and are deprecated, so you can omit them if you are requiring Snow Leopard or later.
I'm working on a Cocoa fullscreen application. I am using 1 NSView that has 1 CALayer that has multiple sublayers. Right now for testing - I am using any keystrokes to add dots (20 x 20 ) to the screen. This is just for testing of drawing the dots. My issue is that I am using a filter on my dot layers - specifically I am using CIDiscBlur - and once I reach about 30 dots - the drawing of the dots significantly slows down. There can be a 1 - 1.5 second delay between the key press and the appearance of the dot. I have noticed that if I remove setting the CIDisBlur filter on the layers - that there is no slow down.
Are there any best practices or tips I should be using when drawing this many sublayers? Any help would be great.
CIFilter *blurFilter = [CIFilter filterWithName:#"CIDiscBlur"];
[blurFilter setDefaults];
[blurFilter setValue:(id)[NSNumber numberWithFloat:15.0] forKey:#"inputRadius"];
dotFilters = [[NSArray arrayWithObjects:(id)blurFilter, nil] retain];
CGColorRef purpleColor = CGColorCreateGenericRGB(0.604, 0.247, 0.463, 1.0);
CALayer *dot = [[CALayer layer] retain];
dot.backgroundColor = purpleColor;
dot.cornerRadius = 15.0f;
dot.filters = dotFilters;
NSRect screenRect = [[self.window screen] frame];
// 10 point border around the screen
CGFloat width = screenRect.size.width - 20;
CGFloat height = screenRect.size.height - 20;
#define ARC4RANDOM_MAX 0x100000000
width = ((CGFloat)arc4random() / ARC4RANDOM_MAX) * width + 10;
height = ((CGFloat)arc4random() / ARC4RANDOM_MAX) * height + 10;
dot.frame = CGRectMake(width, height, 20,20);//30, 30);
[dot addSublayer:dotsLayer];
I also tried using masksToBounds = YES to see if that helped - but no luck.
You can probably get a performance gain by not using corner radius to make your round layers. While it's a nice little shortcut to just make a round layer in a static context, when you're animating, it will degrade performance significantly. You'd be better off specifying a circular path to a CAShapeLayer or dropping down to Core Graphics and just drawing a circle in the drawInContext call. To test if I'm right, just comment out your call to set the corner radius and apply your filter. See if that speeds things up. If not, then I'm not sure what's up. It may mean you'll have to find a different way to get your effect without a filter. If you'll always have the same look for your dots, you'll can probably "cheat" by using an image.
Best regards.