Cocoa: integre NSApplication en un mainloop c++ existente
integration (1)
bueno, después de todo esto me llevó mucho más tiempo del que esperaba, y me gustaría resumir las cosas que intenté y contarles las experiencias que tuve con ellos. Esperamos que esto ahorre a las personas que intentan integrar Cocoa en un mainloop existente mucho tiempo en el futuro. La primera función que encontré al buscar la materia discutida fue la función
nextEventMatchingMask:untilDate:inMode:dequeue:
pero como dije en la pregunta, mi principal problema con esto era que tendría que sondear constantemente nuevos eventos que desperdiciarían bastante tiempo de CPU. Así que probé los dos métodos siguientes para simplemente permitir que se llame a mi función mainloops update desde el mainloop NSApplications:
Publique un evento personalizado en NSApplication , sobrescriba NSApplications
sendEvent:
function y simplemente llame a mi mainloops update function desde allí. Similar a ésto: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 } }
Esto fue solo una buena idea en teoría porque si mi aplicación se actualizaba muy rápidamente (por ejemplo, debido a un temporizador que se activaba rápidamente), toda la cola de eventos de cacao dejaba de responder por completo porque agregaba muchos eventos personalizados. Así que no uses esto ...
Utilice performSelectorOnMainThread con un cacaoFunction que a su vez llama a mi función de actualización
[theAppNotifier performSelectorOnMainThread:@selector(runMyMainLoop) withObject:nil waitUntilDone:NO ];
Esto fue mucho mejor, la aplicación y el cacao EventLoop fue muy sensible. Si solo está tratando de lograr algo simple, le recomiendo seguir este camino, ya que es el más fácil de los que se proponen aquí. De todos modos, tuve muy poco control sobre el orden de las cosas que suceden con este enfoque (esto es crucial si tiene una aplicación de multiproceso), es decir, cuando mis temporizadores se activaron y harían un trabajo bastante largo, muchas veces se reprogramarían antes de cualquier nuevo mouse / la entrada del teclado podría agregarse a mi eventQueue y, por lo tanto, haría que toda la entrada fuera lenta. Activar la sincronización vertical en una ventana dibujada por un temporizador de repetición fue suficiente para que esto sucediera.
Después de todo, tuve que volver a
nextEventMatchingMask:untilDate:inMode:dequeue:
y después de algunas pruebas y errores, encontré una manera de hacerlo funcionar sin un sondeo constante. La estructura de mi bucle es similar a esto:void MyApp::loopFunc() { pollEvents(); processEventQueue(); updateWindows(); idle(); }
donde pollEvents y idle son las funciones importantes, básicamente uso algo similar a esto.
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); }
Para implementar el bloqueo dentro de la función idle () hice esto (no estoy seguro de si esto es bueno, ¡pero parece funcionar muy bien!):
void MyApp::idle() { m_bIsIdle = true; NSEvent * event = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:[NSDate distantFuture] inMode:NSDefaultRunLoopMode dequeue:NO]; m_bIsIdle = false; }
esto hace que el cacao espere hasta que haya un evento, si eso pasa, el ralentí simplemente se cierra y el loopfunc comienza de nuevo. Para activar la función inactiva si, por ejemplo, uno de mis temporizadores ( no uso los temporizadores de cacao ) se activa, una vez más utilizo un evento personalizado:
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]; }
Desde que borro toda la cola de eventos de cacao justo después, no tengo los mismos problemas que se describen en la sección 1 . Sin embargo, también hay algunos inconvenientes con este enfoque porque creo que no hace todo lo que
[NSApplication run]
está haciendo internamente, es decir, delegar aplicaciones como este:- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication*)theApplication { return YES; }
Parece que no funciona, de todos modos puedo vivir con eso, ya que puedes verificar fácilmente si la última ventana se cerró.
Sé que esta respuesta es bastante larga, pero también lo fue mi viaje para llegar allí. Espero que esto ayude a alguien y evite que las personas cometan los errores que yo cometí.
Sé que no soy el primero en tratar de usar Cocoa en OSX junto con un bucle principal de c / c ++ existente, pero realmente no me gustan las soluciones que encontré hasta ahora, así que se me ocurrió una idea diferente. me gustaria discutir La forma más común que encontré (en glut, glfw, SDL y QT, creo) es usar el sondeo para reemplazar el método de ejecución de aplicaciones NSA y procesar los eventos usted mismo con esto:
nextEventMatchingMask:untilDate:inMode:dequeue:
Esto tiene la gran desventaja de que la CPU nunca está realmente inactiva, ya que tiene que sondear todo el tiempo para comprobar si hay nuevos eventos, además, no es lo único que ocurre dentro de la función de ejecución de aplicaciones NSA, por lo que podría romper algunos detalles si Utilice este reemplazo.
Entonces, lo que me gustaría hacer es mantener el cacao RunLoop intacto. Imagina que tendrías tus propios métodos de temporizador implementados en c ++ que normalmente se administrarían y dispararían dentro de tu bucle principal (esto es solo una pequeña parte como ejemplo). Mi idea sería mover todas mis partes de bucle a un subproceso secundario (ya que la ejecución de NSApplication debe llamarse desde el subproceso principal hasta donde sé), y luego publicar eventos personalizados en mi versión derivada de NSApplication que los maneje adecuadamente dentro de su sendEvent: método. Por ejemplo, si mis temporizadores se midieran en mi c ++ loop fire, publicaría un evento personalizado a NSApplication que a su vez ejecuta la función loopFunc () de mi aplicación (que también se encuentra en el mainthread) que envía los eventos de manera apropiada a mi cadena de eventos c ++ . En primer lugar, ¿crees que esta sería una buena solución? En caso afirmativo, ¿cómo implementaría eso en cacao? Solo encontré este método dentro de la Referencia de NSEvent para publicar eventos personalizados de NSApplicationDefined:
otherEventWithType:location:modifierFlags:timestamp:windowNumber:context:subtype:data1:data2:
y luego usar algo como:
[NSApp postEvent:atStart:]
para notificar NSApplication.
Prefiero publicar un evento sin información sobre la ventana (en otherEventWithType), ¿puedo simplemente ignorar esa parte?
Entonces me imagino que se sobrescribiría la función sendEvent de NSApplications similar a esto:
- (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];
}
Lo siento por el largo post, pero esto me ha estado molestando bastante ya que no estoy muy contento con lo que encontré sobre este tema hasta ahora. ¿Es así como publicaría y verificaría un evento personalizado dentro de la aplicación NSA, y crees que este es un enfoque válido para integrar el cacao en un runloop existente sin encuesta?