builtin scripts

This commit is contained in:
2026-06-16 14:34:42 -05:00
parent 7608fc959f
commit 6464e47747
13 changed files with 317 additions and 119 deletions
+60 -37
View File
@@ -58,6 +58,7 @@ use crate::board::Board;
use crate::game::SAY_DURATION;
use crate::log::LogLine;
use crate::map_file::{color_to_hex, parse_color};
use crate::object_def::ObjectDef;
use crate::utils::{Direction, ObjectId, RegistryValue, ScriptArg};
use rhai::{
AST, Array, CallFnOptions, Dynamic, Engine, FuncArgs, ImmutableString, Module,
@@ -102,11 +103,24 @@ struct CompiledScript {
/// The runtime state of one scripted object.
struct ObjectRuntime {
object_id: ObjectId,
script_name: String,
/// Key into [`ScriptHost::scripts`] for this object's compiled script — the
/// pool name for a named script, or the source text for an embedded built-in.
script_key: String,
scope: Scope<'static>,
output_queue: ObjQueue,
}
/// The compile-key for an object's script: the embedded source itself for a
/// built-in (so identical built-ins share one compiled AST), or the world-pool
/// name for a named script. `None` if the object has no script.
fn script_key(obj: &ObjectDef) -> Option<String> {
if let Some(src) = obj.builtin_script {
Some(src.to_string())
} else {
obj.script_name.clone()
}
}
// ── Rhai types exposed to scripts ────────────────────────────────────────────
/// A snapshot of one board object, returned by `Board.tagged`, `Board.named`,
@@ -205,44 +219,53 @@ impl ScriptHost {
let board = board_cell.borrow();
// Compile each referenced script once.
// Compile each referenced script once, keyed by `script_key` (pool name for
// named scripts, source text for embedded built-ins so identical ones share
// one AST).
let mut scripts: HashMap<String, CompiledScript> = HashMap::new();
let mut failed: HashSet<String> = HashSet::new();
for obj in board.objects.values() {
let Some(name) = obj.script_name.as_ref() else {
let Some(key) = script_key(obj) else {
continue;
};
if scripts.contains_key(name) || failed.contains(name) {
if scripts.contains_key(&key) || failed.contains(&key) {
continue;
}
match script_sources.get(name) {
Some(src) => match engine.compile(src) {
Ok(ast) => {
let defines = |n: &str, params: usize| {
ast.iter_functions()
.any(|f| f.name == n && f.params.len() == params)
};
scripts.insert(
name.clone(),
CompiledScript {
has_init: defines("init", 0),
has_tick: defines("tick", 1),
has_bump: defines("bump", 1),
ast,
},
);
}
Err(err) => {
failed.insert(name.clone());
// Source: the embedded built-in, or a lookup in the world script pool.
let source: &str = if let Some(src) = obj.builtin_script {
src
} else {
match script_sources.get(&key) {
Some(src) => src,
None => {
failed.insert(key.clone());
errors.borrow_mut().push(LogLine::error(format!(
"script '{name}' failed to compile: {err}"
"object references unknown script '{key}'"
)));
continue;
}
},
None => {
failed.insert(name.clone());
}
};
match engine.compile(source) {
Ok(ast) => {
let defines = |n: &str, params: usize| {
ast.iter_functions()
.any(|f| f.name == n && f.params.len() == params)
};
scripts.insert(
key,
CompiledScript {
has_init: defines("init", 0),
has_tick: defines("tick", 1),
has_bump: defines("bump", 1),
ast,
},
);
}
Err(err) => {
failed.insert(key.clone());
errors.borrow_mut().push(LogLine::error(format!(
"object references unknown script '{name}'"
"script '{key}' failed to compile: {err}"
)));
}
}
@@ -251,17 +274,17 @@ impl ScriptHost {
// One runtime per object whose script compiled.
let mut objects = Vec::new();
for (&id, obj) in board.objects.iter() {
let Some(name) = obj.script_name.as_ref() else {
let Some(key) = script_key(obj) else {
continue;
};
if !scripts.contains_key(name) {
if !scripts.contains_key(&key) {
continue;
}
let queue: ObjQueue = Rc::new(RefCell::new(VecDeque::new()));
queues.borrow_mut().insert(id, queue.clone());
objects.push(ObjectRuntime {
object_id: id,
script_name: name.clone(),
script_key: key,
scope: new_object_scope(board_cell, &queue, id),
output_queue: queue,
});
@@ -302,7 +325,7 @@ impl ScriptHost {
..
} = self;
let obj = &mut objects[i];
let Some(compiled) = scripts.get(&obj.script_name) else {
let Some(compiled) = scripts.get(&obj.script_key) else {
return;
};
if !compiled.has_bump {
@@ -318,7 +341,7 @@ impl ScriptHost {
) {
errors.borrow_mut().push(LogLine::error(format!(
"script '{}' bump error: {err}",
obj.script_name
obj.script_key
)));
}
}
@@ -344,7 +367,7 @@ impl ScriptHost {
..
} = self;
let obj = &mut objects[i];
let Some(compiled) = scripts.get(&obj.script_name) else {
let Some(compiled) = scripts.get(&obj.script_key) else {
return;
};
@@ -387,7 +410,7 @@ impl ScriptHost {
if let Err(err) = result {
errors.borrow_mut().push(LogLine::error(format!(
"script '{}' send('{}') error: {err}",
obj.script_name, fn_name
obj.script_key, fn_name
)));
}
}
@@ -456,7 +479,7 @@ impl ScriptHost {
..
} = self;
let obj = &mut objects[i];
if let Some(compiled) = scripts.get(&obj.script_name)
if let Some(compiled) = scripts.get(&obj.script_key)
&& defined(compiled)
{
let options = CallFnOptions::default().with_tag(obj.object_id as i64);
@@ -469,7 +492,7 @@ impl ScriptHost {
) {
errors.borrow_mut().push(LogLine::error(format!(
"script '{}' {hook} error: {err}",
obj.script_name
obj.script_key
)));
}
}