Cocoa message loop? (vs. windows message loop)

前端 未结 2 1608
闹比i
闹比i 2021-02-07 11:46

While trying to port my game engine to mac, I stumble upon one basic (but big) problem. On windows, my main code looks like this (very simplified):

PeekMessage(.         


        
2条回答
  •  生来不讨喜
    2021-02-07 12:44

    Although I can't be the only person in the world trying to achieve this, nobody seems to want to answer this (not point at you stackoverflow guys, just pointing at mac developers who know how it works). That's why I want to change this typical behaviour and help those who are looking for the same thing. In other words: I found the solution! I still need to further improve this code, but this is basically it!

    1/ When you need a real own loop, just like in windows, you need to take care of it yourself. So, let cocoa build your standard code, but take main.m (change it to .mm for c++ if necessary, which is in this example because I used c++) and remove the one and only line of code. You will not allow cocoa to set up your application/window/view for you.

    2/ Cocoa normally creates an AutoreleasePool for you. It helps you with memory management. Now, this will no longer happen, so you need to initialize it. Your first line in the main function will be:

    
        [[NSAutoreleasePool alloc] init];
    

    3/ Now you need to set up your application delegate. The XCode wizard has already set up a AppDelegate class for you, so you just need to use that one. Also the wizard has already set up your main menu and probably called it MainMenu.xib. The default ones are fine to get started. Make sure you #import "YourAppDelegate.h" after the #import line. Then add the following code in your main function:

    
        [NSApplication sharedApplication];
        [NSApp setDelegate:[[[YourAppDelegate alloc] init] autorelease]];
        [NSBundle loadNibNamed:@"MainMenu" owner:[NSApp delegate]];
        [NSApp finishLaunching];
    

    4/ Mac OS will now know what the application is about, will add a main menu to it. Running this won't do much anyhow, because there is no window yet. Let's create it:

    
        [Window setDelegate:[NSApp delegate]];
        [Window setAcceptsMouseMovedEvents:TRUE];
        [Window setIsVisible:TRUE];
        [Window makeKeyAndOrderFront:nil];
        [Window setStyleMask:NSTitledWindowMask|NSClosableWindowMask];
    

    To show what you can do here, I added a title bar and enabled the close button. You can do a lot more, but I also need to study this first.

    5/ Now if you run this, you might get lucky and see a window for a microsecond, but you will probably see nothing because... the program is not in a loop yet. Let's add a loop and listen to incoming events:

    
        bool quit = false;
        while (!quit)
        {
            NSEvent *event = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:nil inMode:NSDefaultRunLoopMode dequeue:YES];
            switch([(NSEvent *)event type])
            {
            case NSKeyDown:
                quit = true;
                break;
            default:
                [NSApp sendEvent:event];
                break;
            }
            [event release];
        }
    

    If you run this, you will see the window appear and it will stay visible until you just press a key. When you press the key, the application will quit immediately.

    This is it! But beware... check out the Activity Monitor. You will see that your application is using 100% CPU. Why? Because the loop doesn't put the CPU in sleep mode. That's up to you now. You can make it easy on yourself and put a usleep(10000); in the while. This will do a lot, but isn't optimal. I will probably use the vertical-sync of opengl to make sure the CPU isn't overly used, and I will also wait for events from my threads. Maybe I will even check out the passed time myself and do a calculated usleep to make the total time per frame so that I have a decent frame rate.

    For help with Opengl:

    1/ Add the cocoa.opengl framework to the project.

    2/ Before [Window...] put:

    
        NSOpenGLPixelFormat *format = [[NSOpenGLPixelFormat alloc] initWithAttributes:windowattribs];
        NSOpenGLContext *OGLContext = [[NSOpenGLContext alloc] initWithFormat:format shareContext:NULL];
        [format release];
    

    3/ After [Window setDelegate:...] put:

    
        [OGLContext setView:[Window contentView]];
    

    4/ Somewhere after the window handling, put:

    
        [OGLContext makeCurrentContext];
    

    5/ After [event release], put:

    
        glClearColor(1, 0, 0, 1);
        glClear(GL_COLOR_BUFFER_BIT);
        [OGLContext flushBuffer];
    

    This will clear your window to fully red on a framerate of 3300 on my mac book pro 2011.

    Again, this code is not complete. When you click on the close button and reopen it, it will no longer work. I probably need to hook events for that and I don't know them yet (after further research, these things can be done in the AppDelegate). Also, there are probably lots of other things to do/consider. But this is a start. Please feel free to correct me/fill me in/...

    If I get it fully right, I might set up a tutorial web page for this if no one beats me to it.

    I hope this helps you all!

提交回复
热议问题