JaiScript · embedded scripting for C++ engines

Fluent C++ scripting.

Auto-Binding: Script/Serialize/Reflect
Rock solid hot reload.
Typesafe with an escape hatch.

Declare a field once and scripts read it, write it and save it. Edit a script while the game runs, and the world keeps its state.

  • C++ value semantics
  • strict checker at load
  • coroutines
  • parallel_for
  • safety rails
  • guides from JS / C++ / C#
gator.jairunning
// drag spd or slices, pick what it says: it reloads live
class Gator : Mob {
    int spd = 1;
    int slices = 3;
    void tick() {
        step_toward(cake, spd);
        if (beside(cake)) { slices--; say("Nom."); }
    }
}
Gator gator;  // kept by every reload
void hot_reload_migrate(map<string, var> previous) {
    gator.spd = Gator().spd;  // live gator takes the edit
}
gator at spd 1 · slices 3

One declaration, no glue

Bind a class once: scripts use it, saves store it, the editor reflects it.

C++
class Spawner : public jai::property_owner<Spawner> {
public:
    JAI_PROPERTY((std::string), id, "north-gate");   // scripted and saved
    JAI_PROPERTY((int64_t), level, 1);
    JAI_SIGNAL_PROPERTY(void(int64_t), onWave);      // script-connectable, saved too
    void spawn_wave();
};

static jai::registrar<Spawner, void> _regSpawner("Spawner",
    [](jai::dynamic_binder<Spawner>& b) {
        b.method("spawn_wave", &Spawner::spawn_wave);   // a method is one line
    });

Each JAI_PROPERTY is the field, its script binding, its save key and its reflection, so the four never drift apart.

C++
// turret.h comes from a library you can't edit
struct Turret {
    std::string name = "north";
    int64_t ammo = 10;
    void fire();
};

// your binding file: the class stays untouched
static jai::registrar<Turret, void> _regTurret("Turret",
    [](jai::dynamic_binder<Turret>& b) {
        b.constructor<>()
         .property("name", &Turret::name)   // scripted and saved
         .property("ammo", &Turret::ammo)
         .method("fire", &Turret::fire);
    });

Third-party and engine types bind from the outside, with the same script access and saves keyed by the property names.

Either way, script sees a native class (auto t = Turret(); t.ammo = 3;) and the same object saves to JSON or binary. The Dynamic Binder walks a class through being scripted, reflected, serialized and hot-reloaded with its state intact.

Two lines to hello

C++
auto e = jai::engine::make();
e->execute("print(\"hello\");");

A static library plus headers, with no global state. jai::stdlib::register_all(e); adds formatted print, math, containers and JSON; every sample on this site assumes it.

jaiscript
var anything = 5;            // dynamic — the explicit, visible opt-out
anything = "now a string";   // fine; you asked for this

class Hero {
    int hp = 10;
    void heal(int amount) { hp += amount; }
}
void bump(int& x) { x += 1; }       // mutation is visible at both ends

auto h = Hero();
h.heal(5);
bump(h.hp);            // fields and elements bind by reference too
print("hp = {}", h.hp);
output
hp = 16

No host yet? Save it as hello.jai and run jaiscript hello.jai. jaiscript alone is a REPL, and --watch re-runs on every save (the runner, its flags). For the web, jai_spider app.jai serves the app over HTTP and applies each saved edit to the next request.

The seven pillars

01The type ladder

int x is enforced, auto x inferred then enforced, var x dynamic. The checker is always on: a typed mistake stops the load, before anything runs.

The Type Ladder →

02Value semantics

Assignment, parameters and returns copy. Your data changes only where a signature says &.

Value Semantics →

03Two implementations, one spec

An interpreter and a VM agree byte for byte, error text included, held there by 4,700+ tests and a differential fuzzer.

Backends →

04Zero global state

Every value carries its engine. Two engines on two threads need no locks, and parallel_for gives the same answer at any worker count.

Quickstart →

05Hot reload that never bricks

Live instances migrate and keep their fields. A file that doesn't check doesn't load, and the old code keeps running.

Hot Reload →

06Safe at the boundary

Scripts reach only what the host registered. Overflow checks and an execution budget are on by default; a memory cap is one call.

Safety Rails →

07Parse once, run anywhere

A .jaibite ships precompiled and loads into any engine; even execute(str) in a loop parses once.

jaibite →

The receipts

4,700+tests green on both backends every commit, plus a differential fuzzer
16 bytesper script_value, static-assert-gated: four per cache line
5 programsin the repository, over 11,000 lines of pure JaiScript: a raycaster, a crawler, a demoreel, a 16-player RPG and DOOM
5 languagesrun one raycaster to identical state hashes, so the comparison measures runtimes alone
always strictline-and-column diagnostics before a statement runs
measuredhot loops, calls, boot and features on one page: Performance

Get JaiScript

git, CMake 3.25+ and a C++20 compiler: Visual Studio 2022 or newer with the C++ workload, the Xcode command line tools, or GCC 11 / Clang 14.

macOS, Linux, WSL

shell
curl -fsSL https://jaiscript.com/install.sh | sh

Windows PowerShell

PowerShell
irm https://jaiscript.com/install.ps1 | iex

Windows CMD

CMD
curl -fsSL https://jaiscript.com/install.cmd -o install.cmd && install.cmd && del install.cmd

It clones JaiScript, builds the runner in Release and puts it on your PATH; run it again to update. Then open a new terminal and run jaiscript --version.

What the installer does, its options, and fixing your PATH

Read the scripts before you run them: install.sh, install.ps1 and install.cmd. Not sure which Windows shell you have? PowerShell's prompt shows PS C:\; CMD's shows C:\. The installer also prints the line that starts Gator Quest, an open-world terminal RPG written in JaiScript.

  • They install to ~/.jaiscript, or %LOCALAPPDATA%\jaiscript on Windows. Options pick the folder, a release, a compiler or a generator.
  • --no-modify-path leaves your PATH alone: curl -fsSL https://jaiscript.com/install.sh | sh -s -- --no-modify-path.
  • None of them asks for sudo or administrator rights.

Fix your PATH

If jaiscript --version isn't found, the install folder isn't on your PATH yet:

SystemAdd the runner's folder to your PATH
macOS, Linux, WSLAdd export PATH="$HOME/.jaiscript/bin:$PATH" to ~/.zshrc, ~/.bashrc or ~/.profile, whichever your shell reads, then open a new terminal.
WindowsOpen Start, search for environment variables, choose Edit environment variables for your account, select Path, then Edit, New, and enter %LOCALAPPDATA%\jaiscript\bin. Then open a new terminal.

One line with git and CMake

shell
git clone --depth 1 https://github.com/Devacor/jaiscript.git && cmake -P jaiscript/tools/install/build_runner.cmake && cmake -E chdir jaiscript build/bin/jaiscript examples/gatorquest/scripts/gatorquest.jai

It clones, builds the runner in Release and starts Gator Quest, in bash, zsh, PowerShell 7 or CMD, with no developer prompt and no Ninja. The runner is jaiscript/build/bin/jaiscript; run the middle command alone to build again.

Toolchain presets

Pick a toolchain

The one-line build uses CMake's default generator (Visual Studio on Windows, Makefiles elsewhere) and builds in parallel. Each preset below configures a Release tree in build-<preset> and builds everything (the library, the runner, the test suite and the examples) into build-<preset>/bin:

  1. cmake --preset <preset>
  2. cmake --build --preset <preset>. Add -j 8 on Makefiles, which otherwise build one file at a time, and --target jaiscript_runner for the runner alone.
PresetToolchainNeeds
windows-msvcMSVC with the Visual Studio generatorVisual Studio 2022 or newer with the C++ workload; any shell
windows-clangclang-cl against the MSVC STL, with NinjaA Visual Studio developer prompt, with the C++ Clang tools component or LLVM installed
macosAppleClang with MakefilesThe Xcode command line tools (xcode-select --install)
macos-xcodeAppleClang with the Xcode generatorXcode
linux-gccGCC with Makefilesg++ 11 or newer and make
linux-clangClang with Makefilesclang++ 14 or newer and make

docs/BUILD.md has the rest: targets, options, Debug trees and the Visual Studio Open Folder presets.

To embed it: a CMake subproject, FetchContent, an installed package, or the sources dropped into a Visual Studio or Xcode project. Building JaiScript into a Host covers each route. New to Visual Studio? Visual Studio, Step by Step goes from a fresh install to a C++ program that runs a script.

Coming from another language

Language Bridges set JavaScript, C++ and C# beside JaiScript idiom by idiom. Code Migrations convert real code off Cereal, Boost.Serialization, Boost.Signals2, ChaiScript, sol2, Squirrel, pybind11, nlohmann::json, Qt properties, RTTR or a coroutine scheduler.

Who it's for

Anyone embedding scripting in a C++ engine who wants hot reload and script classes, safety rails a host tightens in a few calls, types that scale from var to strictly checked int within the same file, and behavior enforced by two implementations arguing with each other.

Built alongside a real 2D game engine, hardened by adversarial review and differential fuzzing, and dogfooded on real, playable things. The source is MIT-licensed; Stability lists what is settled before 1.0. Shipping with JaiScript inside? “Scripted with JaiScript” in your credits would be lovely to see: a request, not a condition.