Skip to main content

mecha10_behavior_runtime/
loader.rs

1//! Behavior tree loader for creating trees from JSON configuration
2//!
3//! This module provides the `BehaviorLoader` for loading behavior trees from JSON configuration.
4
5use crate::composition::{
6    ForceFailureNode, ForceSuccessNode, InverterNode, ParallelNode, RepeatNode, RetryNode, SelectOrNode, SequenceNode,
7    SubsumptionNode, TimeoutNode, WatchNode,
8};
9use crate::config::{BehaviorConfig, CompositionConfig};
10use crate::registry::NodeRegistry;
11use crate::BoxedBehavior;
12use anyhow::{anyhow, Context as AnyhowContext, Result};
13use serde_json::Value;
14use tracing::{debug, info};
15
16#[derive(Clone)]
17pub struct BehaviorLoader {
18    registry: NodeRegistry,
19}
20
21impl BehaviorLoader {
22    /// Create a new behavior loader with the given node registry.
23    pub fn new(registry: NodeRegistry) -> Self {
24        Self { registry }
25    }
26
27    /// Load a behavior tree from a `BehaviorConfig`, recursively instantiating all nodes.
28    pub fn load(&self, config: &BehaviorConfig) -> Result<BoxedBehavior> {
29        info!("Loading behavior tree: {}", config.name);
30        if let Some(desc) = &config.description {
31            debug!("Description: {}", desc);
32        }
33
34        self.load_composition(&config.root, config)
35            .with_context(|| format!("Failed to load behavior tree '{}'", config.name))
36    }
37
38    /// Load a behavior tree from a JSON string (parses then loads).
39    pub fn load_from_json(&self, json: &str) -> Result<BoxedBehavior> {
40        self.load(&BehaviorConfig::from_json(json)?)
41    }
42
43    /// Load a behavior tree from a JSON file (reads then loads).
44    pub fn load_from_file(&self, path: impl AsRef<std::path::Path>) -> Result<BoxedBehavior> {
45        self.load(&BehaviorConfig::from_file(path)?)
46    }
47
48    /// Recursively load a composition node and its children.
49    fn load_composition(&self, composition: &CompositionConfig, config: &BehaviorConfig) -> Result<BoxedBehavior> {
50        match composition {
51            CompositionConfig::Sequence { children, name } => {
52                debug!(
53                    "Loading sequence node{}",
54                    name.as_ref().map(|n| format!(" '{}'", n)).unwrap_or_default()
55                );
56
57                let child_nodes = children
58                    .iter()
59                    .enumerate()
60                    .map(|(i, child)| {
61                        self.load_composition(child, config)
62                            .with_context(|| format!("Failed to load sequence child {}", i))
63                    })
64                    .collect::<Result<Vec<_>>>()?;
65
66                debug!("Sequence node loaded with {} children", child_nodes.len());
67                Ok(Box::new(SequenceNode::new(child_nodes)))
68            }
69
70            CompositionConfig::Selector { children, name } => {
71                debug!(
72                    "Loading selector node{}",
73                    name.as_ref().map(|n| format!(" '{}'", n)).unwrap_or_default()
74                );
75
76                let child_nodes = children
77                    .iter()
78                    .enumerate()
79                    .map(|(i, child)| {
80                        self.load_composition(child, config)
81                            .with_context(|| format!("Failed to load selector child {}", i))
82                    })
83                    .collect::<Result<Vec<_>>>()?;
84
85                debug!("Selector node loaded with {} children", child_nodes.len());
86                Ok(Box::new(SelectOrNode::new(child_nodes)))
87            }
88
89            CompositionConfig::Parallel { children, policy, name } => {
90                debug!(
91                    "Loading parallel node{} with policy {:?}",
92                    name.as_ref().map(|n| format!(" '{}'", n)).unwrap_or_default(),
93                    policy
94                );
95
96                let child_nodes = children
97                    .iter()
98                    .enumerate()
99                    .map(|(i, child)| {
100                        self.load_composition(child, config)
101                            .with_context(|| format!("Failed to load parallel child {}", i))
102                    })
103                    .collect::<Result<Vec<_>>>()?;
104
105                debug!("Parallel node loaded with {} children", child_nodes.len());
106                Ok(Box::new(ParallelNode::new(child_nodes, policy.clone().into())))
107            }
108
109            CompositionConfig::Watch { children, policy, name } => {
110                debug!(
111                    "Loading watch node{} with policy {:?}",
112                    name.as_ref().map(|n| format!(" '{}'", n)).unwrap_or_default(),
113                    policy
114                );
115
116                let child_nodes = children
117                    .iter()
118                    .enumerate()
119                    .map(|(i, child)| {
120                        self.load_composition(child, config)
121                            .with_context(|| format!("Failed to load watch child {}", i))
122                    })
123                    .collect::<Result<Vec<_>>>()?;
124
125                debug!("Watch node loaded with {} children", child_nodes.len());
126                Ok(Box::new(WatchNode::new(child_nodes, policy.clone().into())))
127            }
128
129            CompositionConfig::Subsumption { layers, name } => {
130                debug!(
131                    "Loading subsumption node{} with {} layers",
132                    name.as_ref().map(|n| format!(" '{}'", n)).unwrap_or_default(),
133                    layers.len()
134                );
135
136                let mut node = SubsumptionNode::default();
137                for (i, layer) in layers.iter().enumerate() {
138                    let behavior = self
139                        .load_composition(&layer.node, config)
140                        .with_context(|| format!("Failed to load subsumption layer {}", i))?;
141                    let mut subsumption_layer = crate::composition::SubsumptionLayer::new(layer.priority, behavior);
142                    if let Some(layer_name) = &layer.name {
143                        subsumption_layer = subsumption_layer.with_name(layer_name.clone());
144                    }
145                    node.layers.push(subsumption_layer);
146                }
147                node.layers.sort_by_key(|l| std::cmp::Reverse(l.priority));
148
149                debug!("Subsumption node loaded with {} layers", node.layers.len());
150                Ok(Box::new(node))
151            }
152
153            CompositionConfig::Inverter { child, name } => {
154                debug!(
155                    "Loading inverter node{}",
156                    name.as_ref().map(|n| format!(" '{}'", n)).unwrap_or_default()
157                );
158
159                let child_node = self
160                    .load_composition(child, config)
161                    .with_context(|| "Failed to load inverter child")?;
162                Ok(Box::new(InverterNode::new(child_node)))
163            }
164
165            CompositionConfig::ForceSuccess { child, name } => {
166                debug!(
167                    "Loading force_success node{}",
168                    name.as_ref().map(|n| format!(" '{}'", n)).unwrap_or_default()
169                );
170
171                let child_node = self
172                    .load_composition(child, config)
173                    .with_context(|| "Failed to load force_success child")?;
174                Ok(Box::new(ForceSuccessNode::new(child_node)))
175            }
176
177            CompositionConfig::ForceFailure { child, name } => {
178                debug!(
179                    "Loading force_failure node{}",
180                    name.as_ref().map(|n| format!(" '{}'", n)).unwrap_or_default()
181                );
182
183                let child_node = self
184                    .load_composition(child, config)
185                    .with_context(|| "Failed to load force_failure child")?;
186                Ok(Box::new(ForceFailureNode::new(child_node)))
187            }
188
189            CompositionConfig::Repeat { child, count, name } => {
190                debug!(
191                    "Loading repeat node{} (count: {:?})",
192                    name.as_ref().map(|n| format!(" '{}'", n)).unwrap_or_default(),
193                    count
194                );
195
196                let child_node = self
197                    .load_composition(child, config)
198                    .with_context(|| "Failed to load repeat child")?;
199                Ok(Box::new(RepeatNode::new(child_node, *count)))
200            }
201
202            CompositionConfig::Retry {
203                child,
204                max_attempts,
205                name,
206            } => {
207                debug!(
208                    "Loading retry node{} (max_attempts: {})",
209                    name.as_ref().map(|n| format!(" '{}'", n)).unwrap_or_default(),
210                    max_attempts
211                );
212
213                let child_node = self
214                    .load_composition(child, config)
215                    .with_context(|| "Failed to load retry child")?;
216                Ok(Box::new(RetryNode::new(child_node, *max_attempts)))
217            }
218
219            CompositionConfig::Timeout {
220                child,
221                timeout_secs,
222                name,
223            } => {
224                debug!(
225                    "Loading timeout node{} (timeout_secs: {})",
226                    name.as_ref().map(|n| format!(" '{}'", n)).unwrap_or_default(),
227                    timeout_secs
228                );
229
230                let child_node = self
231                    .load_composition(child, config)
232                    .with_context(|| "Failed to load timeout child")?;
233                Ok(Box::new(TimeoutNode::new(
234                    child_node,
235                    std::time::Duration::from_secs_f64(timeout_secs.max(0.0)),
236                )))
237            }
238
239            CompositionConfig::Node {
240                node,
241                config_ref,
242                config: inline_config,
243                name,
244            } => {
245                debug!(
246                    "Loading leaf node '{}' (type: {}){}",
247                    name.as_ref().unwrap_or(node),
248                    node,
249                    config_ref
250                        .as_ref()
251                        .map(|r| format!(" with config_ref '{}'", r))
252                        .unwrap_or_default()
253                );
254
255                let node_config = if let Some(inline) = inline_config {
256                    debug!("Using inline config for node '{}'", node);
257                    inline.clone()
258                } else if let Some(ref_key) = config_ref {
259                    config
260                        .get_config(ref_key)
261                        .ok_or_else(|| {
262                            anyhow!(
263                                "Config reference '{}' not found in configs map for node '{}'",
264                                ref_key,
265                                node
266                            )
267                        })?
268                        .clone()
269                } else {
270                    debug!("No config provided for node '{}'", node);
271                    Value::Null
272                };
273
274                self.registry
275                    .create(node, node_config)
276                    .with_context(|| format!("Failed to create node '{}'", node))
277            }
278        }
279    }
280
281    /// Get a reference to the underlying node registry.
282    pub fn registry(&self) -> &NodeRegistry {
283        &self.registry
284    }
285}
286
287impl std::fmt::Debug for BehaviorLoader {
288    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
289        f.debug_struct("BehaviorLoader")
290            .field("registry", &self.registry)
291            .finish()
292    }
293}