Coroutine Hosting
A script coroutine is how a host runs behavior that spans frames: per-entity AI, cutscenes, spawners. This page is the host's side; the language side is the guide's Coroutines chapter. The model:
- Calling a
coroutinefunction returns a handle and runs nothing.next()runs to the nextyieldand returns its value,next(n)advancesnyields and returns the last one's value, anddone()asks without advancing. - The
next()that finishes the body returns thereturnvalue (null when the body runs off its end). One morenext()raisesCannot resume a completed coroutine, so testdone()first. A range-forover a handle sees only the yielded values. - A handle is an ordinary value. Stored in a global it persists across
execute()calls, and C++ can hold and drive it directly. - Both backends behave identically; nothing a host writes depends on which one runs.
A coroutine yields values to whoever drives it. A whole program that waits on sleep and is resumed by the host's clock is a different tool: eng->start(...) and jai::script_run, in Host API: suspendable runs.
Script surface
coroutine int countdown(int from) {
for (int i = from; i > 0; --i) { yield i; }
return 0;
}
auto c = countdown(3);
while (!c.done()) { print(to_string(c.next())); } // the last next() delivers the return value
var seen = [];
for (auto v : countdown(4)) { seen.push(v); } // range-for drives coroutines
print(to_json(seen)); // yielded values only3
2
1
0
[4,3,2,1]- Spelling.
coroutineprefixes any function spelling, methods and static methods included (coroutine int f(),coroutine function f() -> int,coroutine f()); the guide lists every form. A coroutine cannot return a reference (a parse error). - Methods. A method coroutine's handle keeps its receiver alive while suspended,
thisstays bound, and its writes to the object are visible while it is parked. - Parameters bind by value, reference-shaped arguments included. Captures behave as a lambda's do.
- Nothing can be sent in.
next()takes only its count anddone()takes no arguments. - Types. The checker types a coroutine call as a handle whose
next()returns an unknown type; a member other thannextordoneis a load error. Declared yield and return types are enforced at run time, at thenext()that runs them:coroutine floathands out 2.0 foryield 2, and a value the type rejects raisesCannot yield string from 'k' (declared -> int)(catchable around theyield) orCannot return string from 'k' (declared -> int). A bareyield;hands out nothing. - Budget. The execution budget applies to each
execute()ornext()call. An overrun inside a coroutine is terminal: acatcharound theyielddoes not catch it. - Hot reload. A suspended coroutine keeps the body it started with until it finishes; new calls get the new definition (Hot Reload).
Hosts moving from Lua coroutines or a C++20 scheduler have a mapping in From Lua Coroutines and C++20 Schedulers.
Driving from C++ through executes
The frame-loop pattern: create the behavior once in a global, then tick it each frame with a small next() script. Each execute continues where the last left off. Running the declaring line again redeclares c with a fresh handle, and the same pattern works through a jaibite.
eng->execute(R"( coroutine int spawner(int n) {
for (int i = 1; i <= n; ++i) { yield i * 10; }
return -1;
}
var c = spawner(3); )");
for (int i = 0; i < 4; ++i) {
auto v = eng->execute("c.next();"); // 10, 20, 30, -1
bool finished = eng->execute("c.done()").as<bool>(); // false ×3, then true
}Driving from C++ with the native handle
The script value holds a jai::coroutine_handle. Take the shared_ptr out once and drive it with no script in between:
#include <jaiscript/core/coroutine.hpp>
eng->execute("var c2 = spawner(2);");
auto holder = eng->execute("c2").get_object_holder();
auto handle = std::static_pointer_cast<jai::coroutine_handle>(holder->host_object());
while (!handle->done()) {
jai::checked_result<jai::script_value> step = handle->next(eng.get());
if (!step) break; // error channel (checked_result)
std::cout << "C++ next -> " << step.value().as_int() << "\n"; // 10, 20, then -1 (the return value)
}C++ next -> 10
C++ next -> 20
C++ next -> -1next(engine*)returnschecked_result<script_value>: test it before reading the value.next(engine*, n)is the script'snext(n).get_status()is one ofcreated,running,suspended,completed,failed;done()is true for the last two.- The
shared_ptrkeeps the coroutine alive, and with it a method coroutine's receiver. This is how C++ owns a behavior.