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. Called repeatedly by the execution engine; should
13    /// perform one unit of work, return quickly, and report progress via `NodeStatus`.
14    async fn tick(&mut self, ctx: &Context) -> anyhow::Result<NodeStatus>;
15
16    /// Get the name of this behavior node.
17    ///
18    /// Used for debugging, logging, and monitoring.
19    fn name(&self) -> &str {
20        std::any::type_name::<Self>()
21    }
22
23    /// Return mutable references to this node's child behaviors, in the order
24    /// they should receive lifecycle events. Leaf nodes should not override this;
25    /// composite nodes override it to expose their children.
26    fn children_mut(&mut self) -> Vec<&mut dyn BehaviorNode> {
27        Vec::new()
28    }
29
30    /// The `capability` this node implies should be published on `/behavior/status` while it's
31    /// actively being ticked; `None` for nodes with no such gate (the default). Composite nodes
32    /// override this to forward whichever child is currently active.
33    fn active_capability(&self) -> Option<&str> {
34        None
35    }
36
37    /// Reset this node's own local state, ignoring children.
38    ///
39    /// Called by the default `reset()` before it propagates to children.
40    fn reset_self(&mut self) {}
41
42    /// Reset the behavior to its initial state.
43    ///
44    /// Called when the behavior needs to be restarted (e.g., in a loop or after a failure).
45    async fn reset(&mut self) -> anyhow::Result<()> {
46        self.reset_self();
47        for child in self.children_mut() {
48            child.reset().await?;
49        }
50        Ok(())
51    }
52
53    /// Called when the behavior is first initialized.
54    ///
55    /// Use this for one-time setup like loading models, establishing connections, etc.
56    async fn on_init(&mut self, ctx: &Context) -> anyhow::Result<()> {
57        for child in self.children_mut() {
58            child.on_init(ctx).await?;
59        }
60        Ok(())
61    }
62
63    /// Called when the behavior is being terminated.
64    ///
65    /// Use this for cleanup like closing connections, saving state, etc.
66    async fn on_terminate(&mut self, ctx: &Context) -> anyhow::Result<()> {
67        for child in self.children_mut() {
68            child.on_terminate(ctx).await?;
69        }
70        Ok(())
71    }
72}
73
74pub type BoxedBehavior = Box<dyn BehaviorNode>;
75
76/// Maps an iterator of boxed child behaviors to `BehaviorNode::children_mut`'s trait-object
77/// return type.
78pub fn boxed_children_mut<'a>(children: impl Iterator<Item = &'a mut BoxedBehavior>) -> Vec<&'a mut dyn BehaviorNode> {
79    children.map(|c| c.as_mut() as &mut dyn BehaviorNode).collect()
80}
81
82#[macro_export]
83macro_rules! children_mut_via_wrapped_behavior {
84    ($field:ident, $wrapper:ident) => {
85        fn children_mut(&mut self) -> Vec<&mut dyn $crate::BehaviorNode> {
86            $crate::boxed_children_mut(self.$field.iter_mut().map(|$wrapper| &mut $wrapper.behavior))
87        }
88    };
89}
90
91#[macro_export]
92macro_rules! reset_child_statuses_field {
93    ($field:ident) => {
94        fn reset_self(&mut self) {
95            self.$field
96                .iter_mut()
97                .for_each(|s| *s = $crate::NodeStatus::Running);
98        }
99    };
100}
101
102#[macro_export]
103macro_rules! reset_index_field {
104    ($field:ident) => {
105        fn reset_self(&mut self) {
106            self.$field = 0;
107        }
108    };
109}
110
111#[async_trait]
112pub trait BehaviorNodeExt: BehaviorNode {
113    /// Run this behavior until it completes (Success or Failure).
114    ///
115    /// Convenience method for testing and simple execution.
116    async fn run_until_complete(&mut self, ctx: &Context) -> anyhow::Result<NodeStatus> {
117        loop {
118            match self.tick(ctx).await? {
119                NodeStatus::Success => return Ok(NodeStatus::Success),
120                NodeStatus::Failure => return Ok(NodeStatus::Failure),
121                NodeStatus::Running => continue,
122            }
123        }
124    }
125
126    /// Run this behavior with a maximum number of ticks.
127    ///
128    /// Returns `Err` if the behavior doesn't complete within `max_ticks`.
129    async fn run_with_limit(&mut self, ctx: &Context, max_ticks: usize) -> anyhow::Result<NodeStatus> {
130        for _tick_count in 0..max_ticks {
131            match self.tick(ctx).await? {
132                NodeStatus::Success => return Ok(NodeStatus::Success),
133                NodeStatus::Failure => return Ok(NodeStatus::Failure),
134                NodeStatus::Running => continue,
135            }
136        }
137        anyhow::bail!("Behavior exceeded maximum ticks ({})", max_ticks)
138    }
139}
140
141impl<T: BehaviorNode + ?Sized> BehaviorNodeExt for T {}