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#
// 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 }
This is the file the page runs, on the JaiScript engine compiled to WebAssembly: world.jai gives it Mob and cake, and stage.jai calls tick() every frame.
One declaration, no glue
Bind a class once: scripts use it, saves store it, the editor reflects it.
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.
// 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
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.
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);hp = 16No 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.
02Value semantics
Assignment, parameters and returns copy. Your data changes only where a signature says &.
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.
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.
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.
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.
07Parse once, run anywhere
A .jaibite ships precompiled and loads into any engine; even execute(str) in a loop
parses once.
The receipts
script_value, static-assert-gated: four per cache lineGet 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
curl -fsSL https://jaiscript.com/install.sh | shWindows PowerShell
irm https://jaiscript.com/install.ps1 | iexWindows CMD
curl -fsSL https://jaiscript.com/install.cmd -o install.cmd && install.cmd && del install.cmdIt 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%\jaiscripton Windows. Options pick the folder, a release, a compiler or a generator. --no-modify-pathleaves your PATH alone:curl -fsSL https://jaiscript.com/install.sh | sh -s -- --no-modify-path.- None of them asks for
sudoor administrator rights.
Fix your PATH
If jaiscript --version isn't found, the install folder isn't on your PATH yet:
| System | Add the runner's folder to your PATH |
|---|---|
| macOS, Linux, WSL | Add export PATH="$HOME/.jaiscript/bin:$PATH" to ~/.zshrc, ~/.bashrc or ~/.profile, whichever your shell reads, then open a new terminal. |
| Windows | Open 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
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.jaiIt 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:
cmake --preset <preset>cmake --build --preset <preset>. Add-j 8on Makefiles, which otherwise build one file at a time, and--target jaiscript_runnerfor the runner alone.
| Preset | Toolchain | Needs |
|---|---|---|
windows-msvc | MSVC with the Visual Studio generator | Visual Studio 2022 or newer with the C++ workload; any shell |
windows-clang | clang-cl against the MSVC STL, with Ninja | A Visual Studio developer prompt, with the C++ Clang tools component or LLVM installed |
macos | AppleClang with Makefiles | The Xcode command line tools (xcode-select --install) |
macos-xcode | AppleClang with the Xcode generator | Xcode |
linux-gcc | GCC with Makefiles | g++ 11 or newer and make |
linux-clang | Clang with Makefiles | clang++ 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.