Handmade Hero » Forums » Code » Main game loop on OS X
adge
Adrian
46 posts

None

#7874 Main game loop on OS X
2 years, 4 months ago

Could you post your code? I searched the web for guides on how to use metal but didn't came very far. Would love to see how you did it!

I tried to create my own OS X platform layer but came across a problem with the main game loop. How I'm going to implement it with Cocoa since everything in Cocoa is event driven.

As soon [NSApp run] is called, the main method becomes useless and the only entry point for your code are the callback methods of the Delegate Objects. Under windows this question is obsolete because there the main method is always the main entry point and you can do stuff like

while (running) {
//do framecode here
}

So my question is: How do you properly create a game run loop on OS X?

I found "the windows way" on OS X online and merged it with my code.

 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);


But doing it that way will make the Delegates absolutely useless and people told me that I will encounter even more problems because a lot of stuff isn't done this way [NSApp run] usually does.

I already asked on stack overflow but this didn't really help.
Custom main application loop in cocoa

So how is a game loop on OS X done the right way?

None
mmozeiko
Mārtiņš Možeiko
1831 posts / 1 project
#7886 Main game loop on OS X
2 years, 4 months ago

It is perfectly fine to use game loop like this on OSX. Many game engines or libraries like SDL or glfw does game loop on OSX in this way.
https://hg.libsdl.org/SDL/file/e8...ideo/cocoa/SDL_cocoaevents.m#l364
https://github.com/glfw/glfw/blob/master/src/cocoa_window.m#L1350
adge
Adrian
46 posts

None

#7897 Main game loop on OS X
2 years, 4 months ago Edited by Adrian on Aug. 6, 2016, 11:04 p.m.

But the comment on my stack overflow question says that: "Besides the fact that a lot of code in AppKit expects NSApplicationMain() to be called, the system at large does as well, and with your approach you will probably end up with your application doing a bunch of annoying things like interacting poorly with the Dock and Launchpad."

Furthermore I cant seem to find the int main(int argc, const char * argv[]) {} function in the SDL or GLFW implementation which is as I thought necessary for Implementing a main loop and also necessary for an application to run because its the main entry point right?

Moreover inspecting the hardware while my app is running, the activity application tells me my app is sucking up 100% of the cpus power. I read somewhere that this is because the app is polling constantly for events, which is not the case when [NSAPP run] is called.

Besides that: what does [NSApp run] actually do?

Could you maybe explain to me what this code is actually doing? I have never seen this in any Cocoa application or found any references on how to properly implement a "main application loop".

 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);
    }
}



I am sorry for all these questions but I would really like to understand this topic but can't find any valuable resources especially in terms of Cocoa. Its totally find if you just forward me to some books or websites.

I thank you a lot!

None
mmozeiko
Mārtiņš Možeiko
1831 posts / 1 project
#7901 Main game loop on OS X
2 years, 4 months ago Edited by Mārtiņš Možeiko on Aug. 7, 2016, 1:49 a.m.

SDL and glfw are libraries, they don't define main. It's up to user who uses these libraries to create main function.
Btw it is not only game libraries that use nextEventMatchingMask/sendEvent for their event loop. Even big GUI frameworks like Qt and wxWidgets use same mechanism. Firefox also does it. So there is really nothing wrong with it. All those "annoying things" for "interacting poorly with ..." are only potential bugs with your code and not an API limitation. Handling events with nextEventMatchingMask/sendEvent can be done exactly same as with run method.

nextEventMatchingMask is something similar to GetMessage and sendEvent is something similar to DispatchMessage in Windows.
Basically your loop processes all pending events and then returns. If no events are pending then it returns immediately.

Your code will use 100% of CPU unless you'll do something like vsync when rendering with GL (no idea about Metal), or do manual sleep - something similar to what Casey did with Handmade Hero. 100% without vsync is expected behavior. It will be 100% also on Windows if you don't do vsync with GL/D3D. [NSApp run] blocks and waits for next event instead of trying to get next event over and over again. If you want to simulate this behavior change untilDate argument from nil to "[NSDate distantFuture]". Of course this will make frame function never to return - ev will never be NULL.

[NSApp run] launches new event loop. Purpose of event loop is almost exactly as your frame() function - to wait for next event and then dispatch with sendEvent. You can check one possible implementation of run method in GNUstep source: https://github.com/gnustep/gui/bl...ster/Source/NSApplication.m#L1516 It has a bit more stuff than just nextEventMatchingMask and sendEvent - but core loop is only about getting next event and sending it.

I don't know any good resource on OSX on this kind of information - I don't do much OSX developing (only when I'm forced to do it :) So all I know is read official docs and do a lot of experimenting with code.
adge
Adrian
46 posts

None

#7908 Main game loop on OS X
2 years, 4 months ago Edited by Adrian on Aug. 7, 2016, 9:52 a.m.

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.

None
Flyingsand
73 posts
#7923 Main game loop on OS X
2 years, 4 months ago

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.


You need to implement the delegate methods in your WindowDelegate that you assign to your main window. However, it looks like in your code above, you set this as your delegate, but then almost immediately set your view as the window's delegate? You should assign the view to the window's 'contentView' property. So in your NSWindowDelegate subclass, you can have:

 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


I found these delegate implementations useful to start out with to make sure the window (and application) behave correctly when the window loses/gains focus. i.e. If you haven't set things up right, the window will not regain main/key status after it loses focus, or even when first appearing.

Additionally, since I handle events outside of the window itself (they're handled in the main event loop like you have set up), I override the following methods in NSWindow:

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; }


My event loop is pretty much the same as you have:

 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;


I went through a lot of the same headaches as you are having trying to set things up in this way to have more control over the main event loop. These things aren't well documented at all, and a lot of stuff happens behind the scenes in a traditional Cocoa app managed by AppKit. One of these things is assigning the main window to your application instance (which happens with the first window you create), but explicitly assigning it myself was not working. So I tried examining all the events that were sent when a normal Cocoa app is created, and as I recall, there were at least one or two system-/app- specific events that were sent when the app finishes launching that makes that connection. It seems simple now, but it wasn't entirely obvious at the time that I needed to dispatch every event I get back to the NSApp. :/

And just for completeness, here are the steps I have for setting up an application:

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];

adge
Adrian
46 posts

None

#7924 Main game loop on OS X
2 years, 4 months ago Edited by Adrian on Aug. 7, 2016, 5:37 p.m.

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.

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? Did you use a "sleep" function or something similar?

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?

None
Flyingsand
73 posts
#7928 Main game loop on OS X
2 years, 4 months ago Edited by Flyingsand on Aug. 7, 2016, 6:02 p.m.

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.


No, [NSApp run] doesn't itself forward control to the delegate methods. It's pretty much just responsible for the event loop. Each window itself is responsible for forwarding any messages to its delegate methods. In fact, in a default Cocoa application (created via Xcode), there is no window delegate or window controller assigned unless you create them. The application does, however, observe notifications that are sent by the window.

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?


Yes, doing a sleep in the main event loop will reduce the CPU usage. So if no events come in during a pass trough the loop, you can sleep for some amount of frame time (e.g. 1/30th of a second, 1/60th of a second, etc.).

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. :)

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?


The "while(env);" is part of the do { } while(); loop. Just a slight variation on the while() { } loop, except the do { } while(); loop is guaranteed to run at least once (instead of checking the condition at the beginning, it checks the condition after the loop has run).

The events you will be interested in handling will typically be input events (mouse and keyboard). I have my own structure that contains all the input I'm interested in (such as key downs, key ups, mouse locations, etc.) that I map to when reading events. Processing an event might look something like this:

 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 ...:
}


Under the hood, yes the "while (running) { }" logic is essentially what's happening. The event/delegate system is just a layer on top of this. Cocoa relies heavily on the delegate pattern as a way of customizing behaviour without having to subclass. I would read up on Objective C protocols to get a better idea of how this works. You'll also notice that many events in Cocoa (such as windows closing, appearing, applications terminating, etc.) cause notifications to be posted. This is basically your typical centralized messaging system where you can register functions with an identifier (usually a string) to this centralized system, which stores this info in a table. When you post a notification to it, it looks up to see if an event has been registered with that identifier, and if so, executes the function.

Edit:
So yes, you can observe notifications for things like a window closing instead of implementing a delegate. You would do something like this:

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];
mmozeiko
Mārtiņš Možeiko
1831 posts / 1 project
#7929 Main game loop on OS X
2 years, 4 months ago Edited by Mārtiņš Možeiko on Aug. 7, 2016, 6:09 p.m.

"while (ev)" is simply checking is ev variable NULL or not NULL. It's part of "do { ... } while (ev)" loop construction in C. If ev is not NULL that means nextEventMatchingMask returned valid event so maybe there are more events to process and loop is repeated. if ev is NULL that means nextEventMatchingMask returned NULL and there are no more events currently available. So loop terminates.

You can rewrite loop without "do" construction if you want:
1
2
3
4
5
NSEvent* ev;
while (NULL != (ev = [NSApp nextEventMatchingMask: NSAnyEventMask untilDate: nil inMode: NSDefaultRunLoopMode dequeue: YES]))
{
    [NSApp sendEvent: ev];
}


[NSApp run] is doing exactly the same thing except it never returns NULL from nextEventMatchingMask (it uses untiLDate to choose blocking wait). Checkout source in GNUStep link I gave you above.

You don't need to insert anything in "//handle events here" place. [NSApp sendEvent: ev] handles events - it handles the ev event. You need to attach all these delegates to view (or whatever it's called, I'm not very familiar with OSX) or you override view methods.

Check out how glfw handles events - it creates NSView subclass GLFWContentView where it overrides bunch of methods to get events.
subclassing NSView: https://github.com/glfw/glfw/blob/master/src/cocoa_window.m#L343
getting key events: https://github.com/glfw/glfw/blob/master/src/cocoa_window.m#L547
getting mouse events: https://github.com/glfw/glfw/blob/master/src/cocoa_window.m#L414
etc...

And the main event loop is just calling nextEventMatchingMask/sendEvent and nothing else, exactly like your "frame" function: https://github.com/glfw/glfw/blob/master/src/cocoa_window.m#L1350

For 100% cpu usage you typically use vsync if you are using 3D API. SDL, for example, sets NSOpenGLCPSwapInterval parameter for NSOpenGLContext when using OpenGL. I'm not familiar Metal, but probably something similar is available there.

adge
Adrian
46 posts

None

#7932 Main game loop on OS X
2 years, 4 months ago Edited by Adrian on Aug. 7, 2016, 8:56 p.m.

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. :)


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()
 }
}


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);
}


?

None
Flyingsand
73 posts
#7935 Main game loop on OS X
2 years, 4 months ago

adge
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. :)


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()
 }
}



Correct. All rendering and game logic is handled within the CVDisplayLink callback. For right now (and I would like to stress that this is not a final platform layer or anything, it's very much in flux) the main loop only handles the events. As I mentioned above, I map any Cocoa events that come in to my own struct so I can easily test for things like 'isDown', 'wentDown', and 'pressed' for each key & mouse button with frame accuracy. To do that I maintain a small buffer that remembers the state of the last frame's input, and this needs to be synchronized between the two threads. So that is the reason for the sync. I would like to try another approach at some point, e.g. an input queue that the main thread pushes to and the callback thread reads from, but this is working well for now.

And the reason for multiple threads is simply that the CVDisplayLink callback is executed on a separate high-priority thread by the OS. This is discussed here by Apple:
Technical Q&A QA1385: Driving OpenGL Rendering Loops

You can also see this in action in the GLessentials sample code project on the Apple developer site.

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);
}


?


Yeah, if all your rendering happens in the CVDisplayLink callback and you don't care about events, then there's no need to implement your own run loop if that's the direction you're going. For me though, I found that I was getting more precise input from handling the events myself in the run loop as opposed to getting them as they are delivered to the NSView if you call [NSApp run]. And come to think of it, I do have other stuff in my run loop as well, such as the hot reloading of the game library, and some other debug stuff.
adge
Adrian
46 posts

None

#7977 Main game loop on OS X
2 years, 4 months ago

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.

None
Flyingsand
73 posts
#7980 Main game loop on OS X
2 years, 4 months ago

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.


Yes and no. The CVDisplayLink callback gets called at whatever refresh period is of the display device it is linked to. You can get this information by calling either CVDisplayLinkGetNominalOutputVideoRefreshPeriod or CVDisplayLinkGetActualOutputVideoRefreshPeriod (the display link has to be running for this to return a valid value). You can also get the actual time elapsed between each invocation of the callback by pulling out the information in the CVTimeStamp parameter that is passed to you. e.g.:
1
2
3
4
5
- (CVReturn)getFrameForTime:(const CVTimeStamp *)outputTime
{
    double deltaTime = (double)(outputTime->hostTime - lastFrame) / CVGetHostClockFrequency();
    ...
}


However, if (for example) you're not hitting the frame rate of the display link, you can skip every other frame and that would bring you down to 30 fps (assuming the refresh rate is 60).
adge
Adrian
46 posts

None

#8065 Main game loop on OS X
2 years, 3 months ago

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.

None
Flyingsand
73 posts
#8071 Main game loop on OS X
2 years, 3 months ago

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.


I've picked up most of this stuff from piecing together tidbits from Apple documentation (you're right, they don't explicitly document how to create your own application loop, but you can get insight into what it needs to do by reading the Events Programming Guide and the Run Loop (or Threading?) Programming Guide). Also, looking at others' source code, particularly SDL. And perhaps most importantly, just trying it out, experimenting, asking questions, and fighting through it until it works. :)