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)
所以我的理论为什么会发生这种情况:
还有其他原因导致这种情况发生吗?
完整的代码太长了,不能在这里发布,虽然不会太长(每个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.
一个可能的原因可能是使用了线程。
OpenGL 使用线程本地存储作为其上下文。因此,所有使用 OpenGL 的调用都应从同一操作系统线程进行。HOpenGL(以及 OpenGLRaw)是围绕 OpenGL 库的相对简单的绑定,并且没有为这个“问题”提供任何保护或解决方法。
另一方面,您是否使用forkIO
创建轻量级 Haskell 线程。不保证该线程位于同一操作系统线程上。因此,RTS 可能会将其切换到另一个操作系统线程,其中线程本地 OpenGL 上下文不可用。为了解决这个问题,有一个forkOS
函数,它创建一个绑定的 haskell 线程。这个绑定的 haskell 线程将始终在同一个操作系统线程上运行,因此其线程本地状态可用。有关此的文档可以在Control.Concurrent的“绑定线程”部分中找到,forkOS也可以在那里找到。
编辑:
在当前的测试代码中,这个问题不存在,因为您没有使用 -threaded。(删除了不正确的推理)