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 serde::{Deserialize, Serialize};
9use std::fmt::Debug;
10
11/// A point-in-time snapshot of a node's (and its subtree's) last-tick status, for live
12/// behavior-tree visualization. Captured on demand via `BehaviorNode::status_snapshot` - it does
13/// not itself tick anything, it just reads back what the most recent `tick()` call(s) already
14/// recorded.
15///
16/// `status: None` means this node has never been ticked yet (e.g. right after construction, or
17/// a child a `SelectOrNode`/`SequenceNode` hasn't reached yet this cycle). Intended to eventually
18/// cross a process boundary (serialized as JSON) via `/behavior/status`, hence `Serialize`/
19/// `Deserialize`.
20#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
21pub struct TreeStatusSnapshot {
22    pub name: String,
23    pub status: Option<NodeStatus>,
24    pub active_capability: Option<String>,
25    pub children: Vec<TreeStatusSnapshot>,
26}
27
28#[async_trait]
29pub trait BehaviorNode: Send + Sync + Debug {
30    /// Execute one tick of the behavior. Called repeatedly by the execution engine; should
31    /// perform one unit of work, return quickly, and report progress via `NodeStatus`.
32    async fn tick(&mut self, ctx: &Context) -> anyhow::Result<NodeStatus>;
33
34    /// Get the name of this behavior node.
35    ///
36    /// Used for debugging, logging, and monitoring.
37    fn name(&self) -> &str {
38        std::any::type_name::<Self>()
39    }
40
41    /// Return mutable references to this node's child behaviors, in the order
42    /// they should receive lifecycle events. Leaf nodes should not override this;
43    /// composite nodes override it to expose their children.
44    fn children_mut(&mut self) -> Vec<&mut dyn BehaviorNode> {
45        Vec::new()
46    }
47
48    /// The `capability` this node implies should be published on `/behavior/status` while it's
49    /// actively being ticked; `None` for nodes with no such gate (the default). Composite nodes
50    /// override this to forward whichever child is currently active.
51    fn active_capability(&self) -> Option<&str> {
52        None
53    }
54
55    /// Snapshot of what this node (and, for composite nodes, its subtree) reported on its last
56    /// `tick()`, for live behavior-tree visualization. The default implementation reports
57    /// `status: None` (as if never ticked) and no children - correct for a brand-new node type
58    /// that hasn't been wired up for status tracking yet, but every node type should override
59    /// this once it stores a `last_status` field: leaf nodes return their own `last_status`,
60    /// composite nodes additionally recurse into `children_mut`'s non-`mut` counterpart (there's
61    /// no shared non-mut child accessor, so each composite overrides this to walk its own child
62    /// collection directly - same pattern as `children_mut`).
63    fn status_snapshot(&self) -> TreeStatusSnapshot {
64        TreeStatusSnapshot {
65            name: self.name().to_string(),
66            status: None,
67            active_capability: self.active_capability().map(String::from),
68            children: Vec::new(),
69        }
70    }
71
72    /// Reset this node's own local state, ignoring children.
73    ///
74    /// Called by the default `reset()` before it propagates to children.
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 after a failure).
80    async fn reset(&mut self) -> anyhow::Result<()> {
81        self.reset_self();
82        for child in self.children_mut() {
83            child.reset().await?;
84        }
85        Ok(())
86    }
87
88    /// Called when the behavior is first initialized.
89    ///
90    /// Use this for one-time setup like loading models, establishing connections, etc.
91    async fn on_init(&mut self, ctx: &Context) -> anyhow::Result<()> {
92        for child in self.children_mut() {
93            child.on_init(ctx).await?;
94        }
95        Ok(())
96    }
97
98    /// Called when the behavior is being terminated.
99    ///
100    /// Use this for cleanup like closing connections, saving state, etc.
101    async fn on_terminate(&mut self, ctx: &Context) -> anyhow::Result<()> {
102        for child in self.children_mut() {
103            child.on_terminate(ctx).await?;
104        }
105        Ok(())
106    }
107}
108
109pub type BoxedBehavior = Box<dyn BehaviorNode>;
110
111/// Maps an iterator of boxed child behaviors to `BehaviorNode::children_mut`'s trait-object
112/// return type.
113pub fn boxed_children_mut<'a>(children: impl Iterator<Item = &'a mut BoxedBehavior>) -> Vec<&'a mut dyn BehaviorNode> {
114    children.map(|c| c.as_mut() as &mut dyn BehaviorNode).collect()
115}
116
117#[macro_export]
118macro_rules! children_mut_via_wrapped_behavior {
119    ($field:ident, $wrapper:ident) => {
120        fn children_mut(&mut self) -> Vec<&mut dyn $crate::BehaviorNode> {
121            $crate::boxed_children_mut(self.$field.iter_mut().map(|$wrapper| &mut $wrapper.behavior))
122        }
123    };
124}
125
126#[macro_export]
127macro_rules! reset_child_statuses_field {
128    ($field:ident) => {
129        fn reset_self(&mut self) {
130            self.$field
131                .iter_mut()
132                .for_each(|s| *s = $crate::NodeStatus::Running);
133        }
134    };
135}
136
137#[macro_export]
138macro_rules! reset_index_field {
139    ($field:ident) => {
140        fn reset_self(&mut self) {
141            self.$field = 0;
142        }
143    };
144}
145
146#[async_trait]
147pub trait BehaviorNodeExt: BehaviorNode {
148    /// Run this behavior until it completes (Success or Failure).
149    ///
150    /// Convenience method for testing and simple execution.
151    async fn run_until_complete(&mut self, ctx: &Context) -> anyhow::Result<NodeStatus> {
152        loop {
153            match self.tick(ctx).await? {
154                NodeStatus::Success => return Ok(NodeStatus::Success),
155                NodeStatus::Failure => return Ok(NodeStatus::Failure),
156                NodeStatus::Running => continue,
157            }
158        }
159    }
160
161    /// Run this behavior with a maximum number of ticks.
162    ///
163    /// Returns `Err` if the behavior doesn't complete within `max_ticks`.
164    async fn run_with_limit(&mut self, ctx: &Context, max_ticks: usize) -> anyhow::Result<NodeStatus> {
165        for _tick_count in 0..max_ticks {
166            match self.tick(ctx).await? {
167                NodeStatus::Success => return Ok(NodeStatus::Success),
168                NodeStatus::Failure => return Ok(NodeStatus::Failure),
169                NodeStatus::Running => continue,
170            }
171        }
172        anyhow::bail!("Behavior exceeded maximum ticks ({})", max_ticks)
173    }
174}
175
176impl<T: BehaviorNode + ?Sized> BehaviorNodeExt for T {}