Registry refactor and clear queues on board entry
This commit is contained in:
@@ -17,6 +17,7 @@ use std::rc::Rc;
|
|||||||
use rhai::{Dynamic, Engine, ImmutableString};
|
use rhai::{Dynamic, Engine, ImmutableString};
|
||||||
use crate::{Board, Direction};
|
use crate::{Board, Direction};
|
||||||
use crate::api::object_info::ObjectInfo;
|
use crate::api::object_info::ObjectInfo;
|
||||||
|
use crate::api::registry::Registry;
|
||||||
use crate::script::Registerable;
|
use crate::script::Registerable;
|
||||||
use crate::utils::{ErrorSink, ObjectId};
|
use crate::utils::{ErrorSink, ObjectId};
|
||||||
|
|
||||||
@@ -90,5 +91,8 @@ impl Registerable for BoardRef {
|
|||||||
.collect()
|
.collect()
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Board.registry -> Registry
|
||||||
|
engine.register_get("registry", |b: &mut BoardRef| Registry(b.clone()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,3 +3,4 @@ pub mod player;
|
|||||||
pub mod board;
|
pub mod board;
|
||||||
pub mod object_info;
|
pub mod object_info;
|
||||||
pub mod queue;
|
pub mod queue;
|
||||||
|
pub mod registry;
|
||||||
|
|||||||
@@ -73,13 +73,17 @@ impl ObjQueue {
|
|||||||
pub fn waiting(&mut self) -> bool {
|
pub fn waiting(&mut self) -> bool {
|
||||||
matches!(self.0.borrow().front(), Some(Action::Delay(_)))
|
matches!(self.0.borrow().front(), Some(Action::Delay(_)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn clear(&mut self) {
|
||||||
|
self.0.borrow_mut().clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Registerable for ObjQueue {
|
impl Registerable for ObjQueue {
|
||||||
fn register(engine: &mut Engine, error_sink: ErrorSink) {
|
fn register(engine: &mut Engine, error_sink: ErrorSink) {
|
||||||
engine.register_type_with_name::<ObjQueue>("Queue");
|
engine.register_type_with_name::<ObjQueue>("Queue");
|
||||||
engine.register_get("length", |q: &mut ObjQueue| q.0.borrow().len() as i64);
|
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
|
// Appends a [`Action::Delay`] to the back of `queue`, merging with an existing
|
||||||
// trailing delay to prevent adjacent delays from accumulating.
|
// trailing delay to prevent adjacent delays from accumulating.
|
||||||
|
|||||||
@@ -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<RefCell<Board>>`, 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");
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -585,6 +585,15 @@ impl Board {
|
|||||||
fn terrain_layer_at(&self, x: usize, y: usize) -> Option<usize> {
|
fn terrain_layer_at(&self, x: usize, y: usize) -> Option<usize> {
|
||||||
(0..self.layers.len()).find(|&z| self.get(z, x, y).1 != Archetype::Empty)
|
(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)]
|
#[cfg(test)]
|
||||||
|
|||||||
@@ -413,6 +413,7 @@ impl GameState {
|
|||||||
x: ax as i64,
|
x: ax as i64,
|
||||||
y: ay as i64,
|
y: ay as i64,
|
||||||
};
|
};
|
||||||
|
self.board_mut().clear_all_queues();
|
||||||
// Rebuild the script host for the new board's objects.
|
// Rebuild the script host for the new board's objects.
|
||||||
self.scripts = ScriptHost::new(
|
self.scripts = ScriptHost::new(
|
||||||
&self.world.boards[&self.current_board_name],
|
&self.world.boards[&self.current_board_name],
|
||||||
|
|||||||
+6
-77
@@ -47,6 +47,7 @@ use crate::api::board::BoardRef;
|
|||||||
use crate::api::object_info::ObjectInfo;
|
use crate::api::object_info::ObjectInfo;
|
||||||
use crate::api::player::PlayerWithPos;
|
use crate::api::player::PlayerWithPos;
|
||||||
use crate::api::queue::ObjQueue;
|
use crate::api::queue::ObjQueue;
|
||||||
|
use crate::api::registry::Registry;
|
||||||
use crate::api::state::ScriptState;
|
use crate::api::state::ScriptState;
|
||||||
use crate::glyph::Glyph;
|
use crate::glyph::Glyph;
|
||||||
use crate::keys::Keyring;
|
use crate::keys::Keyring;
|
||||||
@@ -90,18 +91,6 @@ fn script_key(obj: &ObjectDef) -> Option<String> {
|
|||||||
obj.script_name.clone()
|
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<RefCell<Board>>`, so the struct itself can be shared as a scope constant.
|
|
||||||
#[derive(Clone)]
|
|
||||||
struct Registry {
|
|
||||||
board: BoardRef,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// ── ScriptHost ────────────────────────────────────────────────────────────────
|
// ── ScriptHost ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
/// Owns the Rhai engine and per-object script state for a board.
|
/// Owns the Rhai engine and per-object script state for a board.
|
||||||
@@ -135,10 +124,10 @@ impl ScriptHost {
|
|||||||
ObjectInfo::register(&mut engine, errors.clone());
|
ObjectInfo::register(&mut engine, errors.clone());
|
||||||
Glyph::register(&mut engine, errors.clone());
|
Glyph::register(&mut engine, errors.clone());
|
||||||
ObjQueue::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_write_api(&mut engine, board_cell.clone());
|
||||||
register_registry_type(&mut engine);
|
register_global_constants(&mut engine, board_cell.clone());
|
||||||
register_global_constants(&mut engine);
|
|
||||||
|
|
||||||
let board = board_cell.borrow();
|
let board = board_cell.borrow();
|
||||||
|
|
||||||
@@ -200,7 +189,7 @@ impl ScriptHost {
|
|||||||
if !scripts.contains_key(&key) {
|
if !scripts.contains_key(&key) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
scopes.insert(id, new_object_scope(board_cell));
|
scopes.insert(id, Scope::new());
|
||||||
}
|
}
|
||||||
|
|
||||||
drop(board);
|
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");
|
|
||||||
|
|
||||||
// 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 ─────────────────────────────────────────────────────────────────
|
// ── Write API ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
fn register_write_api(engine: &mut Engine, board: BoardRef) {
|
fn register_write_api(engine: &mut Engine, board: BoardRef) {
|
||||||
@@ -677,8 +614,9 @@ fn read_coord_array(arr: &Array) -> Result<Vec<(i64, i64)>, ()> {
|
|||||||
/// Registers direction and color constants as a global Rhai module so they are
|
/// 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.
|
/// 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.)
|
/// (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();
|
let mut m = Module::new();
|
||||||
|
m.set_var("Board", board);
|
||||||
m.set_var("North", Direction::North);
|
m.set_var("North", Direction::North);
|
||||||
m.set_var("South", Direction::South);
|
m.set_var("South", Direction::South);
|
||||||
m.set_var("East", Direction::East);
|
m.set_var("East", Direction::East);
|
||||||
@@ -691,15 +629,6 @@ fn register_global_constants(engine: &mut Engine) {
|
|||||||
engine.register_global_module(m.into());
|
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`.
|
/// Appends `action` to the output queue of the object identified by `source`.
|
||||||
fn emit(board: &BoardRef, source: ObjectId, action: Action) {
|
fn emit(board: &BoardRef, source: ObjectId, action: Action) {
|
||||||
if let Some(def) = board.borrow_mut().objects.get_mut(&source) {
|
if let Some(def) = board.borrow_mut().objects.get_mut(&source) {
|
||||||
|
|||||||
@@ -43,9 +43,9 @@ fn tick(me, state, dt) {
|
|||||||
// the board Registry, keyed per spinner, since script scope resets each tick.
|
// 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 frames = if cw { [47, 0xc4, 92, 0xb3] } else { [92, 0xc4, 47, 0xb3] };
|
||||||
let fkey = `spin_${me.id}`;
|
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]);
|
set_tile(frames[f % 4]);
|
||||||
Registry.set(fkey, (f + 1) % 4);
|
Board.registry.set(fkey, (f + 1) % 4);
|
||||||
|
|
||||||
me.delay(0.5);
|
me.delay(0.5);
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-5
@@ -23,15 +23,16 @@ fn tick(me, state, dt) {
|
|||||||
|
|
||||||
mover = """
|
mover = """
|
||||||
fn init(me, state) {
|
fn init(me, state) {
|
||||||
if Registry.get("dancer_x") != () {
|
log(`q: ${me.queue.length}`);
|
||||||
let new_x = Registry.get("dancer_x");
|
if Board.registry.get("dancer_x") != () {
|
||||||
let new_y = Registry.get("dancer_y");
|
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 {
|
if me.x != new_x || me.y != new_y {
|
||||||
teleport(new_x, new_y);
|
teleport(new_x, new_y);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Registry.set("dancer_x", me.x);
|
Board.registry.set("dancer_x", me.x);
|
||||||
Registry.set("dancer_y", me.y);
|
Board.registry.set("dancer_y", me.y);
|
||||||
log(`Set reg to ${me.x} ${me.y}`);
|
log(`Set reg to ${me.x} ${me.y}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user