Wednesday, February 10, 2010

Your own Custom Carbon Application Event Loop

When one develops a Carbon API application, you normally just setup all your application callbacks and logic in your code before you hit the RunApplicationEventLoop call that doesn't return until you Quit.

Certainly you can run different bits of code based on events and so on, and that works just fine for most cases.

There are certain cases where this may not work for you and you want finer grained controls of when different bits and pieces of your code runs. You may wish you could get into the RunApplicationEventLoop and do things how you would like. If this sounds like you, then there is a way to do this.

I needed this when porting a title to OSX, in order to give the same behaviour as the Windows build. Rather that work out how I could get it all to happen syncing between updates and renders I just implemented my own loop which gave me quick access to easy predictable control.

Apple haven't told us exactly what happens in RunApplicationEventLoop but there is a way write your own loop that certainly has worked for folks so far. See the code below.



static EventHandlerUPP gQuitEventHandlerUPP; // -> QuitEventHandler

static OSStatus QuitEventHandler(EventHandlerCallRef inHandlerCallRef,
EventRef inEvent, void *inUserData)
{
OSStatus err;

err = CallNextEventHandler(inHandlerCallRef, inEvent);
if (err == noErr) {
*((Boolean *) inUserData) = true;
}

return err;
}

static OSStatus EventLoopEventHandler(EventHandlerCallRef inHandlerCallRef,
EventRef inEvent, void* inUserData)
{
OSStatus err;
OSStatus junk;
EventHandlerRef installedHandler;
EventTargetRef theTarget;
EventRef theEvent;
Boolean quitNow;
static const EventTypeSpec eventSpec = {kEventClassApplication, kEventAppQuit};

quitNow = false;

// Install our override on the kEventClassApplication, kEventAppQuit event.
err = InstallEventHandler(GetApplicationEventTarget(), gQuitEventHandlerUPP,
1, &eventSpec, &quitNow, &installedHandler);
if (err == noErr) {

// Run our event loop until quitNow is set.
theTarget = GetEventDispatcherTarget();
do {
err = ReceiveNextEvent(0, NULL, kEventDurationNoWait,
true, &theEvent);
if (err == noErr) {
SendEventToEventTarget(theEvent, theTarget);
ReleaseEvent(theEvent);
}

/// Run application code
RunOurApplicationCodeHere();

} while ( ! quitNow );

junk = RemoveEventHandler(installedHandler);
}

return err;
}


static void RunCustomApplicationEventLoop()
{
static const EventTypeSpec eventSpec = {'KWIN', 'KWIN' };
OSStatus err;
OSStatus junk;
EventTargetRef appTarget;
EventHandlerRef installedHandler;
EventRef dummyEvent;

dummyEvent = nil;

err = noErr;
if (gEventLoopEventHandlerUPP == nil) {
gEventLoopEventHandlerUPP = NewEventHandlerUPP(EventLoopEventHandler);
}
if (gQuitEventHandlerUPP == nil) {
gQuitEventHandlerUPP = NewEventHandlerUPP(QuitEventHandler);
}
if (gEventLoopEventHandlerUPP == nil || gQuitEventHandlerUPP == nil) {
err = memFullErr;
}

if (err == noErr) {
err = InstallEventHandler(GetApplicationEventTarget(), gEventLoopEventHandlerUPP,
1, &eventSpec, nil, &installedHandler);
if (err == noErr) {
err = MacCreateEvent(nil, 'KWIN', 'KWIN', GetCurrentEventTime(),
kEventAttributeNone, &dummyEvent);
if (err == noErr) {
err = PostEventToQueue(GetMainEventQueue(), dummyEvent,
kEventPriorityHigh);
}
if (err == noErr) {
RunApplicationEventLoop();
}

junk = RemoveEventHandler(installedHandler);
}
}

if (dummyEvent != nil) {
ReleaseEvent(dummyEvent);
}
}


What this code does is that it creates a custom event loop that gets entered by the normal RunApplicationEventLoop when the event for it gets fired (very early on). The custom loop runs the normal events pump as expected. A custom quit event handler is inserted to toggle the finalisation of the custom event loop. Simple!