mecha10_behavior_runtime/behavior.rs
1//! Core BehaviorNode trait and types
2//!
3//! This module defines the fundamental `BehaviorNode` trait that all behaviors must implement.
4
5use crate::NodeStatus;
6use async_trait::async_trait;
7use mecha10_core::Context;
8use std::fmt::Debug;
9
10/// Core trait that all behavior nodes must implement.
11///
12/// This is the fundamental building block of the behavior system. Everything - from simple
13/// actions to complex AI models to composition primitives - implements this trait.
14///
15/// # Design Principles
16///
17/// 1. **Simplicity** - One method: `tick()`
18/// 2. **Async-first** - All behaviors are async by default
19/// 3. **Composable** - Nodes can be freely combined
20/// 4. **Type-safe** - Rust's type system ensures correctness
21///
22/// # Example
23///
24/// ```rust
25/// use mecha10_behavior_runtime::prelude::*;
26///
27/// #[derive(Debug)]
28/// struct PrintMessage {
29/// message: String,
30/// }
31///
32/// #[async_trait]
33/// impl BehaviorNode for PrintMessage {
34/// async fn tick(&mut self, ctx: &Context) -> anyhow::Result<NodeStatus> {
35/// println!("{}", self.message);
36/// Ok(NodeStatus::Success)
37/// }
38///
39/// fn name(&self) -> &str {
40/// "print_message"
41/// }
42/// }
43/// ```
44#[async_trait]
45pub trait BehaviorNode: Send + Sync + Debug {
46 /// Execute one tick of the behavior.
47 ///
48 /// This method is called repeatedly by the execution engine. It should:
49 /// - Perform one unit of work
50 /// - Return quickly (non-blocking)
51 /// - Return `NodeStatus` to indicate progress
52 ///
53 /// # Arguments
54 ///
55 /// * `ctx` - Execution context with access to messaging, sensors, etc.
56 ///
57 /// # Returns
58 ///
59 /// * `NodeStatus::Success` - Behavior completed successfully
60 /// * `NodeStatus::Failure` - Behavior failed (irrecoverable error)
61 /// * `NodeStatus::Running` - Behavior is still executing (call again)
62 ///
63 /// # Errors
64 ///
65 /// Return an error for exceptional conditions that should halt execution.
66 /// For recoverable failures, return `Ok(NodeStatus::Failure)` instead.
67 async fn tick(&mut self, ctx: &Context) -> anyhow::Result<NodeStatus>;
68
69 /// Get the name of this behavior node.
70 ///
71 /// Used for debugging, logging, and monitoring. The default implementation
72 /// returns the type name.
73 fn name(&self) -> &str {
74 std::any::type_name::<Self>()
75 }
76
77 /// Return mutable references to this node's child behaviors, in the order
78 /// they should receive lifecycle events.
79 ///
80 /// Leaf nodes have no children and should not override this - the default
81 /// implementation returns an empty list. Composite nodes (e.g. `SequenceNode`,
82 /// `SelectOrNode`, `ParallelNode`, `SubsumptionNode`, `EnsembleNode`) override
83 /// this to expose their children, which drives the default `on_init`/
84 /// `on_terminate`/`reset` propagation below without each composite having to
85 /// hand-roll the same loop.
86 fn children_mut(&mut self) -> Vec<&mut dyn BehaviorNode> {
87 Vec::new()
88 }
89
90 /// Reset this node's own local state, ignoring children.
91 ///
92 /// Called by the default `reset()` implementation before it propagates to children.
93 /// Composite nodes with local progress to rewind (e.g. `SequenceNode`'s current child
94 /// index, `ParallelNode`'s per-child statuses) override this instead of `reset()`
95 /// itself, so they don't have to hand-roll the child-propagation loop below.
96 fn reset_self(&mut self) {}
97
98 /// Reset the behavior to its initial state.
99 ///
100 /// Called when the behavior needs to be restarted (e.g., in a loop or
101 /// after a failure). The default implementation resets local state via
102 /// `reset_self`, then propagates the reset to every child returned by
103 /// `children_mut`.
104 async fn reset(&mut self) -> anyhow::Result<()> {
105 self.reset_self();
106 for child in self.children_mut() {
107 child.reset().await?;
108 }
109 Ok(())
110 }
111
112 /// Called when the behavior is first initialized.
113 ///
114 /// Use this for one-time setup like loading models, establishing connections, etc.
115 /// The default implementation propagates `on_init` to every child returned by
116 /// `children_mut`.
117 async fn on_init(&mut self, ctx: &Context) -> anyhow::Result<()> {
118 for child in self.children_mut() {
119 child.on_init(ctx).await?;
120 }
121 Ok(())
122 }
123
124 /// Called when the behavior is being terminated.
125 ///
126 /// Use this for cleanup like closing connections, saving state, etc.
127 /// The default implementation propagates `on_terminate` to every child
128 /// returned by `children_mut`.
129 async fn on_terminate(&mut self, ctx: &Context) -> anyhow::Result<()> {
130 for child in self.children_mut() {
131 child.on_terminate(ctx).await?;
132 }
133 Ok(())
134 }
135}
136
137/// Type alias for boxed behavior nodes.
138///
139/// This is used for storing heterogeneous collections of behaviors.
140pub type BoxedBehavior = Box<dyn BehaviorNode>;
141
142/// Maps an iterator of boxed child behaviors to `BehaviorNode::children_mut`'s trait-object
143/// return type.
144///
145/// Backs every composite node's `children_mut` override (`SequenceNode`, `SelectOrNode`,
146/// `ParallelNode` here, plus `SubsumptionNode`/`EnsembleNode` in `mecha10-behavior-patterns`,
147/// which map their `SubsumptionLayer`/`WeightedModel` wrappers down to `&mut BoxedBehavior`
148/// first) - cargo-dupes flagged the repeated `iter_mut().map(|c| c.as_mut() as &mut dyn
149/// BehaviorNode).collect()` body as an exact duplicate across all of them.
150pub fn boxed_children_mut<'a>(children: impl Iterator<Item = &'a mut BoxedBehavior>) -> Vec<&'a mut dyn BehaviorNode> {
151 children.map(|c| c.as_mut() as &mut dyn BehaviorNode).collect()
152}
153
154/// Generate the `children_mut` override for a composite node whose children are wrapped in a
155/// per-child metadata struct (e.g. a priority or weight) rather than stored as bare
156/// `BoxedBehavior`s directly.
157///
158/// `SubsumptionNode::layers` (`Vec<SubsumptionLayer>`) and `EnsembleNode::models`
159/// (`Vec<WeightedModel>`), both in `mecha10-behavior-patterns`, need to map their wrapper's
160/// `behavior` field down to `&mut BoxedBehavior` before delegating to [`boxed_children_mut`] -
161/// cargo-dupes flagged the resulting one-line trait impl bodies as an exact duplicate of each
162/// other (same shape, differing only in field/binding names).
163#[macro_export]
164macro_rules! children_mut_via_wrapped_behavior {
165 ($field:ident, $wrapper:ident) => {
166 fn children_mut(&mut self) -> Vec<&mut dyn $crate::BehaviorNode> {
167 $crate::boxed_children_mut(self.$field.iter_mut().map(|$wrapper| &mut $wrapper.behavior))
168 }
169 };
170}
171
172/// Generate the `reset_self` override for a composite node that tracks one [`NodeStatus`] per
173/// child in a `Vec<NodeStatus>` field, resetting them all back to [`NodeStatus::Running`].
174///
175/// `ParallelNode` (this crate) and `EnsembleNode` (`mecha10-behavior-patterns`) both do this -
176/// cargo-dupes flagged their `reset_self` bodies as an exact duplicate of each other.
177#[macro_export]
178macro_rules! reset_child_statuses_field {
179 ($field:ident) => {
180 fn reset_self(&mut self) {
181 self.$field
182 .iter_mut()
183 .for_each(|s| *s = $crate::NodeStatus::Running);
184 }
185 };
186}
187
188/// Generate the `reset_self` override for a composite node that tracks progress through its
189/// children with a single `usize` index field, rewinding it back to `0`.
190///
191/// `SequenceNode` and `SelectOrNode` (this crate) both do this - cargo-dupes flagged their
192/// `reset_self` bodies as an exact duplicate of each other.
193#[macro_export]
194macro_rules! reset_index_field {
195 ($field:ident) => {
196 fn reset_self(&mut self) {
197 self.$field = 0;
198 }
199 };
200}
201
202/// Extension trait for BehaviorNode to provide convenient helpers.
203///
204/// This trait is automatically implemented for all types that implement `BehaviorNode`.
205#[async_trait]
206pub trait BehaviorNodeExt: BehaviorNode {
207 /// Run this behavior until it completes (Success or Failure).
208 ///
209 /// This is a convenience method for testing and simple execution.
210 ///
211 /// # Example
212 ///
213 /// ```rust
214 /// use mecha10_behavior_runtime::prelude::*;
215 ///
216 /// # async fn example(mut behavior: impl BehaviorNode, ctx: &Context) -> anyhow::Result<()> {
217 /// let status = behavior.run_until_complete(ctx).await?;
218 /// assert_eq!(status, NodeStatus::Success);
219 /// # Ok(())
220 /// # }
221 /// ```
222 async fn run_until_complete(&mut self, ctx: &Context) -> anyhow::Result<NodeStatus> {
223 loop {
224 match self.tick(ctx).await? {
225 NodeStatus::Success => return Ok(NodeStatus::Success),
226 NodeStatus::Failure => return Ok(NodeStatus::Failure),
227 NodeStatus::Running => continue,
228 }
229 }
230 }
231
232 /// Run this behavior with a maximum number of ticks.
233 ///
234 /// Returns `Err` if the behavior doesn't complete within `max_ticks`.
235 ///
236 /// # Example
237 ///
238 /// ```rust
239 /// use mecha10_behavior_runtime::prelude::*;
240 ///
241 /// # async fn example(mut behavior: impl BehaviorNode, ctx: &Context) -> anyhow::Result<()> {
242 /// let status = behavior.run_with_limit(ctx, 100).await?;
243 /// # Ok(())
244 /// # }
245 /// ```
246 async fn run_with_limit(&mut self, ctx: &Context, max_ticks: usize) -> anyhow::Result<NodeStatus> {
247 for _tick_count in 0..max_ticks {
248 match self.tick(ctx).await? {
249 NodeStatus::Success => return Ok(NodeStatus::Success),
250 NodeStatus::Failure => return Ok(NodeStatus::Failure),
251 NodeStatus::Running => continue,
252 }
253 }
254 anyhow::bail!("Behavior exceeded maximum ticks ({})", max_ticks)
255 }
256}
257
258impl<T: BehaviorNode + ?Sized> BehaviorNodeExt for T {}