cocoa消息循环? (与Windows消息循环)

在试图将我的游戏引擎移植到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处理主线程中同步队列中的项目。这用于networking内容,图像加载/处理内容,perlin噪声代等等…等等)

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

看看这样的方式:我希望能够在GPU正在做的时候处理剩下的东西,我不想等待v-sync然后开始执行那些应该已经被处理的东西,然后才开始发送东西到GPU。 你明白我的意思吗?

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

如果我需要阅读书籍/指南/教程/任何这个,请告诉我该读什么!

我不是cocoa开发人员,我不是对象-c程序员,我的游戏引擎完全是C ++,但是我知道我的方式足以让一个窗口出现,并显示我在窗口中绘制的内容。 它只是不像我的Windows版本更新,因为我不知道如何得到它的权利。

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

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

1 /当你需要一个真正的自己的循环,就像在窗口,你需要自己照顾它。 因此,让可可建立你的标准代码,但是把main.m(如果需要,把它改成.mm,在这个例子中是因为我使用了c ++),并删除唯一的一行代码。 你不会允许可可为你设置你的应用程序/窗口/视图。

2 / Cocoa通常会为您创建一个AutoreleasePool。 它可以帮助你记忆管理。 现在,这将不再发生,所以你需要初始化它。 您的主要功能的第一行将是:

 [[NSAutoreleasePool alloc] init]; 

3 /现在你需要设置你的应用程序委托。 XCode向导已经为你设置了一个AppDelegate类,所以你只需要使用它。 此外,该向导已经设置了主菜单,可能将其称为MainMenu.xib。 默认的是很好的开始。 确保#import <Cocoa / Cocoa.h>行之后#import“YourAppDelegate.h”。 然后在主函数中添加下面的代码:

 [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的垂直同步,以确保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]; 

这将清除你的窗口,在我的MacBook Pro 2011年3300帧率完全红色。

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

如果我完全正确的话,那么如果没有人能够胜任我的话,我可能会为此设置一个教程网页。

我希望这可以帮助大家!

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

有关更多信息,请参阅CFRunLoopSourceCreate()CFRunLoopSourceSignal()CFRunLoopWakeUp()

请注意,如果您的应用程序构建在Cocoa框架之上,您可以(也可能应该)使用默认的NSRunLoop ,但这不是问题,因为您可以通过向其发送-getCFRunLoop消息来获取底层Core Foundation CFRunLoop