TL;DR
If you're using Jai and need to interface with a C library, the built-in Bindings Generator makes it super easy to generate usable Jai code from C headers. I used it to integrate the `cgltf` library into my 3D prototype, and it saved a ton of time. In this post, I’ll walk through the setup, show the script I used, and share some real output from the generated bindings.
Using the Bindings Generator in Jai
When I created Brainroll the goal was to create a game without using any libraries, it was a challenge and a learning experience but not something that I ever intended to do all the time. Sometimes the best approach is just to use a library rather than writing everything yourself. However when using a new language it is not rare for the libraries you find to not be available to you. Luckily Jai has a module available called "Bindings Generator" and it is a tool just for this specific case. This tool parses C/C++ code helping you with generating enums, structs and function declarations in order for you to call into the library using the FFI
What Are Bindings?
To generate bindings means that you're basically creating an interface to call code and use data structures from a library written in another language. An example is if you're building a simple dynamic library (.DLL or .so) you are not forced to load and call its functions through C, you can do it in almost any language.
Using cgltf with Jai
Recently, I’ve been working on a prototype for a 3D first-person game, and I wanted to save some time by using a library to load GLTF scenes. There’s no Jai library for that yet, so I looked elsewhere and found a C library called **cgltf**, which seemed solid. I decided to give it a try.
Since it is a C library it is quite trivial to set up a Jai script that generates bindings for it. This is the code I started out with:
generate_bindings :: () -> bool {
source_file := tprint("%/cgltf.c", SOURCE_PATH);
success := true;
extra: [..] string;
array_add(*extra, "-fPIC");
success &&= build_cpp_static_lib(lib_path, source_file, target = .WINDOWS, debug = false, extra = extra);
if !success
return false;
options: Generate_Bindings_Options;
options.os = .WINDOWS;
options.cpu = .X64;
{
using options;
array_add(*libnames, "cgltf");
array_add(*source_files, tprint("%/cgltf.h", SOURCE_PATH));
array_add(*typedef_prefixes_to_unwrap, "cgltf_");
generate_library_declarations = false;
try_to_preserve_comments = true;
omit_global_declarations = false;
auto_detect_enum_prefixes = true;
log_stripped_declarations = true;
generate_compile_time_struct_checks = false;
}
output_filename := "bindings.jai";
return generate_bindings(options, output_filename);
}
The first part lets us specify flags when building the library into a `.lib` file. Jai provides functions like `build_cpp_static_lib` and `build_cpp_dynamic_lib` to help with this. This code uses clang I believe to compile the C code with the flags we give it.
The second part is where the bindings generator comes in. If you already have prebuilt libraries, you can skip the first step and only use the generator. It works just as simply. You provide system and architecture info, file paths, and tweak a set of options to customize the output. For example, I had it try to preserve comments.
Some output from the generated bindings
Here is some examples of what was output in the generated bindings:
cgltf_file_type :: enum s32 {
invalid :: 0;
gltf :: 1;
glb :: 2;
max_enum :: 3;
cgltf_file_type_invalid :: invalid;
cgltf_file_type_gltf :: gltf;
cgltf_file_type_glb :: glb;
cgltf_file_type_max_enum :: max_enum;
}
...
cgltf_buffer :: struct {
name: *u8;
size: u64;
uri: *u8;
data: *void; /* loaded by cgltf_load_buffers */
data_free_method: cgltf_data_free_method;
extras: cgltf_extras;
extensions_count: u64;
extensions: *cgltf_extension;
}
...
cgltf_parse :: (options: *cgltf_options, data: *void, size: u64, out_data: **cgltf_data) -> cgltf_result #foreign cgltf;
cgltf_parse_file :: (options: *cgltf_options, path: *u8, out_data: **cgltf_data) -> cgltf_result #foreign cgltf;
As you can see we're getting enumerated types, data structures as well as functions from the public interface in the library. The way of calling a foreign function in Jai is done by using the `#foreign` directive followed by an identifier. The identifier is something that you can declare as a constant somewhere where you're using it. For example in my case I have:
cgltf :: #library,no_dll "cgltf";
This creates a global constant named `cgltf`, which refers to the compiled library.
Notes on C++ libraries
Using bindings like this is a great way to speed up development by leveraging existing tools. I also tried generating bindings for C++ libraries, but that didn’t go as smoothly. As a workaround, I wrote a C wrapper for the C++ library and then generated bindings for that instead. Even with the extra step, it was still a time-saver compared to writing the whole thing myself.
For me I wanted to have gizmos in my 3D model viewer to give users a friendly way with modifying size, rotation and position of models. I found a library called tinygizmo this library uses a math library with abuses C++ templates which makes the bindings generator confused. Because I didn't find any other good C alternatives for libraries I decided to just to wrap all the C++ code with C functions and explicitly only export the C interface which worked. However for this library I precompiled the libraries using visual studio and only had bindingsgenerator generate bindings nothing else. The code for the C wrapper can be found in my fork here: tinygizmo fork.
Final thoughts
This way of interacting with C in Jai is really one of the make it or break it features that I need in order to use the language for something productive. I really love the bindings generator and I will continue experimenting with it when making progress on my prototype.
If you are interested in seeting the full bindings generator and generated bindings for cgltf I have published a gist with the full code I use in my project here: gist.