2012-07-12 8 views
9

Tôi đang thực hiện một số công việc chứng minh-khái niệm cho một trò chơi video khá phức tạp mà tôi muốn viết trong Haskell bằng cách sử dụng thư viện HOpenGL. Tôi bắt đầu bằng cách viết một mô-đun thực hiện truyền thông dựa trên sự kiện client-server. Vấn đề của tôi xuất hiện khi tôi cố gắng móc nó lên một chương trình đơn giản để vẽ các nhấp chuột trên màn hình.HOpenGL hoạt động như thế nào đối với các luồng khác và TChans trong Haskell?

Thư viện sự kiện sử dụng danh sách các TChans được đưa vào hàng đợi ưu tiên để liên lạc. Nó trả về một hàng đợi "out" và một hàng đợi "in" tương ứng với các thông điệp bị ràng buộc bởi máy chủ và máy khách. Gửi và nhận các sự kiện được thực hiện trong các chủ đề riêng biệt bằng cách sử dụng forkIO. Kiểm tra thư viện sự kiện mà không có phần OpenGL cho thấy nó giao tiếp thành công. Dưới đây là đoạn code tôi sử dụng để kiểm tra nó:

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

này tạo ra sản lượng dự kiến, cụ thể là một toàn bộ rất nhiều gửi và nhận các sự kiện. Tôi không nghĩ rằng vấn đề nằm với thư viện xử lý sự kiện.

Phần OpenGL của mã kiểm tra hàng đợi đến cho các sự kiện mới trong displayCallback và sau đó gọi trình xử lý liên quan của sự kiện. Tôi có thể nhận được một sự kiện (sự kiện Init, mà chỉ đơn giản là xóa màn hình) để bị bắt bởi displayCallback, nhưng sau đó không có gì bị bắt. Dưới đây là các mã có liên quan:

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 

Vì vậy, lý thuyết của tôi là tại sao điều này xảy ra là:

  • Màn hình callback được ngăn chặn tất cả các chủ đề gửi và nhận trên retry.
  • Hàng đợi không được trả lại đúng cách, sao cho các hàng đợi mà khách hàng đọc khác với các hàng mà phần OpenGL đọc.

Có bất kỳ lý do nào khác khiến điều này có thể xảy ra không?

Mã hoàn chỉnh cho quá trình này quá dài để đăng trên đây mặc dù không quá dài (5 tệp dưới 100 dòng mỗi dòng), tuy nhiên tất cả trên GitHub here.

Sửa 1:
Các khách hàng được điều hành từ bên trong hàm main trong mã HOpenGL như vậy:

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 

Lá cờ duy nhất tôi vượt qua để GHC khi tôi biên dịch mã là -package GLUT.

Chỉnh sửa 2:
Tôi đã xóa mã trên Github một chút. Tôi đã gỡ bỏ acceptInput vì nó không làm bất cứ điều gì thực sự và mã Client không được cho là lắng nghe các sự kiện của chính nó, đó là lý do tại sao nó trả về hàng đợi.

Chỉnh sửa 3:
Tôi đang làm rõ câu hỏi của mình một chút. Tôi lấy những gì tôi đã học được từ @Shang và @Laar và loại chạy với nó. Tôi đã thay đổi các chủ đề trong Client.hs để sử dụng forkOS thay vì forkIO (và được sử dụng -readed tại ghc), và có vẻ như các sự kiện đang được truyền đạt thành công, tuy nhiên chúng không được nhận trong callback hiển thị. Tôi cũng đã cố gắng gọi postRedisplay ở cuối màn hình gọi lại nhưng tôi không nghĩ rằng nó đã từng được gọi (vì tôi nghĩ việc thử lại đang chặn toàn bộ chuỗi OpenGL).

Có phải thử lại trong lần gọi lại hiển thị chặn toàn bộ chuỗi OpenGL không? Nếu có, nó sẽ an toàn để ngã ba cuộc gọi lại hiển thị thành một chủ đề mới? Tôi không tưởng tượng nó sẽ xảy ra, vì khả năng tồn tại là nhiều thứ có thể cố gắng rút ra màn hình cùng một lúc, nhưng tôi có thể xử lý nó bằng một cái khóa. Một giải pháp khác là chuyển đổi hàm lookupHandler để trả về một hàm được bọc trong một Maybe và không làm gì nếu không có bất kỳ sự kiện nào. Tôi cảm thấy như vậy sẽ ít hơn lý tưởng vì sau đó tôi về cơ bản có một vòng lặp bận rộn mà là một cái gì đó tôi đã cố gắng để tránh.

Sửa 4:
Quên đề cập đến tôi sử dụng -threaded tại GHC khi tôi đã làm forkOS.

Sửa 5:
tôi đã đi và đã làm một bài kiểm tra lý thuyết của tôi rằng retry trong render chức năng (hiển thị callback) đã chặn tất cả các OpenGL. Tôi viết lại hàm render để nó không chặn nữa, và nó hoạt động như tôi muốn nó hoạt động. Một cú nhấp chuột trong màn hình cho hai điểm, một từ máy chủ và từ lần nhấp gốc. Dưới đây là đoạn code cho render chức năng mới (lưu ý: đó là không trong 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 

Tôi đã thử nó có và không có postRedisplay, và nó chỉ làm việc với nó. Vấn đề bây giờ trở thành rằng điều này chốt CPU ở 100% bởi vì nó là một vòng lặp bận rộn. Trong Chỉnh sửa 4, tôi đã đề xuất luồng khỏi gọi lại hiển thị. Tôi vẫn đang nghĩ đến một cách để làm điều đó.

Lưu ý vì tôi chưa đề cập đến nó. Bất kỳ ai muốn xây dựng/chạy mã nên làm điều đó như thế này:

$ 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 

Sửa 6: Giải pháp
Một giải pháp! Đi cùng với các chủ đề, tôi quyết định tạo một chuỗi trong mã OpenGL đã kiểm tra các sự kiện, chặn nếu không có bất kỳ sự kiện nào, và sau đó gọi trình xử lý theo sau là postRedisplay. Ở đây là:

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 

Màn hình gọi lại chỉ đơn giản là:

render = GLUT.swapBuffers 

Và nó hoạt động, nó không peg CPU cho 100% và các sự kiện được xử lý kịp thời. Tôi đăng bài này ở đây vì tôi không thể làm điều đó mà không có câu trả lời khác và tôi cảm thấy xấu khi tham gia câu trả lời khi cả hai đều rất hữu ích, vì vậy tôi chấp nhận câu trả lời của @ Laar kể từ khi anh ấy có Đại diện thấp hơn

+0

Bạn sử dụng cái gì để tạo và chạy ứng dụng khách và đặc biệt là những lá cờ nào bạn chuyển đến ghc/runhaskell/ghci? – Laar

Trả lời

4

Một nguyên nhân có thể là việc sử dụng luồng.

OpenGL sử dụng bộ nhớ cục bộ luồng cho ngữ cảnh của nó. Do đó tất cả các cuộc gọi sử dụng OpenGL phải được thực hiện từ cùng một chuỗi hệ điều hành. HOpenGL (và OpenGLRaw) là một ràng buộc tương đối đơn giản xung quanh thư viện OpenGL và không cung cấp bất kỳ sự bảo vệ hoặc giải pháp nào cho 'vấn đề' này.

Mặt khác, bạn đang sử dụng forkIO để tạo chủ đề haskell trọng lượng nhẹ. Chủ đề này không được đảm bảo để ở trên cùng một chuỗi hệ điều hành. Do đó RTS có thể chuyển nó sang một chuỗi hệ điều hành khác, trong đó ngữ cảnh OpenGL cục bộ không có sẵn. Để giải quyết vấn đề này có chức năng forkOS, tạo ra một chuỗi haskell ràng buộc.Chuỗi haskell ràng buộc này sẽ luôn chạy trên cùng một chuỗi hệ điều hành và do đó có trạng thái địa phương luồng của nó có sẵn. Các tài liệu về điều này có thể được tìm thấy trong phần 'Bound Threads' của Control.Concurrent, forkOS cũng có thể được tìm thấy ở đó.

chỉnh sửa:

Với mã kiểm tra hiện tại, vấn đề này không có mặt, vì bạn không sử dụng - đã đọc. (đã loại bỏ lý do không chính xác)

+1

[Hoogle] (http://www.haskell.org/hoogle) đang hoạt động và bạn có thể sao chép liên kết kết quả để nhận liên kết đến các chức năng khác nhau. – dflemstr

+1

Nhưng ... anh ấy không liên kết với thời gian chạy luồng, do đó, chỉ có một chuỗi hệ điều hành để bắt đầu, và những thứ anh ta 'forkIO'ing không thực hiện cuộc gọi OpenGL nào! -1 –

+0

@DanielWagner, về việc bạn đã đọc đúng, mặc dù mã 'forkIO'-ed của anh ấy sẽ thực hiện cuộc gọi OpenGL (nó lén lút). Vì vậy, tôi đã cập nhật bài đăng của mình. – Laar

4

Chức năng render của bạn sẽ chỉ được gọi một lần, bởi vì gọi lại hiển thị chỉ được gọi khi có thứ gì đó mới để vẽ. Để yêu cầu vẽ lại, bạn cần phải gọi

GLUT.postRedisplay Nothing 

Phải mất một tham số cửa sổ tùy chọn, hoặc báo hiệu một vẽ lại cho cửa sổ "hiện tại" khi bạn vượt qua Nothing. Bạn thường gọi số postRedisplay từ số idleCallback hoặc timerCallback nhưng bạn cũng có thể gọi nó ở cuối số render để yêu cầu vẽ lại ngay lập tức.

+1

Cả hai câu trả lời đều vô cùng hữu ích và tôi không thể thực hiện nó mà không có ai, vì vậy tôi chấp nhận Laar thay vì @ shang vì Laar có đại diện thấp hơn. Tôi đã đăng các chi tiết cụ thể của giải pháp trong câu hỏi ban đầu của mình. – Dwilson