OCaml中的线程延迟和键盘事件

Mat*_*iak 8 multithreading ocaml sleep delay wait

这是OCaml中的简单游戏循环.显示状态,接收输入,并且状态提前.通过将线程延迟每个循环0.025秒,每秒帧数被限制为40.

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
;;
Run Code Online (Sandbox Code Playgroud)

对于此示例,该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
;;
Run Code Online (Sandbox Code Playgroud)

Makefile易于测试:

main: input.cmo main.cmo
    ocamlfind ocamlc -o $@ unix.cma -thread threads.cma graphics.cma $^ 
main.cmo: main.ml
    ocamlfind ocamlc -c $< -thread
input.cmo: input.ml
    ocamlfind ocamlc -c $<
Run Code Online (Sandbox Code Playgroud)

这在大多数情况下都有效,但是当按键很快被按下时,程序会因此错误而崩溃:

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

我相信这与它有关Thread.delay.造成这个问题的原因是什么,实现恒定FPS的最佳方法是什么?

Jef*_*eld 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
;;
Run Code Online (Sandbox Code Playgroud)

(如果使用Unix.select进行延迟,则可以消除对线程的依赖.但是出于其他原因,您可能仍然需要它们.代码看起来相同,除了错误是EINTR而不是EAGAIN.)

  • 杰弗里在各方面都是正确的.您可以获得有关Thread.delay [here](http://ocamlunix.forge.ocamlcore.org/threads.html#htoc63)的更多信息. (2认同)