This post will require some context, as I doubt many people are aware of what JAI is, or why they should even care.
The TL;DR on JAI is that it's a programming language being developed by Jonathan Blow with an eye towards using it for video game development. JAI is interesting for several reasons. Jon Blow, as a successful game developer, is one of them; he’s basically his own best use case. The other is that Jon presents and discusses his language in depth via streams (and recordings on YouTube), which is where it caught my attention.
My post here was triggered by watching his "Data-Oriented Demo”. I assumed he was going to talk about Entity Component Systems, which I played with in my minimalist experiments. But instead it reminded me of Cobol (an occupational hazard, in my case) and JIT compilation...
By all means watch Jon's presentation first, as I will butcher the details in favour of some brevity. You really want to hear the nitty gritty from the creator of the language anyway. Instead I’ll just focus on my two random connections.
In the data-oriented demo Jon is showing us the syntax and semantics of the “using” keyword. Here is a brief example, based on his presentation:
Entity :: struct {
position: Vector3;
}
Human :: struct {
using entity : Entity;
name: string;
}
If you’re familiar with programming —and what are you doing reading my blog if you’re not ?— you’ll see that this defines two datatypes, Entity and Human, where Human has a nested instance of Entity. Ignoring the “using” keyword for now you could write:
print(human.entity.position.x)
and expect the X coordinate of a Human’s position to be printed.
The “using” keyword does two things (in my understanding of it). One is that it lifts the members in the namespace of the given type into the current namespace. So any member in Entity can be addressed as if they belonged to Human, meaning you could shorten the above statement to:
print(human.position.x)
This gives you the effect of a class-like behaviour without actually having used classes. It also makes the general code more robust to changes. I.e. you could move the “position” from Entity to Human without problem, or even introduce an extra type in between to keep hold of it.
Now, of course, this makes us think of Cobol. —Though it may just be me.— Because the effect is one of allowing for "structure-shy qualifiers”. That is, as long as there is an ‘x’ member found somewhere in a ‘position’ member found somewhere in an instance of Human the code will cope with changes. Well in Cobol you can already do this (minus the types) as follows:
DISPLAY X OF POSITION OF HUMAN.
Cobol does not care about the exact path from ‘HUMAN’ to ‘X’, as long as there is only one entry in the structure which matches the selector. Without any more ‘using’ keywords we could as easily have written:
DISPLAY X OF HUMAN.
How is that for robustness to changes ?
So, to some extent, the introduction of the ‘using’ keyword feels like an explicit way of making qualifiers structure-shy. Why not just make all qualifiers behave that way implicitly as in Cobol ?
But there is another side to the “using” keyword, and that is to add flexibility in how data is managed and stored in memory. Let’s make one small modification to the Human type:
Human :: struct {
using entity : ^Entity;
name: string;
}
Rather than nesting an Entity inside it, this time a Human has a pointer to an Entity instance elsewhere. The general code, and the above print statements, will still work as expected. The crucial point is that the Entity can be part of an array. That is, all Entities can be grouped together in memory and that capability, if you have read anything about game engine design, is essential for ensuring performance. What JAI allows through its ‘using’ keyword is changing the memory layout relatively easily without having to update most of the code. (Again, check Jon’s presentation for more advanced examples.)
Being able to experiment with different memory management models in a way which is (mostly) transparent to your code is an amazing feature, and something I have not seen before. We have been moving away from manual memory management because it is error prone and cuts through all of your code. Yet in JAI there’s actually some good motivation to get back to handling things yourself.
But then I made my second connection, and a pretty wild one at that: if changing memory management models for different types becomes so painless, could we not get the runtime to do it dynamically and based on actual runtime statistics ? Think of JIT compilation: depending on critical paths in the code found at runtime it will optimise those paths dynamically and transparently, giving you better performance. Now imagine getting JIT behaviour not just for your code, but for your data as well...
Could JAI be setting us on a path to such a magical world ?