¿Cómo se comporta HOpenGL con respecto a otros subprocesos y TChans en Haskell?

Estoy haciendo un trabajo de prueba de concepto para un videojuego bastante complejo que me gustaría escribir en Haskell usando la biblioteca HOpenGL. Comencé escribiendo un módulo que implementa comunicación basada en eventos cliente-servidor. Mi problema aparece cuando intento conectarlo a un programa simple para dibujar clics en la pantalla.

La biblioteca de eventos utiliza una lista de TChans convertidos en una cola de prioridad para la comunicación. Devuelve una cola de "salida" y una cola de "entrada" correspondiente a los mensajes vinculados al servidor y al cliente. Los eventos de envío y recepción se realizan en subprocesos separados utilizando forkIO. La prueba de la biblioteca de eventos sin la parte OpenGL muestra que se está comunicando correctamente. Aquí está el código que usé para probarlo:

-- Client connects to server at localhost with 3 priorities in the priority queue
do { (outQueue, inQueue) <- client Nothing 3
   -- send 'Click' events until terminated, the server responds with the coords negated
   ; mapM_ (\x -> atomically $ writeThing outQueue (lookupPriority x) x)
           (repeat (Click (fromIntegral 2) (fromIntegral 4)))
   }

Esto produce el resultado esperado, es decir, una gran cantidad de eventos de envío y recepción. No creo que el problema esté en la biblioteca de manejo de eventos.

La parte OpenGL del código verifica la cola entrante en busca de nuevos eventos en el displayCallback y luego llama al controlador asociado del evento. Puedo obtener un evento (el evento Init, que simplemente borra la pantalla) para que sea capturado por displayCallback, pero después de eso no se detecta nada. Aquí está el código relevante:

atomically $ PQ.writeThing inqueue (Events.lookupPriority Events.Init) Events.Init
    GLUT.mainLoop

render pqueue =
    do  event <- atomically $
            do  e <- PQ.getThing pqueue
                case e of
                    Nothing -> retry
                    Just event -> return event
        putStrLn $ "Got event"
        (Events.lookupHandler event Events.Client) event
        GL.flush
        GLUT.swapBuffers

Así que mis teorías sobre por qué esto está sucediendo son:

La devolución de llamada de pantalla está bloqueando todos los subprocesos de envío y recepción en el reintento.Las colas no se devuelven correctamente, por lo que las colas que lee el cliente son diferentes a las que lee la parte OpenGL.

¿Hay otras razones por las que esto podría estar pasando?

El código completo para esto es demasiado largo para publicar aquí, aunque no demasiado largo (5 archivos de menos de 100 líneas cada uno), sin embargo, todo está en GitHubaquí.

Edición 1:
El cliente se ejecuta desde dentro de la función principal en el código HOpenGL así:

main =
    do  args <- getArgs
        let ip = args !! 0
        let priorities = args !! 1
        (progname, _) <- GLUT.getArgsAndInitialize
        -- Run the client here and bind the queues to use for communication
        (outqueue, inqueue) <- Client.client (Just ip) priorities
        GLUT.createWindow "Hello World"
        GLUT.initialDisplayMode $= [GLUT.DoubleBuffered, GLUT.RGBAMode]
        GLUT.keyboardMouseCallback $= Just (keyboardMouse outqueue)
        GLUT.displayCallback $= render inqueue
        PQ.writeThing inqueue (Events.lookupPriority Events.Init) Events.Init
        GLUT.mainLoop

La única bandera que paso a GHC cuando compilo el código es-package GLUT.

Edición 2:
Limpié un poco el código de Github. Eliminé acceptInput, ya que en realidad no estaba haciendo nada y el código del Cliente no debía escuchar los eventos propios de todos modos, por eso está devolviendo las colas.

Edición 3:
Estoy aclarando un poco mi pregunta. Tomé lo que aprendí de @Shang y @Laar y corrí con eso. Cambié los hilos en Client.hs para usar forkOS en lugar de forkIO (y lo usé -trabajo en ghc), y parece que los eventos se están comunicando con éxito, sin embargo, no se reciben en la devolución de llamada de pantalla. También traté de llamarpostRedisplay al final de la devolución de llamada de la pantalla, pero creo que nunca se llama (porque creo que el reintento está bloqueando todo el subproceso de OpenGL).

¿El reintento en la devolución de llamada de pantalla bloquearía todo el subproceso de OpenGL? Si lo hace, ¿sería seguro dividir la devolución de llamada de la pantalla en un nuevo hilo? No me imagino que lo haría, ya que existe la posibilidad de que varias cosas puedan estar tratando de dibujar en la pantalla al mismo tiempo, pero podría ser capaz de manejar eso con un candado. Otra solución sería convertir ellookupHandler Función para devolver una función envuelta en unaMaybe, y no hagas nada si no hay ningún evento. Siento que eso sería menos que ideal, ya que esencialmente tendría un bucle ocupado que era algo que intentaba evitar.

Edición 4:
Olvidé mencionar que usé -trabajo en ghc cuando hice el forkOS.

Edición 5:
Fui e hice una prueba de mi teoría de que el reintento en la función de renderizado (mostrar devolución de llamada) estaba bloqueando todo OpenGL. Reescribí la función de renderizado para que no se bloquee más, y funcionó como quería que funcionara. Un clic en la pantalla da dos puntos, uno desde el servidor y desde el clic original. Aquí está el código para la nueva función de renderización (nota: esno en Github):

render pqueue =
    do  event <- atomically $ PQ.getThing pqueue
        case (Events.lookupHandler event Events.Client) of
            Nothing -> return ()
            Just handler -> 
                do  let e = case event of {Just e' -> e'}
                    handler e
                    return ()
        GL.flush
        GLUT.swapBuffers
        GLUT.postRedisplay Nothing

Lo probé con y sin el postRedisplay, y solo funciona con él. El problema ahora es que esto fija la CPU al 100% porque es un bucle ocupado. En la Edición 4, propuse que el hilo fuera de la pantalla de devolución de llamada. Todavía estoy pensando en una manera de hacer eso.

Una nota ya que no la he mencionado aún. Cualquiera que busque construir / ejecutar el código debería hacerlo así:

$ ghc -threaded -package GLUT helloworldOGL.hs -o helloworldOGL
$ ghc server.hs -o server
-- one or the other, I usually do 0.0.0.0
$ ./server "localhost" 3
$ ./server "0.0.0.0" 3
$ ./helloworldOGL "localhost" 3

Edición 6:Solución
¡Una solución! Siguiendo los hilos, decidí crear un hilo en el código OpenGL que verificaba los eventos, bloqueando si no hay alguno, y luego llamando al controlador seguido de postRedisplay. Aquí está:

checkEvents pqueue = forever $
    do  event <- atomically $
            do  e <- PQ.getThing pqueue
                case e of
                    Nothing -> retry
                    Just event -> return event
        putStrLn $ "Got event"
        (Events.lookupHandler event Events.Client) event
        GLUT.postRedisplay Nothing

La devolución de llamada de pantalla es simplemente:

render = GLUT.swapBuffers

Y funciona, no fija la CPU al 100% y los eventos se manejan rápidamente. Estoy publicando esto aquí porque no podría haberlo hecho sin las otras respuestas y me siento mal al tomar la reputación cuando las respuestas fueron muy útiles, por lo que estoy aceptando la respuesta de @ Laar ya que tiene la Rep más baja.

Respuestas a la pregunta(2)

Su respuesta a la pregunta