Skip to main content

ruvector_robotics/cognitive/
behavior_tree.rs

1//! Composable behavior trees for robot task execution.
2//!
3//! Provides a declarative way to build complex robot behaviors from simple
4//! building blocks: actions, conditions, sequences, selectors, decorators,
5//! and parallel nodes.
6
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9
10// ---------------------------------------------------------------------------
11// Status & decorator types
12// ---------------------------------------------------------------------------
13
14/// Result of ticking a behavior tree node.
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
16pub enum BehaviorStatus {
17    Success,
18    Failure,
19    Running,
20}
21
22/// Decorator modifiers that wrap a single child node.
23#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
24pub enum DecoratorType {
25    /// Inverts Success <-> Failure; Running stays Running.
26    Inverter,
27    /// Repeats the child a fixed number of times.
28    Repeat(usize),
29    /// Keeps ticking the child until it returns Failure.
30    UntilFail,
31    /// Fails the child if it does not finish within `ms` milliseconds (tick count proxy).
32    Timeout(u64),
33}
34
35// ---------------------------------------------------------------------------
36// Node enum
37// ---------------------------------------------------------------------------
38
39/// A single node in the behavior tree.
40#[derive(Debug, Clone, Serialize, Deserialize)]
41pub enum BehaviorNode {
42    /// Leaf that executes a named action. Result is looked up in the context.
43    Action(String),
44    /// Leaf that checks a named boolean condition in the context.
45    Condition(String),
46    /// Runs children left-to-right; stops on first non-Success.
47    Sequence(Vec<BehaviorNode>),
48    /// Runs children left-to-right; stops on first Success.
49    Selector(Vec<BehaviorNode>),
50    /// Applies a [`DecoratorType`] to a single child.
51    Decorator(DecoratorType, Box<BehaviorNode>),
52    /// Runs all children concurrently; succeeds when `threshold` children succeed.
53    Parallel(usize, Vec<BehaviorNode>),
54}
55
56// ---------------------------------------------------------------------------
57// Context (blackboard)
58// ---------------------------------------------------------------------------
59
60/// Shared context passed through the tree during evaluation.
61#[derive(Debug, Clone, Default)]
62pub struct BehaviorContext {
63    /// General-purpose string key-value store.
64    pub blackboard: HashMap<String, String>,
65    /// Monotonically increasing tick counter.
66    pub tick_count: u64,
67    /// Boolean conditions used by `Condition` nodes.
68    pub conditions: HashMap<String, bool>,
69    /// Pre-set results for `Action` nodes.
70    pub action_results: HashMap<String, BehaviorStatus>,
71}
72
73// ---------------------------------------------------------------------------
74// Tree
75// ---------------------------------------------------------------------------
76
77/// A behavior tree with a root node and shared context.
78#[derive(Debug, Clone)]
79pub struct BehaviorTree {
80    root: BehaviorNode,
81    context: BehaviorContext,
82}
83
84impl BehaviorTree {
85    /// Create a new tree with the given root node.
86    pub fn new(root: BehaviorNode) -> Self {
87        Self {
88            root,
89            context: BehaviorContext::default(),
90        }
91    }
92
93    /// Tick the tree once, returning the root status.
94    pub fn tick(&mut self) -> BehaviorStatus {
95        self.context.tick_count += 1;
96        // Split borrow: `root` is borrowed immutably, `context` mutably.
97        // Rust allows this because they are disjoint struct fields.
98        let root = &self.root;
99        let ctx = &mut self.context;
100        eval(root, ctx)
101    }
102
103    /// Reset the context (tick count, blackboard, etc.).
104    pub fn reset(&mut self) {
105        self.context = BehaviorContext::default();
106    }
107
108    /// Set a named boolean condition.
109    pub fn set_condition(&mut self, name: &str, value: bool) {
110        self.context.conditions.insert(name.to_string(), value);
111    }
112
113    /// Set the result that a named action should return.
114    pub fn set_action_result(&mut self, name: &str, status: BehaviorStatus) {
115        self.context
116            .action_results
117            .insert(name.to_string(), status);
118    }
119
120    /// Read-only access to the context.
121    pub fn context(&self) -> &BehaviorContext {
122        &self.context
123    }
124
125    /// Read-only reference to the root node.
126    pub fn root(&self) -> &BehaviorNode {
127        &self.root
128    }
129}
130
131/// Maximum iterations for `UntilFail` before returning `Running` as a safety
132/// guard against infinite loops when the child always succeeds.
133const UNTIL_FAIL_MAX_ITERATIONS: usize = 10_000;
134
135// Free functions that borrow the tree and context independently, avoiding the
136// need to clone the tree on every tick.
137
138fn eval(node: &BehaviorNode, ctx: &mut BehaviorContext) -> BehaviorStatus {
139    match node {
140        BehaviorNode::Action(name) => ctx
141            .action_results
142            .get(name)
143            .copied()
144            .unwrap_or(BehaviorStatus::Failure),
145
146        BehaviorNode::Condition(name) => {
147            if ctx.conditions.get(name).copied().unwrap_or(false) {
148                BehaviorStatus::Success
149            } else {
150                BehaviorStatus::Failure
151            }
152        }
153
154        BehaviorNode::Sequence(children) => {
155            for child in children {
156                match eval(child, ctx) {
157                    BehaviorStatus::Success => continue,
158                    other => return other,
159                }
160            }
161            BehaviorStatus::Success
162        }
163
164        BehaviorNode::Selector(children) => {
165            for child in children {
166                match eval(child, ctx) {
167                    BehaviorStatus::Failure => continue,
168                    other => return other,
169                }
170            }
171            BehaviorStatus::Failure
172        }
173
174        BehaviorNode::Decorator(dtype, child) => eval_decorator(dtype, child, ctx),
175
176        BehaviorNode::Parallel(threshold, children) => {
177            let mut success_count = 0usize;
178            let mut any_running = false;
179            for child in children {
180                match eval(child, ctx) {
181                    BehaviorStatus::Success => success_count += 1,
182                    BehaviorStatus::Running => any_running = true,
183                    BehaviorStatus::Failure => {}
184                }
185            }
186            if success_count >= *threshold {
187                BehaviorStatus::Success
188            } else if any_running {
189                BehaviorStatus::Running
190            } else {
191                BehaviorStatus::Failure
192            }
193        }
194    }
195}
196
197fn eval_decorator(
198    dtype: &DecoratorType,
199    child: &BehaviorNode,
200    ctx: &mut BehaviorContext,
201) -> BehaviorStatus {
202    match dtype {
203        DecoratorType::Inverter => match eval(child, ctx) {
204            BehaviorStatus::Success => BehaviorStatus::Failure,
205            BehaviorStatus::Failure => BehaviorStatus::Success,
206            BehaviorStatus::Running => BehaviorStatus::Running,
207        },
208        DecoratorType::Repeat(n) => {
209            for _ in 0..*n {
210                match eval(child, ctx) {
211                    BehaviorStatus::Failure => return BehaviorStatus::Failure,
212                    BehaviorStatus::Running => return BehaviorStatus::Running,
213                    BehaviorStatus::Success => {}
214                }
215            }
216            BehaviorStatus::Success
217        }
218        DecoratorType::UntilFail => {
219            for _ in 0..UNTIL_FAIL_MAX_ITERATIONS {
220                match eval(child, ctx) {
221                    BehaviorStatus::Failure => return BehaviorStatus::Success,
222                    BehaviorStatus::Running => return BehaviorStatus::Running,
223                    BehaviorStatus::Success => continue,
224                }
225            }
226            // Safety: child never failed within the iteration budget.
227            BehaviorStatus::Running
228        }
229        DecoratorType::Timeout(max_ticks) => {
230            if ctx.tick_count > *max_ticks {
231                return BehaviorStatus::Failure;
232            }
233            eval(child, ctx)
234        }
235    }
236}
237
238// ---------------------------------------------------------------------------
239// Tests
240// ---------------------------------------------------------------------------
241
242#[cfg(test)]
243mod tests {
244    use super::*;
245
246    #[test]
247    fn test_action_success() {
248        let mut tree = BehaviorTree::new(BehaviorNode::Action("move".into()));
249        tree.set_action_result("move", BehaviorStatus::Success);
250        assert_eq!(tree.tick(), BehaviorStatus::Success);
251    }
252
253    #[test]
254    fn test_action_default_failure() {
255        let mut tree = BehaviorTree::new(BehaviorNode::Action("unknown".into()));
256        assert_eq!(tree.tick(), BehaviorStatus::Failure);
257    }
258
259    #[test]
260    fn test_condition_true() {
261        let mut tree = BehaviorTree::new(BehaviorNode::Condition("has_target".into()));
262        tree.set_condition("has_target", true);
263        assert_eq!(tree.tick(), BehaviorStatus::Success);
264    }
265
266    #[test]
267    fn test_condition_false() {
268        let mut tree = BehaviorTree::new(BehaviorNode::Condition("has_target".into()));
269        assert_eq!(tree.tick(), BehaviorStatus::Failure);
270    }
271
272    #[test]
273    fn test_sequence_all_success() {
274        let seq = BehaviorNode::Sequence(vec![
275            BehaviorNode::Action("a".into()),
276            BehaviorNode::Action("b".into()),
277        ]);
278        let mut tree = BehaviorTree::new(seq);
279        tree.set_action_result("a", BehaviorStatus::Success);
280        tree.set_action_result("b", BehaviorStatus::Success);
281        assert_eq!(tree.tick(), BehaviorStatus::Success);
282    }
283
284    #[test]
285    fn test_sequence_early_failure() {
286        let seq = BehaviorNode::Sequence(vec![
287            BehaviorNode::Action("a".into()),
288            BehaviorNode::Action("b".into()),
289        ]);
290        let mut tree = BehaviorTree::new(seq);
291        tree.set_action_result("a", BehaviorStatus::Failure);
292        tree.set_action_result("b", BehaviorStatus::Success);
293        assert_eq!(tree.tick(), BehaviorStatus::Failure);
294    }
295
296    #[test]
297    fn test_selector_first_success() {
298        let sel = BehaviorNode::Selector(vec![
299            BehaviorNode::Action("a".into()),
300            BehaviorNode::Action("b".into()),
301        ]);
302        let mut tree = BehaviorTree::new(sel);
303        tree.set_action_result("a", BehaviorStatus::Success);
304        assert_eq!(tree.tick(), BehaviorStatus::Success);
305    }
306
307    #[test]
308    fn test_selector_fallback() {
309        let sel = BehaviorNode::Selector(vec![
310            BehaviorNode::Action("a".into()),
311            BehaviorNode::Action("b".into()),
312        ]);
313        let mut tree = BehaviorTree::new(sel);
314        tree.set_action_result("a", BehaviorStatus::Failure);
315        tree.set_action_result("b", BehaviorStatus::Success);
316        assert_eq!(tree.tick(), BehaviorStatus::Success);
317    }
318
319    #[test]
320    fn test_selector_all_fail() {
321        let sel = BehaviorNode::Selector(vec![
322            BehaviorNode::Action("a".into()),
323            BehaviorNode::Action("b".into()),
324        ]);
325        let mut tree = BehaviorTree::new(sel);
326        tree.set_action_result("a", BehaviorStatus::Failure);
327        tree.set_action_result("b", BehaviorStatus::Failure);
328        assert_eq!(tree.tick(), BehaviorStatus::Failure);
329    }
330
331    #[test]
332    fn test_inverter_decorator() {
333        let node = BehaviorNode::Decorator(
334            DecoratorType::Inverter,
335            Box::new(BehaviorNode::Action("a".into())),
336        );
337        let mut tree = BehaviorTree::new(node);
338        tree.set_action_result("a", BehaviorStatus::Success);
339        assert_eq!(tree.tick(), BehaviorStatus::Failure);
340    }
341
342    #[test]
343    fn test_repeat_decorator() {
344        let node = BehaviorNode::Decorator(
345            DecoratorType::Repeat(3),
346            Box::new(BehaviorNode::Action("a".into())),
347        );
348        let mut tree = BehaviorTree::new(node);
349        tree.set_action_result("a", BehaviorStatus::Success);
350        assert_eq!(tree.tick(), BehaviorStatus::Success);
351    }
352
353    #[test]
354    fn test_repeat_decorator_failure() {
355        let node = BehaviorNode::Decorator(
356            DecoratorType::Repeat(3),
357            Box::new(BehaviorNode::Action("a".into())),
358        );
359        let mut tree = BehaviorTree::new(node);
360        tree.set_action_result("a", BehaviorStatus::Failure);
361        assert_eq!(tree.tick(), BehaviorStatus::Failure);
362    }
363
364    #[test]
365    fn test_parallel_threshold() {
366        let par = BehaviorNode::Parallel(
367            2,
368            vec![
369                BehaviorNode::Action("a".into()),
370                BehaviorNode::Action("b".into()),
371                BehaviorNode::Action("c".into()),
372            ],
373        );
374        let mut tree = BehaviorTree::new(par);
375        tree.set_action_result("a", BehaviorStatus::Success);
376        tree.set_action_result("b", BehaviorStatus::Success);
377        tree.set_action_result("c", BehaviorStatus::Failure);
378        assert_eq!(tree.tick(), BehaviorStatus::Success);
379    }
380
381    #[test]
382    fn test_parallel_running() {
383        let par = BehaviorNode::Parallel(
384            2,
385            vec![
386                BehaviorNode::Action("a".into()),
387                BehaviorNode::Action("b".into()),
388            ],
389        );
390        let mut tree = BehaviorTree::new(par);
391        tree.set_action_result("a", BehaviorStatus::Success);
392        tree.set_action_result("b", BehaviorStatus::Running);
393        assert_eq!(tree.tick(), BehaviorStatus::Running);
394    }
395
396    #[test]
397    fn test_timeout_decorator() {
398        let node = BehaviorNode::Decorator(
399            DecoratorType::Timeout(2),
400            Box::new(BehaviorNode::Action("a".into())),
401        );
402        let mut tree = BehaviorTree::new(node);
403        tree.set_action_result("a", BehaviorStatus::Running);
404        // tick 1 => within timeout
405        assert_eq!(tree.tick(), BehaviorStatus::Running);
406        // tick 2 => within timeout
407        assert_eq!(tree.tick(), BehaviorStatus::Running);
408        // tick 3 => exceeds timeout
409        assert_eq!(tree.tick(), BehaviorStatus::Failure);
410    }
411
412    #[test]
413    fn test_reset() {
414        let mut tree = BehaviorTree::new(BehaviorNode::Action("a".into()));
415        tree.set_action_result("a", BehaviorStatus::Success);
416        tree.set_condition("flag", true);
417        tree.tick();
418        assert_eq!(tree.context().tick_count, 1);
419        tree.reset();
420        assert_eq!(tree.context().tick_count, 0);
421        assert!(tree.context().conditions.is_empty());
422    }
423
424    #[test]
425    fn test_blackboard() {
426        let mut tree = BehaviorTree::new(BehaviorNode::Action("a".into()));
427        tree.set_action_result("a", BehaviorStatus::Success);
428        tree.context
429            .blackboard
430            .insert("target".into(), "object_1".into());
431        assert_eq!(tree.context().blackboard.get("target").unwrap(), "object_1");
432    }
433}