HOpenGL如何处理Haskell中的其他线程和TChans?

Dwi*_*son 9 opengl concurrency haskell

我正在为一个相当复杂的视频游戏做一些概念验证工作,我想使用HOpenGL库在Haskell中编写.我开始编写一个实现基于客户端 - 服务器事件的通信的模块.当我尝试将其连接到一个简单的程序以在屏幕上绘制点击时,我的问题出现了.

事件库使用TChans列表作为优先级队列进行通信.它返回一个"out"队列和一个对应于服务器绑定和客户端绑定消息的"in"队列.发送和接收事件使用forkIO在单独的线程中完成.在没有OpenGL部分的情况下测试事件库表明它成功通信.这是我用来测试它的代码:

-- 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)))
   }
Run Code Online (Sandbox Code Playgroud)

这会产生预期的输出,即大量的发送和接收事件.我认为问题不在于事件处理库.

代码的OpenGL部分检查传入队列中displayCallback中的新事件,然后调用事件的关联处理程序.我可以得到一个事件(Init事件,它只是清除屏幕)被displayCallback捕获,但之后没有任何事情被捕获.这是相关的代码:

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
Run Code Online (Sandbox Code Playgroud)

所以我的理论为什么会发生这种情况:

  • 显示回调阻止重试时的所有发送和接收线程.
  • 队列未正确返回,因此客户端读取的队列与OpenGL部分读取的队列不同.

还有其他原因导致这种情况发生吗?

完整的代码太长了,不能在这里发布,虽然不会太长(每个100行以下的5个文件),但是这里都是GitHub .

编辑1:
客户端从HOpenGL代码中的main函数内运行,如下所示:

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
Run Code Online (Sandbox Code Playgroud)

我编译代码时传递给GHC的唯一标志是-package GLUT.

编辑2:
我在Github上清理了一些代码.我删除了acceptInput,因为它没有真正做任何事情,客户端代码不应该监听它自己的事件,这就是它返回队列的原因.

编辑3:
我正在澄清我的问题.我从@Shang和@Laar那里学到了我所学到的东西.我更改了Client.hs中的线程以使用forkOS而不是forkIO(并在ghc中使用-threaded),看起来事件正在成功传递,但是它们没有在显示回调中被接收.我也尝试postRedisplay在显示回调结束时调用,但我认为它不会被调用(因为我认为重试阻止了整个OpenGL线程).

显示回调中的重试会阻塞整个OpenGL线程吗?如果是这样,将显示回调分支到新线程是否安全?我不认为它会,因为可能存在多个东西可能同时试图绘制到屏幕上,但我可能能够用锁来处理它.另一种解决方案是将lookupHandler函数转换为返回包含在a中的函数Maybe,如果没有任何事件则不执行任何操作.我觉得这不太理想,因为我基本上有一个忙碌的循环,这是我试图避免的.

编辑4:
忘了提到我在使用forkOS时在ghc上使用了-threaded.

编辑5:
我去做了我的理论测试,渲染函数(显示回调)中的重试阻止了所有的OpenGL.我重新编写了渲染函数,因此它不再阻塞,它就像我希望它工作一样.在屏幕上单击一下即可得到两个点,一个来自服务器,另一个来自原始点击.这是新渲染函数的代码(注意:它不在 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
Run Code Online (Sandbox Code Playgroud)

我在使用和不使用postRedisplay的情况下尝试了它,它只适用于它.现在的问题是,这会将CPU固定在100%,因为它是一个繁忙的循环.在Edit 4中,我建议线程化显示回调.我还在想办法做到这一点.

因为我还没有提到它.任何想要构建/运行代码的人都应该这样做:

$ 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
Run Code Online (Sandbox Code Playgroud)

编辑6:解决方案解决
方案!与线程一起,我决定在OpenGL代码中创建一个检查事件的线程,如果没有则阻塞,然后调用处理程序,然后调用postRedisplay.这里是:

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
Run Code Online (Sandbox Code Playgroud)

显示回调很简单:

render = GLUT.swapBuffers
Run Code Online (Sandbox Code Playgroud)

并且它有效,它不会将CPU固定为100%并且事件会得到及时处理.我在这里发布这个是因为没有其他答案我就不能这样做了,当答案非常有帮助时我很难接受代表,所以我接受@ Laar的回答,因为他有较低的Rep.

Laa*_*aar 4

一个可能的原因可能是使用了线程。

OpenGL 使用线程本地存储作为其上下文。因此,所有使用 OpenGL 的调用都应从同一操作系统线程进行。HOpenGL(以及 OpenGLRaw)是围绕 OpenGL 库的相对简单的绑定,并且没有为这个“问题”提供任何保护或解决方法。

另一方面,您是否使用forkIO创建轻量级 Haskell 线程。不保证该线程位于同一操作系统线程上。因此,RTS 可能会将其切换到另一个操作系统线程,其中线程本地 OpenGL 上下文不可用。为了解决这个问题,有一个forkOS函数,它创建一个绑定的 haskell 线程。这个绑定的 haskell 线程将始终在同一个操作系统线程上运行,因此其线程本地状态可用。有关此的文档可以在Control.Concurrent的“绑定线程”部分中找到,forkOS也可以在那里找到。

编辑:

在当前的测试代码中,这个问题不存在,因为您没有使用 -threaded。(删除了不正确的推理)