2012-07-12 8 views
9

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.

+0

¿Qué usas para crear y ejecutar el cliente, y especialmente qué banderas pasas a ghc/runhaskell/ghci? – Laar

Respuesta

4

Una causa posible podría ser el uso de roscado.

OpenGL utiliza el almacenamiento local de subprocesos para su contexto. Por lo tanto, todas las llamadas que usan OpenGL deben estar hechas del mismo hilo del sistema operativo. HOpenGL (y OpenGLRaw también) es un enlace relativamente simple en torno a la biblioteca de OpenGL y no proporciona ninguna protección o soluciones a este 'problema'.

Por otro lado, está utilizando forkIO para crear un hilo haskell liviano. No se garantiza que este hilo permanezca en el mismo hilo del sistema operativo. Por lo tanto, el RTS podría cambiarlo a otro subproceso del sistema operativo donde el subproceso local OpenGL-context no está disponible. Para resolver este problema, existe la función forkOS, que crea un hilo haskell enlazado.Este hilo enlazado haskell siempre se ejecutará en la misma cadena del sistema operativo y, por lo tanto, tendrá su estado local de subproceso disponible. La documentación sobre esto se puede encontrar en la sección 'Bound Threads' de Control.Concurrent, forkOS también se puede encontrar allí.

ediciones:

Con el código de prueba actual, este problema no está presente, ya que no se está utilizando -enhebrado. (razonamiento incorrecto eliminado)

+1

[Hoogle] (http://www.haskell.org/hoogle) está activo, y puede copiar los enlaces de resultados para obtener enlaces a varias funciones. – dflemstr

+1

Pero ... él no está enlazando con el tiempo de ejecución con subprocesos, por lo que solo hay un hilo del sistema operativo para empezar, ¡y las cosas que hace 'forkIO'ing no hacen llamadas OpenGL de todos modos! -1 –

+0

@DanielWagner, sobre el -thread que está en lo cierto, aunque su código 'forkIO'-ed hará llamadas OpenGL (es astuto). Así que actualicé mi publicación. – Laar

4

Su función render acaba siendo llamada una sola vez, porque la devolución de llamada a la pantalla solo se realiza cuando hay algo nuevo que dibujar. Para solicitar un redibujado, es necesario llamar a

GLUT.postRedisplay Nothing 

Se necesita un parámetro de ventana opcional, o señala un redibujado de la ventana "actual" cuando se pasa Nothing. Por lo general, llama al postRedisplay desde idleCallback o timerCallback, pero también puede llamarlo al final de render para solicitar un redibujado inmediato.

+1

Ambas respuestas fueron de gran ayuda y no podría haberlo hecho sin una, así que estoy aceptando la de Laar en lugar de la de @ shang porque Laar tiene una menor reputación. Publiqué los detalles específicos de la solución en mi pregunta original. – Dwilson

Cuestiones relacionadas