Подтвердить что ты не робот

Cocoa цикл сообщений? (против цикла сообщений Windows)

При попытке перенести мой игровой движок на mac, я наткнулся на одну основную (но большую) проблему. В окнах мой основной код выглядит так (очень упрощен):

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

Я уже читал, что это не малый путь. Макинтош проверяет какое-то событие, такое как v-sync экрана, для рендеринга нового фрейма. Но, как вы можете видеть, я хочу сделать намного больше, чем только рендеринг, я хочу, чтобы другие потоки выполняли работу. Некоторые потоки нужно называть быстрее, чем каждые 1/60 секунды. (кстати, моя инфраструктура потоков: thread помещает событие в синхронизированную очередь, основной поток вызывает processEvents, который обрабатывает элементы в этой синхронизированной очереди в основном потоке. Это используется для сетевого материала, загрузки/обработки изображений, шума perlin поколение и т.д. и т.д.)

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

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

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

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

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

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

4b9b3361

Ответ 1

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

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

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


    [[NSAutoreleasePool alloc] init];

3/Теперь вам нужно настроить делегат приложения. Мастер XCode уже настроил для вас класс AppDelegate, поэтому вам просто нужно использовать его. Также мастер уже настроил ваше главное меню и, вероятно, назвал его MainMenu.xib. По умолчанию они подходят для начала работы. Убедитесь, что #import "YourAppDelegate.h" после #import < Cocoa/Cocoa.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 в спящий режим. Это вам сейчас. Вы можете облегчить себе и погладить (10000); в то время. Это будет делать много, но не оптимально. Я, вероятно, буду использовать вертикальную синхронизацию opengl, чтобы убедиться, что процессор не используется слишком сильно, и я также буду ждать событий из моих потоков. Возможно, я даже проверю пройденное время самостоятельно и сделаю расчетное умение, чтобы сделать общее время на кадр, чтобы у меня была достойная частота кадров.

Для справки по Opengl:

1/Добавьте проект cocoa.opengl в проект.

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/Где-то после обработки окна поместите:


    [OGLContext makeCurrentContext];

5/После [event release] поставьте:


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

Это очистит ваше окно до полного красного цвета с частотой кадров 3300 на моей программе mac book pro 2011.

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

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

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

Ответ 2

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

Подробнее см. CFRunLoopSourceCreate(), CFRunLoopSourceSignal() и CFRunLoopWakeUp().

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