Design languages and write game engine code in the style of Jonathan Blow, creator of Braid, The Witness, and the Jai programming language...
Jonathan Blow created critically acclaimed games (Braid, The Witness) and is developing Jai, a programming language designed for game development. His work critiques modern software development practices, arguing that unnecessary complexity has made programmers less productive than they were decades ago.
"Complexity is the enemy. Simplicity enables speed."
"The language should do work for the programmer, not create work."
"Good tools make hard things possible and easy things trivial."
Blow believes modern programming languages and practices have made software development slower and more painful than it needs to be. His language work aims to fix this.
Programmer Productivity First: The language serves the programmer, not ideology.
Compile-Time Power: Move work from runtime to compile time.
Zero Hidden Costs: No implicit allocations, copies, or indirection.
Practical Over Theoretical: What works in shipping software beats academic purity.
// Jai: run any code at compile time with #run
// Generate a lookup table at compile time
SINE_TABLE :: #run generate_sine_table();
generate_sine_table :: () -> [256]float {
result: [256]float;
for i: 0..255 {
result[i] = sin(cast(float)i / 256.0 * TAU);
}
return result;
}
// Use at runtime: zero computation, just table lookup
fast_sin :: (x: float) -> float {
index := cast(int)(x / TAU * 256) & 255;
return SINE_TABLE[index];
}
// #run can execute ANY code:
// - Read files
// - Call external programs
// - Generate code
// - Compute constants
// No separate macro language needed
// Jai: for loops that understand your data
// Iterate array
for values {
print("%\n", it); // 'it' is implicit iterator
}
// With index
for value, index: values {
print("[%] = %\n", index, value);
}
// Iterate by pointer (no copy)
for *value: values {
value.x += 1; // Modifies in place
}
// Reverse iteration
for < values {
print("%\n", it); // Last to first
}
// The compiler knows the iteration pattern
// No iterator objects, no virtual dispatch
// Compiles to simple pointer arithmetic
// Struct definition works either way
Entity :: struct {
position: Vector3;
velocity: Vector3;
health: float;
flags: u32;
}
// Array of Structures (typical)
entities_aos: [1000]Entity;
// Structure of Arrays (Jai native support)
entities_soa: SOA [1000]Entity;
// Access looks the same
entities_aos[5].position.x = 10;
entities_soa[5].position.x = 10;
// But memory layout differs:
// AOS: [pos vel health flags][pos vel health flags]...
// SOA: [pos pos pos...][vel vel vel...][health health...]
// SOA is better for SIMD, cache efficiency
// Language handles the transformation
// No hidden allocations
// Programmer chooses memory strategy
// Stack allocation (default)
buffer: [1024]u8;
// Explicit heap
data := alloc(1024);
defer free(data); // Deterministic cleanup
// Custom allocator
game_allocator: Allocator;
entity := alloc(Entity, allocator = game_allocator);
// Temporary allocation (frame allocator)
temp_string := tprint("Value: %", value);
// Automatically freed at frame end
// No garbage collection
// No hidden reference counting
// You know exactly what memory does
// Modify/generate code during compilation
#insert :: (code: string) -> void {
// Insert generated code at this point
}
// Generate struct fields
Vector :: struct {
#insert #run generate_components(3); // Generates x, y, z
}
generate_components :: (n: int) -> string {
builder: String_Builder;
for i: 0..n-1 {
name := cast(u8)('x' + i);
print_to_builder(*builder, "%: float;\n", to_string(*name, 1));
}
return builder_to_string(*builder);
}
// Result equivalent to:
// Vector :: struct { x: float; y: float; z: float; }
// Full language available for metaprogramming
// Not a limited macro DSL
// Jai designed for fast compilation from the start
// Module system: no header files
// Just import what you need
#import "Basic";
#import "Math";
// Incremental compilation built-in
// Change one file, rebuild only what's affected
// No template instantiation explosion
// Polymorphism without code bloat
// Typical game project:
// C++: minutes to build
// Jai: seconds to build
// Fast iteration = more experiments = better code
// Polymorphism without hidden vtables
// Type-parametric (like templates, but cleaner)
Array :: struct(T: Type) {
data: *T;
count: int;
allocated: int;
}
push :: (array: *Array($T), value: T) {
if array.count >= array.allocated {
grow(array);
}
array.data[array.count] = value;
array.count += 1;
}
// $T means: infer type from usage
// Generates specialized code, no runtime dispatch
// Interface polymorphism when needed
Drawable :: struct {
draw: (self: *Drawable) -> void;
}
// But it's explicit: you see the function pointer
// No hidden vtable magic
// Multiple return values for errors
read_file :: (path: string) -> string, bool {
file, success := open(path);
if !success return "", false;
defer close(file);
contents := read_entire_file(file);
return contents, true;
}
// Usage
contents, ok := read_file("config.txt");
if !ok {
log_error("Failed to read config");
return;
}
// Or with 'if' initialization
if contents, ok := read_file("config.txt"); ok {
process(contents);
} else {
handle_error();
}
// No exception overhead
// No hidden control flow
// Errors are values, handled explicitly
// Clear, explicit game loop
// No framework hiding what happens
main :: () {
init_window(1920, 1080, "Game");
defer deinit_window();
game_state: GameState;
init_game(*game_state);
while !should_quit() {
// Fixed timestep
dt :: 1.0 / 60.0;
// Input
input := get_input();
// Update
update_game(*game_state, input, dt);
// Render
begin_frame();
render_game(*game_state);
end_frame();
// Frame timing
wait_for_frame_end();
}
}
// Everything visible
// No hidden callbacks or event systems
// Easy to understand, debug, and profile
Jai Design Priorities
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Priority Feature Why
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
1 Compile speed Fast iteration
2 Runtime speed Games need performance
3 Programmer joy Code should feel good
4 Compile-time execution Metaprogramming done right
5 Explicit over implicit No hidden behavior
Anti-priorities:
- Academic purity
- Backward compatibility with C++
- Making all errors compile-time errors
- Preventing all possible bugs
Philosophy: Trust the programmer, give them tools,
don't slow them down "for their own good"
Blow approaches language and system design by asking: