Skip to main content

arora_behavior/
lib.rs

1//! Behavior interpreters: the executors the Arora runtime ticks each step.
2//!
3//! Two things share the word "behavior", and this crate is about only one of
4//! them:
5//!
6//! - A **behavior** (the noun) is an *authored, editable representation* of what
7//!   a device should do — a behavior tree, a node graph — produced in a visual
8//!   editor (Studio, the Vizij Workspace) and shipped as data.
9//! - A [`BehaviorInterpreter`] is the *runtime-level executor* that runs one of
10//!   those: a behavior-tree interpreter, a node-graph interpreter. It is the
11//!   thing the runtime actually ticks.
12//!
13//! The runtime holds a queue of `Box<dyn BehaviorInterpreter>` and ticks them
14//! interchangeably — it just swaps the interpreter. The behavior tree is one
15//! interpreter (`arora-behavior-tree`'s [`BehaviorTreeInterpreter`]); a Vizij
16//! node graph is another. Adding a new *kind of authored behavior* means adding
17//! a new interpreter here. Hand-implementing the trait to hard-code a single
18//! behavior in Rust is possible, but it is a corner case — the promoted path is
19//! to author a behavior in an editor and let an interpreter run it.
20//!
21//! Each tick an interpreter gets a [`BehaviorContext`]: the shared
22//! [`DataStore`](arora_types::data::DataStore) (read inputs, write intent /
23//! outputs) and a [`CallBridge`](arora_types::call::CallBridge) (so a
24//! module-calling interpreter like the behavior tree can reach the engine). An
25//! interpreter uses whichever it needs — a graph reads/writes the store; the
26//! tree drives the caller.
27//!
28//! Timing is not a tick argument. The runtime publishes the frame's clock into
29//! the store under the [`golden`] keys before it ticks, so an interpreter that
30//! needs `dt` or elapsed time reads it from the store like any other slot.
31
32use arora_types::call::CallBridge;
33use arora_types::data::DataStore;
34
35pub mod golden;
36
37/// Whether an interpreter wants to be ticked again.
38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39pub enum BehaviorStatus {
40    /// Tick me again next step (a node graph; a tree still running).
41    Running,
42    /// I reached a terminal state; the runtime may drop me.
43    Done,
44}
45
46/// What a [`BehaviorInterpreter`] receives each
47/// [`tick`](BehaviorInterpreter::tick).
48pub struct BehaviorContext<'a> {
49    /// The shared blackboard: read inputs, write intent / outputs.
50    pub store: &'a dyn DataStore,
51    /// The module-call bridge (the engine), for interpreters that call modules.
52    pub caller: &'a mut dyn CallBridge,
53}
54
55/// An interpreter failed to tick.
56#[derive(Debug, Clone, PartialEq, Eq)]
57pub struct BehaviorError {
58    /// Human-readable description.
59    pub message: String,
60}
61
62impl std::fmt::Display for BehaviorError {
63    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
64        f.write_str(&self.message)
65    }
66}
67
68impl std::error::Error for BehaviorError {}
69
70/// A behavior *executor*: something the Arora runtime ticks once per step to run
71/// an authored behavior.
72///
73/// Implemented by the behavior tree (`arora-behavior-tree`'s
74/// [`BehaviorTreeInterpreter`]) and by other interpreters such as a Vizij
75/// node-graph interpreter. The runtime holds a queue of
76/// `Box<dyn BehaviorInterpreter>` and ticks them without knowing which is which.
77///
78/// Implement this to add a new *kind of executor* (a new authored-behavior
79/// representation the runtime can run), not to hand-code one particular
80/// behavior — authored behaviors come from the visual editors, run by an
81/// existing interpreter.
82///
83/// Not `Send`: the runtime is a single-owner, single-thread step loop.
84pub trait BehaviorInterpreter {
85    /// Advance one step. Return [`BehaviorStatus::Running`] to be ticked again,
86    /// or [`BehaviorStatus::Done`] to be dropped.
87    fn tick(&mut self, ctx: &mut BehaviorContext) -> Result<BehaviorStatus, BehaviorError>;
88}