可可消息循环? (与Windows消息循环对比)

时间:2011-07-11 11:51:02

标签: windows cocoa message-loop

在尝试将我的游戏引擎移植到mac时,我偶然发现了一个基本(但很大)的问题。在Windows上,我的主要代码看起来像这样(非常简化):

PeekMessage(...) // check for windows messages
switch (msg.message)
{
case WM_QUIT: ...;
case WM_LBUTTONDOWN: ...;
...
}
TranslateMessage(&msg);
DispatchMessage (&msg);

for (std::vector<CMyBackgroundThread*>::iterator it = mythreads.begin(); it != mythreads.end(); ++it)
{
  (*it)->processEvents();
}

... do other useful stuff like look if the window has resized and other stuff...

drawFrame(); // draw a frame using opengl
SwapBuffers(hDC); // swap backbuffer/frontbuffer

if (g_sleep > 0) Sleep(g_sleep);

我已经读过这不是mac方式。 mac方式是检查某种事件,例如屏幕的v-sync以呈现新帧。但正如你所看到的,我想做的不仅仅是渲染,我希望其他线程能够工作。有些线程需要比每1/60秒更快地调用一次。 (顺便说一句,我的线程基础结构是:线程将事件放入同步队列中,主线程调用processEvents来处理主线程内该同步队列中的项目。这用于网络内容,图像加载/处理内容,perlin噪声一代等等......)

我希望能够以类似的方式做到这一点,但很少有关于此的信息。我不介意将渲染放在v-sync事件上(我也会在windows上实现),但我希望对其余的代码有更多的响应。

以这种方式看待它:我很乐意能够处理其余部分,而GPU正在做任何事情,我不想等待v-sync然后开始做已经处理过的东西然后才开始向GPU发送内容。你明白我的意思吗?

如果我需要从完全不同的角度来看这个,请告诉我。

如果我需要阅读书籍/指南/教程/任何内容,请告诉我要阅读的内容!

我不是可可开发人员,我不是对象程序员,我的游戏引擎完全是用C ++编写的,但我知道我在xcode周围的方式足以让窗口出现并显示我在该窗口内绘制的内容。它只是不像我的Windows版本更新,因为我不知道如何正确。

更新:我甚至认为我需要某种循环,即使我想在垂直回扫上同步。 MacOSX文档中的OpenGL编程显示,这是通过设置SwapInterval来完成的。因此,如果我理解正确,在Mac上实时渲染时,我总是需要某种循环,使用swapInterval设置来降低功耗。这是真的吗?

2 个答案:

答案 0 :(得分:13)

虽然我不能成为世界上唯一一个想要实现这一目标的人,但似乎没有人愿意回答这个问题(不是指向你的堆栈流,只是指着知道它如何工作的mac开发人员)。 这就是为什么我想要改变这种典型行为并帮助那些寻找同样事物的人。换句话说:我找到了解决方案!我仍然需要进一步改进这段代码,但基本上就是这样!

1 /当你需要一个真正的循环时,就像在windows中一样,你需要自己处理它。所以,让cocoa构建你的标准代码,但是请使用main.m(如果需要,将其更改为.mm for c ++,这是因为我使用了c ++)并删除了唯一的代码行。您不会允许cocoa为您设置应用程序/窗口/视图。

2 / Cocoa通常会为您创建一个AutoreleasePool。它可以帮助您进行内存管理。现在,这将不再发生,因此您需要初始化它。主函数的第一行是:


    [[NSAutoreleasePool alloc] init];

3 /现在您需要设置应用程序委托。 XCode向导已经为您设置了AppDelegate类,因此您只需要使用该类。此外,向导已经设置了主菜单,可能称之为MainMenu.xib。开始时可以使用默认值。确保在#import&lt; Cocoa / Cocoa.h&gt;之后#import“YourAppDelegate.h”。线。然后在main函数中添加以下代码:


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

4 / Mac OS现在将知道应用程序的内容,将为其添加主菜单。无论如何,运行它不会做太多,因为还没有窗口。让我们创建它:


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

为了展示你在这里可以做什么,我添加了一个标题栏并启用了关闭按钮。你可以做更多,但我还需要先学习。

5 /现在,如果你运行它,你可能会很幸运,看到一个微秒的窗口,但你可能看不到任何东西,因为...程序还没有循环。让我们添加一个循环并听取传入的事件:


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

如果你运行它,你会看到窗口出现,只有按一个键才能看到它。当您按下该键时,应用程序将立即退出。

就是这样!但要注意......查看活动监视器。您将看到您的应用程序使用100%CPU。为什么?因为循环不会使CPU处于睡眠模式。这取决于你现在。你可以轻松自己做一个睡眠(10000);在此期间。这将做很多事,但不是最佳的。我可能会使用opengl的vertical-sync来确保CPU没有被过度使用,我也会等待来自我的线程的事件。也许我甚至会自己查看过去的时间,然后做一个计算好的睡眠时间来计算每帧的总时间,以便我有一个不错的帧速率。

有关Opengl的帮助:

1 /将cocoa.opengl框架添加到项目中。

2 /在[Window ...]之前:


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

3 /在[Window setDelegate:...]之后放置:


    [OGLContext setView:[Window contentView]];

4 /窗口处理后的某处,放:


    [OGLContext makeCurrentContext];

5 / [事件发布]之后,输入:


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

这将在我的mac book pro 2011上以3300的帧速率将您的窗口全部清除为红色。

同样,此代码不完整。当您单击关闭按钮并重新打开它时,它将不再起作用。我可能需要挂钩事件,我还不知道它们(经过进一步的研究,这些事情可以在AppDelegate中完成)。此外,还有很多其他事情要做/考虑。但这是一个开始。请随时纠正我/填写/...

如果我完全正确的话,如果没有人打败我的话,我可能会设置一个教程网页。

我希望这能帮到你们所有人!

答案 1 :(得分:1)

您可以使用Core Foundation runloop,并为您的“线程”提供事件源,以便运行循环将唤醒并调用其processEvents()方法。这是对轮询代码的改进,因为如果没有事件等待,运行循环将让线程休眠。

有关详细信息,请参阅CFRunLoopSourceCreate()CFRunLoopSourceSignal()CFRunLoopWakeUp()

请注意,如果您的应用程序是在Cocoa框架之上构建的,那么您可以(也可能应该)使用默认的NSRunLoop,但这不是问题,因为您可以获得基础Core Foundation { {1}}向其发送CFRunLoop消息。

相关问题