Estoy haciendo un trabajo de prueba de concepto para un videojuego bastante complejo que me gustaría escribir en Haskell usando la biblioteca HOpenGL. Empecé escribiendo un módulo que implementa comunicación basada en eventos cliente-servidor. Mi problema aparece cuando trato de conectarlo a un programa simple para dibujar clics en la pantalla.¿Cómo se comporta HOpenGL con respecto a otros hilos y TChans en Haskell?
La biblioteca de eventos usa una lista de TChans en una cola de prioridad para la comunicación. Devuelve una cola de "salida" y una cola de "entrada" correspondientes a mensajes vinculados al servidor y al cliente. Los eventos de envío y recepción se realizan en hilos separados usando forkIO. La prueba de la biblioteca de eventos sin el componente OpenGL muestra que se está comunicando correctamente. Aquí está el código que utiliza para probar que:
-- 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 porción entera de envío y recepción de eventos. No creo que el problema radique en la biblioteca de manejo de eventos.
La parte OpenGL del código comprueba la cola entrante para detectar nuevos eventos en displayCallback y luego llama al controlador asociado del evento. Puedo obtener un evento (el evento Init, que simplemente borra la pantalla) para ser capturado por displayCallback, pero después de eso no se detecta nada. Aquí está el código correspondiente:
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 acerca de por qué ocurre esto son:
- La pantalla de devolución de llamada está bloqueando todos los hilos de envío y recepción en el reintento.
- Las colas no se devuelven correctamente, de modo que las colas que lee el cliente son diferentes de las que lee la parte OpenGL.
¿Hay alguna otra razón por la 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 GitHub here.
Edición 1:
El cliente se ejecuta desde 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 pase a GHC cuando compilo el código es -package GLUT
.
Edición 2:
me limpió el código en Github un poco. Quité acceptInput ya que no estaba haciendo nada realmente y no se supone que el código del Cliente esté escuchando sus propios eventos de todos modos, es por eso que devuelve las colas.
Datos 3:
estoy aclarar mi pregunta un poco. Tomé lo que aprendí de @Shang y @Laar, y de alguna forma lo hice. Cambié los hilos en Client.hs para usar forkOS en vez de forkIO (y usé -threaded en ghc), y parece que los eventos se están comunicando con éxito, sin embargo, no se están recibiendo en la devolución de llamada de la pantalla. También intenté llamar al postRedisplay
al final de la devolución de llamada de la pantalla, pero no creo que alguna vez se llame (porque creo que el reintento está bloqueando todo el hilo de OpenGL).
¿El reintento en la devolución de llamada de la pantalla bloqueará todo el hilo de OpenGL? Si lo hace, ¿sería seguro pasar la devolución de llamada a un nuevo hilo? No me imagino que lo haría, ya que existe la posibilidad de que varias cosas intenten dibujar en la pantalla al mismo tiempo, pero podría manejarlo con un candado. Otra solución sería convertir la función lookupHandler
para devolver una función envuelta en un Maybe
, y simplemente no hacer nada si no hay ningún evento. Siento que eso sería menos que ideal ya que básicamente tendría un ciclo ocupado, que era algo que estaba tratando de evitar.
Editar 4:
olvidó mencionar utilicé -enhebrado en GHC cuando hice las forkOS.
Editar 5:
que fueron e hicieron una prueba de mi teoría de que el reintento en la función de render (devolución de llamada pantalla) estaba bloqueando todos OpenGL. Reescribí la función de renderizado para que no se bloqueara más y funcionó como quería que funcionara. Un clic en la pantalla da dos puntos, uno del servidor y del clic original. Aquí está el código para la nueva función de render (nota: no es 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
he probado con y sin la postRedisplay, y sólo funciona con ella. El problema ahora es que esto bloquea la CPU al 100% porque es un bucle ocupado. En Edit 4, propuse enrutar la devolución de llamada de la pantalla. Todavía estoy pensando en una forma de hacerlo.
Una nota ya que no lo he mencionado todavía. Alguien que busque construir/ejecutar el código debe hacerlo de esta manera:
$ 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
Editar 6: Solución
Una solución! Siguiendo con los hilos, decidí hacer un hilo en el código de OpenGL que buscaba eventos, bloqueando si no había ninguno, 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 pantalla de devolución de llamada es simplemente:
render = GLUT.swapBuffers
Y funciona, no clavija de la CPU para el 100% y los eventos se maneje con rapidez. Estoy publicando esto aquí porque no podría haberlo hecho sin las otras respuestas y me siento mal tomando el representante cuando las respuestas fueron muy útiles, así que estoy aceptando la respuesta de @ Laar, ya que tiene la menor Rep.
¿Qué usas para crear y ejecutar el cliente, y especialmente qué banderas pasas a ghc/runhaskell/ghci? – Laar