Skip to main content

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 {}