¡Parece que estoy respondiendo mi propia pregunta aquí! No acepto mi respuesta hasta que haya podido probarla en un hardware real y obtenerla en la tienda de aplicaciones. Dicho esto, voy a mantener mi información más actualizada aquí, incluidas las opciones que no funcionó.
Idea # 1: Resulta que cada NSRunLoop es específico de subprocesos. Si creo un UIApplicationMain en un hilo separado, no recibe ningún mensaje. Como efecto secundario, esto hace que sea imposible determinar cuándo se ha terminado de inicializar, por lo que si hay algo que no sea seguro para los hilos, simplemente no funcionará. Es posible que pueda enviar un mensaje a través de los hilos para averiguar cuándo se ha terminado de inicializar, pero por ahora llamo a esto un callejón sin salida.
Idea # 2: UIApplicationMain hace muchas cosas sutiles. No estoy seguro de a qué se restringe, pero no pude hacer que nada funcionara sin la participación de UIApplicationMain. La idea n. ° 2 está en claro.
Idea # 3: La recepción de las señales del sistema operativo es importante: debe saber si hay una superposición de llamadas telefónicas o si está a punto de salir. Además de eso, algunos de los mensajes de configuración parecen vitales para iniciar la aplicación correctamente. No pude encontrar ningún método para mantener los mensajes enviados sin estar dentro de UIApplicationMain. Las únicas opciones que surgieron fueron NSRunLoop y CFRunLoop. Ninguno de los dos funcionó; los mensajes no llegaron como yo quería. Puede que no esté usando estos derechos, pero en cualquier caso, la Idea # 3 está fuera.
A estrenar idea # 4: Es posible usar setjmp/longjmp para corutinas falsas en C/C++. El truco consiste en establecer primero el puntero de la pila en algún valor que no bloquee nada importante, luego comenzar su segunda rutina, luego saltar hacia adelante y hacia atrás, simulando que tiene dos pilas. Las cosas se ponen un poco complicadas si su "segunda coroutine" decide regresar de su función principal, pero afortunadamente, UIApplicationMain nunca vuelve, así que esto no es un problema.
No sé si hay una manera de establecer el puntero de la pila de forma explícita en el hardware real, por ejemplo, en una porción de datos que asigné sobre la marcha. Afortunadamente, no importa. El iPhone tiene una pila de 1MB por defecto, que es lo suficientemente fácil para caber unas corutinas.
Lo que estoy haciendo actualmente es usar alloca() para empujar el puntero de la pila hacia delante en 768 kilobytes, y luego UIApplicationMain, luego usando setjmp/longjmp para rebotar entre mi "rutina de UI" y mi "rutina principal". Hasta ahora, esto está funcionando.
Advertencias:
Es imposible saber cuándo la "rutina de interfaz de usuario" no tiene mensajes de manejar, y cuando no tiene mensajes de manejar, se acaba de bloquear de forma indefinida hasta que ya no es el caso. Estoy resolviendo esto haciendo un temporizador que se dispara cada 0.1 milisegundos. Cada vez que se dispara el temporizador, abandono mi "rutina principal", hago un bucle de un solo juego, luego vuelvo a la "rutina UI" para otro tic del temporizador. La lectura de la documentación indica que no acumulará "llamadas de temporizador" indefinidamente. Parece que recibo el mensaje de "finalización" de forma apropiada, aunque no he podido probarlo a fondo todavía, y no he probado ningún otro mensaje importante. (Afortunadamente, solo hay cuatro mensajes en total, y uno de ellos está relacionado con la configuración.)
La mayoría de los sistemas operativos modernos no asignarán toda la pila a la vez. El iPhone es probablemente uno de estos. Lo que no sé es si golpear el puntero de la pila 3/4 de un meg adelante asignará todo "detrás", por así decirlo. Si es así, puedo estar desperdiciando 3/4 de un mego de RAM, lo cual, en el iPhone, es significativo. Esto podría ser manejado al empujar el puntero hacia delante una cantidad menor, pero esto realmente es cortejar el desastre del tamaño de la pila: efectivamente limita tu stack por más lejos que toques el puntero, y tendrás que resolverlo con anticipación. Algunos datos centinelas en la pila, junto con un buen monitoreo y un sistema de registro para problemas de tamaño de pila, probablemente puedan resolver esto, pero es un problema no trivial. (Alternativamente, si puedo encontrar la manera de ensuciar con el puntero de la pila directamente en el hardware nativo, puedo malloc()/new [] algunos kilobytes, apuntar el puntero de la pila y usarlo como mi nueva pila. Tendré que averiguar cuánto espacio necesita pero dudo que sea mucho, teniendo en cuenta que no está haciendo demasiado.)
Esto no se ha probado actualmente en el hardware real (le daría una semana o dos , Tengo otro proyecto para terminar primero.)
No tengo idea si Apple descubrirá lo que estoy haciendo y le pegará una pegatina gigante RECHAZADA cuando intente enviarla a la tienda de aplicaciones. Esto es, digamos, un poco fuera de sus intenciones para la API. Dedos cruzados.
Mantendré esta publicación actualizada, y la aceptaré oficialmente una vez que haya verificado que, ya sabes, funciona.
Actualización tardía: Me distraje por una variedad de otras cosas. Desde entonces, he tenido algunos cambios que me hacen mucho menos interesado en el desarrollo de Apple. Mi enfoque actual no mostró signos de no funcionando, pero en realidad no tengo la motivación para seguir desarrollándolo. ¡Lo siento! Si alguna vez cambio de parecer, lo actualizaré más, pero Outlook no es tan bueno.
Más o menos, sí. Más específicamente, prefiero dejar al usuario con control total sobre el ciclo del juego: mi motor construido sobre la biblioteca en cuestión ya hace algunas cosas poco convencionales en Windows y OSX, y me gustaría preservar esa originalidad. Honestamente, me gustaría brindar esto, no solo con el propósito de permitir main() sin modificaciones (aunque ciertamente es un buen beneficio), sino porque creo que es un diseño de API mejor y más útil para juegos que el evento -Modificado diseño iPhone parece gustarle. A veces solo necesitas hacer cosas locas. – ZorbaTHut