Какао: интегрировать NSApplication в существующий основной цикл C++
Я знаю, что я не первый, кто пытается использовать 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 ответ
Ладно, после всего этого у меня ушло больше времени, чем я ожидал, и я хотел бы рассказать о том, что я пробовал, и рассказать, какой у меня был опыт с ними. Это, мы надеемся, спасет людей, пытающихся интегрировать Какао в существующий основной цикл много времени в будущем. Первой функцией, которую я нашел при поиске обсуждаемого вопроса, была функция
nextEventMatchingMask:untilDate:inMode:dequeue:
но, как я уже сказал в этом вопросе, моей главной проблемой было то, что мне приходилось постоянно опрашивать новые события, которые бы тратили довольно много времени процессора. Поэтому я попробовал следующие два метода, чтобы просто позволить моей функции обновления mainloops вызываться из mainloop NSApplications:
Опубликовать пользовательское событие в 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 } }
Это было только хорошей идеей в теории, потому что, если мое приложение обновлялось очень быстро (например, из-за быстрого срабатывания таймера), вся очередь событий какао полностью перестала отвечать, потому что я добавил так много пользовательских событий. Так что не используйте это...
Используйте executeSelectorOnMainThread с функцией Cocoa, которая, в свою очередь, вызывает мою функцию обновления.
[theAppNotifier performSelectorOnMainThread:@selector(runMyMainLoop) withObject:nil waitUntilDone:NO ];
Это было намного лучше, приложение и какао EventLoop были очень отзывчивыми. Если вы только пытаетесь достичь чего-то простого, я бы порекомендовал вам пойти по этому пути, так как он самый простой из предложенных здесь. В любом случае, у меня было очень мало контроля над порядком вещей, происходящих с этим подходом (это важно, если у вас есть многопоточное приложение), т.е. когда мои таймеры срабатывали и выполняли довольно долгую работу, часто они перепланировали перед любой новой мышью / ввод с клавиатуры может быть добавлен к моему eventQueue и, таким образом, весь ввод будет медленным. Включите вертикальную синхронизацию на окне, которое было нарисовано повторяющимся таймером, было достаточно, чтобы это произошло.
В конце концов я должен был вернуться
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; }
кажется, не работает, в любом случае, я могу с этим смириться, так как вы можете легко проверить, закрыто ли последнее окно.
Я знаю, что этот ответ довольно длинный, но так же и мой путь к нему. Я надеюсь, что это помогает кому-то и мешает людям делать ошибки, которые я сделал.