2012-05-29 24 views
8

Aquí hay un bucle de juego simple en OCaml. Se muestra el estado, se recibe la entrada y el estado avanza. El número de cuadros por segundo se reduce a 40 al retrasar el hilo durante 0.025 segundos en cada ciclo.Retraso de hilo y eventos de teclado en 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 
;; 

Para este ejemplo, la función get_input simplemente imprime pulsaciones de teclas a la ventana.

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 para la prueba fácil:

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 $< 

Esto funciona en su mayor parte, pero cuando se pulsan las teclas muy rápidamente, el programa se bloquea con este error:

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

Creo que tiene algo para hacer con Thread.delay. ¿Qué está causando este problema y cuál es la mejor manera de lograr un FPS constante?

Respuesta

9

No estoy exactamente seguro de lo que está pasando (depende de la implementación de Thread.delay, que no sé). Sin embargo, el error 2 es Unix.EAGAIN, que representa una escasez temporal de recursos del kernel. Como su nombre lo indica, probablemente solo deberías tratar de hacer tu Thread.delay nuevamente. Si utilizo try ... with para detectar la excepción Unix.Unix_error, no veo ningún otro error, excepto EAGAIN. Si solo imprimo un mensaje y continúo, el programa parece funcionar. Al menos, continúa reproduciendo caracteres en la ventana y no se cuelga. Estoy trabajando en OS X 10.7 (Lion). Puede funcionar de manera diferente para ti.

Editar

Otro posible problema con este código es que Sys.time() vuelve procesador tiempo, que sólo aumenta mientras el proceso está haciendo el cálculo real. No aumenta mientras el proceso está esperando la entrada. Esto significa que el retraso siempre se invoca, incluso si espera mucho tiempo entre pulsaciones de teclas (me confundió por un tiempo). Puede ser mejor usar Unix.gettimeofday(), que devuelve el tiempo del reloj de pared.

Editar 2

Después de un poco más de investigación y pruebas, creo que el error Unix.EAGAIN le está diciendo que el retraso total fue interrumpido por algún evento. En tu caso, el evento de interrupción es la llegada de un personaje (creo). Entonces, si desea esperar todo el tiempo, debe reemplazar la llamada simple al Thread.delay() con un bucle.

Esto le da algo así como lo siguiente para su código principal:

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 
;; 

(Si utiliza Unix.select hacer su retraso, puede eliminar la dependencia de los hilos, pero es posible que necesite de todos modos para. otras razones. El código se vería igual excepto que el error es EINTR en lugar de EAGAIN.)

+2

Jeffrey tiene razón en todas las cuentas. Puedes obtener ligeramente más información sobre Thread.demora [aquí] (http://ocamlunix.forge.ocamlcore.org/threads.html#htoc63). –

Cuestiones relacionadas