use crate::composition::{ParallelNode, SelectOrNode, SequenceNode};
use crate::config::{BehaviorConfig, CompositionConfig};
use crate::registry::NodeRegistry;
use crate::BoxedBehavior;
use anyhow::{anyhow, Context as AnyhowContext, Result};
use serde_json::Value;
use tracing::{debug, info};
#[derive(Clone)]
pub struct BehaviorLoader {
registry: NodeRegistry,
}
impl BehaviorLoader {
pub fn new(registry: NodeRegistry) -> Self {
Self { registry }
}
pub fn load(&self, config: &BehaviorConfig) -> Result<BoxedBehavior> {
info!("Loading behavior tree: {}", config.name);
if let Some(desc) = &config.description {
debug!("Description: {}", desc);
}
self.load_composition(&config.root, config)
.with_context(|| format!("Failed to load behavior tree '{}'", config.name))
}
pub fn load_from_json(&self, json: &str) -> Result<BoxedBehavior> {
let config = BehaviorConfig::from_json(json)?;
self.load(&config)
}
pub fn load_from_file(&self, path: impl AsRef<std::path::Path>) -> Result<BoxedBehavior> {
let config = BehaviorConfig::from_file(path)?;
self.load(&config)
}
fn load_composition(&self, composition: &CompositionConfig, config: &BehaviorConfig) -> Result<BoxedBehavior> {
match composition {
CompositionConfig::Sequence { children, name } => {
debug!(
"Loading sequence node{}",
name.as_ref().map(|n| format!(" '{}'", n)).unwrap_or_default()
);
let child_nodes = children
.iter()
.enumerate()
.map(|(i, child)| {
self.load_composition(child, config)
.with_context(|| format!("Failed to load sequence child {}", i))
})
.collect::<Result<Vec<_>>>()?;
debug!("Sequence node loaded with {} children", child_nodes.len());
Ok(Box::new(SequenceNode::new(child_nodes)))
}
CompositionConfig::Selector { children, name } => {
debug!(
"Loading selector node{}",
name.as_ref().map(|n| format!(" '{}'", n)).unwrap_or_default()
);
let child_nodes = children
.iter()
.enumerate()
.map(|(i, child)| {
self.load_composition(child, config)
.with_context(|| format!("Failed to load selector child {}", i))
})
.collect::<Result<Vec<_>>>()?;
debug!("Selector node loaded with {} children", child_nodes.len());
Ok(Box::new(SelectOrNode::new(child_nodes)))
}
CompositionConfig::Parallel { children, policy, name } => {
debug!(
"Loading parallel node{} with policy {:?}",
name.as_ref().map(|n| format!(" '{}'", n)).unwrap_or_default(),
policy
);
let child_nodes = children
.iter()
.enumerate()
.map(|(i, child)| {
self.load_composition(child, config)
.with_context(|| format!("Failed to load parallel child {}", i))
})
.collect::<Result<Vec<_>>>()?;
debug!("Parallel node loaded with {} children", child_nodes.len());
Ok(Box::new(ParallelNode::new(child_nodes, policy.clone().into())))
}
CompositionConfig::Node {
node,
config_ref,
config: inline_config,
name,
} => {
debug!(
"Loading leaf node '{}' (type: {}){}",
name.as_ref().unwrap_or(node),
node,
config_ref
.as_ref()
.map(|r| format!(" with config_ref '{}'", r))
.unwrap_or_default()
);
let node_config = if let Some(inline) = inline_config {
debug!("Using inline config for node '{}'", node);
inline.clone()
} else if let Some(ref_key) = config_ref {
config
.get_config(ref_key)
.ok_or_else(|| {
anyhow!(
"Config reference '{}' not found in configs map for node '{}'",
ref_key,
node
)
})?
.clone()
} else {
debug!("No config provided for node '{}'", node);
Value::Null
};
self.registry
.create(node, node_config)
.with_context(|| format!("Failed to create node '{}'", node))
}
}
}
pub fn registry(&self) -> &NodeRegistry {
&self.registry
}
}
impl std::fmt::Debug for BehaviorLoader {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("BehaviorLoader")
.field("registry", &self.registry)
.finish()
}
}