Looking for example tsdl games

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

1 Like

You can ignore (Sdl.fn args); or Sdl.fn args |> ignore; if you don’t want to handle the trivial error cases.

In practice the number of Sdl calls in code tends to be small, with abstractions, so the explicit ignore isn’t too onerous.

I think Tsdl is a good choice, or Tgls on top of that if you want to use OpenGL.

1 Like

The rresult package provides two options:

  1. 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.
  2. 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.
1 Like

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?

1 Like

thanks, that’s good to know! i’ll give it a try and see.

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 have no idea but it seems to be in @fdopen’s OCaml for windows repository.

Hi Daniel, I’m not sure but maybe you should add a main in the C stub, because there is this about SDL_main in the FAQWindows

You can see that in ocamlsdl2 in file sdlinit_stub.c at line 19 there is a C main() for this reason.

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[]) { }

to the file: src/tsdl_stubs.c

I won’t try to install Tsdl on Windows because of the deps, but I can make a try on Linux, I added:

#include "SDL.h"
int main(int argc, char *argv[]) { }

to the file src/tsdl_stubs.c then the command:

ocamlfind ocamlc -g -thread -c src/tsdl_stubs.c

Should become:

ocamlfind ocamlc -g -thread -c -ccopt -I/usr/include/SDL2 src/tsdl_stubs.c

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:

#include "SDL2/SDL.h"
int main(int argc, char *argv[]) {
    caml_main(argv);
    return 0;
}

Then it seems to work fine.

This one is for the search engines…

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 :slight_smile:

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)

1 Like

Take a look at my Railroad Tycoon reimplementation.

2 Likes