mecha10_behavior_runtime/
loader.rs1use 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 pub fn new(registry: NodeRegistry) -> Self {
24 Self { registry }
25 }
26
27 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 pub fn load_from_json(&self, json: &str) -> Result<BoxedBehavior> {
40 self.load(&BehaviorConfig::from_json(json)?)
41 }
42
43 pub fn load_from_file(&self, path: impl AsRef<std::path::Path>) -> Result<BoxedBehavior> {
45 self.load(&BehaviorConfig::from_file(path)?)
46 }
47
48 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 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}