1

We are building a content editor that brings up a "cocos Player" in an NSWindow for test purposes. The user can test some content and then close the window.

So I need to be able to shutdown cocos and re-start within the same app.

Everything is working if I use the CC_MAC_USE_DISPLAY_LINK_THREAD threading model. I had to make a fix in CCDirectorMac to get this working. In CCDirectorMac | stopAnimation I had to set the _runningThread to nil since it is not set to nil by the #if and #elif when using CC_MAC_USE_DISPLAY_LINK_THREAD.

Anyway so now I am able to "end" a director and then re-start it later with no issues.

My question though is this: If I am building an AppKit editor with occasional use of cocos2D whould my threading model really be CC_MAC_USE_MAIN_THREAD as is suggested in the documentation?

When I do use CC_MAC_USE_MAIN_THREAD I get a HANG in in stopAnimation on the line:

CVDisplayLinkStop(displayLink);

I think the main thread would be fine and would avoid threading issues for our tool. Performance is not a concern. I can't find any sample code that shuts down and restarts cocos2d in an NSWindow ... so my assumption here is that I am in untested waters (or little tested waters).

My steps to shutdown/restart are:

  1. Call [[CCDirector sharedDirector] end]
  2. This calls stopAnimation
  3. Then re-initialize cocos2d the same way I did originally

Any advice on threading models for a Mac desktop app ... and why CVDisplayLinkStop hangs would be greatly appreciated.

Thanks in advance.

Shane Hou
  • 4,808
  • 9
  • 35
  • 50
poundev23
  • 807
  • 3
  • 11
  • 18
  • I'm seeing the exact same behavior in my app. If I switch to CC_MAC_USE_MAIN_THREAD (which solves some weird problems I was getting) it hangs when I try to quit the app (which calls [[CCDirector sharedDirector] end]). Did you ever figure this out? – Mariano Ruggiero Aug 02 '13 at 23:50

2 Answers2

3

Ok, I figured it out after reading this post and its answers on the Apple mailing list: http://lists.apple.com/archives/quartz-dev/2006/Oct/msg00056.html

When using CC_MAC_USE_MAIN_THREAD, the display link thread uses performSelector:onThread:waitUntilDone: to run drawScene: on the main thread. It passes YES for the waitUntilDone: parameter, so the display link thread blocks until the main thread can process the drawScene: call.

Here's the relevant fragment of the cocos2d code. MyDisplayLinkCallback is called on the display link thread.

static CVReturn MyDisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp* now, const CVTimeStamp* outputTime, CVOptionFlags flagsIn, CVOptionFlags* flagsOut, void* displayLinkContext)
{
    CVReturn result = [(CCDirectorDisplayLink*)displayLinkContext getFrameForTime:outputTime];
    return result;
}

- (CVReturn) getFrameForTime:(const CVTimeStamp*)outputTime
{
#if (CC_DIRECTOR_MAC_THREAD == CC_MAC_USE_DISPLAY_LINK_THREAD)
    ...
#else
    // Display link thread blocks here:
    [self performSelector:@selector(drawScene) onThread:_runningThread withObject:nil waitUntilDone:YES];
#endif
    return kCVReturnSuccess;
}

The problem appears when the main thread tries to run CVDisplayLinkStop() which blocks until the display link callback in the display link thread finishes. Since the callback is at the same time waiting for the main thread to process its drawScene: call, both threads become deadlocked.

- (void) stopAnimation
{
    ...

    if( displayLink ) {
        // Main thread blocks here:
        CVDisplayLinkStop(displayLink);
    ...
}

So, now for my workaround. I added a line to run the main thread's runloop in order to force the drawScene: call to be executed, which unblocks the display link thread. That way, when you call CVDisplayLinkStop() you're safe. Here's my addition (CCDirectorMac.m line 473 in cocos2d 2.1 release):

- (void) stopAnimation
{
    ...

    if( displayLink ) {
#if (CC_DIRECTOR_MAC_THREAD != CC_MAC_USE_DISPLAY_LINK_THREAD)
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
#endif
        CVDisplayLinkStop(displayLink);
    ...
}

I'm not sure this is the right thing to do, there's probably a better way to deal with this, but this workaround is good enough for me at the moment.

Mariano Ruggiero
  • 737
  • 4
  • 15
  • Thanks a lot! I had similar issue with custom OpenGLView, but only for Retina display. After replacing `waitUntilDone` from `YES` to `NO` application doesn't hang at start. – olha Feb 10 '17 at 12:49
0

Thanks for this post, it helped me figure out how to solve the same deadlock, in a non-cocos2d app, by processing the display link callback on a separate thread, never on the main thread.

static CVReturn MyDisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp* now, const CVTimeStamp* outputTime,
                                      CVOptionFlags flagsIn, CVOptionFlags* flagsOut, void *userInfo)
{
    static dispatch_queue_t sDisplayQueue;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sDisplayQueue = dispatch_queue_create("com.company.app.displayLink", NULL);
    });

    dispatch_sync(sDisplayQueue, ^{
        <stuff>
    });
    return kCVReturnSuccess;
}
mr. fixit
  • 1,404
  • 11
  • 19