more refactor

This commit is contained in:
2026-06-05 01:09:50 -05:00
parent 6cd34ebb4e
commit 8f2cca2907
5 changed files with 35 additions and 36 deletions
+5 -5
View File
@@ -107,9 +107,9 @@ Owns the board behind an `Rc<RefCell<Board>>`, alongside the message `log` and a
Two optional lifecycle hooks per object: `init()` (zero-arg, run once) and `tick(dt)` (elapsed seconds as `f64`, run per frame), driven by `run_init()` / `run_tick(dt)`. Runtime errors become `Error` commands, not fatal.
**Reads vs writes.** Scripts **read** the world directly: a read-only `BoardView` (a registered type with getters only read-only by construction) is pushed into each scope as `board`, so a script writes `board.player_x`; the getter briefly borrows the shared `Board`. Scripts **write** by enqueuing `Command { source, kind }` where `kind: GameCommand` is `Move(Direction)` / `SetTile(u32)` / `Log(LogLine)` / `Error(String)`. The host fns `move`/`set_tile`/`log` push into a shared `Rc<RefCell<Vec<Command>>>` cloned into each closure (the same `'static`-closure trick the old log sink used, generalized); `GameState::apply_commands` drains and applies them after the batch. Reads-are-live, writes-are-deferred gives frame-coherent semantics and keeps script execution free of any `&mut GameState` borrow.
**Reads vs writes.** Scripts **read** the world directly: read getters are registered on `Rc<RefCell<Board>>` (the `BoardRef` alias, exposed to Rhai as type `Board`) — getters only, so read-only by construction — and a clone of the handle is pushed into each scope as the constant `Board`, so a script writes `Board.player_x`; the getter briefly borrows the shared `Board`. Scripts **write** by enqueuing `Command { source, kind }` where `kind: GameCommand` is `Move(Direction)` / `SetTile(u32)` / `Log(LogLine)` / `Error(String)`. The host fns `move`/`set_tile`/`log` push into a shared `Rc<RefCell<Vec<Command>>>` cloned into each closure (the same `'static`-closure trick the old log sink used, generalized); `GameState::apply_commands` drains and applies them after the batch. Reads-are-live, writes-are-deferred gives frame-coherent semantics and keeps script execution free of any `&mut GameState` borrow.
**Sender identity** (which object issued a command) rides Rhai's per-call **tag**: `run` calls `call_fn_with_options(...with_tag(object_index)...)` and the host fns read it via `NativeCallContext::tag()`, so scripts write `move(north)` without a `this` argument (`north`/`south`/`east`/`west` are `Direction` constants in scope; `impl From<Direction> for (i32,i32)` yields the delta). The object's identity is currently its index into `Board::objects` — a stopgap (a `// TODO` flags replacing it with stable unique ids that survive spawn/destroy). Default `call_fn` rewinds the scope per call, so script-local persistence across ticks is still future work; the per-object `Scope` is the seam for that. `Engine`/`Scope` are not `Send` — fine for the single-threaded kiln-tui.
**Sender identity** (which object issued a command) rides Rhai's per-call **tag**: `run` calls `call_fn_with_options(...with_tag(object_index)...)` and the host fns read it via `NativeCallContext::tag()`, so scripts write `move(North)` without a `this` argument (`North`/`South`/`East`/`West` are `Direction` constants in scope; `impl From<Direction> for (i32,i32)` yields the delta). The object's identity is currently its index into `Board::objects` — a stopgap (a `// TODO` flags replacing it with stable unique ids that survive spawn/destroy). Default `call_fn` rewinds the scope per call, so script-local persistence across ticks is still future work; the per-object `Scope` is the seam for that. `Engine`/`Scope` are not `Send` — fine for the single-threaded kiln-tui.
---
@@ -309,10 +309,10 @@ target_entry = "west_door"
[scripts]
greeter = """
fn init() {
log(`player at ${board.player_x}, ${board.player_y}`); # read
log(`player at ${Board.player_x}, ${Board.player_y}`); # read
set_tile(2); # write
}
fn tick(dt) { move(north); } # dt = elapsed seconds; dir ∈ north/south/east/west
fn tick(dt) { move(North); } # dt = elapsed seconds; dir ∈ North/South/East/West
"""
```
@@ -336,7 +336,7 @@ A direct mapping from palette character → `(Glyph, Archetype)` means the map f
## What's not yet implemented
**Object scripting** — the runtime exists (`script.rs`): objects run optional `init()`/`tick(dt)` hooks, **read** the world via `board.*`, and **write** via queued commands (`move`, `set_tile`, `log`). Still to come:
**Object scripting** — the runtime exists (`script.rs`): objects run optional `init()`/`tick(dt)` hooks, **read** the world via `Board.*`, and **write** via queued commands (`move`, `set_tile`, `log`). Still to come:
1. More event hooks beyond lifecycle — e.g. fire `on_touch` when the player moves onto an object's cell
2. Grow the command vocabulary and read API (`send_message`, randomness, board queries, more glyph setters)
3. **Stable object ids**`Command.source` / `ObjectRuntime.object_index` are array indices into `Board::objects`, which break under spawn/destroy/reorder; replace with monotonically-increasing unique ids and key objects by them (a `// TODO` marks this in code)