2012-05-29 23 views
8

這是OCaml中的一個簡單遊戲循環。顯示狀態,接收輸入,狀態爲高級。通過延遲線程每個循環0.025秒,每秒幀數限制爲40。OCaml中的線程延遲和鍵盤事件

main.ml:

let rec main (* state *) frame_time = 
    (* Display state here. *) 
    Input.get_input(); 
    (* Advance state by one frame here. *) 
    (* If less than 25ms have passed, delay until they have. *) 
    if((Sys.time()) < (frame_time +. 0.025)) then 
    Thread.delay ((frame_time +. 0.025) -. (Sys.time())); 
    main (* next_state *) (Sys.time()) 
;; 

let init = 
    Graphics.open_graph " 800x500"; 
    let start_time = (Sys.time()) in 
    main (* start_state *) start_time 
;; 

對於此示例,該功能get_input簡單地打印鍵擊到窗口。

input.ml:

let get_input() = 
    let s = Graphics.wait_next_event 
    [Graphics.Key_pressed] in 
    if s.Graphics.keypressed then 
    Graphics.draw_char s.Graphics.key 
;; 

的Makefile簡單的測試:

main: input.cmo main.cmo 
    ocamlfind ocamlc -o [email protected] unix.cma -thread threads.cma graphics.cma $^ 
main.cmo: main.ml 
    ocamlfind ocamlc -c $< -thread 
input.cmo: input.ml 
    ocamlfind ocamlc -c $< 

這適用於大多數情況,但是當速度非常快按鍵,程序與此錯誤崩潰:

Fatal error: exception Unix.Unix_error(2, "select", "")

我相信它有東西與Thread.delay有關。造成這個問題的原因是什麼,實現恆定FPS的最佳方式是什麼?

回答

9

我不確定發生了什麼事情(這取決於Thread.delay的實現,我不知道)。但是,錯誤2是Unix.EAGAIN,這表示內核資源暫時短缺。正如名字所說,你應該試着再次做你的Thread.delay。如果我使用try ... with捕獲Unix.Unix_error異常,除EAGAIN正在傳送外,我看不到其他錯誤。如果我只是打印一條消息並繼續,程序似乎工作。至少,它會繼續將字符回顯到窗口並且不會崩潰。我在OS X 10.7(Lion)工作。它可能對你有不同的作用。

編輯

這段代碼的另一個可能的問題是Sys.time()回報處理器時間,只有在過程中做實際的計算增加。當進程等待輸入時它不會增加。這意味着延遲總是被調用,即使您在兩次按鍵之間等待了很長時間(它讓我困惑了一會兒)。使用Unix.gettimeofday()可能會更好,它會返回掛鐘時間。

編輯2

一些更多的研究和測試後,我相信Unix.EAGAIN錯誤是告訴你的全部延誤是由某些事件中斷。在你的情況下,中斷事件是一個角色的到來(我相信)。所以,如果你想等全時,你應該用一個循環替換單個呼叫Thread.delay()

這讓你像你的主代碼如下:

let rec main (* state *) frame_time = 
    (* Display state here. *) 
    Input.get_input(); 
    (* Advance state by one frame here. *) 
    (* If less than 25ms have passed, delay until they have. *) 
    let rec delay() = 
    let duration = frame_time +. 0.025 -. Unix.gettimeofday() in 
    if duration > 0.0 then 
     try 
     Thread.delay duration 
     with Unix.Unix_error (Unix.EAGAIN, _, _) -> delay() 
    in 
    delay(); 
    main (* next_state *) (Unix.gettimeofday ()) 
;; 

let init = 
    Graphics.open_graph " 800x500"; 
    let start_time = (Unix.gettimeofday ()) in 
    main (* start_state *) start_time 
;; 

(如果使用Unix.select做你的拖延,你可以刪除線程上的依賴,但你可能需要他們反正。其他原因,代碼看起來是一樣的,只是錯誤是EINTR而不是EAGAIN。)

+2

Jeffrey對所有帳戶都是正確的。您可以獲得有關Thread的更多信息。延遲[這裏](http://ocamlunix.forge.ocamlcore.org/threads.html#htoc63)。 –