Skip to main content

mecha10_behavior_runtime/
composition.rs

1//! Composition primitives for building behavior trees
2//!
3//! This module provides the fundamental composition nodes: Sequence, Selector, and Parallel.
4
5use crate::{BehaviorNode, BoxedBehavior, NodeStatus};
6use async_trait::async_trait;
7use mecha10_core::Context;
8use tracing::{debug, warn};
9
10/// Executes child behaviors in sequence until one fails or all succeed.
11///
12/// **Behavior:**
13/// - Ticks children in order
14/// - Returns `Success` if all children succeed
15/// - Returns `Failure` if any child fails
16/// - Returns `Running` while executing children
17///
18/// **Use cases:**
19/// - Multi-step tasks that must complete in order
20/// - Pipeline processing
21/// - Sequential actions (approach → grasp → lift)
22///
23/// # Example
24///
25/// ```rust,no_run
26/// use mecha10_behavior_runtime::prelude::*;
27///
28/// // Assuming these behaviors are defined elsewhere
29/// # use mecha10_behavior_runtime::BoxedBehavior;
30/// # fn get_behaviors() -> Vec<BoxedBehavior> { vec![] }
31/// let sequence = SequenceNode::new(get_behaviors());
32/// ```
33#[derive(Debug)]
34pub struct SequenceNode {
35    children: Vec<BoxedBehavior>,
36    current_index: usize,
37}
38
39impl SequenceNode {
40    /// Create a new sequence node with the given children.
41    pub fn new(children: Vec<BoxedBehavior>) -> Self {
42        Self {
43            children,
44            current_index: 0,
45        }
46    }
47
48    /// Add a child behavior to the sequence.
49    pub fn add_child(&mut self, child: BoxedBehavior) {
50        self.children.push(child);
51    }
52
53    /// Get the number of children.
54    pub fn len(&self) -> usize {
55        self.children.len()
56    }
57
58    /// Check if the sequence is empty.
59    pub fn is_empty(&self) -> bool {
60        self.children.is_empty()
61    }
62}
63
64#[async_trait]
65impl BehaviorNode for SequenceNode {
66    async fn tick(&mut self, ctx: &Context) -> anyhow::Result<NodeStatus> {
67        if self.children.is_empty() {
68            return Ok(NodeStatus::Success);
69        }
70
71        let total_children = self.children.len();
72        while self.current_index < total_children {
73            debug!(
74                "Sequence: ticking child {} of {}",
75                self.current_index + 1,
76                total_children
77            );
78
79            let child = &mut self.children[self.current_index];
80            match child.tick(ctx).await? {
81                NodeStatus::Success => {
82                    self.current_index += 1;
83                }
84                NodeStatus::Failure => {
85                    debug!("Sequence: child {} failed", self.current_index + 1);
86                    return Ok(NodeStatus::Failure);
87                }
88                NodeStatus::Running => {
89                    return Ok(NodeStatus::Running);
90                }
91            }
92        }
93
94        debug!("Sequence: all children succeeded");
95        Ok(NodeStatus::Success)
96    }
97
98    crate::reset_index_field!(current_index);
99
100    fn name(&self) -> &str {
101        "sequence"
102    }
103
104    fn children_mut(&mut self) -> Vec<&mut dyn BehaviorNode> {
105        crate::boxed_children_mut(self.children.iter_mut())
106    }
107}
108
109/// Executes child behaviors until one succeeds or all fail.
110///
111/// **Behavior:**
112/// - Ticks children in order
113/// - Returns `Success` if any child succeeds
114/// - Returns `Failure` if all children fail
115/// - Returns `Running` while executing children
116///
117/// **Use cases:**
118/// - Fallback behaviors (try A, if it fails try B)
119/// - Alternative strategies
120/// - Error recovery chains
121///
122/// # Example
123///
124/// ```rust,no_run
125/// use mecha10_behavior_runtime::prelude::*;
126///
127/// // Assuming these behaviors are defined elsewhere
128/// # use mecha10_behavior_runtime::BoxedBehavior;
129/// # fn get_behaviors() -> Vec<BoxedBehavior> { vec![] }
130/// let selector = SelectOrNode::new(get_behaviors());
131/// ```
132#[derive(Debug)]
133pub struct SelectOrNode {
134    children: Vec<BoxedBehavior>,
135    current_index: usize,
136}
137
138impl SelectOrNode {
139    /// Create a new selector node with the given children.
140    pub fn new(children: Vec<BoxedBehavior>) -> Self {
141        Self {
142            children,
143            current_index: 0,
144        }
145    }
146
147    /// Add a child behavior to the selector.
148    pub fn add_child(&mut self, child: BoxedBehavior) {
149        self.children.push(child);
150    }
151
152    /// Get the number of children.
153    pub fn len(&self) -> usize {
154        self.children.len()
155    }
156
157    /// Check if the selector is empty.
158    pub fn is_empty(&self) -> bool {
159        self.children.is_empty()
160    }
161}
162
163#[async_trait]
164impl BehaviorNode for SelectOrNode {
165    async fn tick(&mut self, ctx: &Context) -> anyhow::Result<NodeStatus> {
166        if self.children.is_empty() {
167            return Ok(NodeStatus::Failure);
168        }
169
170        let total_children = self.children.len();
171        while self.current_index < total_children {
172            debug!(
173                "Selector: ticking child {} of {}",
174                self.current_index + 1,
175                total_children
176            );
177
178            let child = &mut self.children[self.current_index];
179            match child.tick(ctx).await? {
180                NodeStatus::Success => {
181                    debug!("Selector: child {} succeeded", self.current_index + 1);
182                    return Ok(NodeStatus::Success);
183                }
184                NodeStatus::Failure => {
185                    warn!("Selector: child {} failed, trying next", self.current_index + 1);
186                    self.current_index += 1;
187                }
188                NodeStatus::Running => {
189                    return Ok(NodeStatus::Running);
190                }
191            }
192        }
193
194        debug!("Selector: all children failed");
195        Ok(NodeStatus::Failure)
196    }
197
198    crate::reset_index_field!(current_index);
199
200    fn name(&self) -> &str {
201        "selector"
202    }
203
204    fn children_mut(&mut self) -> Vec<&mut dyn BehaviorNode> {
205        crate::boxed_children_mut(self.children.iter_mut())
206    }
207}
208
209/// Policy for determining when a parallel node succeeds or fails.
210#[derive(Debug, Clone, Copy, PartialEq, Eq)]
211pub enum ParallelPolicy {
212    /// Succeed when all children succeed, fail if any child fails.
213    RequireAll,
214
215    /// Succeed when any child succeeds, fail when all children fail.
216    RequireOne,
217
218    /// Succeed when at least N children succeed.
219    RequireN(usize),
220}
221
222/// Executes all child behaviors concurrently (in parallel).
223///
224/// **Behavior:**
225/// - Ticks all children every tick
226/// - Success/failure depends on the configured policy
227/// - Returns `Running` while any child is still running
228///
229/// **Use cases:**
230/// - Concurrent actions (move + look around + monitor obstacles)
231/// - Parallel sensor processing
232/// - Multi-objective optimization
233///
234/// # Example
235///
236/// ```rust,no_run
237/// use mecha10_behavior_runtime::prelude::*;
238///
239/// // Assuming these behaviors are defined elsewhere
240/// # use mecha10_behavior_runtime::BoxedBehavior;
241/// # fn get_behaviors() -> Vec<BoxedBehavior> { vec![] }
242/// // All must succeed
243/// let parallel = ParallelNode::new(
244///     get_behaviors(),
245///     ParallelPolicy::RequireAll,
246/// );
247/// ```
248#[derive(Debug)]
249pub struct ParallelNode {
250    children: Vec<BoxedBehavior>,
251    policy: ParallelPolicy,
252    child_statuses: Vec<NodeStatus>,
253}
254
255impl ParallelNode {
256    /// Create a new parallel node with the given children and policy.
257    pub fn new(children: Vec<BoxedBehavior>, policy: ParallelPolicy) -> Self {
258        let child_statuses = vec![NodeStatus::Running; children.len()];
259        Self {
260            children,
261            policy,
262            child_statuses,
263        }
264    }
265
266    /// Add a child behavior to the parallel node.
267    pub fn add_child(&mut self, child: BoxedBehavior) {
268        self.children.push(child);
269        self.child_statuses.push(NodeStatus::Running);
270    }
271
272    /// Get the number of children.
273    pub fn len(&self) -> usize {
274        self.children.len()
275    }
276
277    /// Check if the parallel node is empty.
278    pub fn is_empty(&self) -> bool {
279        self.children.is_empty()
280    }
281}
282
283#[async_trait]
284impl BehaviorNode for ParallelNode {
285    async fn tick(&mut self, ctx: &Context) -> anyhow::Result<NodeStatus> {
286        if self.children.is_empty() {
287            return Ok(NodeStatus::Success);
288        }
289
290        for (i, child) in self.children.iter_mut().enumerate() {
291            if self.child_statuses[i].is_running() {
292                let status = child.tick(ctx).await?;
293                self.child_statuses[i] = status;
294                debug!("Parallel: child {} status: {}", i + 1, status);
295            }
296        }
297
298        let success_count = self.child_statuses.iter().filter(|s| s.is_success()).count();
299        let failure_count = self.child_statuses.iter().filter(|s| s.is_failure()).count();
300        let _running_count = self.child_statuses.iter().filter(|s| s.is_running()).count();
301
302        match self.policy {
303            ParallelPolicy::RequireAll => {
304                if failure_count > 0 {
305                    debug!("Parallel (RequireAll): at least one child failed");
306                    Ok(NodeStatus::Failure)
307                } else if success_count == self.children.len() {
308                    debug!("Parallel (RequireAll): all children succeeded");
309                    Ok(NodeStatus::Success)
310                } else {
311                    Ok(NodeStatus::Running)
312                }
313            }
314            ParallelPolicy::RequireOne => {
315                if success_count > 0 {
316                    debug!("Parallel (RequireOne): at least one child succeeded");
317                    Ok(NodeStatus::Success)
318                } else if failure_count == self.children.len() {
319                    debug!("Parallel (RequireOne): all children failed");
320                    Ok(NodeStatus::Failure)
321                } else {
322                    Ok(NodeStatus::Running)
323                }
324            }
325            ParallelPolicy::RequireN(n) => {
326                if success_count >= n {
327                    debug!("Parallel (RequireN({})): {} children succeeded", n, success_count);
328                    Ok(NodeStatus::Success)
329                } else if (self.children.len() - failure_count) < n {
330                    debug!("Parallel (RequireN({})): not enough children can succeed", n);
331                    Ok(NodeStatus::Failure)
332                } else {
333                    Ok(NodeStatus::Running)
334                }
335            }
336        }
337    }
338
339    crate::reset_child_statuses_field!(child_statuses);
340
341    fn name(&self) -> &str {
342        "parallel"
343    }
344
345    fn children_mut(&mut self) -> Vec<&mut dyn BehaviorNode> {
346        crate::boxed_children_mut(self.children.iter_mut())
347    }
348}
349
350#[cfg(test)]
351mod tests {
352    use super::*;
353
354    /// A behavior whose `tick` is never exercised by these tests - they only cover
355    /// `children_mut`/`reset`, which don't need a real `Context`.
356    #[derive(Debug, Default)]
357    struct DummyBehavior;
358
359    #[async_trait]
360    impl BehaviorNode for DummyBehavior {
361        async fn tick(&mut self, _ctx: &Context) -> anyhow::Result<NodeStatus> {
362            unimplemented!("not exercised by these tests")
363        }
364    }
365
366    #[test]
367    fn sequence_children_mut_exposes_every_child() {
368        let mut node = SequenceNode::new(vec![Box::new(DummyBehavior), Box::new(DummyBehavior)]);
369        assert_eq!(node.children_mut().len(), 2);
370    }
371
372    #[tokio::test]
373    async fn sequence_reset_rewinds_current_index() {
374        let mut node = SequenceNode::new(vec![Box::new(DummyBehavior)]);
375        node.current_index = 1;
376
377        node.reset().await.unwrap();
378
379        assert_eq!(node.current_index, 0);
380    }
381
382    #[test]
383    fn selector_children_mut_exposes_every_child() {
384        let mut node = SelectOrNode::new(vec![Box::new(DummyBehavior), Box::new(DummyBehavior)]);
385        assert_eq!(node.children_mut().len(), 2);
386    }
387
388    #[tokio::test]
389    async fn selector_reset_rewinds_current_index() {
390        let mut node = SelectOrNode::new(vec![Box::new(DummyBehavior)]);
391        node.current_index = 1;
392
393        node.reset().await.unwrap();
394
395        assert_eq!(node.current_index, 0);
396    }
397
398    #[test]
399    fn parallel_children_mut_exposes_every_child() {
400        let mut node = ParallelNode::new(
401            vec![Box::new(DummyBehavior), Box::new(DummyBehavior)],
402            ParallelPolicy::RequireAll,
403        );
404        assert_eq!(node.children_mut().len(), 2);
405    }
406
407    #[tokio::test]
408    async fn parallel_reset_clears_child_statuses_back_to_running() {
409        let mut node = ParallelNode::new(vec![Box::new(DummyBehavior)], ParallelPolicy::RequireAll);
410        node.child_statuses[0] = NodeStatus::Success;
411
412        node.reset().await.unwrap();
413
414        assert_eq!(node.child_statuses[0], NodeStatus::Running);
415    }
416}