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::{ParallelNode, SelectOrNode, SequenceNode};
7use crate::config::{BehaviorConfig, CompositionConfig};
8use crate::registry::NodeRegistry;
9use crate::BoxedBehavior;
10use anyhow::{anyhow, Context as AnyhowContext, Result};
11use serde_json::Value;
12use tracing::{debug, info};
13
14/// Loader for creating behavior trees from JSON configuration.
15///
16/// The `BehaviorLoader` takes a `BehaviorConfig` (which can be loaded from JSON)
17/// and uses a `NodeRegistry` to instantiate the complete behavior tree.
18///
19/// # Features
20///
21/// - Recursive composition parsing (sequence/selector/parallel nodes)
22/// - Config reference resolution from the config map
23/// - Inline config support
24/// - Clear error messages for missing node types
25///
26/// # Example
27///
28/// ```rust
29/// use mecha10_behavior_runtime::prelude::*;
30/// use mecha10_behavior_runtime::{NodeRegistry, BehaviorLoader};
31/// use serde_json::json;
32///
33/// # #[derive(Debug)]
34/// # struct MyBehavior;
35/// # #[async_trait::async_trait]
36/// # impl BehaviorNode for MyBehavior {
37/// #     async fn tick(&mut self, _ctx: &Context) -> anyhow::Result<NodeStatus> {
38/// #         Ok(NodeStatus::Success)
39/// #     }
40/// # }
41///
42/// // Create and populate registry
43/// let mut registry = NodeRegistry::new();
44/// registry.register("my_behavior", |_| Ok(Box::new(MyBehavior)));
45///
46/// // Load behavior tree from JSON
47/// let json = r#"{
48///     "name": "test_behavior",
49///     "root": {
50///         "type": "sequence",
51///         "children": [
52///             {
53///                 "type": "node",
54///                 "node": "my_behavior"
55///             }
56///         ]
57///     }
58/// }"#;
59///
60/// let config = BehaviorConfig::from_json(json).unwrap();
61/// let loader = BehaviorLoader::new(registry);
62/// let behavior = loader.load(&config).unwrap();
63/// ```
64#[derive(Clone)]
65pub struct BehaviorLoader {
66    registry: NodeRegistry,
67}
68
69impl BehaviorLoader {
70    /// Create a new behavior loader with the given node registry.
71    ///
72    /// # Arguments
73    ///
74    /// * `registry` - The node registry containing registered node types
75    ///
76    /// # Example
77    ///
78    /// ```rust
79    /// use mecha10_behavior_runtime::{NodeRegistry, BehaviorLoader};
80    ///
81    /// let registry = NodeRegistry::new();
82    /// let loader = BehaviorLoader::new(registry);
83    /// ```
84    pub fn new(registry: NodeRegistry) -> Self {
85        Self { registry }
86    }
87
88    /// Load a behavior tree from a configuration.
89    ///
90    /// This is the main entry point for loading behavior trees. It takes a
91    /// `BehaviorConfig` (typically loaded from JSON) and recursively instantiates
92    /// all nodes in the tree.
93    ///
94    /// # Arguments
95    ///
96    /// * `config` - The behavior configuration to load
97    ///
98    /// # Returns
99    ///
100    /// A boxed behavior node representing the root of the tree, or an error if:
101    /// - A referenced node type is not registered
102    /// - A config reference is invalid
103    /// - A factory function fails
104    ///
105    /// # Example
106    ///
107    /// ```rust
108    /// use mecha10_behavior_runtime::{BehaviorConfig, NodeRegistry, BehaviorLoader};
109    ///
110    /// # let mut registry = NodeRegistry::new();
111    /// # registry.register("test", |_| Ok(Box::new(mecha10_behavior_runtime::SequenceNode::new(vec![]))));
112    /// let loader = BehaviorLoader::new(registry);
113    ///
114    /// let json = r#"{
115    ///     "name": "test",
116    ///     "root": {
117    ///         "type": "node",
118    ///         "node": "test"
119    ///     }
120    /// }"#;
121    ///
122    /// let config = BehaviorConfig::from_json(json).unwrap();
123    /// let behavior = loader.load(&config).unwrap();
124    /// ```
125    pub fn load(&self, config: &BehaviorConfig) -> Result<BoxedBehavior> {
126        info!("Loading behavior tree: {}", config.name);
127        if let Some(desc) = &config.description {
128            debug!("Description: {}", desc);
129        }
130
131        self.load_composition(&config.root, config)
132            .with_context(|| format!("Failed to load behavior tree '{}'", config.name))
133    }
134
135    /// Load a behavior tree from a JSON string.
136    ///
137    /// This is a convenience method that combines parsing and loading.
138    ///
139    /// # Arguments
140    ///
141    /// * `json` - JSON string containing a BehaviorConfig
142    ///
143    /// # Example
144    ///
145    /// ```rust
146    /// use mecha10_behavior_runtime::{NodeRegistry, BehaviorLoader};
147    ///
148    /// # let mut registry = NodeRegistry::new();
149    /// # registry.register("test", |_| Ok(Box::new(mecha10_behavior_runtime::SequenceNode::new(vec![]))));
150    /// let loader = BehaviorLoader::new(registry);
151    ///
152    /// let json = r#"{
153    ///     "name": "test",
154    ///     "root": {
155    ///         "type": "node",
156    ///         "node": "test"
157    ///     }
158    /// }"#;
159    ///
160    /// let behavior = loader.load_from_json(json).unwrap();
161    /// ```
162    pub fn load_from_json(&self, json: &str) -> Result<BoxedBehavior> {
163        let config = BehaviorConfig::from_json(json)?;
164        self.load(&config)
165    }
166
167    /// Load a behavior tree from a JSON file.
168    ///
169    /// This is a convenience method that reads a file and loads the behavior tree.
170    ///
171    /// # Arguments
172    ///
173    /// * `path` - Path to the JSON file
174    ///
175    /// # Example
176    ///
177    /// ```rust,no_run
178    /// use mecha10_behavior_runtime::{NodeRegistry, BehaviorLoader};
179    ///
180    /// # let registry = NodeRegistry::new();
181    /// let loader = BehaviorLoader::new(registry);
182    /// let behavior = loader.load_from_file("behaviors/patrol.json").unwrap();
183    /// ```
184    pub fn load_from_file(&self, path: impl AsRef<std::path::Path>) -> Result<BoxedBehavior> {
185        let config = BehaviorConfig::from_file(path)?;
186        self.load(&config)
187    }
188
189    /// Recursively load a composition node and its children.
190    fn load_composition(&self, composition: &CompositionConfig, config: &BehaviorConfig) -> Result<BoxedBehavior> {
191        match composition {
192            CompositionConfig::Sequence { children, name } => {
193                debug!(
194                    "Loading sequence node{}",
195                    name.as_ref().map(|n| format!(" '{}'", n)).unwrap_or_default()
196                );
197
198                let child_nodes = children
199                    .iter()
200                    .enumerate()
201                    .map(|(i, child)| {
202                        self.load_composition(child, config)
203                            .with_context(|| format!("Failed to load sequence child {}", i))
204                    })
205                    .collect::<Result<Vec<_>>>()?;
206
207                debug!("Sequence node loaded with {} children", child_nodes.len());
208                Ok(Box::new(SequenceNode::new(child_nodes)))
209            }
210
211            CompositionConfig::Selector { children, name } => {
212                debug!(
213                    "Loading selector node{}",
214                    name.as_ref().map(|n| format!(" '{}'", n)).unwrap_or_default()
215                );
216
217                let child_nodes = children
218                    .iter()
219                    .enumerate()
220                    .map(|(i, child)| {
221                        self.load_composition(child, config)
222                            .with_context(|| format!("Failed to load selector child {}", i))
223                    })
224                    .collect::<Result<Vec<_>>>()?;
225
226                debug!("Selector node loaded with {} children", child_nodes.len());
227                Ok(Box::new(SelectOrNode::new(child_nodes)))
228            }
229
230            CompositionConfig::Parallel { children, policy, name } => {
231                debug!(
232                    "Loading parallel node{} with policy {:?}",
233                    name.as_ref().map(|n| format!(" '{}'", n)).unwrap_or_default(),
234                    policy
235                );
236
237                let child_nodes = children
238                    .iter()
239                    .enumerate()
240                    .map(|(i, child)| {
241                        self.load_composition(child, config)
242                            .with_context(|| format!("Failed to load parallel child {}", i))
243                    })
244                    .collect::<Result<Vec<_>>>()?;
245
246                debug!("Parallel node loaded with {} children", child_nodes.len());
247                Ok(Box::new(ParallelNode::new(child_nodes, policy.clone().into())))
248            }
249
250            CompositionConfig::Node {
251                node,
252                config_ref,
253                config: inline_config,
254                name,
255            } => {
256                debug!(
257                    "Loading leaf node '{}' (type: {}){}",
258                    name.as_ref().unwrap_or(node),
259                    node,
260                    config_ref
261                        .as_ref()
262                        .map(|r| format!(" with config_ref '{}'", r))
263                        .unwrap_or_default()
264                );
265
266                // Resolve configuration (prefer inline over config_ref)
267                let node_config = if let Some(inline) = inline_config {
268                    debug!("Using inline config for node '{}'", node);
269                    inline.clone()
270                } else if let Some(ref_key) = config_ref {
271                    config
272                        .get_config(ref_key)
273                        .ok_or_else(|| {
274                            anyhow!(
275                                "Config reference '{}' not found in configs map for node '{}'",
276                                ref_key,
277                                node
278                            )
279                        })?
280                        .clone()
281                } else {
282                    debug!("No config provided for node '{}'", node);
283                    Value::Null
284                };
285
286                // Create the node using the registry
287                self.registry
288                    .create(node, node_config)
289                    .with_context(|| format!("Failed to create node '{}'", node))
290            }
291        }
292    }
293
294    /// Get a reference to the underlying node registry.
295    ///
296    /// This can be useful for inspecting registered types or adding new types
297    /// after the loader has been created.
298    pub fn registry(&self) -> &NodeRegistry {
299        &self.registry
300    }
301}
302
303impl std::fmt::Debug for BehaviorLoader {
304    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
305        f.debug_struct("BehaviorLoader")
306            .field("registry", &self.registry)
307            .finish()
308    }
309}