behavior_tree_lite/parser/
loader.rs

1use std::collections::{HashMap, HashSet};
2
3use super::nom_parser::{TreeDef, TreeSource};
4use crate::{
5    error::{AddChildError, LoadError},
6    nodes::{IsTrueNode, SubtreeNode, INPUT},
7    BBMap, BehaviorNodeContainer, NumChildren, PortSpec, PortType, Registry, Symbol,
8};
9
10/// Instantiate a behavior tree from a AST of a tree.
11///
12/// `check_ports` enables static checking of port availability before actually ticking.
13/// It is useful to catch errors in a behavior tree source file, but you need to
14/// implement [`crate::BehaviorNode::provided_ports`] to use it.
15pub fn load(
16    tree_source: &TreeSource,
17    registry: &Registry,
18    check_ports: bool,
19) -> Result<BehaviorNodeContainer, LoadError> {
20    let main = tree_source
21        .tree_defs
22        .iter()
23        .find(|tree| tree.name == "main")
24        .ok_or(LoadError::MissingTree)?;
25
26    let top = TreeStack {
27        name: "main",
28        parent: None,
29    };
30
31    let mut vars = HashSet::new();
32
33    load_recurse(
34        &main.root,
35        registry,
36        tree_source,
37        check_ports,
38        &top,
39        &mut vars,
40    )
41}
42
43/// A mechanism to detect infinite recursion. It is a linked list in call stack.
44/// You can traverse the link back to enumerate all the subtree names (which is effectively function names)
45/// and check if a subtree name to be inserted is already there.
46///
47/// We could also use HashSet of subtree names, but it feels silly to use dynamically allocated collection
48/// when you can do the same thing with just the call stack.
49///
50/// # Discussion
51///
52/// It is very interesting discussion if we should allow recursive subtrees.
53/// It will give the source file the power to describe some advanced algorithms and make it
54/// easier to write Turing complete code.
55///
56/// However, in order to do so, we need to "lazily" load the subtree, which means we cannot instantiate
57/// the behavior nodes until the subtree is actually ticked. So we need to keep [`Registry`] and [`TreeSource`]
58/// objects during the lifetime of the entire behavior tree.
59/// It would completely change the design of `TreeSource` and I'm not exactly sure if it's worth it.
60/// After all, BehaviorTreeCPP works without recursive subtrees just fine.
61/// You can always transform algorithms with recursive calls into a flat loop with an explicit stack.
62///
63/// Also, it is not entirely clear how we should render the behavior tree on a graphical editor, when
64/// we get to implement one.
65/// Clearly, we cannot expand all the subtrees that contains itself, but the user would want to expand
66/// all subtrees to get better understanding of the tree structure.
67/// It means the graphical editor also needs some kind of lazy evaluation.
68///
69/// For now, we make recursive subtrees an error. Luckily we can detect it relatively easily.
70///
71/// By the way, if we didn't have this mechanism in place, recursive subtrees cause a stack overflow.
72/// It uses quite some amount of heap memory, but call stack runs short sooner.
73struct TreeStack<'a, 'src> {
74    name: &'src str,
75    parent: Option<&'a TreeStack<'a, 'src>>,
76}
77
78impl<'a, 'src> TreeStack<'a, 'src> {
79    fn find(&self, name: &str) -> bool {
80        if self.name == name {
81            true
82        } else if let Some(parent) = self.parent {
83            parent.find(name)
84        } else {
85            false
86        }
87    }
88}
89
90fn load_recurse(
91    parent: &TreeDef,
92    registry: &Registry,
93    tree_source: &TreeSource,
94    check_ports: bool,
95    parent_stack: &TreeStack,
96    vars: &mut HashSet<Symbol>,
97) -> Result<BehaviorNodeContainer, LoadError> {
98    let mut ret = if let Some(ret) = registry.build(parent.ty) {
99        BehaviorNodeContainer::new_raw_with_name(ret, parent.ty.to_string())
100    } else {
101        let tree = tree_source
102            .tree_defs
103            .iter()
104            .find(|tree| tree.name == parent.ty)
105            .ok_or_else(|| LoadError::MissingNode(parent.ty.to_owned()))?;
106
107        // Prevent infinite recursion
108        if parent_stack.find(parent.ty) {
109            return Err(LoadError::InfiniteRecursion {
110                node: parent.ty.to_owned(),
111            });
112        }
113        let tree_stack = TreeStack {
114            name: parent.ty,
115            parent: Some(parent_stack),
116        };
117
118        // A subtree introduces a new namespace, so the parent tree variables won't affect
119        // the decision of variable or node.
120        let mut vars = HashSet::new();
121
122        let loaded_subtree = load_recurse(
123            &tree.root,
124            registry,
125            tree_source,
126            check_ports,
127            &tree_stack,
128            &mut vars,
129        )?;
130        BehaviorNodeContainer {
131            name: parent.ty.to_owned(),
132            node: Box::new(SubtreeNode::new(
133                HashMap::new(),
134                tree.ports
135                    .iter()
136                    .map(|port| PortSpec {
137                        key: port.name.into(),
138                        ty: port.direction,
139                    })
140                    .collect(),
141            )),
142            blackboard_map: HashMap::new(),
143            child_nodes: vec![loaded_subtree],
144            last_result: None,
145            is_subtree: true,
146            subtree_expanded: std::cell::Cell::new(false),
147        }
148    };
149
150    // "Hoist" declarations
151    for var_def in &parent.vars {
152        vars.insert(var_def.name.into());
153    }
154
155    for child in &parent.children {
156        let mut new_node = if child.port_maps.is_empty() && child.children.is_empty() {
157            if vars.contains(&child.ty.into()) {
158                let mut bbmap = BBMap::new();
159                bbmap.insert(
160                    *INPUT,
161                    crate::BlackboardValue::Ref(child.ty.into(), PortType::Input),
162                );
163                Some(
164                    BehaviorNodeContainer::new(Box::new(IsTrueNode), bbmap)
165                        .with_name("IsTrue".to_owned()),
166                )
167            } else {
168                None
169            }
170        } else {
171            None
172        };
173
174        if new_node.is_none() {
175            let mut child_node = load_recurse(
176                child,
177                registry,
178                tree_source,
179                check_ports,
180                parent_stack,
181                vars,
182            )?;
183            let provided_ports = child_node.node.provided_ports();
184            let mut bbmap = BBMap::new();
185            for entry in child.port_maps.iter() {
186                if check_ports {
187                    if let Some(port) = provided_ports.iter().find(|p| p.key == entry.node_port) {
188                        if port.ty != entry.ty {
189                            return Err(LoadError::PortIOUnmatch {
190                                node: child.ty.to_owned(),
191                                port: entry.node_port.to_owned(),
192                            });
193                        }
194                    } else {
195                        return Err(LoadError::PortUnmatch {
196                            node: child.ty.to_owned(),
197                            port: entry.node_port.to_owned(),
198                        });
199                    }
200                }
201                bbmap.insert(
202                    entry.node_port.into(),
203                    match entry.blackboard_value {
204                        super::nom_parser::BlackboardValue::Ref(ref value) => {
205                            crate::BlackboardValue::Ref(value.into(), entry.ty)
206                        }
207                        super::nom_parser::BlackboardValue::Literal(ref value) => {
208                            crate::BlackboardValue::Literal(value.clone())
209                        }
210                    },
211                );
212            }
213            child_node.blackboard_map = bbmap;
214            new_node = Some(child_node);
215        }
216
217        if let Some(new_node) = new_node {
218            if NumChildren::Finite(ret.child_nodes.len()) < ret.node.max_children() {
219                ret.child_nodes.push(new_node);
220            } else {
221                return Err(LoadError::AddChildError(
222                    AddChildError::TooManyNodes,
223                    parent.ty.to_string(),
224                ));
225            }
226        }
227    }
228
229    Ok(ret)
230}
231
232#[cfg(test)]
233mod test;