Image which I loaded in thread is not appeared - iphone

I'm using the CCScrollLayer and I wanna prepare images during player select the stages. It's simple but when I scroll stages, it tasks time(delay to load images).
so I decided to use NSThread. and I got a message "cocos2d: CCSpriteFrameCache: Trying to use file 'Level3.png' as texture" from cocos2d. then It's supposed to appear. but These images I loaded on thread doesn't to appear as I want. just nothing.
-(void) moveToStagePage:(int)page
{
...
[NSThread detachNewThreadSelector:#selector(prepareTexture:) toTarget:self withObject:[NSNumber numberWithInt:page]];
...
}
below source is the code which preparing images.(Thread)
-(void) prepareTexture:(NSNumber*)number
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
int _page = [number intValue];
NSLog(#"%d Thread start", _page);
if(loadingTexNum != 0 && (_page + 1) != loadingTexNum)
{
[[CCSpriteFrameCache sharedSpriteFrameCache] removeSpriteFramesFromFile:[NSString stringWithFormat:#"Level%d.plist", loadingTexNum]];
loadingTexNum = _page + 1;
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:[NSString stringWithFormat:#"Level%d.plist", loadingTexNum]];
}
if(loadingTexNum == 0 && (_page + 1) != loadingTexNum)
{
loadingTexNum = _page + 1;
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:[NSString stringWithFormat:#"Level%d.plist", loadingTexNum]];
}
[NSThread sleepForTimeInterval:10.0];
NSLog(#"%d Thread release", _page);
[pool release];
}

OpenGL doesn't support loading on multiple threads unless you pass the openGL context.
Each thread in a Mac OS X process has a single current OpenGL rendering context. Every time your application calls an OpenGL function, OpenGL implicitly looks up the context associated with the current thread and modifies the state or objects associated with that context.
OpenGL is not reentrant. If you modify the same context from multiple threads simultaneously, the results are unpredictable. Your application might crash or it might render improperly. If for some reason you decide to set more than one thread to target the same context, then you must synchronize threads by placing a mutex around all OpenGL calls to the context, such as gl* and CGL*. OpenGL commands that block—such as fence commands—do not synchronize threads.
Source
You can either use - (void) addImageAsync:(NSString *)filename target:(id) target selector:(SEL)selector; in the CCTextureCache class, calling this on your main thread.
Or you can implement your own, effectively the same as addImageAsync.
For your reference, this is how CCTextureCache achieves loading the images:
NSAutoreleasePool *autoreleasepool = [[NSAutoreleasePool alloc] init];
// textures will be created on the main OpenGL context
// it seems that in SDK 2.2.x there can't be 2 threads creating textures at the same time
// the lock is used for this purpose: issue #472
[contextLock lock];
if( auxEAGLcontext == nil ) {
auxEAGLcontext = [[EAGLContext alloc]
initWithAPI:kEAGLRenderingAPIOpenGLES1
sharegroup:[[[[CCDirector sharedDirector] openGLView] context] sharegroup]];
if( ! auxEAGLcontext )
CCLOG(#"cocos2d: TextureCache: Could not create EAGL context");
}
if( [EAGLContext setCurrentContext:auxEAGLcontext] ) {
// load / create the texture
CCTexture2D *tex = [self addImage:async.data];
// The callback will be executed on the main thread
[async.target performSelectorOnMainThread:async.selector withObject:tex waitUntilDone:NO];
[EAGLContext setCurrentContext:nil];
} else {
CCLOG(#"cocos2d: TetureCache: EAGLContext error");
}
[contextLock unlock];
[autoreleasepool release];

I believe what is happening is that the main thread tries to use the images before the prepareTexture thread has had a chance to load the textures.
If you immediately try to create sprites with the new textures, for example right in the moveToStagePage method, then that is going to fail. Your threaded texture loading method will need to flag to the other thread that it has completed loading the textures. The easiest way is to simply toggle a BOOL variable. Only when the texture loading thread signaled that the textures have been loaded should you try to create sprites etc. using these textures.

When loading textures on a separate thread you need to use [[CCTextureCache sharedTextureCache] addImageAsync:texture target:target selector:#selector(onTextureLoaded)]; (else the `GLContext gets messed up).

Related

Asynchronous texture loading iPhone OpenGL ES 2

I'm creating and loading a lot of textures (made of strings). To keep the animation running smoothly, I offload the work to a separate worker thread. It seems to work more or less exactly the way I want, but on older devices (iPhone 3GS) I sometimes notice a long (1 sec) lag. It only occurs sometimes. Now I'm wondering if I'm doing this correctly or if there is any conceptual issue. I paste the source code below.
I should also mention that I do not want to use the GLKit TextureLoader because I also want to offload the texture generating work to the other thread, not just the loading part.
In case you're wondering what I need these textures for, have a look at this video: http://youtu.be/U03p4ZhLjvY?hd=1
NSLock* _textureLock;
NSMutableDictionary* _texturesWithString;
NSMutableArray* _texturesWithStringLoading;
// This is called when I request a new texture from the drawing routine.
// If this function returns 0, it means the texture is not ready and Im not displaying it.
-(unsigned int)getTextureWithString:(NSString*)string {
Texture2D* _texture = [_texturesWithString objectForKey:string];
if (_texture==nil){
if (![_texturesWithStringLoading containsObject:string]){
[_texturesWithStringLoading addObject:string];
NSDictionary* dic = [[NSDictionary alloc] initWithObjectsAndKeys:string,#"string", nil];
NSThread* thread = [[NSThread alloc] initWithTarget:self selector:#selector(loadTextureWithDictionary:)object:dic];
thread.threadPriority = 0.01;
[thread start];
[thread release];
}
return 0;
}
return _texture.name;
}
// This is executed on a separate worker thread.
// The lock makes sure that there are not hundreds of separate threads all creating a texture at the same time and therefore slowing everything down.
// There must be a smarter way of doing that. Please let me know if you know how! ;-)
-(void)loadTextureWithOptions:(NSDictionary*)_dic{
[_textureLock lock];
EAGLContext* context = [[SharegroupManager defaultSharegroup] getNewContext];
[EAGLContext setCurrentContext: context];
NSString* string = [_dic objectForKey:#"string"];
Texture2D* _texture = [[Texture2D alloc] initWithStringModified:string];
if (_texture!=nil) {
NSDictionary* _newdic = [[NSDictionary alloc] initWithObjectsAndKeys:_texture,#"texture",string,#"string", nil];
[self performSelectorOnMainThread:#selector(doneLoadingTexture:) withObject:_newdic waitUntilDone:NO];
[_newdic release];
[_texture release];
}
[EAGLContext setCurrentContext: nil];
[context release];
[_textureLock unlock];
}
// This callback is executed on the main thread and marks adds the texture to the texture cache.
-(void)doneLoadingTextureWithDictionary:(NSDictionary*)_dic{
[_texturesWithString setValue:[_dic objectForKey:#"texture"] forKey:[_dic objectForKey:#"string"]];
[_texturesWithStringLoading removeObject:[_dic objectForKey:#"string"]];
}
The problem was that too many threads were started at the same time. Now I am using a NSOperationQueue rather than NSThreads. That allows me to set maxConcurrentOperationCount and only run one extra background thread that does the texture loading.

Memory warning on initializing multiple animations for a sprite

I am creating an app which has around 15 different animations. Some animations are played on button taps while some are played on certain event. As I am new to cocos2d I just cannot figure out how to initialize these animations. I have separate sprite sheets for each animation. What I have done is created a convenience class that has as a data member, a CCSprite object. And in the init function I have placed all the code to create animations. But as soon as the init function is called I get memory warning.
I even tried to initialize the animations only when the request for playing the animation is sent, but I still get the memory warning. Also if I do so the animation doesn't play until the whole sprite sheet is loaded.
Please check the following code for reference
- (void)initEntryAnimation
{
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:#"entry.plist"];
CCSpriteBatchNode *spriteSheet = [CCSpriteBatchNode batchNodeWithFile:#"entry.png"];
[self.sardarSprite addChild:spriteSheet];
NSMutableArray *entryFrames = [NSMutableArray array];
for(int i = 0; i < 77; i++) {
if([[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:
[NSString stringWithFormat:#"enter in scene%04i.png", i+1]])
[entryFrames addObject:[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:
[NSString stringWithFormat:#"enter in scene%04i.png", i+1]]];
}
self.entryAnimation = [CCAnimation animationWithFrames:entryFrames delay:0.05f];
}
- (void)playEntryAnimation
{
[CCSpriteFrameCache purgeSharedSpriteFrameCache];
[self.sardarSprite runAction:[CCAnimate actionWithAnimation:self.entryAnimation restoreOriginalFrame:NO]];
}
I have created similar functions for all the 15 animations. I call all the initialization functions one after another. And the play functions are called whenever a request for the animation is sent.
I desperately need some expert advise. Any help would be highly appreciated.
The problem is the size of your sprite sheets. The sprite sheet file size (the size of the png) may be very small, but that is the size on disk. Once your app has loaded that into memory, it is stored uncompressed so that's 2048x2048x8 per sprite sheet.
You can reduce the colour depth to RGBA4444, for example which may help. See TexturePacker for more details. There are other tools available (I am not affiliated with TexturePacker in any way).
Other options you have are to reduce the colour depth further or to use one of the other formats available. You could also look at your animations and see if there is any way to break them down into more discrete components. For example, if you have an animation of a man running, perhaps instead of having the full screen frames, you could have one frame of the torso and then different leg animations and arm animations which you then use.
Without knowing the exact animation, it's difficult to advise.
Try adding NSAutoreleasePool like this
- (void)initEntryAnimation
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:#"entry.plist"];
CCSpriteBatchNode *spriteSheet = [CCSpriteBatchNode batchNodeWithFile:#"entry.png"];
[self.sardarSprite addChild:spriteSheet];
NSMutableArray *entryFrames = [NSMutableArray array];
for(int i = 0; i < 77; i++) {
if([[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:
[NSString stringWithFormat:#"enter in scene%04i.png", i+1]])
[entryFrames addObject:[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:
[NSString stringWithFormat:#"enter in scene%04i.png", i+1]]];
}
self.entryAnimation = [CCAnimation animationWithFrames:entryFrames delay:0.05f];
[pool release];
}

NSThread problem in iOS

I have an app that loads multiple thumbnail images into a UIScrollVIew. This is a lengthy operation, and so as not to block up the display of the rest of the UI, I am running it in a separate thread. This works fine the first time at application launch, but later a new set of images needs to be loaded into the UIScrollView. When I detach a thread a second time the app crashes (sometimes). Code follows:
// this call is in a separate method
[NSThread detachNewThreadSelector:#selector(addThumbnailsToScrollView) toTarget:self withObject:nil];
// this is the main entry point for the thread
- (void) addThumbnailsToScrollView {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // Top-level pool
// now place all the thumb views as subviews of the scroll view
float xPosition = THUMB_H_PADDING;
int pageIndex = 0;
for (Page *page in self.pages) {
// get the page's bitmap image and scale it to thumbnail size
NSString *name = [page valueForKey:#"pageBackground"];
NSString *basePath = [[NSBundle mainBundle] pathForResource:page.pageBackground ofType:#"jpg" inDirectory:nil];
UIImage *thumbImage = [UIImage imageWithContentsOfFile:basePath];
thumbImage = [thumbImage imageScaledToSize:CGSizeMake(80, 100)];
// create a ThumbImageView for each page and add it to the thumbnailScrollView
if (thumbImage) {
ThumbImageView *thumbView = [[ThumbImageView alloc] initWithImage:thumbImage];
[thumbView setDelegate:self];
[thumbView setImageName:name];
[thumbView setImageSize:CGSizeMake(80, 100)];
[thumbView setPageIndex:pageIndex];
pageIndex ++;
CGRect frame = [thumbView frame];
frame.origin.y = 0;
frame.origin.x = xPosition;
[thumbView setFrame:frame];
[thumbnailPagesScrollView addSubview:thumbView];
[thumbView release];
xPosition += (frame.size.width + THUMB_H_PADDING);
}
}
[self hightlightThumbnailPageAtIndex:0];
[(UIActivityIndicatorView *)[thumbnailPagesScrollView.superview viewWithTag:100] stopAnimating];
[pool release]; // Release the objects in the pool.
}
I thought that a detached thread exits as soon as the main entry routine was completed. Wouldn't the second call to detach a thread be a new thread? Why is the app crashing, but sometimes not?
Thanks
Jk
You cannot touch UIKit (meaning UIScrollVIew) in a secondary thread - you need to reorganize so that the fetch takes place in a secondary thread but you make a NSData object (containing the image binary) available to your primary thread for each thumbnail so that it can do everything related to actually displaying them.
Apple repeatedly warn in documentation that UIKit is not thread-safe.
I would suggest adding the thumbView to the thumbnailPagesScrollView on the main thread rather than a separate thread. There might be issues on the retain count of the object across threads. There is a convenience method performSelectorOnMainThread I think it is to do that. You could pass thumbView to that and then add it to the subview.
Alternatively you could do the whole if statement on the main thread as thats not the thing that will interrupt the user.
Also with your activity indicator this should be stopped on the main thread. Everything UI related should be done on the main thread.

Is calling -setNeedsDisplay from a background task asking for trouble?

I have a background task that updates a view. That task calls -setNeedsDisplay to have the view drawn.
This works:
- (void) drawChangesTask;
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
if (pixels) {
drawChanges((UInt32 *) origPixels, (UInt32 *) pixels, CGBitmapContextGetBytesPerRow(ctx)/4, CGBitmapContextGetHeight(ctx), count--);
if (count < 0) {
count = 150;
}
else
[self performSelectorInBackground:#selector(drawChangesTask) withObject:nil ];
[self performSelectorOnMainThread:#selector(setNeedsDisplay) withObject:nil waitUntilDone:NO ];
}
[pool release];
}
This does not work:
- (void) drawChangesTask;
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
if (pixels) {
drawChanges((UInt32 *) origPixels, (UInt32 *) pixels, CGBitmapContextGetBytesPerRow(ctx)/4, CGBitmapContextGetHeight(ctx), count--);
if (count < 0) {
count = 150;
}
else
[self performSelectorInBackground:#selector(drawChangesTask) withObject:nil ];
[self setNeedsDisplay];
}
[pool release];
}
Anyone know why? When I say it doesn't work, I mean that it runs tens of iterations, sometimes I see portions of my image shifted up or down, or entirely blank, and then the deugger give me an “EXC_BAD_ACCESS” somewhere in CoreGraphics.
Also, if I don't handle the autorelease pool myself, then I get leaking error messages. Don't understand why that is either. My drawChanges() doesn't create any new objects. Here's the error:
2009-08-17 11:41:42.358 BlurApp[23974:1b30f] *** _NSAutoreleaseNoPool(): Object 0xd78270 of class NSThread autoreleased with no pool in place - just leaking
UIKit simply isn't thread-safe — you need to call methods that update UIKit controls on the main thread.
I think that this line:
[self performSelectorInBackground:#selector(drawChangesTask) withObject:nil];
Is causing trouble. Have you tried simply calling it again on the current thread? If you need the runloop to execute between the calls, use:
[self performSelector:#selector(drawChangesTask) withObject:nil afterDelay:0.0];
This will call the method on the current thread after the method you're in has finished and the runloop has gone round once.
Problem here is that UIKit is not thread safe, if you tell your UI to do something from a background thread nothign is guaranteed, what you want to do is use the performSelectorOnMainThread method to do updates t o your UI elements

iPhone Gameloop render update from a separate thread

I'm new to iPhone development. I have a game loop setup as follows.
(void)CreateGameTick:(NSTimeInterval) in_time
{
[NSThread detachNewThreadSelector:#selector(GameTick) toTarget:self withObject:nil];
}
My basic game tick/render looks like this
(void)GameTick
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
CGRect wrect = [self bounds];
while( m_running )
{
[self drawRect: wrect];
}
[pool release];
}
My render function gets called. However nothing gets drawn (I am using Core Graphics to draw some lines on a derived UIView).
If I call my update via a timer then all is well and good.
Can you tell me why the render fails when done via threads? And is it possible to make it work via threads?
Thanks
Rich
You can't (well, shouldn't) call -drawRect: directly. Instead, use -setNeedsDisplay; your view will then be updated the next time through the event loop. If you're running this in a separate thread, you may need to use performSelectorOnMainThread:.