I’ve been trying to get to grips with tsdl, and having a hard time of it. In particular, having to constantly unpack Result return values gets pretty clunky when following sdl tutorials (which assume an imperative API and typically only check the return value when doing things like creating a surface, not e.g. when changing the background colour).
I would love to see some examples of idiomatic tsdl code people have used in an actual game. (I’m also open to suggestions of other libraries if there are any; as far as I can tell the allegro and sfml bindings are dead and I couldn’t find any other options.)
R.failwith_error_msg is a safer alternative to ignore, as it assures that inner result is used in a well-typed context, and ensures that an exception is raised as soon as something fails.
Combine the results with the >>= and >>| operators. This is an elegant solution to avoid unhandled exceptions, but with the caveat during development that you don’t get a backtrace.
Hi,
ocaml-sfml is not completly dead, there is someone who took over the development in a fork.
It’s just easy to not see it since he developed it inside a branch.
I have another question toward Tsdl: did someone already made it work under Windows?
Also you need to know, although SFML is portable I was unable to make it work on Windows.
(This is why I’m using ocamlsdl2 now because it was easy to make it work on Windows)
I thought the main stuff was gone in sld2 (at least it is on macOS). But if that’s needed I suppose it’s still a cpp trick (and thus can’t be expressed via ctypes).
I can’t personally test this but if anyone’s willing to submit a windows only shim that is linked when tsdl is used on Windows® I’ll gladly merge it.
I tryed to remove the main from ocamlsdl2 on Windows, then it still works in interpreted/bytecode (ocaml commmand), but compiled to native binary code I get the expected error:
undefined reference to `SDL_main'
So I bet opam-repository-mingw’s team probably just compiled the bindings, but probably didn’t run the tests.
You can/should probably add:
#include "SDL.h"
int main(int argc, char *argv[]) { }
But I don’t know how to do this with ToPkg, so instead I replaced the include like:
#include "SDL2/SDL.h"
It works because my SDL2 include dir is not in a custom path, it’s just inside /usr/include.
(But for people who installed it in a custom path you should explain how to do.)
Also I just run the tests and adding an empty main is not enough, I had to add:
I was looking for a way to setup a “proper” game loop, functional style.
Looking at the tsdl repo, I noticed the examples in the test folder make heavy use of match expressions. Replicating that style quickly led me to a “callback hell” type of problem though.
Anyhow, this is my take on setting up a basic game loop:
(*
* dune exec --display=quiet bin/main.exe
*)
module Sdl = Tsdl.Sdl
module Window = Sdl.Window
module Event = Sdl.Event
let ( let* ) = Result.bind
type error_ctx =
| Partial of { window : Sdl.window }
| Full of { window : Sdl.window; renderer : Sdl.renderer }
(* Attach extra context to the original error.
* Used for resource cleanup on program exit.
*)
let with_err_ctx ctx = Result.map_error (fun err -> (err, ctx))
let init_window () =
let* () = Sdl.init Sdl.Init.(audio + video) in
let* window =
Sdl.create_window "OCaml/TSDL: CHANGE_ME" ~x:Window.pos_centered
~y:Window.pos_centered ~w:640 ~h:480 Window.shown
in
Ok window
;;
let render_frame renderer (x, y) =
let* () = Sdl.set_render_draw_color renderer 255 127 40 255 in
let* () = Sdl.render_clear renderer in
let* () = Sdl.set_render_draw_color renderer 255 0 0 255 in
let* () =
Sdl.render_fill_rect renderer (Some (Sdl.Rect.create ~x ~y ~w:70 ~h:70))
in
()
; Sdl.render_present renderer
; Ok ()
;;
type state = { quit : bool; pos : int * int }
let initial_state = { quit = false; pos = (0, 0) }
let poll evt state =
let (x, y) = state.pos in
let default = { state with pos = (x + 2, y + 1) } in
if Sdl.poll_event (Some evt) then
match Event.(enum (get evt typ)) with
| `Quit -> { state with quit = true }
| _ -> default
else
default
;;
let run_game_loop renderer =
let rec loop evt state =
if not state.quit then (
let state = poll evt state in
let* () = render_frame renderer state.pos in
()
; Sdl.delay 17l (* approx 60 FPS *)
; loop evt state
) else
Ok ()
in
let evt = Event.create () in
loop evt initial_state
;;
let start_game () =
let* window = init_window () |> with_err_ctx None in
let* renderer =
Sdl.create_renderer window |> with_err_ctx (Some (Partial { window }))
in
let* () =
run_game_loop renderer |> with_err_ctx (Some (Full { window; renderer }))
in
Ok (window, renderer)
;;
let rec destroy_resources = function
| Some (Full { window; renderer }) ->
()
; Sdl.destroy_renderer renderer
; destroy_resources @@ Some (Partial { window })
| Some (Partial { window }) ->
()
; Sdl.destroy_window window
| None -> ()
;;
let () =
match start_game () with
| Ok (window, renderer) ->
()
; Sdl.destroy_renderer renderer
; Sdl.destroy_window window
; Sdl.quit ()
; exit 0
| Error (`Msg msg, ctx) ->
()
; Sdl.log "Error: %s" msg
; destroy_resources ctx
; Sdl.quit ()
; exit 1
;;
It took a bit of fiddling before finding something I was happy with, I’ll gladly take any criticism/feedback
If you have made a game or project with tsdl, please do tell us about it!
(personally, I’m going through the “Nature of Code” book and want to implement the exercises in many languages/libraries)