Какао: интегрировать NSApplication в существующий основной цикл c ++ - PullRequest
31 голосов
/ 18 июля 2011

Я знаю, что я не первый, кто пытается использовать Cocoa на OSX вместе с существующим основным циклом c / c ++, но мне не очень нравятся решения, с которыми я столкнулся до сих пор, поэтому я придумал другое Идея, которую я хотел бы обсудить. Самый распространенный способ, который я нашел (в glut, glfw, SDL, а также QT, я думаю), это использовать опрос для замены метода запуска NSApplications и обрабатывать события самостоятельно следующим образом:

nextEventMatchingMask:untilDate:inMode:dequeue:

Это имеет большой недостаток в том, что процессор никогда по-настоящему не работает, так как вам нужно все время опрашивать, чтобы проверить, есть ли какие-либо новые события, кроме того, это не единственная вещь, которая происходит внутри функции запуска NSApplications, поэтому она может сломать некоторые подробности, если вы используете эту замену.

Итак, я хотел бы сохранить неповрежденным цикл запуска какао. Представьте, что у вас есть собственные методы таймера, реализованные в c ++, которые обычно управляются и запускаются внутри вашего основного цикла (это лишь небольшая часть в качестве примера). Моя идея состояла бы в том, чтобы переместить все мои циклические фрагменты во вторичный поток (так как запуск NSApplication необходимо вызывать из основного потока, насколько я знаю), а затем публиковать пользовательские события в моей производной версии NSApplication, которая обрабатывает их соответствующим образом внутри своего sendEvent: метод. Например, если мои таймеры были измерены в моем цикле c ++, я бы отправил пользовательское событие в NSApplication, которое, в свою очередь, запускает функцию loopFunc () моего приложения (также находящейся в mainthread), которая соответствующим образом отправляет события по моей цепочке событий c ++. , Итак, прежде всего, вы думаете, это было бы хорошим решением? Если да, как бы вы реализовали это в какао, я нашел этот метод только в NSEvent Reference для публикации пользовательских событий NSApplicationDefined:

otherEventWithType:location:modifierFlags:timestamp:windowNumber:context:subtype:data1:data2:

, а затем использовать что-то вроде:

[NSApp postEvent:atStart:]

для уведомления NSApplication.

Я бы предпочел опубликовать событие без какой-либо информации об окне (в otherEventWithType), могу ли я просто проигнорировать эту часть?

Тогда я бы хотел переписать функцию sendEvent NSApplications, подобную этой:

    - (void)sendEvent:(NSEvent *)event
{
    //this is my custom event that simply tells NSApplication 
    //that my app needs an update
    if( [event type] == NSApplicationDefined)
    {
        myCppAppPtr->loopFunc(); //only iterates once
    }
        //transform cocoa events into my own input events
    else if( [event type] == NSLeftMouseDown)
    {
             ...
             myCppAppPtr->loopFunc(); //also run the loopFunc to propagate input events
    }
        ...

    //dont break the cocoa event chain
    [super sendEvent:event];

}

извините за длинный пост, но это меня немного беспокоит, так как я действительно не доволен тем, что я нашел по этому вопросу до сих пор. Так ли я размещал и проверял пользовательское событие внутри NSApplication, и считаете ли вы, что это правильный подход для интеграции какао в существующий цикл запуска без опроса?

1 Ответ

27 голосов
/ 12 августа 2011

хорошо, после всего этого у меня ушло гораздо больше времени, чем я ожидал, и я хотел бы рассказать о том, что я пробовал, и рассказать, какой у меня был опыт с ними. Это, мы надеемся, спасет людей, пытающихся интегрировать Какао в существующий основной цикл много времени в будущем. Первой функцией, которую я нашел при поиске обсуждаемого вопроса, была функция

nextEventMatchingMask:untilDate:inMode:dequeue:

но, как я уже сказал в этом вопросе, моя главная проблема с этим заключалась в том, что мне приходилось постоянно опрашивать новые события, которые бы тратили довольно много времени процессора. Поэтому я попробовал следующие два метода, чтобы просто позволить моей функции обновления mainloops вызываться из mainloop NSApplications:

  1. Опубликовать пользовательское событие в NSApplication , перезаписать NSApplications sendEvent: функция и просто вызвать мою функцию обновления mainloops оттуда. Похоже на это:

    NSEvent* event = [NSEvent otherEventWithType: NSApplicationDefined
                                                         location: NSMakePoint(0,0)
                                                   modifierFlags: 0
                                                       timestamp: 0.0
                                                    windowNumber: 0
                                                         context: nil
                                                         subtype: 0
                                                           data1: 0
                                                           data2: 0];
                    [NSApp postEvent: event atStart: YES];
    
    
    //the send event function of my overwritten NSApplication
       - (void)sendEvent:(NSEvent *)event
    {
        //this is my custom event that simply tells NSApplication 
        //that my app needs an update
        if( [event type] == NSApplicationDefined)
        {
            myCppAppPtr->loopFunc(); //only iterates once
        }
    }
    

    Это была только хорошая идея в теории, потому что, если мое приложение обновится очень быстро (например, из-за быстрого срабатывания таймера), весь очередь событий какао стала полностью безразличной, потому что я добавил так много пользовательских событий. Так что не используйте это ...

  2. Используйте executeSelectorOnMainThread с функцией какао, которая в очередь вызывает мою функцию обновления

    [theAppNotifier
    performSelectorOnMainThread:@selector(runMyMainLoop) withObject:nil
    waitUntilDone:NO ];
    

    Это было намного лучше, приложение и какао EventLoop были очень отзывчивый. Если бы вы только пытались достичь чего-то простого, я бы рекомендуем идти по этому маршруту, так как это самый простой из них предложил здесь. В любом случае у меня было очень мало контроля над порядком что происходит с этим подходом (это важно, если у вас есть многопоточное приложение), т.е. когда мои таймеры сработали и долгая работа, часто они перенесли бы перед любым новым ввод мыши / клавиатуры может быть добавлен в мое eventQueue и, таким образом, будет сделать весь ввод вялым. Включить вертикальную синхронизацию в окне этого было достаточно для повторения таймера.

  3. В конце концов мне пришлось вернуться к nextEventMatchingMask:untilDate:inMode:dequeue:, и после некоторых проб и ошибок я действительно нашел способ заставить его работать без постоянного опроса. Структура моего цикла похожа на это:

    void MyApp::loopFunc()
    {
        pollEvents();
        processEventQueue();
        updateWindows();
        idle();
    }
    

    где pollEvents и idle являются важными функциями, в основном я использую что-то похожее на это.

    void MyApp::pollEvents()
    {
        NSEvent * event;
    
        do
        {
            event = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:[NSDate distantPast] inMode:NSDefaultRunLoopMode dequeue:YES];
    
    
                //Convert the cocoa events to something useful here and add them to your own event queue
    
            [NSApp sendEvent: event];
        }
        while(event != nil);
    }
    

    Чтобы реализовать блокировку внутри функции idle (), я сделал это (не уверен, что это хорошо, но, похоже, работает отлично!):

    void MyApp::idle()
    {
        m_bIsIdle = true;
        NSEvent * event = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:[NSDate distantFuture] inMode:NSDefaultRunLoopMode dequeue:NO];
        m_bIsIdle = false;
    }
    

    это заставляет какао ждать, пока не произойдет событие, если это произойдет, то простоя просто завершается, и loopfunc запускается снова. Чтобы разбудить функцию ожидания, если сработает один из моих таймеров ( я не использую таймеры какао ), я снова использую пользовательское событие:

    void MyApp::wakeUp()
    {
        m_bIsIdle = false;
    
        //this makes sure we wake up cocoas run loop
        NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
        NSEvent* event = [NSEvent otherEventWithType: NSApplicationDefined
                                            location: NSMakePoint(0,0)
                                       modifierFlags: 0
                                           timestamp: 0.0
                                        windowNumber: 0
                                             context: nil
                                             subtype: 0
                                               data1: 0
                                               data2: 0];
        [NSApp postEvent: event atStart: YES];
        [pool release];
    }
    

    Так как сразу после этого я очищаю всю очередь событий какао, у меня не возникает проблем, описанных в раздел 1 . Однако у этого подхода есть некоторые недостатки, так как я думаю, что он не делает всего, что [NSApplication run] делает внутри, то есть приложение делегирует такие вещи:

    - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication*)theApplication
    {
          return YES;
    }
    

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

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

...