okay, after all this took me way more time than I expected, and I’d like to outline the things I tried and tell you what experiences I had with them. This will hopefully save people trying to integrate Cocoa into an existing mainloop alot of time in the future. The first function I found when searching for the discussed matter was the function
nextEventMatchingMask:untilDate:inMode:dequeue:
but as I said in the question, my main problem with this was that I would have to constantly poll for new events which would waste quite some CPU Time.
So I tried the following two methods to simply let my mainloops update function get called from the NSApplications mainloop:
-
Post a custom Event to NSApplication, overwrite NSApplications
sendEvent:
function and simply call my mainloops update function
from there. Similar to this:NSEvent* event = [NSEvent otherEventWithType: NSApplicationDefined location: NSMakePoint(0,0) modifierFlags: 0 timestamp: 0.0 windowNumber: 0 context: nil subtype: 0 data1: 0 data2: 0]; [NSApp postEvent: event atStart: YES]; //the send event function of my overwritten NSApplication - (void)sendEvent:(NSEvent *)event { //this is my custom event that simply tells NSApplication //that my app needs an update if( [event type] == NSApplicationDefined) { myCppAppPtr->loopFunc(); //only iterates once } }
This was only a good idea in theory because if my app updated very
quickly (for instance due to a timer firing quickly), the whole
cocoa event queue became totoally unresponsive because I added so
many custom events. So don’t use this… -
Use performSelectorOnMainThread with a cocoaFunction that in
turn calls my update function[theAppNotifier performSelectorOnMainThread:@selector(runMyMainLoop) withObject:nil waitUntilDone:NO ];
This was alot better, the app and cocoa EventLoop was very
responsive. If you are only trying to achieve something simple I’d
recommend going down this route since it is the easiest of the ones
proposed here. Anyways I had very little control about the order of
things happening with this approach (this is crucial if you have a
multithreaded app), i.e when my timers fired and would do a rather
long job, often times they would reschedule before any new
mouse/keyboard input could be added to my eventQueue and thus would
make the whole input sluggish. Turnin on Vertical Sync on a window
that was drawn by a repeating timer was enough to let this happen. -
After all I had to come back to
nextEventMatchingMask:untilDate:inMode:dequeue:
and after some trial and error I actually found a way to make it work without constant polling. The structure of my loop is similar to this:void MyApp::loopFunc() { pollEvents(); processEventQueue(); updateWindows(); idle(); }
where pollEvents and idle are the important functions, basically I use something similar to this.
void MyApp::pollEvents() { NSEvent * event; do { event = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:[NSDate distantPast] inMode:NSDefaultRunLoopMode dequeue:YES]; //Convert the cocoa events to something useful here and add them to your own event queue [NSApp sendEvent: event]; } while(event != nil); }
To implement the blocking inside the idle() function I did this (not sure if this is good, but it seems to work great!) :
void MyApp::idle() { m_bIsIdle = true; NSEvent * event = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:[NSDate distantFuture] inMode:NSDefaultRunLoopMode dequeue:NO]; m_bIsIdle = false; }
this causes cocoa to wait till there is an event, if that happenes idle simply exits and the loopfunc starts again. To wake up the idle function if i.e. one of my timers (i dont use cocoa timers) fires, i once again use a custom event:
void MyApp::wakeUp() { m_bIsIdle = false; //this makes sure we wake up cocoas run loop NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; NSEvent* event = [NSEvent otherEventWithType: NSApplicationDefined location: NSMakePoint(0,0) modifierFlags: 0 timestamp: 0.0 windowNumber: 0 context: nil subtype: 0 data1: 0 data2: 0]; [NSApp postEvent: event atStart: YES]; [pool release]; }
Since I clear the whole cocoa event queue right afterwards i don’t have the same problems as described in section 1.
However, there are some drawbacks with this approach aswell because I think it does not do everything that[NSApplication run]
is doing internally, i.e. application delegate things like this:- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication*)theApplication { return YES; }
don’t seem to work, anyways I can live with that since you can easily check yourself if the last window just closed.
I know this answer is pretty lengthy, but so was my journey to get there. I hope this helps someone and prevents people from doing the mistakes I did.