Sharing what we know
Scripts and the host live in the same memory. Passing something between us is direct: for most things, we both end up holding the very same value.
Values
Everything a script can hold is a Value.
| A script’s | Is a Value:: | You get at it with |
|---|---|---|
| Whole number | Integer(i64) | the number |
| Number with a fraction | Float(f64) | the number |
| Text | Str | to_string() |
NULL | Null | |
| Coordinate | Coordinate(x, y) and friends | component(n) |
| List | Array | read() / write() on what it holds |
| Map | Map | read() / write() on what it holds |
An object of a TYPE | Object | get("field") / set("field", value) |
| Function or block | Function, Closure | script.call(&value, args) |
| Error | Error | its code and values |
Make them with Value::Integer(5), Value::string("hi"),
Value::array(vec![…]) and Value::map(…). value.to_string() gives what
PRINT would show, and value.type_name() gives words for a message: “an
array”, “a string”.
A script’s variables
// A variable is read and written by name. In a namespace it has its
// full name: `army.recruits`.
let score = script.global("score");
assert!(matches!(score, Some(Value::Integer(7))));
script.set_global("score", Value::Integer(8));
// An object's fields are read and written by name too.
if let Some(Value::Object(hero)) = script.global("ada") {
let hp = hero.get("hp");
assert!(matches!(hp, Some(Value::Integer(10))));
hero.set("hp", Value::Integer(99));
}
One thing, two holders
This is the important one. Numbers, text and coordinates are copied when they’re passed. Lists, maps and objects are shared. If a script hands you a list and you keep it, you and the script are holding the same list.
// An array, a map and an object are shared, not copied. Keep the one a
// script hands over and both sides are looking at the same thing.
let tracked: Arc<Mutex<Vec<Value>>> = Arc::default();
let keep = tracked.clone();
let track = "/// Has the engine move this position every frame.\nFUNCTION track(position)";
engine.function(track, move |call| {
keep.lock().unwrap().push(call.args[0].clone());
Value::Null
})?;
spot = [0, 0]
track(spot)
From then on, whatever you write into it, the script sees:
// Each frame, the engine writes where the thing has got to. The script
// sees it the next time it reads `spot`.
for position in tracked.lock().unwrap().iter() {
if let Value::Array(numbers) = position {
let mut numbers = numbers.write().unwrap();
numbers[0] = Value::Integer(120);
numbers[1] = Value::Integer(64);
}
}
No message was sent and nothing was copied back. The next time the script
reads spot(0), it’s 120.
This is how an engine keeps a script’s view of something up to date without the script asking: a position that physics moves, a list of who’s in a room, a table of high scores. Give the script the list (or let it give you one), keep hold of it, and write into it.
Each read and write is a single safe step, even with script threads running
on other cores. Take the lock, do one thing, let it go: don’t hold a
write() across a call back into a script.
Calling into a script
// Any function of the script can be called from outside, by name.
let report = script.function("report").expect("the script declares it");
let mut thread = script.call(&report, Vec::new())?;
while thread.run(10_000)? != Status::Finished {}
script.function finds a function by its full name: update,
Army.recruit, Unit::Hit. What you get stays good even if the function
is replaced later by live coding: the next call runs the new one. This is
how an engine calls a script’s update every frame.
Between two machines
Two copies of me that loaded the same scripts can send values to each
other. script.to_wire(&value) gives bytes and script.from_wire(&bytes)
turns them back. Carrying the bytes is your business.
Nearly everything crosses, including functions, blocks with the locals they
kept, and ASYNC calls that haven’t started — which is how you ask another
machine to run something. What arrives is always a copy. Compare
script.fingerprint() on both sides when they connect: it only matches if
both loaded the same code.