Wie verhält sich HOpenGL in Bezug auf andere Threads und TChans in Haskell?

Ich mache einige Proof-of-Concept-Arbeiten für ein ziemlich komplexes Videospiel, das ich mit der HOpenGL-Bibliothek in Haskell schreiben möchte. Zunächst habe ich ein Modul geschrieben, das die ereignisbasierte Client-Server-Kommunikation implementiert. Mein Problem tritt auf, wenn ich versuche, es mit einem einfachen Programm zu verbinden, um Klicks auf den Bildschirm zu zeichnen.

Die Ereignisbibliothek verwendet eine Liste von TChans, die zu einer Prioritätswarteschlange für die Kommunikation gemacht wurden. Es gibt eine Warteschlange "out" und eine Warteschlange "in" zurück, die server- und clientgebundenen Nachrichten entsprechen. Das Senden und Empfangen von Ereignissen erfolgt mit forkIO in separaten Threads. Das Testen der Ereignisbibliothek ohne den OpenGL-Teil zeigt, dass die Kommunikation erfolgreich war. Hier ist der Code, den ich zum Testen verwendet habe:

-- 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)))
   }

Dies erzeugt die erwartete Ausgabe, nämlich eine ganze Reihe von Sende- und Empfangsereignissen. Ich glaube nicht, dass das Problem bei der Event-Handling-Bibliothek liegt.

Der OpenGL-Teil des Codes überprüft die eingehende Warteschlange auf neue Ereignisse im displayCallback und ruft dann den zugeordneten Handler des Ereignisses auf. Ich kann ein Ereignis (das Init-Ereignis, das einfach den Bildschirm löscht) vom displayCallback abfangen lassen, aber danach wird nichts mehr abgefangen. Hier ist der relevante Code:

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

Meine Theorien, warum dies geschieht, sind:

Der Display-Rückruf blockiert alle sendenden und empfangenden Threads bei dem erneuten Versuch.Die Warteschlangen werden nicht ordnungsgemäß zurückgegeben, sodass sich die vom Client gelesenen Warteschlangen von den vom OpenGL-Teil gelesenen unterscheiden.

Gibt es noch andere Gründe, warum dies passieren könnte?

Der komplette Code dafür ist zu lang, um hier zu posten, obwohl nicht zu lang (5 Dateien mit jeweils 100 Zeilen), aber alles auf GitHubHier.

Bearbeiten 1:
Der Client wird innerhalb der Hauptfunktion im HOpenGL-Code wie folgt ausgeführt:

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

Das einzige Flag, das ich an GHC weitergebe, wenn ich den Code kompiliere, ist-package GLUT.

Bearbeiten 2:
Ich habe den Code auf Github ein bisschen aufgeräumt. Ich habe acceptInput entfernt, da es nicht wirklich funktioniert und der Client-Code sowieso nicht auf eigene Ereignisse warten soll. Deshalb werden die Warteschlangen zurückgegeben.

Edit 3:
Ich kläre meine Frage ein bisschen. Ich habe das, was ich von @Shang und @Laar gelernt habe, genommen und bin irgendwie damit gelaufen. Ich habe die Threads in "Client.hs" geändert, um "forkOS" anstelle von "forkIO" zu verwenden (und "used -threaded" bei "ghc"). Es sieht so aus, als würden die Ereignisse erfolgreich kommuniziert, sie werden jedoch nicht im Display-Rückruf empfangen. Ich habe auch versucht anzurufenpostRedisplay am Ende der Anzeige Rückruf, aber ich glaube nicht, dass es jemals aufgerufen wird (weil ich denke, dass die Wiederholung den gesamten OpenGL-Thread blockiert).

Würde der Neuversuch im Display-Callback den gesamten OpenGL-Thread blockieren? Wenn ja, wäre es sicher, den Display-Rückruf in einen neuen Thread zu unterteilen? Ich kann mir das nicht vorstellen, da die Möglichkeit besteht, dass mehrere Dinge gleichzeitig versuchen, auf den Bildschirm zu zeichnen, aber ich könnte das mit einer Sperre bewältigen. Eine andere Lösung wäre, die zu konvertierenlookupHandler Funktion, um eine in a eingeschlossene Funktion zurückzugebenMaybeund nichts tun, wenn es keine Ereignisse gibt. Ich denke, das wäre alles andere als ideal, da ich dann im Wesentlichen eine belegte Schleife hätte, die ich vermeiden wollte.

Bearbeiten 4:
Ich habe vergessen zu erwähnen, dass ich -threaded bei ghc verwendet habe, als ich das forkOS gemacht habe.

Edit 5:
Ich ging und testete meine Theorie, dass der Wiederholungsversuch in der Render-Funktion (Display Callback) OpenGL blockierte. Ich habe die Render-Funktion umgeschrieben, damit sie nicht mehr blockiert, und es hat so funktioniert, als wollte ich, dass es funktioniert. Ein Klick auf dem Bildschirm gibt zwei Punkte, einen vom Server und einen vom ursprünglichen Klick. Hier ist der Code für die neue Renderfunktion (Anmerkung: es istnicht in 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

Ich habe es mit und ohne postRedisplay versucht und es funktioniert nur damit. Das Problem besteht nun darin, dass die CPU zu 100% damit verbunden ist, da es sich um eine Besetztschleife handelt. In Edit 4 schlug ich vor, den Display-Callback abzuschalten. Ich denke immer noch über einen Weg nach, das zu tun.

Eine Notiz, da ich sie noch nicht erwähnt habe. Jeder, der den Code erstellen / ausführen möchte, sollte dies folgendermaßen tun:

$ 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

Bearbeiten 6:Lösung
Eine Lösung! Als Teil der Threads habe ich beschlossen, im OpenGL-Code einen Thread zu erstellen, der nach Ereignissen sucht, blockiert, wenn keine vorhanden sind, und dann den Handler gefolgt von postRedisplay aufruft. Hier ist es:

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

Der Display-Rückruf lautet einfach:

render = GLUT.swapBuffers

Und es funktioniert, es bindet die CPU nicht zu 100% und Ereignisse werden sofort behandelt. Ich poste dies hier, weil ich es ohne die anderen Antworten nicht geschafft hätte und es mir leid tut, die Wiederholung zu übernehmen, wenn die Antworten beide sehr hilfreich waren. Deshalb akzeptiere ich die Antwort von @ Laar, da er die niedrigere Wiederholung hat.

Antworten auf die Frage(2)

Ihre Antwort auf die Frage