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#[async_trait]
11pub trait BehaviorNode: Send + Sync + Debug {
12 /// Execute one tick of the behavior.
13 ///
14 /// This method is called repeatedly by the execution engine. It should:
15 /// - Perform one unit of work
16 /// - Return quickly (non-blocking)
17 /// - Return `NodeStatus` to indicate progress
18 ///
19 /// # Arguments
20 ///
21 /// * `ctx` - Execution context with access to messaging, sensors, etc.
22 ///
23 /// # Returns
24 ///
25 /// * `NodeStatus::Success` - Behavior completed successfully
26 /// * `NodeStatus::Failure` - Behavior failed (irrecoverable error)
27 /// * `NodeStatus::Running` - Behavior is still executing (call again)
28 ///
29 /// # Errors
30 ///
31 /// Return an error for exceptional conditions that should halt execution.
32 /// For recoverable failures, return `Ok(NodeStatus::Failure)` instead.
33 async fn tick(&mut self, ctx: &Context) -> anyhow::Result<NodeStatus>;
34
35 /// Get the name of this behavior node.
36 ///
37 /// Used for debugging, logging, and monitoring. The default implementation
38 /// returns the type name.
39 fn name(&self) -> &str {
40 std::any::type_name::<Self>()
41 }
42
43 /// Return mutable references to this node's child behaviors, in the order
44 /// they should receive lifecycle events.
45 ///
46 /// Leaf nodes have no children and should not override this - the default
47 /// implementation returns an empty list. Composite nodes (e.g. `SequenceNode`,
48 /// `SelectOrNode`, `ParallelNode`, `SubsumptionNode`, `EnsembleNode`) override
49 /// this to expose their children, which drives the default `on_init`/
50 /// `on_terminate`/`reset` propagation below without each composite having to
51 /// hand-roll the same loop.
52 fn children_mut(&mut self) -> Vec<&mut dyn BehaviorNode> {
53 Vec::new()
54 }
55
56 /// The `capability` this node implies should be published on `/behavior/status` while it's
57 /// the one actively being ticked (e.g. `rl_task`'s hardcoded `"rl"`, gating `RlNode::is_active()`
58 /// on the `rl` node crate's side) - `None` for a node with no such gate (the default, correct
59 /// for the vast majority of leaves and for a composite with no currently-active child).
60 ///
61 /// Composite nodes (`SequenceNode`, `SelectOrNode`, `ParallelNode`) override this to forward
62 /// whichever child is currently active/running - `behavior-executor` calls this on the tree
63 /// root each status tick rather than each leaf declaring anything back up on its own, since
64 /// which child is "current" is already tracked by the composite ticking it.
65 fn active_capability(&self) -> Option<&str> {
66 None
67 }
68
69 /// Reset this node's own local state, ignoring children.
70 ///
71 /// Called by the default `reset()` implementation before it propagates to children.
72 /// Composite nodes with local progress to rewind (e.g. `SequenceNode`'s current child
73 /// index, `ParallelNode`'s per-child statuses) override this instead of `reset()`
74 /// itself, so they don't have to hand-roll the child-propagation loop below.
75 fn reset_self(&mut self) {}
76
77 /// Reset the behavior to its initial state.
78 ///
79 /// Called when the behavior needs to be restarted (e.g., in a loop or
80 /// after a failure). The default implementation resets local state via
81 /// `reset_self`, then propagates the reset to every child returned by
82 /// `children_mut`.
83 async fn reset(&mut self) -> anyhow::Result<()> {
84 self.reset_self();
85 for child in self.children_mut() {
86 child.reset().await?;
87 }
88 Ok(())
89 }
90
91 /// Called when the behavior is first initialized.
92 ///
93 /// Use this for one-time setup like loading models, establishing connections, etc.
94 /// The default implementation propagates `on_init` to every child returned by
95 /// `children_mut`.
96 async fn on_init(&mut self, ctx: &Context) -> anyhow::Result<()> {
97 for child in self.children_mut() {
98 child.on_init(ctx).await?;
99 }
100 Ok(())
101 }
102
103 /// Called when the behavior is being terminated.
104 ///
105 /// Use this for cleanup like closing connections, saving state, etc.
106 /// The default implementation propagates `on_terminate` to every child
107 /// returned by `children_mut`.
108 async fn on_terminate(&mut self, ctx: &Context) -> anyhow::Result<()> {
109 for child in self.children_mut() {
110 child.on_terminate(ctx).await?;
111 }
112 Ok(())
113 }
114}
115
116pub type BoxedBehavior = Box<dyn BehaviorNode>;
117
118/// Maps an iterator of boxed child behaviors to `BehaviorNode::children_mut`'s trait-object
119/// return type.
120///
121/// Backs every composite node's `children_mut` override (`SequenceNode`, `SelectOrNode`,
122/// `ParallelNode` here, plus `SubsumptionNode`/`EnsembleNode` in `mecha10-behavior-patterns`,
123/// which map their `SubsumptionLayer`/`WeightedModel` wrappers down to `&mut BoxedBehavior`
124/// first) - cargo-dupes flagged the repeated `iter_mut().map(|c| c.as_mut() as &mut dyn
125/// BehaviorNode).collect()` body as an exact duplicate across all of them.
126pub fn boxed_children_mut<'a>(children: impl Iterator<Item = &'a mut BoxedBehavior>) -> Vec<&'a mut dyn BehaviorNode> {
127 children.map(|c| c.as_mut() as &mut dyn BehaviorNode).collect()
128}
129
130#[macro_export]
131macro_rules! children_mut_via_wrapped_behavior {
132 ($field:ident, $wrapper:ident) => {
133 fn children_mut(&mut self) -> Vec<&mut dyn $crate::BehaviorNode> {
134 $crate::boxed_children_mut(self.$field.iter_mut().map(|$wrapper| &mut $wrapper.behavior))
135 }
136 };
137}
138
139#[macro_export]
140macro_rules! reset_child_statuses_field {
141 ($field:ident) => {
142 fn reset_self(&mut self) {
143 self.$field
144 .iter_mut()
145 .for_each(|s| *s = $crate::NodeStatus::Running);
146 }
147 };
148}
149
150#[macro_export]
151macro_rules! reset_index_field {
152 ($field:ident) => {
153 fn reset_self(&mut self) {
154 self.$field = 0;
155 }
156 };
157}
158
159#[async_trait]
160pub trait BehaviorNodeExt: BehaviorNode {
161 /// Run this behavior until it completes (Success or Failure).
162 ///
163 /// This is a convenience method for testing and simple execution.
164 ///
165 /// # Example
166 ///
167 /// ```rust
168 /// use mecha10_behavior_runtime::prelude::*;
169 ///
170 /// # async fn example(mut behavior: impl BehaviorNode, ctx: &Context) -> anyhow::Result<()> {
171 /// let status = behavior.run_until_complete(ctx).await?;
172 /// assert_eq!(status, NodeStatus::Success);
173 /// # Ok(())
174 /// # }
175 /// ```
176 async fn run_until_complete(&mut self, ctx: &Context) -> anyhow::Result<NodeStatus> {
177 loop {
178 match self.tick(ctx).await? {
179 NodeStatus::Success => return Ok(NodeStatus::Success),
180 NodeStatus::Failure => return Ok(NodeStatus::Failure),
181 NodeStatus::Running => continue,
182 }
183 }
184 }
185
186 /// Run this behavior with a maximum number of ticks.
187 ///
188 /// Returns `Err` if the behavior doesn't complete within `max_ticks`.
189 ///
190 /// # Example
191 ///
192 /// ```rust
193 /// use mecha10_behavior_runtime::prelude::*;
194 ///
195 /// # async fn example(mut behavior: impl BehaviorNode, ctx: &Context) -> anyhow::Result<()> {
196 /// let status = behavior.run_with_limit(ctx, 100).await?;
197 /// # Ok(())
198 /// # }
199 /// ```
200 async fn run_with_limit(&mut self, ctx: &Context, max_ticks: usize) -> anyhow::Result<NodeStatus> {
201 for _tick_count in 0..max_ticks {
202 match self.tick(ctx).await? {
203 NodeStatus::Success => return Ok(NodeStatus::Success),
204 NodeStatus::Failure => return Ok(NodeStatus::Failure),
205 NodeStatus::Running => continue,
206 }
207 }
208 anyhow::bail!("Behavior exceeded maximum ticks ({})", max_ticks)
209 }
210}
211
212impl<T: BehaviorNode + ?Sized> BehaviorNodeExt for T {}