ruvector_robotics/cognitive/
behavior_tree.rs1use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
16pub enum BehaviorStatus {
17 Success,
18 Failure,
19 Running,
20}
21
22#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
24pub enum DecoratorType {
25 Inverter,
27 Repeat(usize),
29 UntilFail,
31 Timeout(u64),
33}
34
35#[derive(Debug, Clone, Serialize, Deserialize)]
41pub enum BehaviorNode {
42 Action(String),
44 Condition(String),
46 Sequence(Vec<BehaviorNode>),
48 Selector(Vec<BehaviorNode>),
50 Decorator(DecoratorType, Box<BehaviorNode>),
52 Parallel(usize, Vec<BehaviorNode>),
54}
55
56#[derive(Debug, Clone, Default)]
62pub struct BehaviorContext {
63 pub blackboard: HashMap<String, String>,
65 pub tick_count: u64,
67 pub conditions: HashMap<String, bool>,
69 pub action_results: HashMap<String, BehaviorStatus>,
71}
72
73#[derive(Debug, Clone)]
79pub struct BehaviorTree {
80 root: BehaviorNode,
81 context: BehaviorContext,
82}
83
84impl BehaviorTree {
85 pub fn new(root: BehaviorNode) -> Self {
87 Self {
88 root,
89 context: BehaviorContext::default(),
90 }
91 }
92
93 pub fn tick(&mut self) -> BehaviorStatus {
95 self.context.tick_count += 1;
96 let root = &self.root;
99 let ctx = &mut self.context;
100 eval(root, ctx)
101 }
102
103 pub fn reset(&mut self) {
105 self.context = BehaviorContext::default();
106 }
107
108 pub fn set_condition(&mut self, name: &str, value: bool) {
110 self.context.conditions.insert(name.to_string(), value);
111 }
112
113 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 pub fn context(&self) -> &BehaviorContext {
122 &self.context
123 }
124
125 pub fn root(&self) -> &BehaviorNode {
127 &self.root
128 }
129}
130
131const UNTIL_FAIL_MAX_ITERATIONS: usize = 10_000;
134
135fn 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 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#[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 assert_eq!(tree.tick(), BehaviorStatus::Running);
406 assert_eq!(tree.tick(), BehaviorStatus::Running);
408 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}