mecha10_behavior_runtime/
loader.rs1use 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 pub fn new(registry: NodeRegistry) -> Self {
38 Self { registry }
39 }
40
41 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 pub fn load_from_json(&self, json: &str) -> Result<BoxedBehavior> {
116 self.load(&BehaviorConfig::from_json(json)?)
117 }
118
119 pub fn load_from_file(&self, path: impl AsRef<std::path::Path>) -> Result<BoxedBehavior> {
137 self.load(&BehaviorConfig::from_file(path)?)
138 }
139
140 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 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}