├── JaiPrimer.md └── Undocumented.md /JaiPrimer.md: -------------------------------------------------------------------------------- 1 | Introduction 2 | ============ 3 | 4 | NOTE: Jai is created by Jon Blow. I, the person who made this guide, am not Jon Blow. I did my best to capture the spirit of Jai from the first few videos Jon made about it in 2015 or so. Since then Jon has said that he doesn't think the contents of this document describe Jai well or his intentions with it. The best source of information about the language is its designer, Jon Blow. 5 | 6 | Jai is a high-level programming language developed by [Jonathan Blow](https://twitter.com/Jonathan_Blow), creator of indie games _Braid_ and, most recently, _The Witness_. It is an imperative static/strongly typed C-style language, but with a variety of modern language features that C lacks. Blow began work on Jai in late September 2014. It is still in development and as of yet is unavailable to the general public. Blow developed it with an eye towards video games, but in fact it’s a general purpose programming language that could be used for any task. 7 | 8 | **Disclaimer:** I have no association with Jon Blow. As of this writing there are no public compilers for Jai, so all information in this text is collated from his [YouTube videos](https://www.youtube.com/playlist?list=PLmV5I2fxaiCKfxMBrNsU1kgKJXD3PkyxO). Therefore nothing in this post is official. There may be information more up to date than what is available on this page. That said, I believe everything in this post to be up to date as of this writing. (If you are Jon Blow and want me to correct anything in this post, [I would be happy to](http://twitter.com/VinoBS)). 9 | 10 | Everything in this document, unless otherwise noted, is implemented and currently working in the (currently private) prototype. As it is not yet released, everything in this document is subject to change. 11 | 12 | Brief Description 13 | ----------------- 14 | 15 | In short, Jai could be described as a modern replacement for C. Some of the coolest features: 16 | 17 | - **Arbitrary compile-time code execution** – Any function of the program can be made to run at compile time with `#run` 18 | - **Syntax-facilitated code refactoring** – The language syntax facilitates code reuse by making it easy to move code from local block → local function → global function 19 | - **Integrated build process** – The build process and parameters are specified by the source code itself, for consistency 20 | - **Data-oriented structures** – Automatic conversion between Structure of Arrays and Array of Structures, avoids classes and inheritance 21 | - **Reflection and run-time type information** – Static type information for every structure available at runtime 22 | - **A new approach to polymorphic procedures** – Polymorphism at the function level, with programmer control by special procedures 23 | - **Low-level memory management tools** – Better control over how libraries allocate memory, automatic ownership management, no garbage collection 24 | - **Explicit control over optimization and performance characteristics** – Explicit control over things like inlining, bounds checking, and initialization 25 | 26 | The Philosophy of Jai 27 | ===================== 28 | 29 | **THE JOY OF PROGRAMMING** 30 | 31 | At some point after programming for many years, the line between “exciting programming adventure” and “please not another code refactoring” can start to disappear. Having to update the function declaration in the header when you change its signature gets old fast. When C was originally written in 1973, there was a good reason for all that header stuff, but today there isn’t. 32 | 33 | Quality of life improvements afforded by the language can have quantifiable benefits to the productivity of the programmer using the language. (If you aren’t convinced of that, try programming anything in [Brainfuck](http://en.wikipedia.org/wiki/Brainfuck).) Compiling should be fast if not instantaneous, refactoring code should require a minimum of changes, and error messages should be helpful and pleasant. The belief is that an improvement of the tools that programmers use can produce more than a 20% increase in productivity, and this was a primary motivation in creating a new language. 34 | 35 | **MACHINES THAT FILL MEMORY** 36 | 37 | The original designer said that "video games are machines that fill memory". The majority of the time, game programmers are thinking about how to fill memory with huge reams of data in ways that allow the data to be efficiently accessed and processed. Hundreds of megabytes of memory must be moved from the hard disk into main memory, and from there into the video card or the processor cache to be processed and returned back to main memory. Because video game players don’t like to wait, all this must be done as fast as is allowable by laws of our universe. The primary purpose of a programming language is to allow the specification of algorithms to manage data. Language features like garbage collection and templated data streams and dynamic string classes may help the programmer write code faster, but they don’t help the programmer write faster code. 38 | 39 | **FRICTION REDUCTION** 40 | 41 | Another major design goal of Jai is to reduce friction when programming. Friction happens when the syntax of a language interferes with the programmer’s workflow. For example: 42 | - Java requires that all objects be classes, forcing programmers to put the global variables they need into global classes. 43 | - Haskell requires that all procedures be functions and have no side effects. 44 | - C++’s lambda function syntax is different from its class method syntax, which is itself different from its global function syntax. 45 | 46 | Java, Haskell and C++ are examples of what could be called “big agenda” languages, where the languages' idealism (and in C++’s case, its lack of a consistent vision) gets in the programmer’s way. Jai is being designed with a low tolerance for friction, especially when it is unnecessary. 47 | 48 | **DESIGN FOR GOOD PROGRAMMERS** 49 | 50 | Jai is a language designed for good programmers, not against bad programmers. Languages like Java were marketed as idiot-proof, in that it’s much more difficult for programmers to write code that can hurt them. The Jai philosophy is, if you don’t want idiots writing bad code for your project, then don’t hire any idiots. Jai allows programmers direct access to the sharp tools that can get the job done. Game programmers are not afraid of pointers and manual memory management. Programmers do make mistakes and cause crashes, perhaps even serious ones, but the argument is that the increase in productivity and reduction of friction when memory-safe mechanisms are absent more than make up for the time lost in tracking down errors, especially when good programmers tend to produce relatively few errors. 51 | 52 | **PERFORMANCE AND DATA-ORIENTED PROGRAMMING** 53 | 54 | If as a programmer you care about user experience (which you should), then you should care about the performance of your program. You should reason about your code’s behavior on the range of machines that you’re shipping on, and design your data and control structures to use that hardware’s capability most efficiently. (Here I’m describing [Mike Acton’s “Data-Oriented Design” methodology](https://www.youtube.com/watch?v=rX0ItVEVjHc).) Programmers who care about the performance of their software on their target hardware are inhibited by programming languages that sit between them and the hardware. Mechanisms like virtual machines and automatic memory management interfere with the programmer’s ability to reason about the program’s performance on the target hardware. Abstractions like RAII, constructors and destructors, polymorphism, and exceptions were invented with the intention of solving problems that game programmers don’t have, and with the result of interfering with the solutions to problems that game programmers do have. Jai jettisons these abstractions so that programmers can think more about their actual problems—the data and their algorithms. 55 | 56 | Jai Language Features 57 | ===================== 58 | 59 | Types and Declarations 60 | ---------------------- 61 | 62 | The syntax `name: type = value;` specifies that a variable named `name` is of the type `type` and is to receive the value `value`. It was proposed by [Sean Barrett](https://twitter.com/nothings). Some examples: 63 | 64 | ```cpp 65 | counter: int = 0; 66 | name: string = "Jon"; 67 | average: float = 0.5 * (x+y); 68 | ``` 69 | 70 | If the type is omitted then the compiler infers it based on the value: 71 | 72 | ```cpp 73 | counter := 0; // an int 74 | name := "Jon"; // a string 75 | average := 0.5 * (x+y); // a float 76 | ``` 77 | 78 | If the value is omitted then you have a declaration without an initialization. 79 | 80 | ```cpp 81 | counter: int; 82 | name: string; 83 | average: float; 84 | ``` 85 | 86 | All of this is probably backwards from what you’re used to, but the learning curve is shallow and you get used to it quickly. Function declarations look like this: 87 | 88 | ```cpp 89 | // A function that accepts 3 floats as parameters and returns a float 90 | sum :: (x: float, y: float, z: float) -> float { 91 | return x + y + z; 92 | } 93 | 94 | print("Sum: %\n", sum(1, 2, 3)); 95 | ``` 96 | 97 | and structure declarations like this: 98 | 99 | ```cpp 100 | Vector3 :: struct { 101 | x: float; 102 | y: float; 103 | z: float; 104 | } 105 | ``` 106 | 107 | Arrays can be created like this: 108 | 109 | ```cpp 110 | a: [50] int; // An array of 50 integers 111 | b: [..] int; // A dynamic array of integers 112 | ``` 113 | 114 | Arrays do not automatically cast to pointers as in C. Rather, they are “wide pointers” that contain array size information. Functions can take array types and query for the size of the array. 115 | 116 | ```cpp 117 | print_int_array :: (a: [] int) { 118 | n := a.count; 119 | for i : 0..n-1 { 120 | print("array[%] = %\n", i, a[i]); 121 | } 122 | } 123 | ``` 124 | 125 | Retaining the array size information can help developers avoid the pattern of passing array lengths as additional parameters and assist in automatic bounds checking (see _Walter Bright – C’s Biggest Mistake_). 126 | 127 | Arbitrary Compile-Time Code Execution 128 | ------------------------------------- 129 | 130 | Suppose I want to write a function in C that converts a linear color value to [sRGB](http://en.wikipedia.org/wiki/SRGB). This involves the `pow()` function, which is on the expensive side. We can avoid `pow()` by doing the calculation ourselves instead and distributing the results as part of our program. So we write a table of values and return those. 131 | 132 | ```cpp 133 | #define SRGB_TABLE_SIZE 256 134 | float srgb_table[SRGB_TABLE_SIZE] = { /* ... values here ... */ } 135 | 136 | float linear_to_srgb(float f) 137 | { 138 | // Find the index in our table for this SRGB value, 139 | // assuming f is in the range [0, 1] 140 | int table_index = (int)(f * SRGB_TABLE_SIZE); 141 | return srgb_table[table_index]; 142 | } 143 | ``` 144 | 145 | (_Note:_ The above is bad code, only used for example. For better code, try [stb_image_resize’s sRGB functions](https://github.com/nothings/stb/blob/master/stb_image_resize.h).) So far so good, except how will we get the values for the srgb_table? We can write another small program that outputs values. For example: 146 | 147 | ```cpp 148 | float real_linear_to_srgb(float f) 149 | { 150 | if (f <= 0.0031308f) 151 | return f * 12.92f; 152 | else 153 | return 1.055f * (float)pow(f, 1 / 2.4f) - 0.055f; 154 | } 155 | 156 | #define SRGB_TABLE_SIZE 256 157 | 158 | int main(int c, char* s) { 159 | printf("float srgb_table[SRGB_TABLE_SIZE] = { "); 160 | for (int i = 0; i < SRGB_TABLE_SIZE; i++) 161 | printf("%f, ", real_linear_to_srgb((float)i/SRGB_TABLE_SIZE)); 162 | printf("}\n"); 163 | return 0; 164 | } 165 | ``` 166 | 167 | We can compile this small program, which will output a table of sRGB values, and then we can copy the output into our actual program. 168 | 169 | This is a big bucket of problems with it. For example, notice how `SRGB_TABLE_SIZE` is defined twice, once in the actual program and once in the helper program. So we now have to maintain two separate source codes. This can get unwieldy for large programs. 170 | 171 | In Jai, the same task looks like this: 172 | 173 | ```cpp 174 | generate_linear_srgb :: () -> [] float { 175 | srgb_table: float[SRGB_TABLE_SIZE]; 176 | for srgb_table { 177 | << it = real_linear_to_srgb(cast(float)it_index / SRGB_TABLE_SIZE) 178 | } 179 | return srgb_table; 180 | } 181 | 182 | srgb_table: [] float = #run generate_linear_srgb(); // #run invokes the compile time execution 183 | 184 | real_linear_to_srgb :: (f: float) -> float { 185 | table_index := cast(int)(f * SRGB_TABLE_SIZE); 186 | return srgb_table[table_index]; 187 | } 188 | ``` 189 | 190 | The `#run` directive instructs Jai to run the function `generate_linear_srgb()` at compile time. Jai’s compile time function execution runs the command at compile time and returns a table of values, which is then compiled directly into the binary as `srgb_table`. When the program is run, the `generate_linear_srgb()` function no longer exists. Only the table it generated exists, which is used by `linear_to_srgb()`. 191 | 192 | The compile-time function execution has very few limitations; in fact, you can run arbitrary code in your code base as part of the compiler. The first demonstration of Jai shows how to run [an entire game as part of the compiler](http://youtu.be/UTqZNujQOlA?t=43m57s), and bake the data from the game into the program binary. (I hope `#run invaders();` is shipped with the language.) The compiler builds the compile-time executed functions to a special bytecode language and runs them in an interpreter, and the results are funneled back into the source code. The compiler then continues as normal. 193 | 194 | Here are some examples of things that a compile-time function could do: 195 | 196 | - Compile-time asserts 197 | - Run test cases 198 | - Do code style checks 199 | - Dynamically generate code and insert it to be compiled 200 | - Insert build time data 201 | - Download the OpenGL spec and build the most recent gl.h header file 202 | - Contact a build server and retrieve/send build data 203 | - Talk to your Mars probe on Mars and wait for the packets to come back and get a photo of what Mars looks like 204 | 205 | Code Refactoring 206 | ---------------- 207 | 208 | All code begins its life in some kind of code block like this before moving on to be used in more general cases. Jai has some special syntaxes that can assist the programmer in moving code from specific cases out into general cases, to facilitate code reuse. 209 | 210 | As an example, let’s say you’re writing some code like this: 211 | 212 | ```cpp 213 | draw_particles :: () { 214 | view_left: Vector3 = get_view_left(); 215 | view_up: Vector3 = get_view_up(); 216 | 217 | for particles { 218 | // Inside for loops the "it" object is the iterator for the current object. 219 | particle_left := view_left * it.particle_size; 220 | particle_up := view_up * it.particle_size; 221 | 222 | // m is a global object that helps us build meshes to send to the graphics API 223 | m.Position3fv(it.origin - particle_left - particle_up); 224 | m.Position3fv(it.origin + particle_left - particle_up); 225 | m.Position3fv(it.origin + particle_left + particle_up); 226 | m.Position3fv(it.origin - particle_left + particle_up); 227 | } 228 | } 229 | ``` 230 | 231 | These mesh generation calls are actually a special case of some general quad rendering, so they can be factored out into another function so they can be used in other places. Jai makes this refactoring very straightforward. The first step is to enclose the code in a new scope with a special capture syntax. 232 | 233 | ```cpp 234 | particle_left := view_left * it.particle_size; 235 | particle_up := view_up * it.particle_size; 236 | origin := it.origin; 237 | 238 | [m, origin, particle_left, particle_up] { 239 | m.Position3fv(origin - particle_left - particle_up); 240 | m.Position3fv(origin + particle_left - particle_up); 241 | m.Position3fv(origin + particle_left + particle_up); 242 | m.Position3fv(origin - particle_left + particle_up); 243 | } 244 | ``` 245 | 246 | (_Disclaimer:_ This step hasn’t been implemented yet. It’s one of the planned features.) The `[m, origin, particle_left, particle_up]` notation is a capture that prevents any object not in the capture from being accessed inside the inner scope of the new bracket. Notice that we had to change `it.origin` to `origin` and add `origin` to the capture list—`it` is not captured and is unavailable inside the inner scope. 247 | 248 | Captures help in refactoring code as we’re seeing here but they can also help in other ways. For example, when programmers are moving code from being singlethreaded to multithreaded, captures could enforce that only thread-local data is accessed. Captures are an insurance policy that the code inside the capture only reads or writes the state specified in the capture. 249 | 250 | Now we’ve identified all of the parts of our code that depend on external things, so we’ve improved our code’s hygiene and made it easy to pull this code out into its own function. Now we want to continue so that we can use the quad drawing code in other places. So we create a function out of this block capture: 251 | 252 | 253 | ```cpp 254 | particle_left := view_left * it.particle_size; 255 | particle_up := view_up * it.particle_size; 256 | origin := it.origin; 257 | 258 | () [m, origin, particle_left, particle_up] { 259 | m.Position3fv(origin - particle_left - particle_up); 260 | m.Position3fv(origin + particle_left - particle_up); 261 | m.Position3fv(origin + particle_left + particle_up); 262 | m.Position3fv(origin - particle_left + particle_up); 263 | } (); // Call the function 264 | ``` 265 | 266 | Notice how the only change we needed to make was to add the function syntax `()`. The capture remained intact. So we went from a blocked capture to a function with very little effort. Now if we like we can move the vectors to be function parameters: 267 | 268 | ```cpp 269 | (origin: Vector3, left: Vector3, up: Vector3) [m] { 270 | m.Position3fv(origin - left - up); 271 | m.Position3fv(origin + left - up); 272 | m.Position3fv(origin + left + up); 273 | m.Position3fv(origin - left + up); 274 | } 275 | ``` 276 | 277 | With parameter names we’re able to change the names of the variables inside the function’s scope to match their new function. Now we can use this function to draw any type of quad, not just particles. The capture retains `m` because it is a global object that doesn’t need to be passed as a parameter. And now we have an anonymous, locally scoped function that can be used in our draw code: 278 | 279 | ```cpp 280 | draw_particles :: () { 281 | view_left: Vector3 = get_view_left(); 282 | view_up: Vector3 = get_view_up(); 283 | 284 | for particles { 285 | particle_left := view_left * it.particle_size; 286 | particle_up := view_up * it.particle_size; 287 | 288 | (origin: Vector3, left: Vector3, up: Vector3) [m] { 289 | m.Position3fv(origin - left - up); 290 | m.Position3fv(origin + left - up); 291 | m.Position3fv(origin + left + up); 292 | m.Position3fv(origin - left + up); 293 | } (origin, particle_left, particle_up); // Call the function with the specified parameters 294 | } 295 | } 296 | ``` 297 | 298 | Anonymous functions are useful for passing as arguments to other functions, and this syntax makes them easy to create and manipulate. The next step is to give our function a name: 299 | 300 | ```cpp 301 | draw_quad :: (origin: Vector3, left: Vector3, up: Vector3) [m] { 302 | m.Position3fv(origin - left - up); 303 | m.Position3fv(origin + left - up); 304 | m.Position3fv(origin + left + up); 305 | m.Position3fv(origin - left + up); 306 | } 307 | 308 | draw_quad(origin, particle_left, particle_up); 309 | ``` 310 | 311 | Now we could call it multiple times in the local scope, if we like. But we want to access our quad drawing function from the global scope. Moving the function out of the local scope requires zero changes to the function’s code: 312 | 313 | ```cpp 314 | draw_quad :: (origin: Vector3, left: Vector3, up: Vector3) [m] { 315 | m.Position3fv(origin - left - up); 316 | m.Position3fv(origin + left - up); 317 | m.Position3fv(origin + left + up); 318 | m.Position3fv(origin - left + up); 319 | }; 320 | 321 | draw_particles :: () { 322 | view_left: Vector3 = get_view_left(); 323 | view_up: Vector3 = get_view_up(); 324 | 325 | for particles { 326 | particle_left:= view_left * it.particle_size; 327 | particle_up:= view_up * it.particle_size; 328 | 329 | draw_quad(particle_left, particle_up, origin); 330 | } 331 | } 332 | ``` 333 | 334 | The strength of Jai’s function syntax is that it doesn’t change whether the function is an anonymous function, a local function (i.e. lives inside the scope of another function) a member function of a class or a global function. This is in contrast to in C++, where a local function is called a lambda, and has completely different syntax than a member function, which must have a class name and `::` etc, which is slightly different syntax than a global function which has no class name or `::`. The result is that as code matures and moves from a local context to a global context, the work of refactoring can be done with minimal edits. 335 | 336 | Here is Jai’s code maturation process in full: 337 | 338 | ```cpp 339 | { ... } // Anonymous code block 340 | [capture] { ... } // Captured code block 341 | (i: int) -> float [capture] { ... } // Anonymous function 342 | f :: (i: int) -> float [capture] { ... } // Named local function 343 | f :: (i: int) -> float [capture] { ... } // Named global function 344 | ``` 345 | 346 | Integrated Build Process 347 | ------------------------ 348 | 349 | All information for building a program is contained within the source code of the program. Thus there is no need for a `make` command or project files to build a Jai program. As a simple example: 350 | 351 | ```cpp 352 | build :: () { 353 | build_options.executable_name = "my_program"; 354 | print("Building program '%'\n", build_options.executable_name); 355 | build_options.optimization_level = Optimization_Level.DEBUG; 356 | build_options.emit_line_directives = false; 357 | 358 | update_build_options(); 359 | 360 | // Jai will automatically build any files included with the #load directive, 361 | // but other files can also be manually added. 362 | add_build_file("misc.jai"); 363 | add_build_file("checks.jai"); 364 | } 365 | 366 | #run build(); 367 | ``` 368 | 369 | When the program is built, the #run directive runs build() at compile-time. Then `build()` establishes all of the build options for this project. No external build tools are required, all build scripting is done within Jai, and in the same environment of the rest of the code. 370 | 371 | Data-Oriented Structures 372 | ------------------------ 373 | 374 | **SOA AND AOS** 375 | 376 | Modern processors and memory models are much faster when spatial locality is adhered to. This means that grouping together data that is modified at the same time is advantageous for performance. So changing a struct from an array of structures (AoS) style: 377 | 378 | ```cpp 379 | struct Entity { 380 | Vector3 position; 381 | Quaternion orientation; 382 | // ... many other members here 383 | }; 384 | 385 | Entity all_entities[1024]; // An array of structures 386 | 387 | for (int k = 0; k < 1024; k++) 388 | update_position(&all_entities[k].position); 389 | 390 | for (int k = 0; k < 1024; k++) 391 | update_orientation(&all_entities[k].orientation); 392 | ``` 393 | 394 | to a structure of arrays (SoA) style: 395 | 396 | ```cpp 397 | struct Entity { 398 | Vector3 positions[1024]; 399 | Quaternion orientations[1024]; 400 | // ... many other members here 401 | }; 402 | 403 | Entity all_entities; // A structure of arrays 404 | 405 | for (int k = 0; k < 1024; k++) 406 | update_position(&all_entities.positions[k]); 407 | 408 | for (int k = 0; k < 1024; k++) 409 | update_orientation(&all_entities.orientations[k]); 410 | ``` 411 | 412 | can improve performance a great deal because of fewer cache misses. 413 | 414 | However, as programs get larger, it becomes much more difficult to reorganize the data. Testing whether a single, simple change has any effect on performance can take the developer a long time, because once the data structures must change, all of the code that acts on that data structure breaks. So Jai provides mechanisms for automatically transitioning between SoA and AoS without breaking the supporting code. For example: 415 | 416 | ```cpp 417 | Vector3 :: struct { 418 | x: float = 1; 419 | y: float = 4; 420 | z: float = 9; 421 | } 422 | 423 | v1 : [4] Vector3; // Memory will contain: 1 4 9 1 4 9 1 4 9 1 4 9 424 | 425 | Vector3SOA :: struct SOA { 426 | x: float = 1; 427 | y: float = 4; 428 | z: float = 9; 429 | } 430 | 431 | v2 : [4] Vector3SOA; // Memory will contain: 1 1 1 1 4 4 4 4 9 9 9 9 432 | ``` 433 | 434 | Getting back to our previous example, in Jai: 435 | 436 | ```cpp 437 | Entity :: struct SOA { 438 | position : Vector3; 439 | orientation : Quaternion 440 | // .. many other members here 441 | } 442 | 443 | all_entities : [4] Entity; 444 | 445 | for k : 0..all_entities.count-1 446 | update_position(&all_entities[k].position); 447 | 448 | for k : 0..all_entities.count-1 449 | update_orientation(&all_entities[k].orientation); 450 | ``` 451 | 452 | Now the only thing that needs to be changed to convert between SoA and AoS is to insert or remove the `SOA` keyword at the struct definition site, and Jai will work behind the scenes to make everything else work as expected. 453 | 454 | Reflection and Run-Time Type Information 455 | ---------------------------------------- 456 | 457 | Jai stores a table of all type information in the data segment of each compiled program. It can be examined like this: 458 | 459 | ```cpp 460 | for _type_table { 461 | // it is the iterator, it is the Type being examined. it_index is the iteration index, it is an integer 462 | print("%:\n", it_index); 463 | print(" name: %\n", it.name); 464 | print(" type: %\n", it.type); // type is an enum, INTEGER, FLOAT, BOOL, STRUCT, etc 465 | } 466 | ``` 467 | 468 | Full introspection data is available for all structs, functions, and enums. For example, a procedure may look something like this: 469 | 470 | ```cpp 471 | print("% (", info_procedure.name); 472 | for info_procedure.argument_types { 473 | print_type(it); 474 | if it_index != info_procedure.argument_types.count-1 then print(", "); 475 | } 476 | print(") ->"); 477 | print_type(info_procedure.return_type); 478 | ``` 479 | 480 | The preceding code could print something like `get_name(id : uint32) -> string`. An enum can be examined like this: 481 | 482 | ```cpp 483 | Hello :: enum u16 { 484 | FIRST, 485 | SECOND, 486 | THIRD = 80, 487 | FOURTH, 488 | } 489 | 490 | for Hello.names { 491 | print("Name: % value: %\n", Hello.names[it_index], Hello.values[it_index]); 492 | } 493 | ``` 494 | 495 | Reflection data such as this can be used to write serialization procedures, commonly used e.g. in network replication of entities and save game data. Current C/C++ methods for this involve heavy use of operator overloading and preprocessor directives. 496 | 497 | Polymorphic Procedures 498 | ---------------------- 499 | 500 | **FUNCTION POLYMORPHISM** 501 | 502 | Jai’s primary polymorphism mechanism is at the function level, and is best described with an example. 503 | 504 | ```cpp 505 | sum :: (a: $T, b: T) -> T { 506 | return a + b; 507 | } 508 | 509 | f1: float = 1; 510 | f2: float = 2; 511 | f3 := sum(f1, f2); 512 | 513 | i1: int = 1; 514 | i2: int = 2; 515 | i3 := sum(i1, i2); 516 | 517 | x := sum(f1, i1); 518 | 519 | print("% % %\n", f3, i3, x); // Output is "3.000000 3 2.000000" 520 | ``` 521 | 522 | When `sum()` is called, the type is determined by the `T` which is preceded by the `$` symbol. In this case, `$` precedes the `a` variable, and so the type `T` is determined by the first parameter. So, the first call to `sum()` is float + float, and the second call is int + int. In the third call, since the first parameter is float, both parameters and the return value become float. The second parameter is converted from int to float, and the variable `x` is deduced to be float as well. 523 | 524 | **THE ANY TYPE** 525 | 526 | Jai has a type called `Any`, which any other type can be implicitly casted to. Example: 527 | 528 | ```cpp 529 | print_any :: (a: Any) { 530 | if a.type.type == Type_Info_Tag.FLOAT 531 | print("a is a float\n"); 532 | else if a.type.type == Type_Info_Tag.INT 533 | print("a is an int\n"); 534 | } 535 | ``` 536 | 537 | **BAKING** 538 | 539 | … this section is not written yet! Sorry. (The `#bake` directive emits a function with a combination of arguments baked in. For example, `#bake sum(a, 1)` becomes equivalent to `a += 1`.) 540 | 541 | Memory Management 542 | ----------------- 543 | 544 | Jai does not and will never feature garbage collection or any kind of automatic memory management. 545 | 546 | **STRUCT POINTER OWNERSHIP** 547 | 548 | Marking a pointer member of a struct with `!` indicates that the object pointed to is owned by the struct and should be deleted when the struct is deallocated. Example: 549 | 550 | ```cpp 551 | node :: struct { 552 | owned_a : !* node = null; 553 | owned_b : !* node = null; 554 | } 555 | 556 | example: node = new node; 557 | example.owned_a = new node; 558 | example.owned_b = new node; 559 | 560 | delete example; // owned_a and owned_b are also deleted. 561 | ``` 562 | 563 | Here, `owned_a` and `owned_b` are marked as being owned by `node`, and will be automatically deleted when the node is deleted. In C++ this is accomplished through a `unique_ptr`, but Jai considers this the wrong way to do it, because the template approach now masks the true type of the object. A `unique_ptr` is no longer a `node`—it’s a `unique_ptr` masquerading as a `node`. It’s preferable to retain the type of `node*`, and retain the properties of `node*`-ness that go along with it, because we don’t really actually care about `unique_ptr` for its own sake. 564 | 565 | **LIBRARY ALLOCATORS** 566 | 567 | … this section is not written yet! Sorry. (Jai provides mechanisms for managing the allocations of an imported library without requiring work from the library writers.) 568 | 569 | [//]: # (Explicit Performance Control) 570 | 571 | **INITIALIZATION** 572 | 573 | Member variables of a class are automatically initialized. 574 | 575 | ```cpp 576 | Vector3 :: struct { 577 | x: float; 578 | y: float; 579 | z: float; 580 | } 581 | 582 | v : Vector3; 583 | print("% % %\n", v.x, v.y, v.z); // Always prints "0 0 0" 584 | ``` 585 | 586 | You can replace these with default initializations: 587 | 588 | ```cpp 589 | Vector3 :: struct { 590 | x: float = 1; 591 | y: float = 4; 592 | z: float = 9; 593 | } 594 | 595 | v : Vector3; 596 | print("% % %\n", v.x, v.y, v.z); // Always prints "1 4 9" 597 | 598 | va : [100] Vector3; // An array of 100 Vector3 599 | print("% % %\n", va[50].x, va[50].y, va[50].z); // Always prints "1 4 9" 600 | ``` 601 | 602 | Or you can block the default initialization: 603 | 604 | ```cpp 605 | Vector3 :: struct { 606 | x: float = ---; 607 | y: float = ---; 608 | z: float = ---; 609 | } 610 | 611 | v : Vector3; 612 | print("% % %\n", v.x, v.y, v.z); // Undefined behavior, could print anything 613 | ``` 614 | 615 | You can also block default initialization at the variable declaration site: 616 | 617 | ```cpp 618 | Vector3 :: struct { 619 | x: float = 1; 620 | y: float = 4; 621 | z: float = 9; 622 | } 623 | 624 | v : Vector3 = ---; 625 | print("% % %\n", v.x, v.y, v.z); // Undefined behavior, could print anything 626 | 627 | va : [100] Vector3 = ---; 628 | print("% % %\n", va[50].x, va[50].y, va[50].z); // Undefined behavior, could print anything 629 | ``` 630 | 631 | By explicitly uninitializing variables rather than explicitly initializing variables, Jai hopes to reduce cognitive load while retaining the potential for optimization. 632 | 633 | **INLINING** 634 | 635 | ```cpp 636 | test_a :: () { /* ... */ } 637 | test_b :: () inline { /* ... */ } 638 | test_c :: () no_inline { /* ... */ } 639 | 640 | test_a(); // Compiler decides whether to inline this 641 | test_b(); // Always inlined due to "inline" above 642 | test_c(); // Never inlined due to "no_inline" above 643 | 644 | inline test_a(); // Always inlined 645 | inline test_b(); // Always inlined 646 | inline test_c(); // Always inlined 647 | 648 | no_inline test_a(); // Never inlined 649 | no_inline test_b(); // Never inlined 650 | no_inline test_c(); // Never inlined 651 | ``` 652 | 653 | Additionally, there exist directives to always or never inline certain procedures, to make it easier to inline or avoid inline conditionally depending on the platform. 654 | 655 | ```cpp 656 | test_d :: () { /* ... */ } 657 | test_e :: () { /* ... */ } 658 | 659 | #inline test_d // Directive to always inline test_d 660 | #no_inline test_e // Directive to never inline test_e 661 | ``` 662 | 663 | Other Cool Stuff 664 | ---------------- 665 | 666 | Things that C/C++ should have had a long time ago: 667 | 668 | - Multi-line block comments 669 | - Nested block comments 670 | - Specific data types for 8, 16, and 32 bit integers 671 | - No implicit type conversions 672 | - No header files 673 | - `.` operator for both struct membership and pointer dereference access—no more `->` 674 | - A `defer` statement, [similar to that of Go](http://blog.golang.org/defer-panic-and-recover) 675 | 676 | Planned 677 | ------- 678 | 679 | Here’s a short list of planned features to be added to Jai. 680 | 681 | - Automatic build management—the program specifies how to build it 682 | - Captures 683 | - LLVM integration 684 | - Automatic versioning (see below) 685 | - A better concurrency model 686 | - Named argument passing 687 | - A permissive license 688 | 689 | Not Planned 690 | ----------- 691 | 692 | Jai will not have: 693 | 694 | - Smart pointers 695 | - Garbage collection 696 | - Automatic memory management of any kind 697 | - Templates or template metaprogramming 698 | - RAII 699 | - Subtype polymorphism 700 | - Exceptions 701 | - References 702 | - A virtual machine (at least, not usually—see below) 703 | - A preprocessor (at least, not one resembling C’s—see below) 704 | - Header files 705 | 706 | If it sounds odd to you that Jai is a modern high-level language but does not have some of the above features, then consider that Jai is not trying to be as high-level as Java or C#. It is better described as trying to be a better C. It wants to allow programmers to get as low-level as they desire. Features like garbage collection and exceptions stand as obstacles to low-level programming. 707 | 708 | Further Notes 709 | ============= 710 | 711 | **ADOPTION** 712 | 713 | A compelling argument for not writing an entirely new language for games is that the momentum and volume of C and C++ code in current game engines is too high, and switching to a new language is too much work for the amount of benefit. Blow argues that engines periodically rewrite their codebase anyway, and since Jai and C are so closely related, C code and Jai code can live side by side while the rewrites that would normally happen anyway take place. Since C and Jai interoperate seamlessly, Jai code can be built on top of existing C libraries. In fact, Blow uses the C interfaces to the OpenGL and [stb_image](https://github.com/nothings/stb/blob/master/stb_image.h) libraries for his Jai test code. So, replacing C and C++ can be done with no added cost to development. Meanwhile, the benefits of replacing C with a language that has all of C’s benefits but fewer drawbacks means that programmers will be happier, and thus more productive. 714 | 715 | **WHY NOT USE C++/RUST/GO/D/SWIFT/HASKELL/LISP/ETC?** 716 | 717 | Those are strong languages, but none of them contain the right combination of features (or lack of features) that game programmers need. Automatic memory management is a non-starter for game programmers who need direct control over their memory layouts. Any interpreted language will be too slow. Functional-only languages are pointlessly restricting. Object-oriented-only languages are overly complex. 718 | 719 | The idea behind Jai is to develop a new language with the qualities that game programmers need, and without the qualities they don’t. 720 | 721 | Proposed Features 722 | ----------------- 723 | 724 | These are a few proposed features that have not been implemented yet. To my knowledge they’re not yet in the language. Syntax is preliminary and likely to change. 725 | 726 | - Joint Allocations: 727 | 728 | ```cpp 729 | Mesh :: struct { 730 | name: string; 731 | filename: string; 732 | flags: uint; 733 | 734 | positions: [] Vector3; 735 | indices: [] int; @joint positions 736 | uvs: [] Vector2; @joint positions 737 | } 738 | 739 | example_mesh: Mesh; 740 | example_mesh.positions.reserve(positions: num_positions, 741 | indices: num_indices, 742 | uvs: num_uvs); 743 | ``` 744 | 745 | Here we want to avoid multiple memory allocations, so we have the compiler do a joint allocation between positions, indices, and uvs, and divide the memory up accordingly. Currently this is done manually in C, and is prone to errors. 746 | 747 | - Optional Types: 748 | 749 | ```cpp 750 | do_something :: (a: Entity?) { 751 | a.x = 0; // ERROR! 752 | if (a) { 753 | a.x = 0; // OK 754 | } 755 | } 756 | ``` 757 | 758 | The idea here is to prevent one of the most common causes of crashes, null pointer dereferencing. The ? in the above code means that we don’t know whether or not the pointer is null. Trying to dereference the variable without testing to see if it is null would result in a compile time error. 759 | 760 | - Automatic Versioning: 761 | 762 | ```cpp 763 | Entity_Substance :: struct { @version 37 764 | flags: int; 765 | scale_color: Vector4; @15 766 | spike_flags: int; @[10, 36] 767 | } 768 | ``` 769 | 770 | Here we are providing markup for our data structures that indicates to the compiler what version of the struct each member was present in. These versioning schemes would be used as part of an automatic serialization/introspection interface, but there are no details on that, other than that the language should have some capability of introspection. 771 | -------------------------------------------------------------------------------- /Undocumented.md: -------------------------------------------------------------------------------- 1 | 2 | Compilation keyword #bake to specialize polymorphic functions. 3 | Polymorphic function can contain also 'internal' polymorphic type but they can only be used after 'baking' them. 4 | 5 | Compilation keyword #modify is used to make constraints/transformations on polymorphic types. 6 | For example, it allows to define default output polymorphic type, make only one implementation functions for different type u8..32-->u64, complex mapping to reduce the number of generated functions 7 | NdT: Powerful, but can be used to build function difficult to understand. It uses parameters names as input. 8 | 9 | Compilation keyword #body_text takes the parameters types, then return a string contain the body of the function 10 | It's planned to have another keyword #body_if for 'inline' compile time code generation. 11 | 12 | #bake_values: a compile time specialisation of some function with some (or all) parameters hardcoded. 13 | Useful to control inlining, somewhat duplicate (force) inline, but easier to use with functions with lots of arguments. 14 | 15 | $$: an autobaker for function parameters prefixed by $$ and called with literals (currently no analysis to check if the parameter is known at compile time). 16 | 17 | End of new features, demonstration of map which show 'high-levelness' of Jay, but requires to free the allocated memory or there is a memory leak (no GC). 18 | And of map taking an allocator param which allow usage of stack (faster than heap) for 'inline map'. 19 | 20 | In the Q&A video, Blow think that 'libraries' will be pre-parsed code but not binary, to allow these features working. 21 | 22 | 04/08/2015 Demo: Bounds check, here strings, overloading. [renox, I didn't have the patience to listen to the full Q&A] 23 | 24 | * Array Bound Checking (ABC): 25 | By default arrays(both SoA and AoS) and strings are bound checked, for optimisation there is a 'no array bound checking' directive: #no_abc (which works either on a statement or on a block), 26 | you can also disable globally ABC, or enforce always ABC (ignoring the #no_abc directive). 27 | 28 | SoA pointers are also bound checked as they're 'fat pointers' aka arrays. 29 | 30 | 31 | * Here strings: 32 | ``` 33 | # string 34 | ... 35 | 36 | ``` 37 | Currently no indentation support and the must be at the beginning of the line. 38 | Strings are Unicode strings. Can be empty. 39 | 40 | * overloaded function: 41 | Overloading works as usual (and also with 'inheritance': Jai 'using' feature). 42 | One main improvement: with integer literals, the smaller int type which can support the literal is called. 43 | Works with multiple parameters. 44 | If there are several possible overload with the same level of conversion, it's a compilation error. 45 | Integer conversion are preferred to float conversion. 46 | 47 | There are 'surprising' features related to overloading on which JB ask feedback: 48 | 1) overloading is scoped: if you can find a match in the local scope, it is selected, even if there is another better match in the outside scope 49 | baz :: (a: u8) { ... } 50 | { 51 | baz :: (a: u16) { ... } 52 | baz(100) // this use the u16 version. 53 | } 54 | 55 | 2) overloading only work on constant function definition (not on functions pointers, lambda). 56 | 57 | 3) overloading induce a 'file scope'? [renox. I didn't catch this point] 58 | 59 | In the Q&A: parametrized type cannot be overloaded currently. 60 | 61 | 62 | --------------------------------------------------------------------------------