behavior_tree_lite/
context.rs

1use crate::{
2    BBMap, BehaviorCallback, BehaviorNodeContainer, BehaviorResult, Blackboard, BlackboardValue,
3    PortType, Symbol,
4};
5use std::{any::Any, rc::Rc, str::FromStr};
6
7/// Our custom wrapper struct to stop propagation of Debug trait macro.
8/// Borrowed the concept from `debug-ignore` crate, but grossly simplified, and without dependency.
9#[derive(Default)]
10pub struct DebugIgnore<T: ?Sized>(pub T);
11
12impl<T: ?Sized> std::fmt::Debug for DebugIgnore<T> {
13    #[inline]
14    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
15        write!(f, "...")
16    }
17}
18
19impl<T: ?Sized> std::ops::Deref for DebugIgnore<T> {
20    type Target = T;
21
22    #[inline]
23    fn deref(&self) -> &Self::Target {
24        &self.0
25    }
26}
27
28#[derive(Default, Debug)]
29pub struct Context {
30    pub(crate) blackboard: Blackboard,
31    pub(crate) blackboard_map: BBMap,
32    pub(crate) child_nodes: DebugIgnore<Vec<BehaviorNodeContainer>>,
33    strict: bool,
34}
35
36impl Context {
37    pub fn new(blackboard: Blackboard) -> Self {
38        Self {
39            blackboard,
40            blackboard_map: BBMap::new(),
41            child_nodes: DebugIgnore(vec![]),
42            strict: true,
43        }
44    }
45
46    pub fn take_blackboard(self) -> Blackboard {
47        self.blackboard
48    }
49
50    pub fn strict(&self) -> bool {
51        self.strict
52    }
53
54    pub fn set_strict(&mut self, b: bool) {
55        self.strict = b;
56    }
57
58    pub fn tick_child(&mut self, idx: usize, arg: BehaviorCallback) -> Option<BehaviorResult> {
59        // Take the children temporarily because the context's `child_nodes` will be used by the child node (for grandchildren)
60        let mut children = std::mem::take(&mut self.child_nodes.0);
61        let res = children.get_mut(idx).map(|child| {
62            let res = child.tick(arg, self);
63            child.last_result = Some(res);
64            res
65        });
66        self.child_nodes.0 = children;
67        res
68    }
69
70    pub fn num_children(&self) -> usize {
71        self.child_nodes.len()
72    }
73}
74
75impl Context {
76    /// Get a blackboard variable with downcasting to the type argument.
77    /// Returns `None` if it fails to downcast.
78    pub fn get<T: 'static>(&self, key: impl Into<Symbol>) -> Option<&T> {
79        let key: Symbol = key.into();
80        let mapped = self.blackboard_map.get(&key);
81        let mapped = match mapped {
82            None => &key,
83            Some(BlackboardValue::Ref(mapped, ty)) => {
84                if matches!(*ty, PortType::Input | PortType::InOut) {
85                    mapped
86                } else {
87                    if self.strict {
88                        panic!("Port {:?} is not specified as input or inout port", key);
89                    }
90                    return None;
91                }
92            }
93            Some(BlackboardValue::Literal(mapped)) => {
94                return (mapped as &dyn Any).downcast_ref();
95            }
96        };
97
98        self.blackboard.get(mapped).and_then(|val| {
99            // println!("val: {:?}", val);
100            val.downcast_ref()
101        })
102    }
103
104    /// Get a blackboard variable without downcasting.
105    pub fn get_any(&self, key: impl Into<Symbol>) -> Option<Rc<dyn Any>> {
106        let key: Symbol = key.into();
107        let mapped = self.blackboard_map.get(&key);
108        let mapped = match mapped {
109            None => &key,
110            Some(BlackboardValue::Ref(mapped, ty)) => {
111                if matches!(*ty, PortType::Input | PortType::InOut) {
112                    mapped
113                } else {
114                    if self.strict {
115                        panic!("Port {:?} is not specified as input or inout port", key);
116                    }
117                    return None;
118                }
119            }
120            Some(BlackboardValue::Literal(mapped)) => {
121                return Some(Rc::new(mapped.clone()));
122            }
123        };
124
125        self.blackboard.get(mapped).cloned()
126    }
127
128    /// Convenience method to get raw primitive types such as f64 or parse from string
129    pub fn get_parse<F>(&self, key: impl Into<Symbol> + Copy) -> Option<F>
130    where
131        F: FromStr + Copy + 'static,
132    {
133        self.get::<F>(key).copied().or_else(|| {
134            self.get::<String>(key)
135                .and_then(|val| val.parse::<F>().ok())
136        })
137    }
138
139    pub fn set<T: 'static>(&mut self, key: impl Into<Symbol>, val: T) {
140        if let Some(key) = self.map_out_key(key) {
141            self.blackboard.insert(key, Rc::new(val));
142        }
143    }
144
145    pub fn set_any(&mut self, key: impl Into<Symbol>, val: Rc<dyn Any>) {
146        if let Some(key) = self.map_out_key(key) {
147            self.blackboard.insert(key, val);
148        }
149    }
150
151    fn map_out_key(&self, key: impl Into<Symbol>) -> Option<Symbol> {
152        let key = key.into();
153        let mapped = self.blackboard_map.get(&key);
154        match mapped {
155            None => Some(key),
156            Some(BlackboardValue::Ref(mapped, ty)) => {
157                if matches!(ty, PortType::Output | PortType::InOut) {
158                    Some(*mapped)
159                } else {
160                    if self.strict {
161                        panic!("Port {:?} is not specified as output or inout port", key);
162                    }
163                    None
164                }
165            }
166            Some(BlackboardValue::Literal(_)) => panic!("Cannot write to a literal!"),
167        }
168    }
169
170    // pub fn get_env(&mut self) -> Option<&mut E> {
171    //     self.env
172    // }
173}