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

Cocoa: интегрировать NSApplication в существующий С++ mainloop

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

nextEventMatchingMask:untilDate:inMode:dequeue:

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

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

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

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

[NSApp postEvent:atStart:]

чтобы уведомить NSApplication.

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

Тогда я мог бы переписать функцию sendAvent 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, и считаете ли вы, что это допустимый подход для интеграции Cocoa в существующую runloop без опроса?

4b9b3361

Ответ 1

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

nextEventMatchingMask:untilDate:inMode:dequeue:

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

  • Поместить пользовательское событие в NSApplication, перезаписать NSApplications sendEvent: и просто вызвать функцию обновления mainlops оттуда. Аналогично этому:

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

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

  • Используйте функцию performSelectorOnMainThread с функцией cocoa, которая в переверните вызовы моей функции обновления

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

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

  • В конце концов мне пришлось вернуться к 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;
    }
    

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

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

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

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

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

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