8

これがOCamlの簡単なゲームループです。状態が表示され、入力が受信され、状態が進みます。スレッドをループごとに0.025秒間遅延させることにより、1秒あたりのフレーム数を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
;;

この例では、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 $@ 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が配信される以外に他のエラーは表示されません。メッセージを印刷して続行すると、プログラムは機能しているようです。少なくとも、ウィンドウに文字をエコーし​​続け、クラッシュしません。私はOSX10.7(Lion)で作業しています。動作が異なる場合があります。

編集

このコードで考えられるもう1つの問題は、プロセッサ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を使用して遅延を行う場合は、スレッドへの依存を取り除くことができます。ただし、他の理由でスレッドへの依存を取り除くことができます。エラーがEAGAINではなくEINTRであることを除いて、コードは同じように見えます。)

于 2012-05-30T01:33:24.247 に答える