3

I have UITableView, which is shown in front of my Layer `[[CCDirector sharedDirector].view addSubview:myTableView] and takes some part of the screen.

Everything works fine: it detects touches. But during the scroll animation of the tableView all cocos2d freezes untill tableView ends this scrolling. (Under freezing i mean all nodes actions pauses).

The question is : what is the reason of such freezing and how can i avoid it?


-(void) mainLoop:(id)sender
{
if (dispatch_semaphore_wait(frameRenderingSemaphore, DISPATCH_TIME_NOW) != 0)
{
    return;
}

if (self.useCocoaFriendlyRendering)
{
    dispatch_sync(frameRenderingDispatchQueue, ^{ // HERE dispatch_async make black screen, i shows everything but still bad performance.
        CCGLView *openGLview = (CCGLView*)[self view];
        [EAGLContext setCurrentContext:openGLview.context];
        [self drawScene];
        dispatch_semaphore_signal(frameRenderingSemaphore);
    });
}
else
{
    CCGLView *openGLview = (CCGLView*)[self view];
    [EAGLContext setCurrentContext:openGLview.context];
    [self drawScene];
    dispatch_semaphore_signal(frameRenderingSemaphore);
}
}
CodeSmile
  • 64,284
  • 20
  • 132
  • 217
B.S.
  • 21,660
  • 14
  • 87
  • 109

2 Answers2

7

The reason is because UIKit views are not designed to be cooperative with other views. They're for user interaction, and so it usually doesn't matter if scrolling stops updating other views. In a user-interface driven app that makes a lot of sense because you want to give the best response and feel to the view that has the user's focus and needs a lot of resources to animate (ie scrolling).

The only way to fix this properly is to improve the cocos2d render loop in CCDirectorDisplayLink with a semaphore:

-(void) mainLoop:(id)sender
{
    if (dispatch_semaphore_wait(frameRenderingSemaphore, DISPATCH_TIME_NOW) != 0)
    {
        return;
    }

    if (useCocoaFriendlyRendering)
    {
        dispatch_async(frameRenderingDispatchQueue, ^{
            [EAGLContext setCurrentContext:openGLView_.context];
            [self drawScene];
            dispatch_semaphore_signal(frameRenderingSemaphore);
        });
    }
    else
    {
        [EAGLContext setCurrentContext:openGLView_.context];
        [self drawScene];
        dispatch_semaphore_signal(frameRenderingSemaphore);
    }
}

This code puts the cocos2d drawscene method in its own dispatch queue. I once understood what it did but I can't remember it anymore, so instead of incorrectly explaining what it does or why it works I'm hoping someone can fill this in in a comment. :)

Note: the last dispatch semaphore may not need to be called every time, but I did because switching the cocoaFriendlyRendering flag might otherwise cause the semaphore_wait to wait indefinitely (freezes rendering). So I just signal it every time.

I updated my CCDirectorDisplayLink as follows (CCDirectorIOS has the cocoaFriendlyRendering BOOL):

@interface CCDirectorDisplayLink : CCDirectorIOS
{
    id displayLink;
    dispatch_semaphore_t frameRenderingSemaphore;
    dispatch_queue_t frameRenderingDispatchQueue;
}
-(void) mainLoop:(id)sender;
@end

In startAnimation the semaphore and dispatch queue are created:

- (void) startAnimation
{
        ...
    displayLink = [NSClassFromString(@"CADisplayLink") displayLinkWithTarget:self selector:@selector(mainLoop:)];
    [displayLink setFrameInterval:frameInterval];
    [displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];

    frameRenderingSemaphore = dispatch_semaphore_create(1);
    frameRenderingDispatchQueue = dispatch_queue_create("render", DISPATCH_QUEUE_SERIAL);
}

And released in dealloc:

-(void) dealloc
{
    dispatch_release(frameRenderingDispatchQueue);
    dispatch_release(frameRenderingSemaphore);
    [displayLink release];
    [super dealloc];
}

This prevents the scroll view from blocking the cocos2d frame drawing. I added the cocoaFriendlyRendering BOOL (I assume you know how to add a BOOL property) so I could turn this behavior on only for scenes that do use scroll views. I thought this might be better for performance and to prevent any odd issues during gameplay, but it's probably not necessary.

This code is for cocos2d 1.0.1 and might need to be slightly adapted to work with cocos2d 2.x, for example EAGLView -> CCGLView, EAGLContext -> CCGLContext.

Other solutions revolve around timers and/or reducing the framerate (animationInterval) and/or changing the run loop mode of the display link object. They don't really work too well, scrolling might not be smooth or scrolling might suddenly stop. I tried all of those solutions because I didn't want to mess with cocos2d's render loop - but the semaphore was the only flawlessly working solution to make UIKit scrolling cooperative with cocos2d (and vice versa).

UPDATE:

I forgot to mention, I made a subclass of UIScrollView that handles cocoaFriendlyRendering (YES on init, NO on dealloc) and more importantly, it overrides the touch events as to forward them to cocos2d as needed (whenever the scroll view isn't scrolling).

These are the overridden touch methods:

-(void) touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event
{
    if (self.dragging)
        [super touchesBegan:touches withEvent:event];
    else
        [[CCDirector sharedDirector].openGLView touchesBegan:touches withEvent:event];
}

-(void) touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event
{
    if (self.dragging)
        [super touchesMoved:touches withEvent:event];
    else
        [[CCDirector sharedDirector].openGLView touchesMoved:touches withEvent:event];
}

-(void) touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event
{
    [super touchesCancelled:touches withEvent:event];
    [[CCDirector sharedDirector].openGLView touchesCancelled:touches withEvent:event];
}

-(void) touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event
{
    [super touchesEnded:touches withEvent:event];
    [[CCDirector sharedDirector].openGLView touchesEnded:touches withEvent:event];
}
Community
  • 1
  • 1
CodeSmile
  • 64,284
  • 20
  • 132
  • 217
  • Asking this question do not know why, i was sure that you will answer it:) – B.S. Mar 03 '13 at 13:20
  • I tried to understand everything you wrote here, made all replaces, still have black screen – B.S. Mar 03 '13 at 13:20
  • What can i say more. I set useCocoaFriendlyRendering = NO, and it works for me!:) I see that tableView scrolling is less qualitative, but monkey replacing and copy-pasting worked for me. Now i will try to understand all the changes i have done:) Thank you! – B.S. Mar 03 '13 at 13:25
  • Can you explain how to use cocoaFriendlyRendering(i added this property to CCDirectorIOS), because it has horrible influence on performance if it is NO, and if YES than i have black screen. – B.S. Mar 03 '13 at 13:37
  • Is this with cocos2d 2.x? I haven't tried that. I didn't measure performance because there was no notable difference, but of course this was in menu-screens without physics or other performance intensive tasks. I only enable cocoaFriendlyRendering before adding scrollviews (changing scenes) and disable it when the scrollview is gone. That's all. – CodeSmile Mar 03 '13 at 20:37
  • Yes, cocos2d 2.1 beta, maybe the influence is big on sumulator only, shall test – B.S. Mar 03 '13 at 20:43
  • I has more horrible result on device – B.S. Mar 04 '13 at 08:03
  • When `useCocoaFriendlyRendering` is set to `YES` I have strange problems relating to touches of the user – Tomer Jun 09 '15 at 15:04
2

Go into the CCDirector.m OR CCDirectorIOS.m file and find this line:

[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];

Change it to this:

[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
GurPreet_Singh
  • 386
  • 2
  • 16