diff --git a/kiln-core/src/api/board.rs b/kiln-core/src/api/board.rs index ca9f15c..0932c99 100644 --- a/kiln-core/src/api/board.rs +++ b/kiln-core/src/api/board.rs @@ -17,6 +17,7 @@ use std::rc::Rc; use rhai::{Dynamic, Engine, ImmutableString}; use crate::{Board, Direction}; use crate::api::object_info::ObjectInfo; +use crate::api::registry::Registry; use crate::script::Registerable; use crate::utils::{ErrorSink, ObjectId}; @@ -90,5 +91,8 @@ impl Registerable for BoardRef { .collect() }, ); + + // Board.registry -> Registry + engine.register_get("registry", |b: &mut BoardRef| Registry(b.clone())); } } diff --git a/kiln-core/src/api/mod.rs b/kiln-core/src/api/mod.rs index fdedc2e..f8d5515 100644 --- a/kiln-core/src/api/mod.rs +++ b/kiln-core/src/api/mod.rs @@ -3,3 +3,4 @@ pub mod player; pub mod board; pub mod object_info; pub mod queue; +pub mod registry; diff --git a/kiln-core/src/api/queue.rs b/kiln-core/src/api/queue.rs index 3cfa929..bb1e03c 100644 --- a/kiln-core/src/api/queue.rs +++ b/kiln-core/src/api/queue.rs @@ -73,13 +73,17 @@ impl ObjQueue { pub fn waiting(&mut self) -> bool { matches!(self.0.borrow().front(), Some(Action::Delay(_))) } + + pub fn clear(&mut self) { + self.0.borrow_mut().clear(); + } } impl Registerable for ObjQueue { fn register(engine: &mut Engine, error_sink: ErrorSink) { engine.register_type_with_name::("Queue"); engine.register_get("length", |q: &mut ObjQueue| q.0.borrow().len() as i64); - engine.register_fn("clear", |q: &mut ObjQueue| q.0.borrow_mut().clear()); + engine.register_fn("clear", ObjQueue::clear); // Appends a [`Action::Delay`] to the back of `queue`, merging with an existing // trailing delay to prevent adjacent delays from accumulating. diff --git a/kiln-core/src/api/registry.rs b/kiln-core/src/api/registry.rs new file mode 100644 index 0000000..494733f --- /dev/null +++ b/kiln-core/src/api/registry.rs @@ -0,0 +1,63 @@ +use rhai::{Dynamic, Engine, ImmutableString}; +use crate::api::board::BoardRef; +use crate::script::Registerable; +use crate::utils::{ErrorSink, RegistryValue}; + +/// The board's script registry, pushed into scope as the constant `Registry`. +/// `get`/`set`/`get_or` methods let scripts read and write per-board key→value pairs +/// that persist across board transitions. Mutation goes through the inner +/// `Rc>`, so the struct itself can be shared as a scope constant. +#[derive(Clone)] +pub struct Registry(pub BoardRef); + +impl Registerable for Registry { + fn register(engine: &mut Engine, error_sink: ErrorSink) { + engine.register_type_with_name::("Registry"); + + // Registry.get(key) -> Dynamic — returns () if the key is absent. + engine.register_fn("get", |r: &mut Registry, key: ImmutableString| -> Dynamic { + r.0 + .borrow() + .registry + .get(key.as_str()) + .map(|v| v.clone().into()) + .unwrap_or(Dynamic::UNIT) + }); + + // Registry.set(key, value) — stores a primitive value; () removes the key; + // unsupported types (closures, custom objects) are silently ignored. + engine.register_fn( + "set", + |r: &mut Registry, key: ImmutableString, value: Dynamic| { + match RegistryValue::try_from(value.clone()) { + Ok(rv) => { + r.0.borrow_mut().registry.insert(key.to_string(), rv); + } + Err(()) if value.is_unit() => { + r.0.borrow_mut().registry.remove(key.as_str()); + } + Err(()) => {} // unsupported type — silently ignore + } + }, + ); + + // Registry.get_or(key, default) — returns the stored value when present and the + // same Rhai type as `default`; falls back to `default` otherwise. + engine.register_fn( + "get_or", + |r: &mut Registry, key: ImmutableString, default: Dynamic| -> Dynamic { + if let Some(stored) = r.0.borrow().registry.get(key.as_str()).cloned() { + let candidate: Dynamic = stored.into(); + // Only return the stored value if it matches the type of the default. + if candidate.type_id() == default.type_id() { + candidate + } else { + default + } + } else { + default + } + }, + ); + } +} \ No newline at end of file diff --git a/kiln-core/src/board.rs b/kiln-core/src/board.rs index 402b6f7..87f24fe 100644 --- a/kiln-core/src/board.rs +++ b/kiln-core/src/board.rs @@ -585,6 +585,15 @@ impl Board { fn terrain_layer_at(&self, x: usize, y: usize) -> Option { (0..self.layers.len()).find(|&z| self.get(z, x, y).1 != Archetype::Empty) } + + /// Clear the queues of all objects on this board: called when entering a board, objects + /// don't retain their state across board visits (they get initialized again, but can + /// store things in the board registry) + pub fn clear_all_queues(&mut self) { + for obj in self.objects.values_mut() { + obj.queue.clear() + } + } } #[cfg(test)] diff --git a/kiln-core/src/game.rs b/kiln-core/src/game.rs index 6515074..6a98a92 100644 --- a/kiln-core/src/game.rs +++ b/kiln-core/src/game.rs @@ -413,6 +413,7 @@ impl GameState { x: ax as i64, y: ay as i64, }; + self.board_mut().clear_all_queues(); // Rebuild the script host for the new board's objects. self.scripts = ScriptHost::new( &self.world.boards[&self.current_board_name], diff --git a/kiln-core/src/script.rs b/kiln-core/src/script.rs index 3e0e326..8a20a31 100644 --- a/kiln-core/src/script.rs +++ b/kiln-core/src/script.rs @@ -47,6 +47,7 @@ use crate::api::board::BoardRef; use crate::api::object_info::ObjectInfo; use crate::api::player::PlayerWithPos; use crate::api::queue::ObjQueue; +use crate::api::registry::Registry; use crate::api::state::ScriptState; use crate::glyph::Glyph; use crate::keys::Keyring; @@ -90,18 +91,6 @@ fn script_key(obj: &ObjectDef) -> Option { obj.script_name.clone() } -// ── Rhai types exposed to scripts ──────────────────────────────────────────── - -/// The board's script registry, pushed into scope as the constant `Registry`. -/// `get`/`set`/`get_or` methods let scripts read and write per-board key→value pairs -/// that persist across board transitions. Mutation goes through the inner -/// `Rc>`, so the struct itself can be shared as a scope constant. -#[derive(Clone)] -struct Registry { - board: BoardRef, -} - - // ── ScriptHost ──────────────────────────────────────────────────────────────── /// Owns the Rhai engine and per-object script state for a board. @@ -135,10 +124,10 @@ impl ScriptHost { ObjectInfo::register(&mut engine, errors.clone()); Glyph::register(&mut engine, errors.clone()); ObjQueue::register(&mut engine, errors.clone()); + Registry::register(&mut engine, errors.clone()); register_write_api(&mut engine, board_cell.clone()); - register_registry_type(&mut engine); - register_global_constants(&mut engine); + register_global_constants(&mut engine, board_cell.clone()); let board = board_cell.borrow(); @@ -200,7 +189,7 @@ impl ScriptHost { if !scripts.contains_key(&key) { continue; } - scopes.insert(id, new_object_scope(board_cell)); + scopes.insert(id, Scope::new()); } drop(board); @@ -358,58 +347,6 @@ impl ScriptHost { } } -// ── Register Registry type ──────────────────────────────────────────────────── - -fn register_registry_type(engine: &mut Engine) { - engine.register_type_with_name::("Registry"); - - // Registry.get(key) -> Dynamic — returns () if the key is absent. - engine.register_fn("get", |r: &mut Registry, key: ImmutableString| -> Dynamic { - r.board - .borrow() - .registry - .get(key.as_str()) - .map(|v| v.clone().into()) - .unwrap_or(Dynamic::UNIT) - }); - - // Registry.set(key, value) — stores a primitive value; () removes the key; - // unsupported types (closures, custom objects) are silently ignored. - engine.register_fn( - "set", - |r: &mut Registry, key: ImmutableString, value: Dynamic| { - match RegistryValue::try_from(value.clone()) { - Ok(rv) => { - r.board.borrow_mut().registry.insert(key.to_string(), rv); - } - Err(()) if value.is_unit() => { - r.board.borrow_mut().registry.remove(key.as_str()); - } - Err(()) => {} // unsupported type — silently ignore - } - }, - ); - - // Registry.get_or(key, default) — returns the stored value when present and the - // same Rhai type as `default`; falls back to `default` otherwise. - engine.register_fn( - "get_or", - |r: &mut Registry, key: ImmutableString, default: Dynamic| -> Dynamic { - if let Some(stored) = r.board.borrow().registry.get(key.as_str()).cloned() { - let candidate: Dynamic = stored.into(); - // Only return the stored value if it matches the type of the default. - if candidate.type_id() == default.type_id() { - candidate - } else { - default - } - } else { - default - } - }, - ); -} - // ── Write API ───────────────────────────────────────────────────────────────── fn register_write_api(engine: &mut Engine, board: BoardRef) { @@ -677,8 +614,9 @@ fn read_coord_array(arr: &Array) -> Result, ()> { /// Registers direction and color constants as a global Rhai module so they are /// visible to every function at any call depth, including Rhai-to-Rhai calls. /// (Scope-level constants are only visible to the top-level function Rust calls.) -fn register_global_constants(engine: &mut Engine) { +fn register_global_constants(engine: &mut Engine, board: BoardRef) { let mut m = Module::new(); + m.set_var("Board", board); m.set_var("North", Direction::North); m.set_var("South", Direction::South); m.set_var("East", Direction::East); @@ -691,15 +629,6 @@ fn register_global_constants(engine: &mut Engine) { engine.register_global_module(m.into()); } -/// Builds a fresh per-object scope containing only the `Registry` handle. The -/// per-object `me` (an [`ObjectInfo`]) and `state` (a [`ScriptState`]) views are -/// passed as hook parameters, not scope constants. -fn new_object_scope(board: &BoardRef) -> Scope<'static> { - let mut scope = Scope::new(); - scope.push_constant("Registry", Registry { board: board.clone() }); - scope -} - /// Appends `action` to the output queue of the object identified by `source`. fn emit(board: &BoardRef, source: ObjectId, action: Action) { if let Some(def) = board.borrow_mut().objects.get_mut(&source) { diff --git a/kiln-core/src/scripts/spinner.rhai b/kiln-core/src/scripts/spinner.rhai index c459f06..b6a13d1 100644 --- a/kiln-core/src/scripts/spinner.rhai +++ b/kiln-core/src/scripts/spinner.rhai @@ -43,9 +43,9 @@ fn tick(me, state, dt) { // the board Registry, keyed per spinner, since script scope resets each tick. let frames = if cw { [47, 0xc4, 92, 0xb3] } else { [92, 0xc4, 47, 0xb3] }; let fkey = `spin_${me.id}`; - let f = Registry.get_or(fkey, 0); + let f = Board.registry.get_or(fkey, 0); set_tile(frames[f % 4]); - Registry.set(fkey, (f + 1) % 4); + Board.registry.set(fkey, (f + 1) % 4); me.delay(0.5); } diff --git a/maps/start.toml b/maps/start.toml index deb88a5..a0e596b 100644 --- a/maps/start.toml +++ b/maps/start.toml @@ -23,15 +23,16 @@ fn tick(me, state, dt) { mover = """ fn init(me, state) { - if Registry.get("dancer_x") != () { - let new_x = Registry.get("dancer_x"); - let new_y = Registry.get("dancer_y"); + log(`q: ${me.queue.length}`); + if Board.registry.get("dancer_x") != () { + let new_x = Board.registry.get("dancer_x"); + let new_y = Board.registry.get("dancer_y"); if me.x != new_x || me.y != new_y { teleport(new_x, new_y); } } else { - Registry.set("dancer_x", me.x); - Registry.set("dancer_y", me.y); + Board.registry.set("dancer_x", me.x); + Board.registry.set("dancer_y", me.y); log(`Set reg to ${me.x} ${me.y}`); } }