My Experience With Jai (Part 1)

I recently got access to the beta of a new programming language called “Jai.” My goal is to try converting my existing set of C files that I collectively call “Pig Engine” over to Jai in order to get a sense for how the language works and also find places where my approach to the same problems may differ in Jai (hopefully in a positive way) compared to C/C++.

Along the way I figure it might be useful to keep a running dialogue of my thoughts on various features and how I approach learning and using the language. I don’t use this blog much, so I figure almost no one will read this, but I think it’ll still be a helpful practice for me.

part 1: Reading HOW_To’s

The language beta comes with a folder called “how_to” which has around 70 files worth of examples code and explanations for how the language works. So I figure before I start trying to write anything, I should at least read some of these files. Mostly starting from the beginning (jumping forward to particular sections out of order when I wanted to know something specific about the language) I read through about 15/66 files before switching to proper work in the language.

Although it’s a lot of reading, I found myself pleasently surprised at how many features of the language line up with my experience of the hard problems I encounter while making games.

As a simple example, the 013_enums.jai file explains the syntax for enums and one of the “new” features this language provides is a tag you can put on enums called "#specified.” The whole purpose of the tag is to force the programmer to specify every enum value explicitly (no implicit value assignment) and generally act as a callout that the exact values of this enum are being used for serialization and changing/shifting the values of existing entries is probably a bad idea unless you go change this serialization code. Serializing enums might sound like a random niche problem, but I actually have run into this many times. The choice of whether to serialize the integer value or the string name is a tricky one, particularly if your game goes through an early access period with user generated content that you need backwards compatibility for, while still actively developing the game and changing enums. Having this one small feature really goes a long way to making this particular type of problem recognized as a thing that should be considered. In a lot of ways it’s not much more than a comment with a small amount of compiler backing to enforce specified values, but the fact that it exists in the language means that it can become a standard idea with a commonly understood name, and I think that’s pretty valuable.

Part 2: Converting Gylib (Dependency Order)

Alright so now that we’ve got an understanding of the basic features that the language has, I started trying to convert my common library code into Jai. For dumb reasons, this library is called “Gylib” and it includes everything that I use in basically every project; from Vector structs and math to UTF-8 conversion functions. For a somewhat full list of features you can check out the block diagram for Gylib that’s on the Pig Engine page.

So if we want to “port” Pig Engine to Jai, I figured the best place to start would be this common library. The files are largely organized by “features” but they also have had a dependency order baked into them because C does not allow out of order compilation. And since they were all built in a single header style fashion, I was forced to make sure that they had a #include order that would work. So we’re going to start from the files that are near of the top of the dependency chain and work our way downwards. However, as we go along we may find that a file near the bottom of the chain can actually get split and merged with upper level files now since they used to harbor functions that would fit, logically, in another file. But because they were dependent on a item lower in the chain, they had to be separated and given a name. Here’s an incomplete diagram that roughly shows that dependency order of many of the files:

A good example of something that might get merged is gy_sorting.h. Because the concept of sorting is not uniquely owned by gy_variable_array.h, gy_bucket_array.h and gy_linked_list.h it generally has to get split into another file and then the specific applications of sorting on each data structure type has to get jammed into a very dependent file near the bottom (for example, gy_extras.h, which got missed in the diagram for time constraints but is literally just things that couldn’t fit in upper files because they depend on two or more items in an unsavory way)

So starting from the top we have gy_defines_check.h which mostly translates directly to our module.jai since that’s where we can define our module_parameters. Fortunately some of the defines we’d normally need to enforce ourselves (like WINDOWS_BUILD) are actually nicely handled by compiler constants. So the main things we have left are two defines

DEBUG_BUILD: Enables debug only macros like DebugAssert. This is often tied to whether we want the program to be compiled in an optimized way, but it’s really just “Am I compiling this for me to test right now or to distribute or transfer for someone else to use.” So it’s often nice to control this define ourselves, even if the compiler provides some way to detect if we are compiling for “release”

ASSERT_FAILURE_FUNC: This one is a bit interesting in jai. Essentially it’s nice to have a function run when an assertion fails BEFORE we hit the breakpoint/abort. This function needs to be implemented on a per-application basis but the Gylib library provides all the common Assert() #defines and it’s variants. So we need a way to tell it what function to call when an assertion fails. In C we used to just do extern AssertFailureFunc(…) in the gy_assert.h and that would force you to implement the function elsewhere. In Jai we can actually pass in the function as a properly typed variable

Here’s our module_parameters directive in module.jai

#module_parameters (DEBUG_BUILD := false, ASSERT_FAILURE_FUNC : (message: string) -> bool = null);

Besides that our module.jai #imports a couple common modules like “Basic” and “Math” and then #loads all of our gy_[whatever].jai files.

Next up on the chopping block is gy_std.h which (I hope) is mostly going to be unnecessary. This file mostly handles aliasing standard library functions with our own names (like memcpy becomes MyMemCopy) simply so we can reroute these standard library calls to our implementations or other stdlib implementations in certain contexts (web assembly for example). For the time being, we are going to trust that we can solve this problem later, given the tools that jai provides us when loading modules.

Next we have gy_basic_macros.h which has a bunch of #define function-like macros for common tasks we want function-like syntax for. For example ArrayCount(array) which is just an alias for sizeof(array)/sizeof(array[0]). Luckily arrays in jai come with length info so this is not needed. Also macros in general are actually just functions with the #expand directive attached, so many of the “macros” in this file are going to get bumped to gy_intrinsics.h because the distinction between the two is pointless now (we no longer have a preprocessor that acts like a second language. Macros ARE functions). So yay, another file down.

Next up, gy_types.h. This file mostly deals with #including stdint.h and defining my own naming convention for fixed size types like int32_t becomes i32, uint32_t becomes u32, float becomes r32, and so forth. Jai, luckily, has some built-in, fixed size, types with really similarly short names. Unfortunately i32 is actually s32 in Jai. And r32 is actually float32 (not very short). For now I think we’ll try using this naming convention and forego porting any of gy_types.h. If we want our own naming convention back, we can do a find and replace on our files later to convert from s32 to i32 and float32 to r32.

So that brings us to our first dependency level that has more than 1 file. I think that’s a good place to wrap up this blog post for now (running out of time to write this). In the next post we’ll take a look at vectors, rectangles, directions, and colors and see how these common type implementations map to Jai.