Какао цикл сообщений? (против цикла сообщений Windows) - PullRequest
13 голосов
/ 11 июля 2011

Пытаясь портировать свой игровой движок на 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-синхронизацию экрана, для рендеринга нового кадра.Но, как вы видите, я хочу сделать гораздо больше, чем просто рендеринг, я хочу, чтобы другие потоки работали.Некоторые потоки должны вызываться быстрее, чем каждые 1/60 секунды.(кстати, моя потоковая инфраструктура: поток помещает событие в синхронизированную очередь, основной поток вызывает processEvents, который обрабатывает элементы в этой синхронизированной очереди в основном потоке. Это используется для сетевого содержимого, загрузки / обработки изображения, перлин-шумапоколение и т. д. и т. д.)

Я бы хотел иметь возможность сделать это подобным образом, но очень мало информации об этом доступно.Я не против поставить рендеринг на событие v-sync (я буду реализовывать это и в Windows), но я бы хотел, чтобы остальная часть кода была немного более отзывчивой.

Посмотрите на этотаким образом: я хотел бы иметь возможность обрабатывать все остальное, пока GPU что-то делает, я не хочу ждать v-sync, чтобы потом начать делать вещи, которые уже должны были быть обработаны, чтобы только потом начать посылать вещи вГПУ.Вы понимаете, о чем я?

Если мне нужно взглянуть на это с совершенно иной точки зрения, пожалуйста, скажите мне.

Если мне нужно читать книги / руководства / учебные пособия / что-нибудьдля этого, пожалуйста, скажите мне, что читать!

Я не разработчик какао, я не программист объектного c, мой игровой движок полностью на C ++, но я достаточно разбираюсь в xcode, чтобы сделатьпоявится окно и покажет, что я рисую внутри этого окна.Он просто не обновляется, как моя версия для Windows, потому что я не знаю, как это сделать правильно.

Обновление: я даже считаю, что мне нужен какой-то цикл, даже если я хочу синхронизироваться по вертикалиобратный ход.Программирование OpenGL на MacOSX документация показывает, что это делается путем установки SwapInterval.Так что, если я правильно понимаю, мне всегда понадобится какой-то цикл при рендеринге в реальном времени на Mac, используя настройку swapInterval для снижения энергопотребления.Это правда?

Ответы [ 2 ]

13 голосов
/ 12 июля 2011

Хотя я не могу быть единственным человеком в мире, пытающимся достичь этого, похоже, никто не хочет на это отвечать (не обращайте на вас внимание, ребята из stackoverflow, просто указываю на разработчиков Mac, которые знают, как это работает). Вот почему я хочу изменить это типичное поведение и помочь тем, кто ищет то же самое. Другими словами: я нашел решение! Мне все еще нужно улучшить этот код, но это в основном все!

1 / Когда вам нужен настоящий собственный цикл, как в Windows, вам нужно позаботиться об этом самостоятельно. Итак, позвольте какао построить ваш стандартный код, но возьмите main.m (при необходимости измените его на .mm для c ++, как в этом примере, потому что я использовал c ++) и удалите одну-единственную строку кода. Вы не позволите какао настроить ваше приложение / окно / представление для вас.

2 / Какао обычно создает AutoreleasePool для вас. Это поможет вам с управлением памятью. Теперь этого больше не будет, поэтому вам нужно его инициализировать. Ваша первая строка в основной функции будет:


    [[NSAutoreleasePool alloc] init];

3 / Теперь вам нужно настроить делегата приложения. Мастер XCode уже настроил класс AppDelegate для вас, так что вам просто нужно его использовать. Также мастер уже настроил ваше главное меню и, вероятно, назвал его MainMenu.xib. По умолчанию хорошо, чтобы начать. Убедитесь, что вы #import "YourAppDelegate.h" после строки #import . Затем добавьте следующий код в вашу основную функцию:


    [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. Зачем? Потому что цикл не переводит процессор в спящий режим. Теперь тебе решать. Вы можете облегчить себе задачу и уснуть (10000); в то время. Это будет много, но не оптимально. Я, вероятно, буду использовать вертикальную синхронизацию opengl, чтобы убедиться, что процессор не используется чрезмерно, и я также буду ждать событий из моих потоков. Может быть, я даже сам проверил прошедшее время и подсчитал время, чтобы сделать общее время на кадр, чтобы у меня была приличная частота кадров.

Для помощи с 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];

Это очистит ваше окно до полностью красного на частоте кадров 3300 на моем MacBook Pro 2011.

Опять же, этот код не является полным. Когда вы нажимаете на кнопку закрытия и снова открываете ее, она больше не будет работать. Мне, вероятно, нужно перехватить события для этого, и я их пока не знаю (после дальнейших исследований эти вещи можно сделать в AppDelegate). Кроме того, вероятно, есть много других вещей, которые можно сделать / рассмотреть. Но это только начало. Пожалуйста, не стесняйтесь поправлять меня / заполните меня /...

Если я все сделаю правильно, я мог бы создать для этого веб-страницу учебника, если меня никто не побьет.

Надеюсь, это поможет вам всем!

1 голос
/ 11 июля 2011

Вместо этого вы можете использовать базовый цикл запуска Core Foundation и иметь источники событий для ваших «потоков», чтобы цикл запуска проснулся и вызвал их методы processEvents(). Это улучшение по сравнению с вашим кодом опроса, поскольку цикл выполнения позволит потоку спать, если нет ожидающих событий.

См. CFRunLoopSourceCreate(), CFRunLoopSourceSignal() и CFRunLoopWakeUp() для получения дополнительной информации.

Обратите внимание, что если ваше приложение построено поверх платформы Cocoa, вы можете (и, вероятно, должны) использовать значение по умолчанию NSRunLoop, но это не проблема, поскольку вы можете получить базовую базовую основу CFRunLoop отправив ему сообщение -getCFRunLoop.

...