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
4//! `BehaviorConfig` definitions using a `NodeRegistry`.
5
6use crate::composition::{
7    ForceFailureNode, ForceSuccessNode, InverterNode, ParallelNode, RepeatNode, RetryNode, SelectOrNode, SequenceNode,
8    SubsumptionNode, TimeoutNode, WatchNode,
9};
10use crate::config::{BehaviorConfig, CompositionConfig};
11use crate::registry::NodeRegistry;
12use crate::BoxedBehavior;
13use anyhow::{anyhow, Context as AnyhowContext, Result};
14use serde_json::Value;
15use tracing::{debug, info};
16
17#[derive(Clone)]
18pub struct BehaviorLoader {
19    registry: NodeRegistry,
20}
21
22impl BehaviorLoader {
23    /// Create a new behavior loader with the given node registry.
24    ///
25    /// # Arguments
26    ///
27    /// * `registry` - The node registry containing registered node types
28    ///
29    /// # Example
30    ///
31    /// ```rust
32    /// use mecha10_behavior_runtime::{NodeRegistry, BehaviorLoader};
33    ///
34    /// let registry = NodeRegistry::new();
35    /// let loader = BehaviorLoader::new(registry);
36    /// ```
37    pub fn new(registry: NodeRegistry) -> Self {
38        Self { registry }
39    }
40
41    /// Load a behavior tree from a configuration.
42    ///
43    /// This is the main entry point for loading behavior trees. It takes a
44    /// `BehaviorConfig` (typically loaded from JSON) and recursively instantiates
45    /// all nodes in the tree.
46    ///
47    /// # Arguments
48    ///
49    /// * `config` - The behavior configuration to load
50    ///
51    /// # Returns
52    ///
53    /// A boxed behavior node representing the root of the tree, or an error if:
54    /// - A referenced node type is not registered
55    /// - A config reference is invalid
56    /// - A factory function fails
57    ///
58    /// # Example
59    ///
60    /// ```rust
61    /// use mecha10_behavior_runtime::{BehaviorConfig, NodeRegistry, BehaviorLoader};
62    ///
63    /// # let mut registry = NodeRegistry::new();
64    /// # registry.register("test", |_| Ok(Box::new(mecha10_behavior_runtime::SequenceNode::new(vec![]))));
65    /// let loader = BehaviorLoader::new(registry);
66    ///
67    /// let json = r#"{
68    ///     "name": "test",
69    ///     "root": {
70    ///         "type": "node",
71    ///         "node": "test"
72    ///     }
73    /// }"#;
74    ///
75    /// let config = BehaviorConfig::from_json(json).unwrap();
76    /// let behavior = loader.load(&config).unwrap();
77    /// ```
78    pub fn load(&self, config: &BehaviorConfig) -> Result<BoxedBehavior> {
79        info!("Loading behavior tree: {}", config.name);
80        if let Some(desc) = &config.description {
81            debug!("Description: {}", desc);
82        }
83
84        self.load_composition(&config.root, config)
85            .with_context(|| format!("Failed to load behavior tree '{}'", config.name))
86    }
87
88    /// Load a behavior tree from a JSON string.
89    ///
90    /// This is a convenience method that combines parsing and loading.
91    ///
92    /// # Arguments
93    ///
94    /// * `json` - JSON string containing a BehaviorConfig
95    ///
96    /// # Example
97    ///
98    /// ```rust
99    /// use mecha10_behavior_runtime::{NodeRegistry, BehaviorLoader};
100    ///
101    /// # let mut registry = NodeRegistry::new();
102    /// # registry.register("test", |_| Ok(Box::new(mecha10_behavior_runtime::SequenceNode::new(vec![]))));
103    /// let loader = BehaviorLoader::new(registry);
104    ///
105    /// let json = r#"{
106    ///     "name": "test",
107    ///     "root": {
108    ///         "type": "node",
109    ///         "node": "test"
110    ///     }
111    /// }"#;
112    ///
113    /// let behavior = loader.load_from_json(json).unwrap();
114    /// ```
115    pub fn load_from_json(&self, json: &str) -> Result<BoxedBehavior> {
116        self.load(&BehaviorConfig::from_json(json)?)
117    }
118
119    /// Load a behavior tree from a JSON file.
120    ///
121    /// This is a convenience method that reads a file and loads the behavior tree.
122    ///
123    /// # Arguments
124    ///
125    /// * `path` - Path to the JSON file
126    ///
127    /// # Example
128    ///
129    /// ```rust,no_run
130    /// use mecha10_behavior_runtime::{NodeRegistry, BehaviorLoader};
131    ///
132    /// # let registry = NodeRegistry::new();
133    /// let loader = BehaviorLoader::new(registry);
134    /// let behavior = loader.load_from_file("configs/behaviors/@mecha10/patrol/config.json").unwrap();
135    /// ```
136    pub fn load_from_file(&self, path: impl AsRef<std::path::Path>) -> Result<BoxedBehavior> {
137        self.load(&BehaviorConfig::from_file(path)?)
138    }
139
140    /// Recursively load a composition node and its children.
141    fn load_composition(&self, composition: &CompositionConfig, config: &BehaviorConfig) -> Result<BoxedBehavior> {
142        match composition {
143            CompositionConfig::Sequence { children, name } => {
144                debug!(
145                    "Loading sequence node{}",
146                    name.as_ref().map(|n| format!(" '{}'", n)).unwrap_or_default()
147                );
148
149                let child_nodes = children
150                    .iter()
151                    .enumerate()
152                    .map(|(i, child)| {
153                        self.load_composition(child, config)
154                            .with_context(|| format!("Failed to load sequence child {}", i))
155                    })
156                    .collect::<Result<Vec<_>>>()?;
157
158                debug!("Sequence node loaded with {} children", child_nodes.len());
159                Ok(Box::new(SequenceNode::new(child_nodes)))
160            }
161
162            CompositionConfig::Selector { children, name } => {
163                debug!(
164                    "Loading selector node{}",
165                    name.as_ref().map(|n| format!(" '{}'", n)).unwrap_or_default()
166                );
167
168                let child_nodes = children
169                    .iter()
170                    .enumerate()
171                    .map(|(i, child)| {
172                        self.load_composition(child, config)
173                            .with_context(|| format!("Failed to load selector child {}", i))
174                    })
175                    .collect::<Result<Vec<_>>>()?;
176
177                debug!("Selector node loaded with {} children", child_nodes.len());
178                Ok(Box::new(SelectOrNode::new(child_nodes)))
179            }
180
181            CompositionConfig::Parallel { children, policy, name } => {
182                debug!(
183                    "Loading parallel node{} with policy {:?}",
184                    name.as_ref().map(|n| format!(" '{}'", n)).unwrap_or_default(),
185                    policy
186                );
187
188                let child_nodes = children
189                    .iter()
190                    .enumerate()
191                    .map(|(i, child)| {
192                        self.load_composition(child, config)
193                            .with_context(|| format!("Failed to load parallel child {}", i))
194                    })
195                    .collect::<Result<Vec<_>>>()?;
196
197                debug!("Parallel node loaded with {} children", child_nodes.len());
198                Ok(Box::new(ParallelNode::new(child_nodes, policy.clone().into())))
199            }
200
201            CompositionConfig::Watch { children, policy, name } => {
202                debug!(
203                    "Loading watch node{} with policy {:?}",
204                    name.as_ref().map(|n| format!(" '{}'", n)).unwrap_or_default(),
205                    policy
206                );
207
208                let child_nodes = children
209                    .iter()
210                    .enumerate()
211                    .map(|(i, child)| {
212                        self.load_composition(child, config)
213                            .with_context(|| format!("Failed to load watch child {}", i))
214                    })
215                    .collect::<Result<Vec<_>>>()?;
216
217                debug!("Watch node loaded with {} children", child_nodes.len());
218                Ok(Box::new(WatchNode::new(child_nodes, policy.clone().into())))
219            }
220
221            CompositionConfig::Subsumption { layers, name } => {
222                debug!(
223                    "Loading subsumption node{} with {} layers",
224                    name.as_ref().map(|n| format!(" '{}'", n)).unwrap_or_default(),
225                    layers.len()
226                );
227
228                let mut node = SubsumptionNode::default();
229                for (i, layer) in layers.iter().enumerate() {
230                    let behavior = self
231                        .load_composition(&layer.node, config)
232                        .with_context(|| format!("Failed to load subsumption layer {}", i))?;
233                    let mut subsumption_layer = crate::composition::SubsumptionLayer::new(layer.priority, behavior);
234                    if let Some(layer_name) = &layer.name {
235                        subsumption_layer = subsumption_layer.with_name(layer_name.clone());
236                    }
237                    node.layers.push(subsumption_layer);
238                }
239                node.layers.sort_by_key(|l| std::cmp::Reverse(l.priority));
240
241                debug!("Subsumption node loaded with {} layers", node.layers.len());
242                Ok(Box::new(node))
243            }
244
245            CompositionConfig::Inverter { child, name } => {
246                debug!(
247                    "Loading inverter node{}",
248                    name.as_ref().map(|n| format!(" '{}'", n)).unwrap_or_default()
249                );
250
251                let child_node = self
252                    .load_composition(child, config)
253                    .with_context(|| "Failed to load inverter child")?;
254                Ok(Box::new(InverterNode::new(child_node)))
255            }
256
257            CompositionConfig::ForceSuccess { child, name } => {
258                debug!(
259                    "Loading force_success node{}",
260                    name.as_ref().map(|n| format!(" '{}'", n)).unwrap_or_default()
261                );
262
263                let child_node = self
264                    .load_composition(child, config)
265                    .with_context(|| "Failed to load force_success child")?;
266                Ok(Box::new(ForceSuccessNode::new(child_node)))
267            }
268
269            CompositionConfig::ForceFailure { child, name } => {
270                debug!(
271                    "Loading force_failure node{}",
272                    name.as_ref().map(|n| format!(" '{}'", n)).unwrap_or_default()
273                );
274
275                let child_node = self
276                    .load_composition(child, config)
277                    .with_context(|| "Failed to load force_failure child")?;
278                Ok(Box::new(ForceFailureNode::new(child_node)))
279            }
280
281            CompositionConfig::Repeat { child, count, name } => {
282                debug!(
283                    "Loading repeat node{} (count: {:?})",
284                    name.as_ref().map(|n| format!(" '{}'", n)).unwrap_or_default(),
285                    count
286                );
287
288                let child_node = self
289                    .load_composition(child, config)
290                    .with_context(|| "Failed to load repeat child")?;
291                Ok(Box::new(RepeatNode::new(child_node, *count)))
292            }
293
294            CompositionConfig::Retry {
295                child,
296                max_attempts,
297                name,
298            } => {
299                debug!(
300                    "Loading retry node{} (max_attempts: {})",
301                    name.as_ref().map(|n| format!(" '{}'", n)).unwrap_or_default(),
302                    max_attempts
303                );
304
305                let child_node = self
306                    .load_composition(child, config)
307                    .with_context(|| "Failed to load retry child")?;
308                Ok(Box::new(RetryNode::new(child_node, *max_attempts)))
309            }
310
311            CompositionConfig::Timeout {
312                child,
313                timeout_secs,
314                name,
315            } => {
316                debug!(
317                    "Loading timeout node{} (timeout_secs: {})",
318                    name.as_ref().map(|n| format!(" '{}'", n)).unwrap_or_default(),
319                    timeout_secs
320                );
321
322                let child_node = self
323                    .load_composition(child, config)
324                    .with_context(|| "Failed to load timeout child")?;
325                Ok(Box::new(TimeoutNode::new(
326                    child_node,
327                    std::time::Duration::from_secs_f64(timeout_secs.max(0.0)),
328                )))
329            }
330
331            CompositionConfig::Node {
332                node,
333                config_ref,
334                config: inline_config,
335                name,
336            } => {
337                debug!(
338                    "Loading leaf node '{}' (type: {}){}",
339                    name.as_ref().unwrap_or(node),
340                    node,
341                    config_ref
342                        .as_ref()
343                        .map(|r| format!(" with config_ref '{}'", r))
344                        .unwrap_or_default()
345                );
346
347                let node_config = if let Some(inline) = inline_config {
348                    debug!("Using inline config for node '{}'", node);
349                    inline.clone()
350                } else if let Some(ref_key) = config_ref {
351                    config
352                        .get_config(ref_key)
353                        .ok_or_else(|| {
354                            anyhow!(
355                                "Config reference '{}' not found in configs map for node '{}'",
356                                ref_key,
357                                node
358                            )
359                        })?
360                        .clone()
361                } else {
362                    debug!("No config provided for node '{}'", node);
363                    Value::Null
364                };
365
366                self.registry
367                    .create(node, node_config)
368                    .with_context(|| format!("Failed to create node '{}'", node))
369            }
370        }
371    }
372
373    /// Get a reference to the underlying node registry.
374    ///
375    /// This can be useful for inspecting registered types or adding new types
376    /// after the loader has been created.
377    pub fn registry(&self) -> &NodeRegistry {
378        &self.registry
379    }
380}
381
382impl std::fmt::Debug for BehaviorLoader {
383    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
384        f.debug_struct("BehaviorLoader")
385            .field("registry", &self.registry)
386            .finish()
387    }
388}