views:

445

answers:

1

Does anyone know or have good links that explain what iPhone's event loop does under the hood?

We are using a custom event loop in our OpenGL-based iPhone game framework. It calls our game rendering system, calls presentRenderbuffer and pumps events using CFRunLoopRunInMode. See the code below for details.

It works well when we are not using UIKit controls (as a proof, try Facetap, our first released game).

However, when using UIKit controls, everything almost works, but not quite. Specifically, scrolling of UIKit controls doesn't work properly.

For example, let's consider following scenario.

  • We show UIImagePickerController on top of our own view.
  • UIImagePickerController covers our custom view
  • We also pause our own rendering, but keep on using the custom event loop.

As said, everything works, except scrolling.

  • Picking photos works.
  • Drilling down to photo albums works and transition animations are smooth.
  • When trying to scroll photo album view, the view follows your finger.

Problem: when scrolling, scrolling stops immediately after you lift your finger. Normally, it continues smoothly based on the speed of your movement, but not when we are using the custom event loop. It seems that iPhone's event loop is doing some magic related to UIKit scrolling that we haven't implemented ourselves.

Now, we can get UIKit controls to work just fine and dandy together with our own system by using Apple's event loop and calling our own rendering via NSTimer callbacks. However, I'd still like to understand, what is possibly happening inside iPhone's event loop that is not implemented in our custom event loop.

- (void)customEventLoop { OBJC_METHOD;
  float excess = 0.0f;
  while(isRunning) {
    animationInterval = 1.0f / openGLapp->ticks_per_second();

    // Calculate the target time to be used in this run of loop
    float wait = max(0.0, animationInterval - excess); 
    Systemtime target = Systemtime::now().after_seconds(wait);

    Scope("event loop");

    NSAutoreleasePool* pool = [[ NSAutoreleasePool alloc] init];

    // Call our own render system and present render buffer 
    [self drawView];
    // Pump system events
    [self handleSystemEvents:target];

    [pool release];

    excess = target.seconds_to_now();
  }
}

- (void)drawView { OBJC_METHOD;

  // call our own custom rendering
  bool bind = openGLapp->app_render();

  // bind the buffer to be THE renderbuffer and present its contents
  if (bind) {
    opengl::bind_renderbuffer(renderbuffer);
    [context presentRenderbuffer:GL_RENDERBUFFER_OES];
  }
}

- (void) handleSystemEvents:(Systemtime)target { OBJC_METHOD;
  SInt32 reason = 0;
  double time_left = target.seconds_since_now();
  if (time_left <= 0.0) {
    while((reason = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, TRUE)) == kCFRunLoopRunHandledSource) {}
  } else {
    float dt = time_left;
    while((reason = CFRunLoopRunInMode(kCFRunLoopDefaultMode, dt, FALSE)) == kCFRunLoopRunHandledSource) {
      double time_left = target.seconds_since_now();
      if (time_left <= 0.0) break;
      dt = (float) time_left;
    }
  }
}
+2  A: 

If you NSLog [[NSRunLoop currentRunLoop] currentMode] from [UIScrollView setContentOffset:] when [UIScrollView isDecelerating] is true you will see UITrackingRunLoopMode.

In general, the system will use modes besides kCFRunLoopDefaultMode on the main UI thread run loop, only some of which are documented. The only way to get full system behavior is to cooperate with the system run loop on the main thread.

You could try using an NSTimer and letting the system call you instead of calling CFRunLoopRunInMode yourself. An NSTimer is free to run over time, and when no other UI is shown the system run loop would not be doing anything besides calling the timer.

The alternative would be to return from your customEventLoop function while system controls are being displayed, and call it again when resuming your custom UI.

drawnonward
I've used timers for an OpenGL game and despite what you might first think, I got the highest framerates using this method. It just seems to play much nicer with the main event loop.
Mike Weller
drawnonward, thanks, run loop modes explains it all. We have also been using NSTimer based approach and it had certain anomalies with fast animations . In iPhone OS 3.1 there is CADisplayLink that could help with the problem.
tequilatango
drawnonward, for some reason, I can't mark your answer as accepted. This question had a bounty earlier, but you answered after the bounty hunt had closed and it seems that Stackoverflow prevents me to mark answer accepted after that. Weird.
tequilatango
Seems that after a few months, I was now able to accept your answer.
tequilatango