关于Haskell中的其他线程和TChans,HOpenGL如何工作? [英] How does HOpenGL behave with regards to other threads and TChans in Haskell?

查看:179
本文介绍了关于Haskell中的其他线程和TChans,HOpenGL如何工作?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm doing some proof-of-concept work for a fairly complex video game I'd like to write in Haskell using the HOpenGL library. I started by writing a module that implements client-server event based communication. My problem appears when I try to hook it up to a simple program to draw clicks on the screen.

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

The event library uses a list of TChans made into a priority queue for communication. It returns an "out" queue and an "in" queue corresponding to server-bound and client-bound messages. Sending and receiving events are done in separate threads using forkIO. Testing the event library without the OpenGL part shows it communicating successfully. Here's the code I used to test it:

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

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

This produces the expected output, namely a whole lot of send and receive events. I don't think the problem lies with the Event handling library.

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

The OpenGL part of the code checks the incoming queue for new events in the displayCallback and then calls the event's associated handler. I can get one event (the Init event, which simply clears the screen) to be caught by the displayCallback, but after that nothing is caught. Here's the relevant 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

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

So my theories as to why this is happening are:


  • 显示回调会阻止重试的所有发送和接收线程。

  • 队列未正确返回,因此客户端读取的队列与队列不同

是否还有其他原因可能发生这种情况?

Are there any other reasons why this could be happening?

这个完整的代码太长,不能在这里发布,虽然不太长(5个文件在100行以下),但它是所有在GitHub 此处

The complete code for this is too long to post on here although not too long (5 files under 100 lines each), however it is all on GitHub here.

编辑1:

客户端从HOpenGL代码中的main函数中运行如下:

Edit 1:
The client is run from within the main function in the HOpenGL code like so:

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

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

The only flag I pass to GHC when I compile the code is -package GLUT.

编辑2:

我清理了Github的代码。我删除了acceptInput,因为它没有做任何真正的事情,客户端代码不应该在侦听自己的事件,这就是为什么它返回队列。

Edit 2:
I cleaned up the code on Github a bit. I removed acceptInput since it wasn't doing anything really and the Client code isn't supposed to be listening for events of its own anyway, that's why it's returning the queues.

编辑3:

我稍微澄清一下我的问题。我拿了我从@Shang和@Laar学到的和那种跑了。我将Client.hs中的线程更改为使用forkOS而不是forkIO(并且在ghc中使用了-threaded),并且看起来这些事件正在成功传递,但是它们没有在显示回调中被接收。我也尝试在显示回调结束时调用 postRedisplay ,但我不认为它被调用(因为我认为重试是阻塞整个OpenGL线程)。

Edit 3:
I'm clarifying my question a little bit. I took what I learned from @Shang and @Laar and kind of ran with it. I changed the threads in Client.hs to use forkOS instead of forkIO (and used -threaded at ghc), and it looks like the events are being communicated successfully, however they are not being received in the display callback. I also tried calling postRedisplay at the end of the display callback but I don't think it ever gets called (because I think the retry is blocking the entire OpenGL thread).

在显示回调中重试会阻塞整个OpenGL线程吗?如果是这样,将显示回调分支到一个新线程是安全的吗?我不想象它会,因为存在的可能性,多个事情可能正试图绘制到屏幕上的同时,但我可能能够处理与锁。另一个解决方案是转换 lookupHandler 函数返回一个包装在 Maybe 中的函数,不是任何事件。我觉得这将不太理想,因为我基本上有一个繁忙的循环,这是我想要避免的东西。

Would the retry in the display callback block the entire OpenGL thread? If it does, would it be safe to fork the display callback into a new thread? I don't imagine it would, since the possibility exists that multiple things could be trying to draw to the screen at the same time, but I might be able to handle that with a lock. Another solution would be to convert the lookupHandler function to return a function wrapped in a Maybe, and just do nothing if there aren't any events. I feel like that would be less than ideal as I'd then essentially have a busy loop which was something I was trying to avoid.

编辑4: / em>

忘了提及我在使用forkOS时使用了ghc。

Edit 4:
Forgot to mention I used -threaded at ghc when I did the forkOS.

编辑5:

我走了,做了一个测试我的理论,在render函数(显示回调)重试阻塞所有的OpenGL。我重写了render函数,所以它不再阻塞,它的工作原理,我想要它的工作。在屏幕上的一次点击给出两点,一个来自服务器和来自原始点击。这里是新的render函数的代码(注意:它不是
在Github中)

Edit 5:
I went and did a test of my theory that the retry in the render function (display callback) was blocking all of OpenGL. I rewrote the render function so it didn't block anymore, and it worked like I wanted it to work. One click in the screen gives two points, one from the server and from the original click. Here's the code for the new render function (note: it's not 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

我尝试使用和不使用postRedisplay,它只适用于它。现在的问题是,这将CPU锁定在100%,因为它是一个繁忙的循环。在编辑4我提议关闭显示回调。我仍然在想办法做到这一点。

I tried it with and without the postRedisplay, and it only works with it. The problem now becomes that this pegs the CPU at 100% because it's a busy loop. In Edit 4 I proposed threading off the display callback. I'm still thinking of a way to do that.

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

A note since I haven't mentioned it yet. Anybody looking to build/run the code should do it like this:

$ 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


$ b b

编辑6:解决方案

解决方案!与线程一起,我决定在OpenGL代码中创建一个线程来检查事件,阻塞如果没有任何,然后调用处理程序后跟随postRedisplay。这是:

Edit 6: Solution
A solution! Going along with the threads, I decided to make a thread in the OpenGL code that checked for events, blocking if there aren't any, and then calling the handler followed by postRedisplay. Here it is:

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

显示回调简单:

render = GLUT.swapBuffers

并且它的工作原理,它不会挂起CPU 100%立即处理。我在这里发布这里,因为我不能没有其他的答案,我感到不好的采取代表,当答案都非常有帮助,所以我接受@拉尔的答案,因为他有较低的Rep。

And it works, it doesn't peg the CPU for 100% and events are handled promptly. I'm posting this here because I couldn't have done it without the other answers and I feel bad taking the rep when the answers were both very helpful, so I'm accepting @Laar's answer since he has the lower Rep.

推荐答案

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

One possible cause could be the use of threading.

线程本地存储为它的上下文。因此,使用OpenGL的所有调用都应该从相同的操作系统线程。 HOpenGL(也是OpenGLRaw)是一个相对简单的约束OpenGL库,并没有提供任何保护或解决这个问题。

OpenGL uses thread local storage for it's context. Therefore all calls using OpenGL should be made from the same OS thread. HOpenGL (and OpenGLRaw too) is a relatively simple binding around the OpenGL library and is not providing any protection or workarounds to this 'problem'.

另一方面,你是使用 forkIO 创建一个轻量级的haskell线程。这个线程不能保证在同一个操作系统线程上。因此,RTS可能会将其切换到另一个线程本地OpenGL上下文不可用的OS线程。为了解决这个问题,有一个 forkOS 函数,它创建一个绑定的haskell线程。这个绑定的haskell线程将总是在相同的OS线程上运行,从而使其线程本地状态可用。有关这方面的文档可以在 Control.Concurrent forkOS

On the other hand are you using forkIO to create a light weight haskell thread. This thread is not guaranteed to stay on the same OS thread. Therefore the RTS might switch it to another OS thread where the thread local OpenGL-context is not available. To resolve this problem there is the forkOS function, which creates a bound haskell thread. This bound haskell thread will always run on the same OS thread and thus having its thread local state available. The documentation about this can be found in the 'Bound Threads' section of Control.Concurrent, forkOS can also be found there.

编辑:

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

With the current testing code this problem is not present, as you're not using -threaded. (removed incorrect reasoning)

这篇关于关于Haskell中的其他线程和TChans,HOpenGL如何工作?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆