1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 | static void createWindow() { NSUInteger windowStyle = NSTitledWindowMask | NSClosableWindowMask | NSResizableWindowMask | NSMiniaturizableWindowMask; NSRect screenRect = [[NSScreen mainScreen] frame]; NSRect viewRect = NSMakeRect(0, 0, 1024, 768); NSRect windowRect = NSMakeRect(NSMidX(screenRect) - NSMidX(viewRect), NSMidY(screenRect) - NSMidY(viewRect), viewRect.size.width, viewRect.size.height); window = [[NSWindow alloc] initWithContentRect:windowRect styleMask:windowStyle backing:NSBackingStoreBuffered defer:NO]; [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; id menubar = [[NSMenu new] autorelease]; id appMenuItem = [[NSMenuItem new] autorelease]; [menubar addItem:appMenuItem]; [NSApp setMainMenu:menubar]; // Then we add the quit item to the menu. Fortunately the action is simple since terminate: is // already implemented in NSApplication and the NSApplication is always in the responder chain. id appMenu = [[NSMenu new] autorelease]; id appName = [[NSProcessInfo processInfo] processName]; id quitTitle = [@"Quit " stringByAppendingString:appName]; id quitMenuItem = [[[NSMenuItem alloc] initWithTitle:quitTitle action:@selector(terminate:) keyEquivalent:@"q"] autorelease]; [appMenu addItem:quitMenuItem]; [appMenuItem setSubmenu:appMenu]; NSWindowController * windowController = [[NSWindowController alloc] initWithWindow:window]; [windowController autorelease]; //View view = [[[View alloc] initWithFrame:viewRect] autorelease]; [window setContentView:view]; //Window Delegate windowDelegate = [[WindowDelegate alloc] init]; [window setDelegate:windowDelegate]; [window setAcceptsMouseMovedEvents:YES]; [window setDelegate:view]; // Set app title [window setTitle:appName]; // Add fullscreen button [window setCollectionBehavior: NSWindowCollectionBehaviorFullScreenPrimary]; [window makeKeyAndOrderFront:nil]; } void initApp() { [NSApplication sharedApplication]; appDelegate = [[AppDelegate alloc] init]; [NSApp setDelegate:appDelegate]; running = true; [NSApp finishLaunching]; } void frame() { @autoreleasepool { NSEvent* ev; do { ev = [NSApp nextEventMatchingMask: NSAnyEventMask untilDate: nil inMode: NSDefaultRunLoopMode dequeue: YES]; if (ev) { // handle events here [NSApp sendEvent: ev]; } } while (ev); } } int main(int argc, const char * argv[]) { initApp(); createWindow(); while (running) { frame(); RenderWeirdGradient(XOffset, YOffset); [view setNeedsDisplay:YES]; XOffset++; YOffset++; } return (0); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | void frame() { @autoreleasepool { NSEvent* ev; do { ev = [NSApp nextEventMatchingMask: NSAnyEventMask untilDate: nil inMode: NSDefaultRunLoopMode dequeue: YES]; if (ev) { // handle events here [NSApp sendEvent: ev]; } } while (ev); } } |
adge
Wow thank you very much this helps me alot!
There is one more thing I would like to know. Since all the stuff is now happening in my main function, what ways are there to ger Delegtate events like -(void)windowShouldClose() to work. Right now I cant close my application because I don,t know how to connect the red Quit button to a function that then sets running to false so that the main loop is quit.
Normaly -(void)windoeShouldClose is called on klicking the red button but since I'm stull keeping control with my own Event queue the Delegates are useless.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | @implementation WindowDelegate - (void)windowDidBecomeKey:(NSNotification *)notification { NSLog(@"Window: become key"); } - (void)windowDidBecomeMain:(NSNotification *)notification { NSLog(@"Window: become main"); } - (void)windowDidResignKey:(NSNotification *)notification { NSLog(@"Window: resign key"); } - (void)windowDidResignMain:(NSNotification *)notification { NSLog(@"Window: resign main"); } // This will close/terminate the application when the main window is closed. - (void)windowWillClose:(NSNotification *)notification { Window *window = notification.object; if (window.isMainWindow) { [NSApp terminate:nil]; } } @end |
1 2 3 4 5 6 7 8 | // Empty implementations here so that the window doesn't complain when the events aren't handled or passed on to its view. // i.e. A traditional Cocoa app expects to pass events on to its view(s). - (void)keyDown:(NSEvent *)theEvent {} - (void)keyUp:(NSEvent *)theEvent {} - (BOOL)acceptsFirstResponder { return YES; } - (BOOL)canBecomeKeyWindow { return YES; } - (BOOL)canBecomeMainWindow { return YES; } |
1 2 3 4 5 6 7 8 9 10 11 12 | NSCAssert([NSThread isMainThread], @"Processing Application events must occur on main thread."); BOOL eventsProcessed = NO; NSEvent *event; while ((event = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:nil inMode:NSDefaultRunLoopMode dequeue:YES])) { // Process event.. [NSApp sendEvent:event]; [NSApp updateWindows]; eventsProcessed = YES; } return eventsProcessed; |
1 2 3 4 5 6 7 8 | [NSApplication sharedApplication]; [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; [NSApp setPresentationOptions:NSApplicationPresentationDefault]; [NSApp activateIgnoringOtherApps:YES]; appDelegate = [[FSAppDelegate alloc] init]; [NSApp setDelegate:appDelegate]; [NSApp finishLaunching]; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | void frame() { @autoreleasepool { NSEvent* ev; do { ev = [NSApp nextEventMatchingMask: NSAnyEventMask untilDate: nil inMode: NSDefaultRunLoopMode dequeue: YES]; if (ev) { // handle events here [NSApp sendEvent: ev]; } } while (ev); } } |
adge
God I'm an idiot!
Yeah I set the delegates the wrong way. It works now perfectly fine. But why is it working? I thought [NSApp run] forwards control to the delegates callback methods? But since I'm not calling [NSApp run] it shouldn't work at all right? Or am I wrong with this assumption.
adge
What did you do about the 100% CPU usage because of the application now constantly polling for events? Will Caseys for this problem work with my Cocoa application?
adge
I "stole" this code from someone but don't understand it. What does "while (ev)" do? Nothing right since there is no block after the condition.
[NSApp nextEventMatchingMask] just gets the next event?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 void frame() { @autoreleasepool { NSEvent* ev; do { ev = [NSApp nextEventMatchingMask: NSAnyEventMask untilDate: nil inMode: NSDefaultRunLoopMode dequeue: YES]; if (ev) { // handle events here [NSApp sendEvent: ev]; } } while (ev); } }
Is [NSApp run] doing the same? What am I supposed to insert in //handle events here. Which events? Since the Delegation methods are now working.
I'm sorry for all these questions but I've never heard of this event system in Cocoa. I always thought every application is running like
while (running) {
//do
}
As soon es the application is executed the main method is the starting point right? Normally [NSApp run] is called after setting the Application up. But now I'm doing all myself and I also have an own way on how to process my events. Would I need frame() at all if I didn't care about keyboard, mouseclicks or resized windows?
Is there a different way of getting notified when the close button is pressed or other system wise stuff happens than through the delegates?
1 2 3 4 5 6 7 8 9 10 11 | switch (event.type) { case NSLeftMouseDown: // do something when left mouse is down break; case NSKeyDown: if (event.keyCode == kVK_Escape) { // virtual key code for Esc // do something when escape is down } break; case ...: } |
1 2 3 4 5 6 | // This registers an instance (target) which implements the given selector (doSomething:, which has one argument of type NSNotification*) when the notification with the // name NSWindowWillCloseNotification is posted. Whenever this happens, the selector is invoked on the target. [[NSNotificationCenter defaultCenter] addObserver:target selector:@selector(doSomething:) name:NSWindowWillCloseNotification object:nil]; // This removes the target from receiving this notification (required for cleanup so you don't leak resources). [[NSNotificationCenter defaultCenter] removeObserver:target name:NSWindowWillCloseNotification object:nil]; |
1 2 3 4 5 | NSEvent* ev; while (NULL != (ev = [NSApp nextEventMatchingMask: NSAnyEventMask untilDate: nil inMode: NSDefaultRunLoopMode dequeue: YES])) { [NSApp sendEvent: ev]; } |
Flyingsand
I do it slightly differently however. In my case, I have a CVDisplayLink callback for rendering which handles vsync (pretty much the only way to handle it on OS X these days AFAIK..), so I synchronize the two threads together. i.e. The CVDisplayLink callback signals to the main thread when it is done. So the main thread polls for events then waits for the signal from the callback to continue. This may not be the best way to handle this (?), but I put it in for the time being and it's been working well so far. So use this method at your own discretion. :)
1 2 3 | - (CVReturn) getFrameForTime:(const CVTimeStamp*)outputTime { return kCVReturnSuccess; } |
1 2 3 4 5 | int main() { while (running) { render() } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | void frame() { @autoreleasepool { NSEvent* ev; do { ev = [NSApp nextEventMatchingMask: NSAnyEventMask untilDate: nil inMode: NSDefaultRunLoopMode dequeue: YES]; if (ev) { // handle events here [NSApp sendEvent: ev]; } } while (ev); } } int main(int argc, const char * argv[]) { initApp(); createWindow(); while (running) { frame(); RenderWeirdGradient(XOffset, YOffset); [view setNeedsDisplay:YES]; XOffset = XOffset + 10; YOffset++; } return (0); } |
adgeFlyingsand
I do it slightly differently however. In my case, I have a CVDisplayLink callback for rendering which handles vsync (pretty much the only way to handle it on OS X these days AFAIK..), so I synchronize the two threads together. i.e. The CVDisplayLink callback signals to the main thread when it is done. So the main thread polls for events then waits for the signal from the callback to continue. This may not be the best way to handle this (?), but I put it in for the time being and it's been working well so far. So use this method at your own discretion. :)
I guess the applications refresh rate has to be aligned to the screen refresh rate in order to get the best results? Why did you use multiple threads all? I know that later on more threads will increase performance but for a small platform layer?
So you are doing your rendering in:
1 2 3 - (CVReturn) getFrameForTime:(const CVTimeStamp*)outputTime { return kCVReturnSuccess; }
and not in:
1 2 3 4 5 int main() { while (running) { render() } }
adge
But I don't see a need for my own main application run loop and handling the event queue if I do the rendering in getFrameForTime. The reason for avoiding [NSApp run], a custom event loop and making use of the main function with while (running) {//render} was to create a loop because there was none before. But just using getFrameForTime simplifies a lot right? So why would you still use this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 void frame() { @autoreleasepool { NSEvent* ev; do { ev = [NSApp nextEventMatchingMask: NSAnyEventMask untilDate: nil inMode: NSDefaultRunLoopMode dequeue: YES]; if (ev) { // handle events here [NSApp sendEvent: ev]; } } while (ev); } } int main(int argc, const char * argv[]) { initApp(); createWindow(); while (running) { frame(); RenderWeirdGradient(XOffset, YOffset); [view setNeedsDisplay:YES]; XOffset = XOffset + 10; YOffset++; } return (0); }
?
adge
Am I wrong in assuming that using CVDisplayLink ties you to 60 frames per second? Isn't this a bad thing? Especially if you're doing your rendering by software? Or can you somehow do the rendering 30 frames per second using the CVDisplayLink callback.
1 2 3 4 5 | - (CVReturn)getFrameForTime:(const CVTimeStamp *)outputTime { double deltaTime = (double)(outputTime->hostTime - lastFrame) / CVGetHostClockFrequency(); ... } |
adge
Ok, thank you guys, this literally kicked me into the stratosphere. Could you recommend some resources like books for stuff like that. Where do you know this stuff from? I've never seen an apple document explaining how you can create your own application loop.