Here is a simple game loop in OCaml. The state is displayed, input is received, and the state is advanced. The number of frames per second is contrained to 40 by delaying the thread for 0.025 seconds each loop.
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
;;
For this example, the get_input
function simply prints keystrokes to the window.
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 for easy testing:
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 $<
This works for the most part, but when keys are pressed very quickly, the program crashes with this error:
Fatal error: exception Unix.Unix_error(2, "select", "")
I believe it has something to do with Thread.delay
. What is causing this issue, and what is the best way to achieve a constant FPS?
I'm not exactly sure what's going on (it depends on the implementation of Thread.delay, which I don't know). However, error 2 is Unix.EAGAIN
, which represents a temporary shortage of kernel resources. As the name says, you should probably just try to do your Thread.delay again. If I use try
... with
to catch the Unix.Unix_error exception I see no other errors except EAGAIN being delivered. If I just print a message and continue, the program seems to work. At least, it continues to echo characters to the window and doesn't crash. I'm working in OS X 10.7 (Lion). It might work differently for you.
Edit
Another possible problem with this code is that Sys.time()
returns processor time, which only increases while the process is doing real computation. It doesn't increase while the process is waiting for input. This means that the delay is always invoked, even if you wait a long time between keypresses (it was confusing me for a while). It might be better to use Unix.gettimeofday ()
, which returns wall clock time.
Edit 2
After some more research and testing, I believe that the Unix.EAGAIN
error is telling you that the full delay was interrupted by some event. In your case the interrupting event is the arrival of a character (I believe). So if you want to wait the full time, you should replace the single call to Thread.delay()
with a loop.
This gives you something like the following for your main code:
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
;;
(If you use Unix.select to do your delaying, you can remove the dependence on threads. But you might need them anyway for other reasons. The code would look the same except the error is EINTR rather than EAGAIN.)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With