Real object ids, fixed warping bug
This commit is contained in:
+36
-33
@@ -31,7 +31,7 @@
|
||||
//! [`take_board_queue`]: ScriptHost::take_board_queue
|
||||
//! [`GameState`]: crate::game::GameState
|
||||
|
||||
use crate::game::Board;
|
||||
use crate::game::{Board, ObjectId};
|
||||
use crate::log::LogLine;
|
||||
use rhai::{AST, CallFnOptions, Engine, FuncArgs, ImmutableString, NativeCallContext, Scope};
|
||||
use std::cell::RefCell;
|
||||
@@ -68,18 +68,14 @@ impl Action {
|
||||
/// An action promoted from an object's output queue onto the board queue, tagged
|
||||
/// with the object that issued it.
|
||||
pub struct BoardAction {
|
||||
/// The object that issued the action.
|
||||
// TODO: `source` is an index into `Board::objects`, which is a stopgap — it
|
||||
// breaks under object spawn/destroy/reorder. Replace with a monotonically
|
||||
// increasing unique object id, with objects stored keyed by it (e.g.
|
||||
// `BTreeMap<ObjectId, ObjectDef>`). Mirrored on `ObjectRuntime::object_index`.
|
||||
pub source: usize,
|
||||
/// The stable [`ObjectId`] of the object that issued the action.
|
||||
pub source: ObjectId,
|
||||
/// The action to apply.
|
||||
pub action: Action,
|
||||
}
|
||||
|
||||
/// A cardinal movement direction.
|
||||
#[derive(Clone, Copy)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum Direction {
|
||||
North,
|
||||
South,
|
||||
@@ -112,9 +108,9 @@ type ObjQueue = Rc<RefCell<VecDeque<Action>>>;
|
||||
/// so the `blocked` host function can scan pending moves during script execution.
|
||||
type BoardQueue = Rc<RefCell<Vec<BoardAction>>>;
|
||||
|
||||
/// Maps an object index to its output queue, so the global write host functions can
|
||||
/// Maps an [`ObjectId`] to its output queue, so the global write host functions can
|
||||
/// route an emitted action to the right object via the per-call tag.
|
||||
type QueueMap = Rc<RefCell<HashMap<usize, ObjQueue>>>;
|
||||
type QueueMap = Rc<RefCell<HashMap<ObjectId, ObjQueue>>>;
|
||||
|
||||
/// Channel for engine/compile/runtime errors, surfaced straight to the game log
|
||||
/// (errors never go through the action queues).
|
||||
@@ -135,10 +131,9 @@ struct CompiledScript {
|
||||
/// The runtime state of one scripted object: which script it runs, its persistent
|
||||
/// Rhai scope, its pending actions, and its cooldown timer.
|
||||
struct ObjectRuntime {
|
||||
/// Index into [`crate::game::Board::objects`]; passed to scripts as the
|
||||
/// per-call tag so host functions know which object issued an action.
|
||||
// TODO: see [`BoardAction::source`] — array indices should become stable ids.
|
||||
object_index: usize,
|
||||
/// The stable [`ObjectId`] of this object; passed to scripts as the per-call
|
||||
/// tag so host functions know which object issued an action.
|
||||
object_id: ObjectId,
|
||||
/// Key into [`ScriptHost::scripts`] naming this object's compiled script.
|
||||
script_name: String,
|
||||
/// Per-object scope (holds the `Board`/`Queue` views + direction constants, and
|
||||
@@ -187,7 +182,7 @@ impl ScriptHost {
|
||||
// object that references the script.
|
||||
let mut scripts: HashMap<String, CompiledScript> = HashMap::new();
|
||||
let mut failed: HashSet<String> = HashSet::new();
|
||||
for obj in board.objects.iter() {
|
||||
for obj in board.objects.values() {
|
||||
let Some(name) = obj.script_name.as_ref() else {
|
||||
continue;
|
||||
};
|
||||
@@ -230,7 +225,7 @@ impl ScriptHost {
|
||||
// compiled. The queue is registered in `queues` so host functions can find
|
||||
// it by the object's index, and shared into the scope as the `Queue` object.
|
||||
let mut objects = Vec::new();
|
||||
for (i, obj) in board.objects.iter().enumerate() {
|
||||
for (&id, obj) in board.objects.iter() {
|
||||
let Some(name) = obj.script_name.as_ref() else {
|
||||
continue;
|
||||
};
|
||||
@@ -238,9 +233,9 @@ impl ScriptHost {
|
||||
continue;
|
||||
}
|
||||
let queue: ObjQueue = Rc::new(RefCell::new(VecDeque::new()));
|
||||
queues.borrow_mut().insert(i, queue.clone());
|
||||
queues.borrow_mut().insert(id, queue.clone());
|
||||
objects.push(ObjectRuntime {
|
||||
object_index: i,
|
||||
object_id: id,
|
||||
script_name: name.clone(),
|
||||
scope: new_object_scope(board_cell, &queue),
|
||||
output_queue: queue,
|
||||
@@ -271,12 +266,12 @@ impl ScriptHost {
|
||||
self.run("tick", |c| c.has_tick, (dt,));
|
||||
}
|
||||
|
||||
/// Calls `bump(id)` on the object at `object_index`, if it defines the hook.
|
||||
/// Calls `bump(id)` on the object with [`ObjectId`] `object_id`, if it defines the hook.
|
||||
///
|
||||
/// `bumper` is the index of the object that moved into it, or `-1` for the player.
|
||||
/// `bumper` is the id of the object that moved into it, or `-1` for the player.
|
||||
/// Does **not** pump: any actions the hook emits wait for the next tick's pump, so
|
||||
/// a bump can't cascade within the same resolution pass.
|
||||
pub fn run_bump(&mut self, object_index: usize, bumper: i64) {
|
||||
pub fn run_bump(&mut self, object_id: ObjectId, bumper: i64) {
|
||||
let Self {
|
||||
engine,
|
||||
scripts,
|
||||
@@ -284,7 +279,7 @@ impl ScriptHost {
|
||||
errors,
|
||||
..
|
||||
} = self;
|
||||
let Some(obj) = objects.iter_mut().find(|o| o.object_index == object_index) else {
|
||||
let Some(obj) = objects.iter_mut().find(|o| o.object_id == object_id) else {
|
||||
return;
|
||||
};
|
||||
let Some(compiled) = scripts.get(&obj.script_name) else {
|
||||
@@ -293,7 +288,7 @@ impl ScriptHost {
|
||||
if !compiled.has_bump {
|
||||
return;
|
||||
}
|
||||
let options = CallFnOptions::default().with_tag(object_index as i64);
|
||||
let options = CallFnOptions::default().with_tag(object_id as i64);
|
||||
if let Err(err) = engine.call_fn_with_options::<()>(
|
||||
options,
|
||||
&mut obj.scope,
|
||||
@@ -333,7 +328,7 @@ impl ScriptHost {
|
||||
/// action (which arms the timer and ends the pump). The board-queue guard keeps a
|
||||
/// second pump in the same tick from double-promoting.
|
||||
fn pump(&mut self, i: usize) {
|
||||
let oid = self.objects[i].object_index;
|
||||
let oid = self.objects[i].object_id;
|
||||
// Don't re-promote if this object already has actions awaiting resolution.
|
||||
if self.board_queue.borrow().iter().any(|ba| ba.source == oid) {
|
||||
return;
|
||||
@@ -388,8 +383,8 @@ impl ScriptHost {
|
||||
if let Some(compiled) = scripts.get(&obj.script_name)
|
||||
&& defined(compiled)
|
||||
{
|
||||
// The tag carries this object's index to the host functions.
|
||||
let options = CallFnOptions::default().with_tag(obj.object_index as i64);
|
||||
// The tag carries this object's id to the host functions.
|
||||
let options = CallFnOptions::default().with_tag(obj.object_id as i64);
|
||||
if let Err(err) = engine.call_fn_with_options::<()>(
|
||||
options,
|
||||
&mut obj.scope,
|
||||
@@ -436,9 +431,14 @@ fn register_read_api(engine: &mut Engine, board: &BoardRef, board_queue: &BoardQ
|
||||
///
|
||||
/// Pending moves are matched by their *immediate* target cell only; a queued move that
|
||||
/// would push a chain of solids into the cell is not accounted for (an approximation).
|
||||
fn is_blocked(board: &BoardRef, board_queue: &BoardQueue, source: usize, dir: Direction) -> bool {
|
||||
fn is_blocked(
|
||||
board: &BoardRef,
|
||||
board_queue: &BoardQueue,
|
||||
source: ObjectId,
|
||||
dir: Direction,
|
||||
) -> bool {
|
||||
let board = board.borrow();
|
||||
let Some(obj) = board.objects.get(source) else {
|
||||
let Some(obj) = board.objects.get(&source) else {
|
||||
return true;
|
||||
};
|
||||
let (dx, dy): (i32, i32) = dir.into();
|
||||
@@ -453,7 +453,7 @@ fn is_blocked(board: &BoardRef, board_queue: &BoardQueue, source: usize, dir: Di
|
||||
// Any pending move whose immediate target is this cell would put a solid here.
|
||||
board_queue.borrow().iter().any(|ba| {
|
||||
if let Action::Move(d) = &ba.action
|
||||
&& let Some(mover) = board.objects.get(ba.source)
|
||||
&& let Some(mover) = board.objects.get(&ba.source)
|
||||
{
|
||||
let (mdx, mdy): (i32, i32) = (*d).into();
|
||||
return (mover.x as i32 + mdx, mover.y as i32 + mdy) == target;
|
||||
@@ -500,17 +500,20 @@ fn register_queue_api(engine: &mut Engine) {
|
||||
}
|
||||
|
||||
/// Appends `action` to the output queue of the object identified by `source`.
|
||||
fn emit(queues: &QueueMap, source: usize, action: Action) {
|
||||
fn emit(queues: &QueueMap, source: ObjectId, action: Action) {
|
||||
if let Some(queue) = queues.borrow().get(&source) {
|
||||
queue.borrow_mut().push_back(action);
|
||||
}
|
||||
}
|
||||
|
||||
/// Reads the issuing object's index from the call tag (set in [`ScriptHost::run`]).
|
||||
fn source_of(ctx: &NativeCallContext) -> usize {
|
||||
/// Reads the issuing object's [`ObjectId`] from the call tag (set in [`ScriptHost::run`]).
|
||||
///
|
||||
/// Falls back to `0` (never a valid id, since ids start at 1) if the tag is missing
|
||||
/// or out of range, which makes [`emit`] a harmless no-op for an unknown source.
|
||||
fn source_of(ctx: &NativeCallContext) -> ObjectId {
|
||||
ctx.tag()
|
||||
.and_then(|t| t.as_int().ok())
|
||||
.and_then(|n| usize::try_from(n).ok())
|
||||
.and_then(|n| ObjectId::try_from(n).ok())
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user