Using the sprite frame cache in cocos2d-the right way - objective-c

I am using the sprite frame cache to upload plists,and sprite-sheets,to use for animation .
i have 2 approaches to that and i am sure that one of them is wrong .
i have to load everything i can to cache at the start because it takes time, so at the start of the scene i load all what i need to the close future .(but now my cache is full! )
cache should stay as empty as i can , so i am loading to cache at the moment i start the animation (it takes some time isn't it ? i think it flicks my game ) , and remove it at the same moment i am done with it . (now cache is not full but i have to load/reload many times.)
do i have to take care to remove the unused sprites from cache every time ?
load to cache is :
[[CCSpriteFrameCache sharedSpriteFrameCache] removeSpriteFrameByName:#"stopAnim.plist"];
removing unused is :
[[CCSpriteFrameCache sharedSpriteFrameCache] removeUnusedSpriteFrames];
i can see in my iPad that the system is trying to remove unused sprites and have problems.
whats exactly the way to work ?

After lots of research , i came to some conclusions :
First , it do takes time to load the frames in real time (while the animation is starting) to the batchNode , and in cases of big images (iPad) it sometimes freezing the scene .
So , in some cases like that ,you MUST load the images to cache at the init method to avoid that, because you have no other way,and this is why we have cache anyway :
[[CCTextureCache sharedTextureCache] addImage:#"the_heavy_image.png"];
and if this animation happens frequently , you don't want to clear it from the cache .
Other stuff should be cleared from cache if you dont use it
[[CCTextureCache sharedTextureCache] removeUnusedTextures];
will clear from cache only the sprites that was removed from screen already.
its not enough to just clear the frames :
[[CCSpriteFrameCache sharedSpriteFrameCache] removeUnusedSpriteFrames];
In other scenarios where sprites-sheets images are not that big, you can load them to the batchNode at the moment the animation is starts, and remove them at the end .

Related

IOS7: Webview memory leak

I know this is a very recurring topic but, i tried all the suggested solutions they seem not to work...
The scenario:
I load an empty view and i add at run time an UIWebview.It is a global variable,initiated only once.
the initial memory usage (by Xcode 5.1) is ~6MBytes.
i will load in the view a series of url using a button event.Each time is pressed a new page will be loaded.
i load in the webView the first page from internet with few image and one video.
After the loadRequest is completed, the memory is ~20Mbytes.
Every time i load a new page the memory increases.
The memory stays the same even when i load a new controller...
What I tried so far:
To use the [NSURLCache sharedURLCache] and related functions to instantiate a fixed amount of cache in the Application delegate: the App does not care...memory changes despite the limit of 32MBytes i set.
To set to nil the global variable: no effect.
To use temporary UIWebview(s) instead of the global one; views are removed from the superview each time the controller changes: no effect.
None of this tentatives have been successful...the memory increase and it is never released.
If i start the video from the web browser and then i load a new controller, the sound of the video still runs in the background...
What am I missing???
Thank you in advance

Switch the cocos2d retina display background in runtime.

I've a background in my app, but clicking on a button should change it and use a different one.
I cannot add all of them in the sharedTextureCache because each one has a size of >16MB in the cache, and I've 30 different backgrounds.
What's the best way for switching the background without a loading time? I don't want the user to wait when clicking that button.
Thanks
remove all unused data. or texture...
[[CCDirector sharedDirector] purgeCachedData];
[[CCTextureCache sharedTextureCache] removeAllTextures];
[CCTextureCache purgeSharedTextureCache];
[[CCSpriteFrameCache sharedSpriteFrameCache] removeSpriteFrames];
[CCSpriteFrameCache purgeSharedSpriteFrameCache];
If you know what background will be shown next, you can preload it beforehand. Each retina device can use about 100 megabytes of memory (about 5 spritesheets 2048x2048). In this case you begin to receive memory warnings but app will work stable. All preloads you can make asynchronously to shared texture cache. Just don't forget to clean up unused textures by calling
[[CCTextureCache sharedTextureCache] removeUnusedTextures];
to force unload of unnesessary textures.
The only way I managed to not slow down the app was with this line:
[_background setTexture: [[CCTexture2D alloc] initWithImage:...]]
When I try to use the shared Texture Cache, even asynchronously, the app slows down. :-O

CCSpriteFrameCache optimization

[[CCSpriteFrameCache sharedSpriteFrameCache]addSpriteFramesWithFile:];
That will add sprite frames through a .plist file. I would like to ask: if I tried, for whatever reason, to load the same .plist file again, is CCSpriteFrameCache smart enough as to ignore it? Or will it reload it all again, consuming more processing/memory?
It will re-use the already loaded texture, but reload the sprite frames in the plist. It will create new sprite frames and throw out the existing ones.
If you plan to modify a texture atlas and reload it, you'll have to make sure to remove the texture from the texture cache as well, so that it gets reloaded as well.
If you're just lazy and call this method over and over again, it shouldn't be an issue if you do this between scenes but I wouldn't do that during gameplay, since each sprite frame is deallocated and a new one allocated, and a lot of other code runs behind the scenes to load the sprite frames.

CGImage, NSArray and Memory

This is a multiple part question, mostly because my ignorance on the matter has multiple layers.
First, I put together a caching system for caching CGImageRef objects. I keep it at the CGImageRef level (rather than UIImage) as I am loading images in background threads. When an image is loaded I put it into a NSMutableDictionary. I had to do a bit of arm twisting to get CGImageRef's into the array:
//Bunch of stuff drawing into a context
CGImageRef imageRef = CGBitmapContextCreateImage(context);
CGContextRelease(context);
[(id)imageRef autorelease];
[self.cache setObject:(id)imageRef forKey:#"SomeKey"];
So, as you can see, I'm trying to treat the Image Ref as an NSObject, setting it to autorelease then placing it in the dictionary. My expectation is this will allow the image to be cleaned up after being removed from the dictionary. Now, I am beginning to have my doubts.
My application clears the cache array when the user "restarts" to play with different images. Running the application in Instruments shows that the memory is not dropping back to the "start" level on restart, but instead remains steady. My gut tells me that when the array has all objects removed the CGImageRef is not being cleared.
However, I'm unable to confirm this as I don't quite know how to track down the actual source of the memory in instruments. It's just a list of (Malloc 16 Bytes, Malloc 32 Bytes, etc), drilling into them just show a list of dyld callers. Not sure how to properly read it.
So, first question, is my way of caching CGImageRef objects completely flawed? And is there a better way to confirm such things in instruments?
First of all, caching CGImages is OK and I don't see any problems with the code you posted.
Am I correctly assuming you use an NSMutableDictionary as the cache? If so, you can clear it by sending it -removeAllObjects, which should release all the keys and values. If you just set different images for the same keys, memory usage may remain roughly the same because you replace previous images with new ones. If the images have the same size, memory usage should be constant except brief spikes when you create a new batch of images.
As for Instruments, I've seen it both report false positives and miss real leaks. Try running it several times, making pauses, if possible, for the Leaks instrument to "catch up". This sounds crazy, but I think it may make it a bit more reliable.
If all else fails, you can log the contents of the cache before and after loading a set of images to make sure the cache itself works as expected.
Why not just cache UIImage objects; you can make them fine on a background thread?
It's UIImageView objects that you have to be more careful with and even they are OK for most operations in the background.

Swapping UIImageView Images in a loop

I have a NSArray of UIImageViews that I want to loop over and quickly swap out an "on" and "off" state. I wrote the code to do so in a for loop instead a method that was called when the user tapped a UIButton ( the button's action ).
Here's that loop:
for(int i = 0; i < [Images count]; i++) {
if( i > 0 ){
[self toggleImageViewOff:[Images objectAtIndex:i - 1]];
}
[self toggleImageViewOn:[Images objectAtIndex:i]];
[NSThread sleepForTimeInterval:0.5f];
}
The UI did not update as I expected as I only ever saw the last UIImageView in the "on" state. I figured that the drawing update of the views must occur in the main thread this code was also executing in. So I learned about performSelectorInBackground:withObject: . Performing the toggleImageViewOn/Off methods using this made the loop work. The problem is if I make the sleep interval too short I can have an "on" update after an "off" with Threads operating out of order.
So I had the bright idea of moving the whole loop with the sleep into its own method and calling that from the action method using performSelectorInBackground:withObject: . I tried that and I'm back to not getting an updated view until the loop is over.
That's a long winded way to get to my question:
What's the best way to animate this to guarantee the on/off code fires in the right order and still get view updates, even at high speeds? ( i.e. looping very quickly )
I tried to think about how I'd do it with CoreAnimation, but I can't seem to get my head around how to do it there.
For bonus, here are the toggle methods:
- (void)toggleImageViewOn:(UIImageView *)theImageView {
[theImageView setImage:[UIImage imageNamed:#"on.png"]];
}
- (void)toggleImageViewOff:(UIImageView *)theImageView {
[theImageView setImage:[UIImage imageNamed:#"off.png"]];
}
Did you set up an animation context (UIView class method does that) around this for loop? Without it changes are immediate instead of animated.
The problem is that you are not giving any of the UIImages time to draw. The drawing code is optimised to only draw what's needed - rendering all those intermediate stages is optimised out.
Sleeping the main thread doesn't actually give it chance to run.
Bill is right in that you need to set up an animation context around your loop. This will capture all of the UIView changes you make and then play them out. The easiest way to do this is using Core Animation. Core animation 'records' changes in UIElemenets and plays them back. Your code (without the sleep) will work just fine in a Core Animation block.
Apple have a reasonable cookbook for Core Animation on their site
You're on the right track with moving the loop to a background thread, but you also need to make sure that you give the main run loop a chance to update the UI. You should be able to replace the direct calls to toggleImageViewOn: and toggleImageViewOff: with something like
[self performSelectorOnMainThread:#selector(toggleImageViewOn:) withObject:[Images objectAtIndex:i] waitUntilDone:NO];
This will do the UI update on the main thread, and by not waiting until the update is done you give the main run loop a chance to reach its end. You run into the same issue with things like progress bars, where they won't change until the loop ends unless you do your updates from a background thread with a UI update call like the one above.
Hey Patrick. Have a look at UIImageView's animationImages property, as well as the animationRepeatCount and animationDuration properties. If you put your on/off images into an array and assign that as the animationImages property, you should be able to control the repeat and duration to get the desired effect.
Hope that helps!
Thanks for that. I've already looked into UIIMageView's animationImages property. That's not exactly what I'm attempting to do. I'm cycling between several UIImageView's that are placed near each other to give the impression that a light is moving between them and cycling over them. So an individual UIImageView's animation is separate from each other as I need to swap the image as necessary in code.
Calling peformSelectorOnMainThread:withObject:waitUntilDone: from the loop on the background thread does indeed update the view as quickly as I can think I will ever need. I'm curious why I need to do the UIImageView swap on the Main thread? Why wouldn't changing it on the background thread and then using NSThread's sleepForTimeInterval allow the main thread to update the drawing anyway?
I guess I need to go read up on the run loop and where drawing updates occur.
Thanks so much for the help. ( I'm also going to try some additional suggestions from Bill Dudney, that I think will work based on CoreAnimation )