Hello, I’ve completed my first game using SDL3 and Zig. I wrote a little article with some of the challenges I faced My first complete game.
I can’t help but say that the experience was amazing for a programmer who regularly works on web backends. Using Zig felt so right! You might notice that all of the dependencies of the project are C libraries. I know about All Your Codebase · GitHub, but there were some incompatibilities. Nevertheless, I just compiled the C libraries and used them! There were some inconveniences related to the code writing itself. For example: ZLS stopped providing completions for the libraries at some moment. However, before the moment the experience was as if I used native libraries! Another inconvenience, which I remember, was a compilation error when I used a pointer to some structure from SDL3. If the structure was defined in the generated binding for SDL3 and SDL3 ttf, I got a compilation error about incompatibility of those structures. Initially, I did @ptrCast, because the structure was the same. However, later I realized that I don’t need to import the header form SDL3 and went on with the header from SDL3 TTF only.
In comparison with Rust, I’m currently working on another project where I decided to use SDL3 without any binding off the shelf. The process is a bit more complicated. I had to use an additional tool: Rust bindgen to generate the code with the functions from the C headers. The bindgen failed to create some of the constants (like SDL_WINDOW_RESIZABLE or SDL_EVENT_QUIT). Therefore, I have to write them manually.
By the way, there was one more thing I couldn’t figure out. It’s about Zig Build System. As I’m using std.Build.Step.Run to compile the shared objects of SDL3, I don’t understand how to run the command only if libSDL3.so does not exist. The command runs fast enough if the file is ready, but I’d like to avoid the running. Is it possible?
Good article! I liked how thoroughly you described your learning process and journey of discovery.
std.Build.Step.Run wants commands to be “pure” and free from side effects, i.e. they want you to provide an input that the build system can hash and detect changes in with addFile/Directory/ArtifactArg() and then capture an output with addOutputFile/DirectoryArg() or captureStdOut/Err() in order to be fully integrated with the cache system. If your do all of this, the command will only be rerun when any of the inputs change.
However, this isn’t how CMake or other make-ish build systems normally operate, they prefer creating a build directory that they then modify in-place each time you invoke their commands. This means that you might need to pull the “configure/build/install” commands out into a separate (shell) script that can be invoked like build.sh <path_to_source_directory> <path_to_output_directory> in order to be able to integrate it with the Zig build system caching. You might need to use CMake’s --prefix argument to install the build shared objects to a specific directory and then dig a file out from that generated directory with LazyPath.path().
Another limitation is that currently, the contents of directories added with addDirectoryArg() (or any of the sibling functions) aren’t actually hashed or checked for changes, so any modifications to files within those directories won’t cause the step to be rerun. For your specific use case this might not be a problem, but it’s good to be aware of.