2012-07-12 2 views
9

HOpenGLライブラリを使ってHaskellに書きたいかなり複雑なビデオゲームのための概念実証作業をしています。私はクライアント・サーバー・イベント・ベースの通信を実装するモジュールを作成することから始めました。私の問題は、画面にクリックを描画する簡単なプログラムに接続しようとすると表示されます。Haskellの他のスレッドやTChanに関してHOpenGLはどのように動作しますか?

イベントライブラリは、通信の優先度キューに入れられた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))) 
    } 

これは、予想される出力、センドのすなわち全体の多くを生成し、イベントを受け取ります。私は問題がイベント処理ライブラリにあるとは思わない。

コードのOpenGL部分は、着信キューでdisplayCallbackの新しいイベントをチェックし、イベントに関連付けられたハンドラを呼び出します。私は、displayCallbackによってキャッチされる1つのイベント(画面を単にクリアするInitイベント)を得ることができますが、その後は何もキャッチされません。ここでは、関連するコードがあります:

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 

だから私の理論をこれが起こっている理由としては、以下のとおりです。

  • 表示コールバックが再試行に送信側と受信側のすべてのスレッドをブロックしています。
  • キューが正しく返されないため、クライアントが読み取るキューとOpenGLの一部が読み取るキューが異なります。

これが起こりうる他の理由はありますか?

長すぎるわけではありませんが、完全なコードはここに投稿するには長すぎます(それぞれ100行未満の5つのファイル)が、すべてGitHub​​にあります。

編集1:クライアントはそうようHOpenGLコードでメイン関数内から実行される

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 

Iコードが-package GLUTでコンパイルするとき、私はGHCに渡すだけのフラグ。

編集2:
Githubでコードを少しきれいにしました。私はacceptInputを削除しました。実際に何もしていなかったので、クライアントコードはそれ自身のイベントをリッスンすることになっていないため、キューを返すのです。

編集3:
私は少し質問を明確にしています。私は@ Shangと@Laarから学んだことを取って、それと一緒に走った。私は、ClientHsのスレッドをforkIOの代わりにforkOSを使用するように変更しました(ghcで使用されました)。イベントは正常に通信されているように見えますが、表示コールバックでは受信されません。私はまた、表示コールバックの最後にpostRedisplayを呼び出すことを試みましたが、私は再試行がOpenGLスレッド全体をブロックしていると考えているので、呼び出されるとは思いません。

ディスプレイコールバックの再試行でOpenGLスレッド全体がブロックされますか?表示されている場合、表示コールバックを新しいスレッドにフォークするのは安全でしょうか?同時に複数のものが画面に描画しようとしている可能性があるので、私は想像しませんが、ロックでそれを処理できるかもしれません。別の解決方法は、lookupHandler関数をMaybeにラップされた関数を返すように変換することです。イベントがない場合は何もしないでください。私は基本的には、私が避けようとしていたものだった忙しいループを持っていたので、それが理想的ではないように感じる。

編集4:
は、私がforkOSをしたとき、私はGHCで-threaded使用に言及し忘れました。

編集5:
私は私の理論のテストを行っていたレンダリング機能(表示コールバック)での再試行は、OpenGLのすべてをブロックしていること。レンダリング機能を書き直してもうブロックしないようにして、動作させたいと思ったように機能しました。画面内を1回クリックすると、サーバーと元のクリックから2つのポイントが得られます。ここでは、新たなレンダリング機能のコードは(:それは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では、ディスプレイコールバックをスレッドオフにすることを提案しました。私はまだそれを行う方法を考えています。

私はまだ言及していないので注意してください。

$ 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 

編集:6:ソリューション
ソリューションのコードを実行する/構築するために探している誰もがこのようにそれを行う必要があります!スレッドに沿って、イベントをチェックし、何もない場合はブロックし、その後にハンドラを呼び出してpostRedisplayを呼び出すスレッドをOpenGLコードで作成することにしました。ここでは、次のとおりです。

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 

そして、それは動作しますが、それは100%のCPUをペグしないとイベントが速やかに処理されます。私は他の答えなしでそれをしなかったので私はこれを掲示していると答えが両方非常に有用なので、私は悪いと感じるので、彼は下院を持っているので@ラアの答えを受け入れている

+0

クライアントの作成と実行にはどのようなものがありますか?特に、ghc/runhaskell/ghciにどのようなフラグを渡しますか? – Laar

答えて

4

考えられる原因の1つにスレッディングがあります。

OpenGLは、コンテキストのスレッドローカルストレージを使用します。したがって、OpenGLを使用するすべての呼び出しは、同じOSスレッドから作成する必要があります。 HOpenGL(とOpenGLRawも)は、OpenGLライブラリの周りの比較的単純な束縛であり、この「問題」に対する保護や回避策を提供していません。

一方、軽量のhaskellスレッドを作成するには、forkIOを使用していますか?このスレッドは、同じOSスレッド上にとどまることは保証されません。したがって、RTSは、スレッドローカルOpenGLコンテキストが利用できない別のOSスレッドにRTSを切り替える可能性があります。この問題を解決するには、forkOS関数があります。これは、バインドされたhaskellスレッドを作成します。この結合されたhaskellスレッドは、常に同じOSスレッド上で実行され、そのスレッドローカル状態が利用可能になります。これに関する文書はControl.Concurrentの 'Bound Threads'セクションにあり、そこにはforkOSもあります。

編集:

現在のテストコードを使用すると、-threaded使用していないと、この問題は、存在しません。 (間違った推論を削除しました)

+1

[Hoogle](http://www.haskell.org/hoogle)が起動しており、結果リンクをコピーしてさまざまな機能へのリンクを得ることができます。 – dflemstr

+1

しかし、彼はスレッドランタイムにリンクしていないので、まずは1つのOSスレッドしかありません。そして、彼が 'forkIO'していることは、とにかくOpenGL呼び出しをしません! -1 –

+0

@DanielWagnerについて、あなたは正しいですが、彼の 'forkIO'-edコードはOpenGL呼び出しを行います(それは卑劣です)。だから私は自分の投稿を更新しました。 – Laar

4

render関数は、表示するコールバックが新たに描画される場所でのみ呼び出されるため、1回だけ呼び出されます。再描画を要求するには、あなたが

GLUT.postRedisplay Nothing 

を呼び出す必要があることは、オプションのウィンドウパラメータを取るか、Nothingを渡すと、「現在の」ウィンドウの再描画を通知します。 idleCallbackまたはtimerCallbackからpostRedisplayに電話するのが通常ですが、renderの最後に電話して、即時再描画をリクエストすることもできます。

+1

両方の答えが非常に役立っていて、どちらもせずにできなかったので、Laarは@ shangの代わりにLaarを受け入れます。私は、私の元々の質問にその解決策の具体的な詳細を掲載しました。 – Dwilson

関連する問題