1

I have a function drawView which is thread safe and does drawing for short periods of game animation. I have functions startAnimating and stopAnimating. I want a background thread to call drawView at a regular rate but only during the period that the animation is enabled.

In startAnimating I was going to call the view's performSelectorInBackground:withObject: to get the thread running.

I'm a little confused about how to do the thread communication and initialize the drawing thread: specifically, setting up a runloop to receive display link messages and then at the end notifying the thread that it should exit and exiting the run loop cleanly when stopAnimating is called from the main thread. I want to ensure that drawView is never called after stopAnimating, and also that the drawing thread is not cancelled abruptly in the middle of the drawing operation. I have seen a lot of very poor answers to this kind of question on line.

Robotbugs
  • 4,307
  • 3
  • 22
  • 30

1 Answers1

0

OK after reading the Apple pages all evening, I finally solved it with this code:

// object members
NSThread *m_animationthread;
BOOL m_animationthreadrunning;

- (void)startAnimating
{
    //called from UI thread
    DEBUG_LOG(@"creating animation thread");
    m_animationthread = [[NSThread alloc] initWithTarget:self selector:@selector(animationThread:) object:nil];
    [m_animationthread start];
}

- (void)stopAnimating
{
    // called from UI thread
    DEBUG_LOG(@"quitting animationthread");
    [self performSelector:@selector(quitAnimationThread) onThread:m_animationthread withObject:nil waitUntilDone:NO];

    // wait until thread actually exits
    while(![m_animationthread isFinished])
        [NSThread sleepForTimeInterval:0.01];
    DEBUG_LOG(@"thread exited");

    [m_animationthread release];
    m_animationthread = nil;
}

- (void)animationThread:(id)object
{
    @autoreleasepool
    {
        DEBUG_LOG(@"animation thread started");
        m_animationthreadrunning = YES;

        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];

        CADisplayLink *displaylink = [CADisplayLink displayLinkWithTarget:self selector:@selector(displayLinkAction:)];
        [displaylink setFrameInterval:3];

        [displaylink addToRunLoop:runLoop forMode:NSDefaultRunLoopMode];

        while(m_animationthreadrunning)
        {
            [runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
            DEBUG_LOG(@"runloop gap");
        }

        [displaylink removeFromRunLoop:runLoop forMode:NSDefaultRunLoopMode];

        DEBUG_LOG(@"animation thread quit");
    }
}

- (void)quitAnimationThread
{
    DEBUG_LOG(@"quitanimationthread called");
    m_animationthreadrunning = NO;
}

- (void)displayLinkAction:(CADisplayLink *)sender
{
    DEBUG_LOG(@"display link called");
    //[self drawView];
}

The reason I use the line [self performSelector:@selector(quitAnimationThread) onThread:m_animationthread withObject:nil waitUntilDone:NO] and not simply set m_animationthreadrunning = NO in stopAnimating is because the run loop may not return in a timely fashion, but calling a selector forces it to return.

Robotbugs
  • 4,307
  • 3
  • 22
  • 30