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