Host and Runner Reference​

Engine API quick reference​

One call per row; the linked embedding chapter explains it.

Engine lifecycle & execution: Quickstart​

APIWhat it does
jai::engine::make()An engine, always a shared_ptr<engine>. No global state.
jai::stdlib::register_all(eng)Opt-in stdlib: formatted print, format, to_string, to_json, time::, reflection, other optional helpers.
A bare engineEngine core (math, array transforms, text and binary primitives), basic print, format_value.
eng->execute(src)Runs a script; returns the last expression's value, a jai::script_value. A script the checker rejects throws jai::static_check_error before anything runs.
r.as<T>() / as_int() / as_float() / as_string() / as_bool()Convert a result.
eng->execute_file(path)Runs a file under its canonical path (listings, traces, breakpoints). A .jaibite in the .cache folder beside it caches the parse.
eng->start(src) / start_file(path) / start(callable, args)Suspendable run: each sleep hands back a jai::script_run; resume() continues it once wake_at() has passed.
eng->get_variable(name) / has_variable(name) / has_function(name)Read a global; a function is a value too.
fn.as_function()(args)Calls a script function; returns checked_result<script_value> (text: eng->format_error(r)). An uncaught script throw arrives as jai::script_exception.
engine::external_call_guardWraps that call only while a script is running.
jai::convert_script_function<Sig>(fn)Typed std::function over a script function. A script error or uncaught throw throws jai::runtime_error to the C++ caller; a host function called from script returns it as that call's error.
v.as<std::vector<T>>() / as<std::map<K, V>>()Typed arrays and maps copy out.
as_array()Views an array<var> only.
unchecked_array_node()->for_each(eng, f)for_each walks any array kind.
eng->set_backend(jai::backend_type::interpreter)The tree-walking interpreter; the bytecode VM is the default. Before the first execute only, in a program that links jaiscript_interpreter.
eng->set_output_stream(s)Redirects print(); nullptr restores std::cout.
eng->set_script_error_handler(fn)Sink for script errors nothing can catch (a destructor failing at engine teardown).
eng->format_stack_trace() / last_stack_trace()Script stack of the most recent failure.

Registering the host surface: Quickstart, dynamic_binder​

APIWhat it does
eng->add_function(name, lambda)Typed host function. Repeated names form an overload set resolved by argument types.
Non-const T& parameterThe callee's final value is written back into the script lvalue argument (variable, field, element, map value, static, C++ property, vector component v.x) by that slot's store rule. A literal or temporary is a copy.
std::shared_ptr<T> or std::weak_ptr<T> parameterTakes null as an empty handle.
std::weak_ptr<T> parameterTakes a live shared_ptr or weak_ptr handle as one to the same object.
eng->add_variadic_function(name, fn)Receives the raw std::vector<script_value>: print-shaped functions, optionals.
eng->add_host_direct(name, arity, invoke, target)Direct entry for the function name holds: a VM call site with arity non-reference arguments calls invoke(target, args, arity) over the arguments in place. The function takes every other call.
eng->add_global(name, value)Copies a value in.
eng->add_global_ref(name, var)Live binding (cpp_bound) to a C++ primitive or string: script reads and writes the real variable.
eng->make_value(v) / make_null() / make_array() / make_map()Value factories; every script_value carries its engine. make_value of a script_value hands it back; as<script_value>() is the value itself.
jai::dynamic_binder<T>(*eng, "Name")Class binding: .constructor<Args...>() .method(...) .property(...) .static_method(...) .static_property(...) .base_class<B>(). See binder verbs.
.method("+", &T::operator+)Operator by token name: bound operators.
extend(object, Class) (script)Gives a host-owned object a script class over its C++ class or a base, in place. Idempotent; re-derives for another class (Script-Extensible Objects).
eng->extend(obj, "Class")The same from C++. add_global, make_object and every other entry hand script the object's layer, extended before or after.
eng->script_layer(*obj) / script_layer_detach(obj)The object's live layer / drop it (fields returned as JSON).
obj->script_layer_to_json() / script_layer_from_json(s)script_extensible (every property_owner): the layer's {"$type", "$script"} state, out and back.

Safety rails: Safety Rails​

APIWhat it does
eng->execution_budget(seconds)Wall-clock cap per execute()/next(). Default 5.0 s; 0 disables. Overrun is terminal: script catch cannot swallow it.
eng->execution_step_budget(checks)Clock-free twin: at most checks budget checks (one per 1024 back-edges and call entries) per host entry, then the same terminal error. Default 0 (off).
eng->memory_cap(bytes)Approximate high-water per top-level execute. First raise: catchable, denies in place. Second: terminal. 0 = unlimited.
eng->max_call_depth(jai::backend_type::vm, n)Recursion cap per backend; default interpreter 256, VM 250,000. Overrun is the catchable recursion error (depth limits).
eng->throw_on_overflow()The build's integer-overflow policy (checked = default).
jai::debug::listen(*eng, port) / conn->listening() / conn->error()The VS Code (DAP) listener. A taken port does not throw: listening() is false, error() says why.
eng->add_include_path(dir)Search root for script include/import; first added wins on collisions.
eng->confine_paths({dirs})Opt-in: include, import, resolve_path and file_data open only files under these folders; no .cache is written beside a source outside them. Empty (default) opens any readable path (Safety Rails).
eng->reset_import(path) / reset_imports() / is_imported(path) / get_imported_files()Import-cache management.
eng->set_import_behavior(mode)file_timestamp (default) / once / always.
eng->import_recheck_interval(seconds)A reached import asks the filesystem whether its module changed at most once per window. Default 0.2 s; 0 checks on every reach. Script builtin: import_recheck_interval(s).
eng->hot_reload_reporting(true) / hot_reload_reports() / clear_hot_reload_reports()Off by default. On: each field operation of a class reload on live instances, and each saved value a load converts or cannot fit, aggregates into std::vector<jai::field_migration_report> until cleared.
Report rowclass_name, field_name, old_type, new_type, outcome (preserved / converted / reset / added / removed), count across instances.
eng->disable(jai::filesystem) / enable(...) / is_enabled(...) / register_privilege("name")The filesystem privilege (on by default) and custom named permissions.
add_function(name, fn, {"app.edit"})A final requirement: the call raises Insufficient privilege: app.edit. while it is off.

Time source: Quickstart, Time​

APIWhat it does
eng->time().source(monotonic, wall, sleep)Replaces the clock every time:: call reads. An empty provider selects the system one.
ProvidersMonotonic and wall return std::chrono::nanoseconds (wall = since the Unix epoch, UTC); sleep takes a duration.
Provider readsOnly inside a time:: call, plus the monotonic one at each host entry once a loaded program names time::execute_elapsed.
eng->time().monotonic_now() / wall_now() / sleep_for(d)The providers from C++, one read each. eng->time() is declared in <jaiscript/core/engine_time.hpp>.
eng->engine_elapsed() / execute_elapsed()What time::engine_elapsed() and time::execute_elapsed() answer.

Static checking & precompiled scripts: Static Checking, jaibite​

APIWhat it does
eng->check(src)Parses (cached) and checks without running; returns jai::check_report.
eng->last_check_diagnostics()Report of the last checked execute/bite, warnings included.
eng->type_proofs(bool)Default on: type proofs let both backends skip conversions shown to be identities, each behind a run-time guard. type_proofs(false) is observationally identical (same values, output, error text), slower.
eng->parameter_alias(bool) · eng->local_alias(bool)Default on: a by-value parameter or local provably only read binds its argument or source instead of a copy (Choosing a strategy). Off copies every time, observationally identical.
eng->jaibite(src)Parses once into an engine-bound handle; bite.execute() skips the parse, and the VM caches the compiled chunk. Checked at creation; strict throws there.
bite.checked_clean() / bite.check_diagnostics()That check's result.
bite.save(path) / save_bytes()The parsed AST as .jaibite, stamped with the build's format version (jai::detail::k_jaibite_version).
Another format versionThe load fails: jaibite: unsupported format version N (expected M). The automatic disk cache reparses instead.
eng->jaibite_load(path) / jaibite_load_bytes(b)Loads into any engine: symbols re-intern, one file serves both backends. A registration fingerprint that differs from this engine's re-checks the bite against this engine's surface.
bite.registration_mismatch()Advisory: the registered surface differs between saving and loading engines. Covers every registration form, zero-arg and variadic functions included; a load never fails on it.
eng->jaibite_cache(bool) / jaibite_cache()Disk cache for file-based loads, default ON: include/import/execute_file keep .cache/foo.jai.jaibite beside foo.jai, making the folder when missing.
Cache validityLoads only for the exact source text it records (size + hash) and a matching registration fingerprint, whatever the mtimes. Anything else reparses and rewrites it.
Cache writesA temporary file renamed into place; silent best-effort, once per source text. jaibite_cache_write_failures() counts them.
eng->jaibite_cache_directory(dir, root) / jaibite_cache_directory()Caches go below dir at the source's path relative to root (outside it, or with no root, its own path). A valid .cache beside the source still loads, never rewritten. For read-only bundles; empty restores the default.
eng->jaibite_cache_path(source)The cache file written for source; empty for a .jaibite source.

Serialization & coroutines: Serialization, Coroutine Hosting​

APIWhat it does
to_json(x) / to_json(x, n) / from_json(s) (script)JSON round-trip of any script value; n spaces per level pretty-print.
jai::to_json(*eng, v) / jai::to_binary(*eng, v) (C++)C++ objects the archives know: a property_owner, a type with serialize/save, a handle to one.
jai::script_valueNot one of them: a script value goes through the script builtin to_json.
eng->execute("co.next()") / native handle e->next(eng), e->next(eng, n) / e->done()Drive script coroutines from C++.

Thread pool: Thread Pool for Host Work, Parallel worker count​

Each call with a compiled example: Thread Pool for Host Work. A task is a thread_pool::task_type, a std::function<void()>.

Call (<jaiscript/thread_pool.hpp>)Does
thread_pool(n = 0)Starts n workers and keeps them for the pool's lifetime. Neither copyable nor movable.
thread_pool(0)0 means thread_pool::default_worker_count(): one less than the hardware threads, never less than one.
worker_count()The number of workers.
submit(task)Shared queue: the first free worker runs it.
submit_to(worker, task)That worker's own queue: only its thread runs the task, in submission order, between the shared tasks it also takes. For thread-affine state.
submit_to, bad indexAn index outside 0 to worker_count() - 1 throws std::out_of_range.
submit(task, then, where)Runs then once after task returns or throws.
submit_batch(tasks, then, where)Queues a std::vector of tasks under one lock and runs then once after the last finishes. An empty batch continues at once.
submit_batch, queueing throwsThe tasks already queued run, then is dropped and the caller gets the exception.
wherethen_on::pool (default): on the worker that finished the work. then_on::posted: on the thread that calls run_posted().
wait_idle()Blocks until every task submitted so far has finished, pool continuations included, and their captures are destroyed. Does not wait for posted work.
post(task)Queues a task for the draining thread, from any thread.
run_posted()Runs the posted queue on the calling thread, in posting order, and returns how many tasks ran.
on_exception(handler)Installs a std::function<void(std::exception_ptr)>; replaceable at any time from any thread.
spin_budget_ns(ns), spin_budget_ns()Sets and reads how long an idle worker spins before it parks (default 1,000,000: 1 ms).
thread_pool::performance_core_count()A hybrid CPU's performance cores, between 1 and the hardware threads; detected once.
jai::thread_pool pool{n, jai::thread_pool::fast_threads}Workers that ask the system not to throttle them to save power (off by default).
spin_budget_ns(0)Parks at once.
eng->parallel_thread_count(n) / parallel_thread_count()Threads of a parallel region, the caller included. Default: the performance cores, at most the hardware threads minus one; 0 restores it.

dynamic_binder: Binding C++ Classes​

When a bound object is copied or shared​

The boundary never copies; from there a bound object behaves as a script class instance does (chapter).

ScriptResult
A value into a variable, field or element (Turret b = a;, var c = b;), a by-value parameter or return, a container literal element ([a]), a by-value capture ([a], [=])A separate C++ object made by the copy constructor.
A call's T& result into var c, auto c or Turret cA copy, as C++ auto drops the reference.
A T& argument the host passes into a var p or Turret p parameterA copy.
A reference: var& g = gate;, auto& c = owner.child();, C& c, a & parameterThe same object. c = other; runs the class's bound =.
A handle: new Turret(...), a shared_ptr<Turret> variable, a std::shared_ptr<Turret> the host returns, passes to a callback, or gives add_global or make_objectEvery copy of the handle shares the one object: var g = gate; names the host's gate.
A class with a deleted copy constructor (std::atomic, std::mutex)Each copy above raises Cannot copy C++ object of type 'T': the type is not copy-constructible. Hold it by reference (auto&/var&) or share it (shared_ptr<T>).
A typed slot the object converts into: int n = obj; as a declaration, assignment, parameter, return or fieldCalls the bound to_int on the object itself, with no copy.
A float, bool, char or string slotCalls to_float, to_bool, to_char or to_string.
Assigning into a variable, field or element declared C (a = other;)Runs the bound = on the held object. With none bound, a copy-constructed object replaces the held one.
Assigning into a var variableTakes a copy of other in place of what it held.
A bound property passed to a & parameter: gate.level, or this.level or a bare level inside a method of a script class deriving SpawnerBinds the property itself; the callee's writes land in the C++ member.
A bound property in a lambdaA [=] lambda copies its value at creation; a [&] lambda reads the live property.

Binder verbs​

Explained in The binder chain, verb by verb.

VerbWhat it binds
.constructor<Args...>()A constructor overload, resolved by arity and C++ parameter types.
.constructor<>()Prefers a T(engine*) constructor when the type has one.
.method(name, &T::fn)A member function, const or not. A member data pointer here is a compile error that points to .property().
.method(name, lambda)A lambda whose first parameter is T& or const T& is a method on that object.
Any other lambdaA method that ignores its receiver (.method("twice", [](int64_t k) { return k * 2; })).
A stateful functorKeeps one state on every call path.
OverloadsRegistering a name again with another C++ signature, same arity included, adds an overload.
.static_method(name, fn)A free function or lambda, called as Vec2::axes(). Bound-class arguments work.
A static method returning a bound classDoes not compile: return primitives or strings, and bind a constructor overload or an engine-level function as a factory.
.property(name, &T::member)Field access. A setter is generated only for a copy-assignable member type. For a class with no serialization route of its own the member is a key of its archive record, written and read in place; a const member stays out (binding records).
A std::vector<E> memberBinds as a live view without a copy (size, push, pop, at, []).
A member of a registered class typeBinds by reference.
A jai::signal<Sig> or signal_emitter<Sig> memberBinds as a connect surface.
.property(name, getter, setter)A computed property from lambdas or a member function pair. A nullptr setter makes it read-only: a store raises Cannot assign to 'x': it is read-only. In a binding record the getter's value is written, and a load reads over it and hands it to the setter; a read-only one stays out.
.transient_property(name, &T::member), .transient_property(name, getter, setter)The same property, kept out of the class's binding record, as JAI_TRANSIENT_PROPERTY is out of a property_owner's.
A member of a class type not registered yetBinds; the type may register later, in any order (std::shared_ptr<Node> next inside Node's own chain). The first load, check or host call into script raises Bindings are incomplete: with one line per type still missing: the class and member, the C++ type and the registration to write.
.static_property(name, &var), .static_property(name, getter, setter)A class-level value, read and written as Class::name. The pointer form binds the live variable.
.base_class<Base>()An inheritance edge: base methods and properties are callable on the derived with virtual dispatch, and a Base slot may hold this type on a polymorphic load. Diamond inheritance throws. A binding record writes the base's binding first, and a polymorphic one takes its $type from the binding's name.
.auto_bind(mode)Everything the declarations know: property_owner schema properties, observables and signals; base classes; and to_string(), size(), empty() and ==/!= where the class has them. Modes: all, properties, hierarchy.
static T::jai_auto_bind(dynamic_binder<T>&)A hook through which a class binds its own private members.
.transparent_wrapper(unwrap)An operation not found on the wrapper retries on the unwrapped value: gate.threat + 2 reads the observable's int while .on_change stays on the wrapper.
.post_load_hook(fn)The class's post_load, [](T& self) or [](T& self, int version), run by a script-side load (from_json, from_binary, from_base64) after it writes the fields, and by a C++ archive after it loads a binding record.
.version(n)A number (default 1) for the script archives only; the C++ archives never read it (versions).
.deserialization_factory(fn)Constructs a non-default-constructible type on load. The archive-only, context-only and context-plus-archive signatures are detected.
.serial_only_constructor(), .serial_method(name), .serial_static_method(name)Keep the constructor or the call out of parallel bodies.
.bound_engine()The engine this binder targets, for a configure function that calls bind_signal_type or adds globals.
.build()Optional: commits the registration there and throws what fails. Without it the binding commits when the binder's lifetime ends, or at the engine's first load while the binder is still alive; a failure then joins the Bindings are incomplete: error of the next load. Chain calls after the commit apply to the registered class. A binder moves but does not copy.

Bound operators​

Operators are methods named by token and follow the script rules of the operators chapter.

BindingRule
Tokens+ - * / % & | ^ << >> == != < <= > >= <=>, unary - + ~ (a lambda taking only the object), [], (), =, and to_bool for !v and if (v).
Supplied<=> supplies < <= > >= and ==; == supplies !=; 5 == m asks m's == taking an int.
++, --C++'s two forms, which a script class cannot declare: a lambda taking only the object answers ++v; one taking the object and an ignored int64_t answers v++.
A missing ++ or -- formCannot apply '++' to T
Compound ("+=" ...)Runs once on the object in place, for a variable, field, element, map value or reference. Nothing is stored back; the expression is what the operator returns.
Compound, none boundm += o is m = m + o.
ReflectedA lambda whose first parameter is another type and whose second is the class, C++'s free operator-(int64_t, const Money&), answers 5 - m.
No reflected formArithmetic never reverses on its own; the checker warns at a number left of a bound class's operator that has none.
Overload choiceAn operator counts for an operand only when one of its overloads takes it.
operator<(const Money&) and operator<=>(int64_t) boundm < 5 runs the <=>.
Only operator+(const Money&) boundm + "!" is the builtin concatenation of m's text.
No matchNo bound 'Money * int': bind operator "*" taking int on Money, or convert the operand

Template instantiation names​

A bound template instantiation is named the way a script spells its argument (static binders).

C++ argumentScript spelling
int64_t, doubleint, float (a script double is the same type)
int32_t, uint8_t, C++ floatint32, uint8, float32
std::vector<E>, std::map<K, V>, std::shared_ptr<T>array<E>, map<K,V>, shared_ptr<T>
A classIts JAI_STATIC_BINDER or template-binder name
Anything elseA compile error: specialize jai::type_name_helper<T> with the name scripts write.

Atomics​

The script surface of a bound std::atomic (chapter).

ScriptTypesValue
a.load(), int n = a;, float f = a;, bool b = a;, if (a)allThe value. A typed slot converts through the atomic's own value type; an int or float one is true when non-zero.
a.store(v), a = vall= yields v.
a.exchange(v)allThe value before.
a.compare_exchange(expected, desired)allC++'s compare_exchange_strong: stores desired and yields true when the value equals expected; else writes the value into the variable expected and yields false.
a.fetch_add(n), a.fetch_sub(n)int, floatThe value before.
a.fetch_and(bits), a.fetch_or(bits), a.fetch_xor(bits)intThe value before.
a += n, a -= nint, floatThe value after.
a &= bits, a |= bits, a ^= bitsintThe value after.
++a, --a / a++, a--intThe value after / the value before.
a == v, a != v, to_string(a), print(a)allCompare or print the loaded value.

Safety rails: Safety Rails​

Named permissions​

Explained in Named permissions for your APIs.

CaseRule
register_privilege(name)Returns nothing. A new name starts disabled.
Registering a name again, or registering "filesystem" (the built-in flag)Leaves the grant as it is.
An unknown nameA query is false. Enabling, disabling, requiring or attaching it to a callable is rejected, never auto-registered: register names before binding functions that require them.
A missing permissionInsufficient privilege: savegame.write.
Several missingThe text names a built-in first, then the first custom one in registration order, whatever the list order.
Several enginesNames are reusable across engines; registration and grants are per engine. There is no global registry.
CostA binding resolves its requirements once to engine-local slots: a call's check looks up no name, allocates nothing and takes no lock or atomic. Registration and binding may allocate.

Depth limits​

Only script calls have a counted cap; the errors are catchable, the static listing's aside (chapter).

DepthBoundError
Script callsA per-engine cap for each backend: 256 active calls on the interpreter, 250,000 on the VM.Maximum recursion depth exceeded - possible infinite recursion (uncaught, the error reads as this text alone)
Native stack at a callLess than 256 KB of stack left, whatever the cap.Native stack exhausted before the call-depth cap: the host thread's stack bounds this recursion
Which stackThe host thread's, or an engine::start run's own fiber stack. Windows, Linux, Android and macOS report its bounds; a stack the OS calls unlimited counts as 1 GB.
Source nesting: parser, checkerEach stops with 192 KB of stack left.nesting too deep for the host thread's stack in the static listing, one error at the deepest point reached
Source nesting: VM compiler, interpreter's evaluationThe same margin.Nesting too deep for the host thread's stack when the code runs
A platform that cannot report its stack's boundsThe source walks stop 1 MB below where the walk started: give a script-loading thread there more stack.
Saved-program nestingThe .jaibite writer and reader stop at the parser's margin: what loads from source also saves and loads as a bite on the same thread.jaibite: AST nesting too deep or jaibite: type nesting too deep
Nested dataNone but memory: JSON parsing, from_json/from_binary, to_json/to_binary and a deep copy walk a value on work lists.
Host record typesA host type whose own serialize/save/load/load_and_construct recurses natively stops at the stack's margin, or at 1024 levels (JAI_MAX_HOST_RECORD_DEPTH) where the stack cannot be asked.Record nesting depth exceeds the host thread's stack or Maximum record nesting depth (1024) exceeded

Static checking: Static Checking​

Member checks​

A member missing from a fully known class is a load error (Members).

MemberRule
AccessorsA member a user _get_x/_set_x declares anywhere in the chain is known, and typed when the declaration is unambiguous (the map lists the ambiguous shapes).
One parameterless, non-coroutine int _get_hp()Makes char c = obj.hp an error.
One by-value void _set_hp(int)Makes obj.hp = "s" the error cannot assign 'string' to property 'hp' declared 'int'.
Bare names in a classA bare name in a method resolves locals, then members, then globals, the run-time order: a bare x is checked as the field and ::x names the global.
Enum valuesChecked for existence: no value 'zzz' in enum 'Direction'.
Builtin methodsA method of a known string, array or map: its name, its argument count and each argument's shape (a number, text, an array, a map, a function) as the API reference states it.
Bound methodsA method the binder bound on a known receiver: its argument count and each argument against the C++ signature, unless a script class below the bound class declares the method or the object was extended.
UncheckedSubscripts, and the handle builtins of a shared_ptr (use_count, cpp_ref_count, unique, same_as, reset).
A class method named as a builtinWins. Where the program holds that class or a subclass by shared_ptr, the method warns that the builtin is unreachable on its handles; a class held only by value keeps its reset() unwarned.
Open classesAn ancestor the checker cannot resolve opens the whole class: no member diagnostics.

What still fails at run time (the precision map)​

The whole list of spots where the checker is silent: a mistake there is a run-time error, never a load error (chapter).

Lenient by designWhy
var anywhere, and everything computed from it; a null initializerNo declared type to check against.
auto without an initializerauto locks at a store the checker cannot order.
Stores through an auto&, a var& or a reference to a classThe referenced variable's table runs at run time. A store through a T& of a scalar, text, container or math type is checked as a T slot's; binding the reference and member lookup through it are checked too.
Container elements the checker cannot type: map keys, elements of untyped containers and var receivers, subscripts, builtin container/string method resultsThe runtime element boundary is the check.
Literal elements, pushes and element stores into a declared array<T> or map<K,V>Checked.
Engine globals (typed off the value tag; a later retag is invisible), host-object conversions, callables held in variablesNo static signature in view.
Host parameters that take any value or a bound object, a bound method a script subclass declares again, binder properties, a name declared extern function f;The C++ signature does not decide them. A host function's and a bound method's count and primitive, text and container arguments are checked.
A function or global two units declare, a class-typed global of another unitMore than one declaration could answer, or the slot may hold a subclass. One unit's function signature and typed global are checked in every other unit of the program.
Dynamic-path import(expr)/include(expr) units (undeclared identifiers drop to warnings, unknown types and namespace members too), untyped extern names, a parent found nowhereSuch a unit or name can define anything.
Accessor-bearing members whose declaration is ambiguous: two getter/setter overloads, var/auto/coroutine/parameterized getters, reference or untyped setter parameters, an accessor on a registered-only ancestorNo single declaration to trust.
Coroutines: yield and return types, and what a handle's next() returnsThe checker does not model what a coroutine yields. The handle itself is typed: next and done are its only members.
Mixed-branch ternaries, this/super as values, extended classes and everything passed to extend(...)The shape is decided at run time.
Hot-reload redefinitionsReceivers that carry the old shape are open.

Hot reload: Hot Reload​

Instance migration on a class reload​

What a reloaded class does to its live instances (chapter). The cost is linear in live instances and fields.

ChangeLive instances
Field kept (same name and type)Value preserved, even if the initializer changed: an initializer is a new instance's default.
Field addedTakes the new initializer.
Field removedGone; access raises Player has no member 'speed'.
Field retypedThe value converts as an assignment would (int to string through to_string, int to float). A float truncates into an int as cast<int> does.
Field retyped, no conversionTakes the new initializer.
Method body changedThe next call runs the new body, including through a method bound earlier with reflect::bind.
Static membersValues preserved, not reset to the initializer. Removed statics are inaccessible. Static method bodies update.
Constructor overloadsReplaced with the class; dispatch by arity works as before.
Base class reloadedDerived instances keep their own data and take the base's new and retyped fields, converted the same way.
Script class extending a C++ classThe live C++ object is carried across, neither destroyed nor rebuilt. Inherited C++ methods stay callable.

Serialization archives: Serialization​

Archive calls inside save, load and serialize​

Named properties are the normal form; the unnamed calls exist for hand-written layouts (chapter).

CallWriterReader
ar.serialize("name", value), ar("name", value), ar(JAI_NVP(x), JAI_NVP(y)), ar(make_nvp("name", value))A named property through the dispatch. JAI_NVP spells the key from the variable's name.The named property into value. A record without it throws Property 'name' not found during deserialization.
ar(obj)obj's own save or serialize, in place.obj's own load or serialize, from the current record.
ar.has_property("name")Whether the current record has the property.
ar.seek_property("name")The same; seek_property also positions the reader at it (false when absent).
ar.write_element(value) / ar.read_element(value)The next element between begin_array and end_array.
write_int8 … write_int64, write_uint8 … write_uint64, write_float32, write_float64, write_bool, write_string; read_int32(), read_string(), … in the same widthsOne unnamed primitive.
begin_object(type_name, version) / end_object()Opens a record.Fills both arguments.
begin_array / end_array()begin_array(size)size_t n = begin_array()
begin_map / end_map()begin_map(size), write_map_key(key)begin_map(), read_map_key(key)
ar.get_version()The version of the record opened last (versions).

Versions​

The archives keep no class version: a reader finds a property by name (Versions and missing fields). The std::uint32_t version parameter of a member save, load or serialize carries a chosen number only for a record the code opened itself.

CaseVersion
A saveAlways receives 0.
A load reached through a named read (ar("name", obj), a JAI_PROPERTY, a non-polymorphic pointer's $val)Receives the number stored in that record.
Every other load (ar(obj), a polymorphic pointer's $val)Receives 0.
ar.get_version()Answers the same number for the record opened last.
A record the archive opens itselfStores none: JSON writes no "$type" or "$version" key, which reads back as 1; binary stores an empty type name and 0.
The root record of to_json / to_binaryVersion 1, which JSON leaves unwritten.
begin_object(type_name, version) in a hand-written saveBinary keeps any number. JSON writes "$version" only above 1, so 0 reads back as 1.
A binder chain's .version(n)Never reaches the C++ archives. A number (1 by default) the engine keeps for the class, used only by the script archives for bound objects inside script values.
Script to_binary / to_base64Write it after the class name, beside each bound object.
Script to_jsonWrites no "$version".
from_binary / from_base64Hand it to the class's .post_load_hook only when the class also has a .deserialization_factory. Without a factory the object is rebuilt by its class name and the hook receives 1.
from_jsonRuns the hook with the object's "$version" key (beside its "$type") when it has one, and 1 otherwise.

Binary format​

Both formats run the same calls. JSON writes a record as an object of its properties; a record opened with a type name leads with "$type", then "$version" when it is above 1. Binary is little-endian.

Binary valueBytes
record, opening0x01, the type name (a uint32 length, then the bytes; empty for a record the archive opens itself), a uint32 version.
record, closingIts property count, every property name, every property's byte size, the values, and 0x02.
property byte sizesA reader jumps to a property by name and skips unknown ones unparsed.
array0x03, a count, the elements, 0x04
map0x05 … 0x06
null0x00

Class and handle keys​

The script archives and the C++ archives spell classes and handles with the same keys, in JSON and in binary, numbered by per-save object ids.

ValueWritten as
an object that names its class{"$type": "Name", "$val": {...}}
shared_ptr, first reference to its object{"$id": N, "$val": {...}}, with "$type" when the object names its class
shared_ptr to an object already written{"$id": N}, which the load re-aliases, in any order and through cycles
weak_ptr{"$ref": N}
weak_ptr as the first referenceCarries the object's "$val" (and "$type") inline, so a weak pointer that precedes its owner still loads.
unique_ptr (C++ only){"$val": {...}} by the pointer's static type, with no $type.
a unique_ptr<Base> holding a derived objectWrites only the Base fields and loads a Base: a polymorphic owner is a shared_ptr<Base>.
null pointer, empty handle, expired weak handlenull

Script archives and C++ archives​

The two layers differ only where a static type exists.

Script archivesC++ archives
an object's classnamed on every objectnamed only when a base pointer holds a registered derived class; a nested C++ record carries no $type
the root, a record typethe value itselfits properties, flat, with no type name
the root, anything else (an owning pointer, a container, a number)the value itselfunder "value0", as the binder chapter shows
a load that returns T by valueneeds a default-constructible, movable T; a property_owner is both
a shared_ptr rootkeeps identity and a derived object's $type
a class name the engine does not knowthe object loads as a plain mapthe load throws Unknown polymorphic type: Name

Signal threading policies​

The policy is the emitter's second template argument: Signals, Threads.

Unsynchronized (default)Synchronized
Emittersignal_emitter<Sig> (jai::signal_unsynchronized)signal_emitter<Sig, jai::signal_synchronized>
Viewjai::signal<Sig>jai::signal<Sig, jai::signal_synchronized>
On a property_ownerJAI_SIGNAL_PROPERTYJAI_SYNCHRONIZED_SIGNAL_PROPERTY(Sig, name)
Script bindingbind_signal_type<Sig>bind_signal_type<Sig, jai::signal_synchronized>
LockingNone. One thread at a time uses the emitter and its view, for every call.One recursive mutex, held by the whole of an emit (receivers included) and by every other call on the emitter, a save or a load among them.
Emits from different threadsRun one after another.
A connect from another threadWaits for an emit in progress.
A receiver while it firesMay connect, disconnect or re-emit. Must not wait on another thread that is connecting to or emitting the same signal.

Language server​

The server and its editor extension: The Language Server.

Features​

FeatureCovers
DiagnosticsParse errors and every checker error and warning, in the checker's text, on each change.
Diagnostics, imported fileAn error in an imported file is reported in that file.
Diagnostics, unparsed fileReports its one parse error. The names it declares stay declared for its importers.
Definition, referencesFunctions, classes, fields, methods, constructors, globals, locals, parameters, enums and their values, namespace members, across imports and includes.
Definition on a pathOn an import or include path, opens the file.
HoverA variable's declared type, or the type inferred for auto; a function's signatures; a class with its fields and methods; an enum value with its number; a host function with its registered parameter types.
CompletionNames in scope, the enclosing class's members, the program's and the host's declarations, keywords.
Completion after .The receiver's members (inherited and bound C++ ones included) or the builtin methods of an array, map or string.
Completion after ::A namespace's members, a class's statics, an enum's values.
Signature helpEvery overload inside a call's parentheses, the current parameter highlighted.
RenameThe declaration and every use across the program (rules).
SymbolsThe document outline, and every declaration in the workspace's .jai files, fuzzy-matched.

VS Code extension settings​

A settings change restarts the server: The VS Code extension.

SettingDefaultMeaning
jaiscript.languageServer.enabledtrueRun the server for .jai files.
jaiscript.languageServer.pathemptyThe server executable; ${workspaceFolder} expands.
jaiscript.languageServer.hostrunnerThe host profile.
jaiscript.languageServer.includePaths[]Include paths for import and include.
jaiscript.languageServer.logfalseLog requests and timings to the JaiScript Language Server output channel.

More host calls​

The calls a host reaches for after the quickstart: The Host API.

Registration and engine queries​

Typed and variadic registration: Registering C++ functions.

CallWhat it does
add_function_with_arity(name, fn, arity)Registers a raw jai::script_function (it receives the argument vector) of exactly arity arguments; SIZE_MAX takes any count.
add_overloaded_function(name, arg_count, fn)Adds a raw script_function of arg_count arguments to name's overload set.
add_custom_conversion<T>(from, to)Crosses a C++ T with no binding: from(const script_value&) -> T and to(const T&) -> script_value.
add_vector_conversion<T>(), add_map_conversion<K, V>()Registers the std::vector<T> or std::map<K, V> conversion ahead of the first binding that names it. Bindings register the shapes they name.
registered_class_names()Every class the engine can name, host-bound and script-declared, sorted.
has_registered_class<T>()Whether a dynamic_binder bound T on this engine.
get_backend_type(), get_backend_name()The backend (jai::backend_type::vm unless set_backend chose another) and its name.
text_of(v)A value's text as print spells it, a class's own to_string() included. A failing to_string()'s error is the result.
app_data_dir(app), app_cache_dir(app)The per-user data and cache folders for an app. Queries only; the folders need the filesystem privilege.
app_directory_resolvers(data, cache)Installs the resolvers the two folder queries answer from.
engine::executable_path()The running executable; empty when the platform cannot say. A query only.
set_output_stream(stream)Redirects print(); nullptr restores std::cout.

script_run calls​

Where a run suspends, what each resume costs and teardown: Suspendable runs.

CallDoes
eng->start(source) start_file(path) start_file_source(path, content) start(bite) start(callable, args)Starts a run and runs it to its first sleep or its end.
what each start runsWhat execute, execute_file, execute_file_source, a jaibite's run or a call of the function value would be. A jaibite must outlive its run.
run.state()script_run::status::suspended, completed or failed.
run.done()done() is true for completed or failed.
run.wake_at()When the suspending sleep ends, as a monotonic reading of the time source; zero once done.
run.resume()Continues to the next sleep or the end and returns the state. Before wake_at() it returns suspended at once, so a host may call it every frame.
run.cancel()Ends a suspended run: its sleep raises the terminal Run cancelled, no catch runs, every frame unwinds with its destructors, and the run is failed.
run.result()The run's value once completed, its error once failed (eng->format_error(run.result())).
~script_run()Cancels a run still suspended. script_run is move-only.

script_run misuse​

Each throws jai::runtime_error.

MisuseThrows jai::runtime_error
resume() or cancel() on another threadA run resumes only on the thread that started it
Resuming or cancelling a run from code that run is runningThe run is running: it resumes only while suspended
resume(), cancel() or result() on a moved-from script_runThe script_run holds no run
start of a value that is not a functionCannot start a run of a value that is not a function

HTTP transport headers​

Opt-in headers that nothing in the library includes: The HTTP transport.

HeaderProvides
<jaiscript/net/http_socket_transport.hpp>jai::net::http_socket_transport: HTTP/1.1 over Winsock or POSIX sockets, one connection per call. Every wait is bounded by the timeout, name resolution excepted.
http_socket_transport, httpsAn https URL needs a jai::net::tls_layer passed to the constructor. Without one it fails with HTTPS needs a transport with TLS: this one speaks plain HTTP only.
<jaiscript/net/openssl_tls.hpp>jai::net::openssl_tls: that layer on OpenSSL 1.1.1 or 3.x (TLS 1.2 and up, SNI, the chain and host name verified). Include it only where OpenSSL is linked.
openssl_tls, trust setupThe constructor takes the trust setup, a function of the SSL_CTX*. Without one it uses OpenSSL's default certificate paths.
openssl_tls, platform storesOn Android and iOS the trust setup fills the store from the platform (SecTrust, /system/etc/security/cacerts); on Windows, from the ROOT store.

jai_spider​

The web host and its script library lib/spider.jai: jai_spider.

Command-line options​

Starting an app: Running an app.

OptionMeaning
--port NThe port (default 8080; 0 picks a free one, named in the spinning line).
--host ADDRThe IPv4 address to listen on (default 127.0.0.1; 0.0.0.0 for every interface).
--root DIRThe static root (default: the app's public/ folder when it exists).
--max-body NThe largest request body, with K, M or G (default 2M).
--timeout SSeconds a request may take to arrive and a response to leave (default 30).
--budget SThe execution budget per request (default 30; 0 is unlimited).
--backend vm|interpThe engine's backend.
--devError pages show the error and its trace, absolute paths included. The library is imported from the source checkout's lib/, so edits to it reload too.
--allow-shutdown TOKENPOST /__spider/shutdown from the same machine with the header X-Spider-Shutdown: TOKEN stops the server; without the token it is 403. A cross-site page cannot send the header without a preflight the server never grants.
--quietNo banner and no request log.

spider::spin options​

The second argument of spider::spin(handler[, options]): A first app. Each overrides the command line from the next request on; any other key raises.

KeyMeaning
budgetSeconds a request may run.
max_bodyThe largest request body, in bytes.

spider::Request​

A script class in lib/spider.jai: Request and Response.

MemberWhat it holds
method, query_string, versionFrom the request line.
pathThe path, percent-decoded and without the query.
targetThe request target as it arrived.
querymap<string, string>: the parsed query (+ is a space).
cookies, cookie(name)map<string, string> of the Cookie header; cookie(name) is "" when absent.
params, param(name)map<string, string> of the route's captures; param(name) is "" when absent.
headers, header(name)Names lowercased; header("Content-Type") reads any spelling.
bodyThe raw body.
form()The body's urlencoded or multipart fields.
files()The body's uploads as map<string, spider::Upload> (filename, content_type, data, size(), save(path)).
json()from_json of the body.
remote_addrThe peer's address.

Response helpers​

spider::Response members: spider::Response. What a handler may return: A first app.

HelperWhat it answers
spider::Response(status, body)Both optional: 200 and an empty body.
spider::html(text), spider::plain_text(text), spider::json(value)A body with its Content-Type. Each takes an optional status; spider::html takes a web::html too.
spider::redirect(url)303 See Other by default, the answer to a form post.
spider::file(path, content_type)A file streamed from disk, relative to the app's folder, typed by its extension unless one is given.
spider::file, the headerThe response names the file by a random token in an X-Sendfile header that the host consumes, so an X-Sendfile header from anywhere else serves nothing.
spider::not_found(text)A 404.

Failures and limits​

What the server answers when a request or a handler fails: Errors and limits.

FailureAnswer
A run-time error or an uncaught throw in a handler500. The error and its trace are logged; the page shows them under --dev only.
A Response with a status outside 200 to 599500, logged: Response status 101 is not a final HTTP status (200 to 599)
A header name that is not a token, or a header value holding a line break or NUL500, logged: Response header 'N' is not a header name, or its value holds a line break or NUL
A cookie line holding a line break or NUL500, logged: Response cookie 'L' holds a line break or NUL
An app that never called spider::spin500 naming the missing call.
A handler that runs past its budget500 through the execution budget (Safety Rails); the engine serves on.
A body over the maximum (2 MiB by default)413 before the body is read.
A request head over 64 KiB431 (414 for a request line alone).
A malformed request, both Content-Length and Transfer-Encoding, a header field holding a bare CR, LF or NUL, a dot segment in the path400.
Another HTTP version505.
Another transfer coding501.
A Host header that is not localhost, a 127.x address or [::1], when the server listens on a loopback address421, so a page whose own name resolves to 127.0.0.1 (DNS rebinding) cannot reach the app.
A request that takes longer than --timeout to arrive, or a response to leaveThe connection closes.
More than 512 open connections, or more than 128 from one address503.

Command-line runner​

jaiscript, the reference embedding: the jaiscript_runner target (on by default) builds it into build/bin/. Full notes: tools/jaiscript/README.md. It always registers:

  • the full stdlib, the web:: library
  • the spider:: HTTP client (WinHTTP on Windows, sockets elsewhere, the network permission granted)
  • file IO (read_file, write_file, file_exists, delete_file, move_file, dir_exists, make_dirs, list_dir, writable_dir, plus Download, zip_entries and zip_extract)
  • a console-game surface (A Terminal Game in One File: read_key, key_down, term_cols, term_rows, env_var, and sound: load_sound, play_sound, stop_sounds; clocks are the standard library's time::)
  • two socket classes, NetServer and NetChannel, and net_host_name and net_local_addresses
InvocationWhat happens
jaiscript file.jai [-- args...]Runs the file as a suspendable run (each sleep returns to the runner, which waits on the engine's clock and resumes it), then exits.
Exit codeThe script's trailing int expression if any; 1 on a script error; 2 on a usage error (unknown flag, missing or malformed value).
Exit code outside 0 to 255Its low byte, on every platform: 300 exits 44, -1 exits 255.
--Everything after it is the global ARGS.
A .jai dropped onto the executableRuns the same and pauses before closing.
jaiscript -e "source" [-e ...] [-- args...]Runs the source text as a file run runs a file's text, then exits. Combines with the flags below.
Several -eRun in order in one engine: declarations carry over, the first error stops the run.
-e exit codeThe last source's int result; 1 on a script error.
-e outputOnly what the source prints (jaiscript -e "print(1 + 2)"). The REPL is the mode that echoes values (echo 1 + 2 | jaiscript).
-e with include/importResolve from the working directory.
-e with a script file or --watchA usage error.
jaiscriptREPL with a persistent engine: :load, :reset, :backend vm|interp|vm-reference, :time on|off, :help, :quit.
Piped input (echo 1 + 2 | jaiscript)Exits 1 when any input failed, else 0.
jaiscript --watch file.jaiRe-runs the file on every save, in a fresh engine each time. To keep the world, reload with a guarded import: Reloading from a running program.
FlagMeaning
--backend=vm|interp|vm-referenceExecution backend, default vm; the backends agree. vm-reference: the VM with every optimization off (engine::vm_reference_mode).
--warningsLists the checker's warnings on stderr before the run. Typed mistakes and undeclared names always fail the load.
--workers=NWorker count of parallel_for, parallel_transform and parallel_reduce; default the engine's own.
--budget=NExecution budget in seconds per execute. Default 0 = unlimited (the embedded default is 5.0 s).
--timePrints wall time per run.
--debug / --debug-port=NStarts the DAP debug listener on localhost (VS Code attaches) and names the bound port on stderr. Without either, nothing listens.
Debug port--debug-port=N tries port N first. Default 52477, probing upward in steps of 10.
--pause / --no-pauseForce or suppress the "press Enter to close" hold after a file run.
--version / --help
--benchmark-main=NRuns the file once as a warm-up, then calls its main() N times.
--no-param-alias / --no-local-aliasNo read-only copy elision for by-value parameters / locals: every one copies. Results never change.
--no-proofsNo type proofs, typed ops or cell blocks: the general form, same results.
--miss-every=NThe VM misses at every Nth plain cell instruction, exercising the flush at every position (a build configured with JAISCRIPT_MISS_INJECTION).
--disasm=FILE / --manifest=FILEEvery chunk the VM compiles, written to FILE: readable listings, or exact semantic manifests.
--coverage=FILEThe VM's execution-coverage census at exit (a build configured with JAISCRIPT_VM_COVERAGE).
--selftestRuns the runner's built-in smoke tests and exits.

Ctrl+C. Ctrl+C or Ctrl+Break on Windows, and SIGINT, SIGTERM or SIGHUP on Linux and macOS, end the runner at once, in every mode the REPL included.

  • The script stops where it is, without running its destructors. Exit code 130; on Linux and macOS the signal itself ends the process, so the shell reports 130, 143 or 129.
  • A signal the runner was started with ignored (nohup, a background job) stays ignored.
  • Before it exits, and when a script ends or fails, the runner gives a terminal back: colours reset, cursor shown, alternate screen left, each only where the script changed it. The input mode, and on Windows the console mode and code pages, come back too. A pipe or a file receives none of it.
  • A script that must stop itself has the execution budget (--budget=N).

The runner's host API​

Registered on every engine the runner builds; a host embedding JaiScript registers its own. Signatures: API reference, under the standalone runner.

  • Calls that name a file (the file functions, Download.start, load_sound, play_sound) need the engine's filesystem privilege, else Insufficient privilege: filesystem.
  • Download.start needs the network privilege too.
  • Locations (executable_dir(), root_script_dir(), current_script_dir(), resolve_path(name), app_data_dir(name) and the rest) are language builtins on every engine: Modules.
Files and foldersBehavior
a relative pathResolves from the working directory (import and include: from the script's folder). A path built from root_script_dir() names the same file from any working directory.
read_file(path) -> stringThe file's bytes exactly (binary mode; "" for an empty file). Missing or unreadable: Cannot read file '<path>': <reason>.
write_file(path, text) -> boolWrites the bytes exactly, creating missing folders; false when it cannot.
file_exists(path), dir_exists(path) -> boolWhether the path names something, or a folder.
delete_file(path) -> boolfalse when nothing was removed.
move_file(from, to) -> boolReplaces to; false when from is not a file. One volume: a single operation, so a reader sees the old file or the new one. Across volumes: copies, then removes the source.
Safe saveWrite save.tmp, then move it over save.txt.
make_dirs(path) -> boolCreates the folder and its parents; true when the folder exists afterwards.
list_dir(path) -> array<string>Entry names, sorted; empty for a folder that cannot be read.
writable_dir(path) -> boolCreates the folder if missing, then writes and removes a probe file: whether it can take a save.
zip_entries(zip) -> array<string>, zip_extract(zip, entry, dest) -> boolA zip archive's entry names; one entry written to dest, CRC-32 verified.
Download()Asynchronous download, polled from the script's loop. A failed transfer leaves no partial file.
start(url, path)Begins it (http or https); false with error() set when it cannot begin.
pump()true once it is done.
success(), error()The outcome.
received(), total(), fraction()Progress; total() is -1 until known.
cancel()Stops it.
Console gamesBehavior
read_key() -> stringThe next key press, without waiting: "" when none is queued, else "up", "down", "left", "right", "enter", "esc", "tab", "space", "backspace", a lowercase letter, a digit or a punctuation character (on Windows also "shift", "ctrl" and "alt").
key_down(name) -> boolWhether the key is held now: a letter, a digit, "space", an arrow, "enter", "esc", "tab", and on Windows "shift", "ctrl" and "alt".
key_down, POSIX terminalNo key releases arrive: a key reads as held for half a second after its last press or auto-repeat; modifiers never read as held.
term_cols(), term_rows() -> intConsole size in characters; 80 by 25 when it cannot be read.
env_var(name) -> stringAn environment variable, "" when unset. env_var("COLORTERM") is "truecolor" or "24bit" in a terminal that takes 24-bit colour.
SoundBehavior
load_sound(path) -> SoundOpens a WAV, MIDI or MP3 file and returns at once; it preloads in the background. null for a missing file or another format.
FormatNamed by the first bytes (RIFF/WAVE, MThd, an ID3 tag or an MPEG frame); an unrecognized header falls back to the extension (.wav, .mid, .midi, .mp3).
s.play(), s.play(true) -> boolStarts from the beginning, looping forever with true; returns at once. A playing Sound restarts. false once the file proved unplayable (its data did not decode).
s.stop(), s.playing() -> boolplaying() is true from play() until stop(), stop_sounds() or the end of a sound played once.
s.volume(level)0.0 to 1.0, clamped; a Sound starts at 1.0.
s.format() -> string"wav", "midi" or "mp3"
play_sound(path), play_sound(path, true) -> Soundload_sound then play. Plays to its end (or forever, looped) even when the result is dropped; the result stops it. null as load_sound.
stop_sounds()Stops every Sound.
VoicesEvery Sound is its own voice; several play at once, mixed.
LifetimeA Sound closes when the last variable holding it lets go; a copy of a Sound is the same sound.
CostLoading costs more than playing: load once, play again. No call waits on the audio device (one audio thread drives the devices).
PlatformsWindows, and the site's Play and Sandbox pages. Elsewhere load_sound and play_sound return null: a script that checks for null runs unchanged, silent.
jaiscript
var music = load_sound("theme.mid");
var hit = load_sound("hit.wav");
if (music != null) { music.volume(0.6); music.play(true); }   // loops until stopped
if (hit != null) { hit.play(); }                               // again and again, each restarting it
play_sound("door.wav");                                        // fire and forget
NetworkingBehavior
NetChannel()One framed TCP connection: listen(port) waits for one peer, connect(host, port) dials one, send(bytes) queues a packet, pump() moves the bytes and fires the signals, close() drops the connection.
connected(), status()"idle", "listening", "connecting", "connected", "closed: <reason>", "error: <reason>"
NetChannel signalson_connect(), on_packet(bytes) and on_disconnect(reason)
NetChannel.connect is asynchronousfalse only when the attempt fails at once (the host name does not resolve, the socket cannot be made), reason in status(). Otherwise true while the connection is still being made.
Connect outcomeArrives through pump(): on_connect() when the peer accepts, on_disconnect("connect refused") when not. Packets sent before then wait in the queue.
NetServer()Many framed TCP clients on one port: listen(port), send(id, bytes), broadcast(bytes) (the count of clients it reached), pump(), kick(id), client_count(), listening(), close(). Client ids count up from 1, never reused by one server.
NetServer signalson_client_connect(id), on_client_packet(id, bytes) and on_client_disconnect(id, reason)
PacketOne send's bytes, delivered whole and in order. On the wire: a 4-byte big-endian length, then the bytes.
ThreadsNone. The sockets do not block; signals fire only inside pump(), on the script's own call stack. A game calls pump() once a frame.
Disconnect reason"closed by peer", "recv error", "send error", "oversized frame" (a length over 64 MiB) or, for a server's client, "kicked".
Packets ahead of a closeDelivered before its disconnect.
net_host_name() -> string, net_local_addresses() -> array<string>This machine's name and its IPv4 addresses, loopback left out.

Performance rules of thumb: Backends, Performance​

RuleWhy
Reuse enginesEngine creation dwarfs a small script's execution.
Prefer jaibite for anything run more than onceThe handle skips the parse; the VM also caches the compiled chunk.
Repeated execute(string) is already cheapAn LRU source cache (64 entries): identical strings skip the parse.
The VM backend is the fast one for hot loopsLoops fuse into superinstructions; the interpreter remains the reference for debugging.
Pass heavy things by &A by-value copy of a big structure deep-copies it.
Declare hot containers with an element typearray<int> / array<float> are raw buffers and skip the per-element boxing.

The measurements: Performance.

Documents​

This site is the specification of the language and its embedding API: a rule stated here is the rule, and a disagreement with the code is a bug worth reporting. The repository's other documents open on GitHub.

DocumentWhat it holds
README.mdwhat JaiScript is, the shortest path to a running engine
BUILD.mdrequirements, CMake options, where the binaries land
CONTRIBUTING.mdhow to build, test and propose a change
tools/jaiscript/README.mdthe runner and REPL
tools/jai_spider/README.mdthe web application host (jai_spider)
examples/gloom · examples/crawler · examples/demoreeleach game's README: flags, keys, the determinism checkpoints