8

这是 OCaml 中的一个简单的游戏循环。状态被显示,输入被接收,状态被推进。通过在每个循环中将线程延迟 0.025 秒,将每秒帧数限制为 40。

主要.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函数只是将击键打印到窗口。

输入.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 $@ 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 的最佳方法是什么?

4

1 回答 1

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

于 2012-05-30T01:33:24.247 回答