mecha10_behavior_runtime/
composition.rs1use crate::{BehaviorNode, BoxedBehavior, NodeStatus};
6use async_trait::async_trait;
7use mecha10_core::Context;
8use tracing::{debug, warn};
9
10#[derive(Debug)]
34pub struct SequenceNode {
35 children: Vec<BoxedBehavior>,
36 current_index: usize,
37}
38
39impl SequenceNode {
40 pub fn new(children: Vec<BoxedBehavior>) -> Self {
42 Self {
43 children,
44 current_index: 0,
45 }
46 }
47
48 pub fn add_child(&mut self, child: BoxedBehavior) {
50 self.children.push(child);
51 }
52
53 pub fn len(&self) -> usize {
55 self.children.len()
56 }
57
58 pub fn is_empty(&self) -> bool {
60 self.children.is_empty()
61 }
62}
63
64#[async_trait]
65impl BehaviorNode for SequenceNode {
66 async fn tick(&mut self, ctx: &Context) -> anyhow::Result<NodeStatus> {
67 if self.children.is_empty() {
68 return Ok(NodeStatus::Success);
69 }
70
71 let total_children = self.children.len();
72 while self.current_index < total_children {
73 debug!(
74 "Sequence: ticking child {} of {}",
75 self.current_index + 1,
76 total_children
77 );
78
79 let child = &mut self.children[self.current_index];
80 match child.tick(ctx).await? {
81 NodeStatus::Success => {
82 self.current_index += 1;
83 }
84 NodeStatus::Failure => {
85 debug!("Sequence: child {} failed", self.current_index + 1);
86 return Ok(NodeStatus::Failure);
87 }
88 NodeStatus::Running => {
89 return Ok(NodeStatus::Running);
90 }
91 }
92 }
93
94 debug!("Sequence: all children succeeded");
95 Ok(NodeStatus::Success)
96 }
97
98 crate::reset_index_field!(current_index);
99
100 fn name(&self) -> &str {
101 "sequence"
102 }
103
104 fn children_mut(&mut self) -> Vec<&mut dyn BehaviorNode> {
105 crate::boxed_children_mut(self.children.iter_mut())
106 }
107}
108
109#[derive(Debug)]
133pub struct SelectOrNode {
134 children: Vec<BoxedBehavior>,
135 current_index: usize,
136}
137
138impl SelectOrNode {
139 pub fn new(children: Vec<BoxedBehavior>) -> Self {
141 Self {
142 children,
143 current_index: 0,
144 }
145 }
146
147 pub fn add_child(&mut self, child: BoxedBehavior) {
149 self.children.push(child);
150 }
151
152 pub fn len(&self) -> usize {
154 self.children.len()
155 }
156
157 pub fn is_empty(&self) -> bool {
159 self.children.is_empty()
160 }
161}
162
163#[async_trait]
164impl BehaviorNode for SelectOrNode {
165 async fn tick(&mut self, ctx: &Context) -> anyhow::Result<NodeStatus> {
166 if self.children.is_empty() {
167 return Ok(NodeStatus::Failure);
168 }
169
170 let total_children = self.children.len();
171 while self.current_index < total_children {
172 debug!(
173 "Selector: ticking child {} of {}",
174 self.current_index + 1,
175 total_children
176 );
177
178 let child = &mut self.children[self.current_index];
179 match child.tick(ctx).await? {
180 NodeStatus::Success => {
181 debug!("Selector: child {} succeeded", self.current_index + 1);
182 return Ok(NodeStatus::Success);
183 }
184 NodeStatus::Failure => {
185 warn!("Selector: child {} failed, trying next", self.current_index + 1);
186 self.current_index += 1;
187 }
188 NodeStatus::Running => {
189 return Ok(NodeStatus::Running);
190 }
191 }
192 }
193
194 debug!("Selector: all children failed");
195 Ok(NodeStatus::Failure)
196 }
197
198 crate::reset_index_field!(current_index);
199
200 fn name(&self) -> &str {
201 "selector"
202 }
203
204 fn children_mut(&mut self) -> Vec<&mut dyn BehaviorNode> {
205 crate::boxed_children_mut(self.children.iter_mut())
206 }
207}
208
209#[derive(Debug, Clone, Copy, PartialEq, Eq)]
211pub enum ParallelPolicy {
212 RequireAll,
214
215 RequireOne,
217
218 RequireN(usize),
220}
221
222#[derive(Debug)]
249pub struct ParallelNode {
250 children: Vec<BoxedBehavior>,
251 policy: ParallelPolicy,
252 child_statuses: Vec<NodeStatus>,
253}
254
255impl ParallelNode {
256 pub fn new(children: Vec<BoxedBehavior>, policy: ParallelPolicy) -> Self {
258 let child_statuses = vec![NodeStatus::Running; children.len()];
259 Self {
260 children,
261 policy,
262 child_statuses,
263 }
264 }
265
266 pub fn add_child(&mut self, child: BoxedBehavior) {
268 self.children.push(child);
269 self.child_statuses.push(NodeStatus::Running);
270 }
271
272 pub fn len(&self) -> usize {
274 self.children.len()
275 }
276
277 pub fn is_empty(&self) -> bool {
279 self.children.is_empty()
280 }
281}
282
283#[async_trait]
284impl BehaviorNode for ParallelNode {
285 async fn tick(&mut self, ctx: &Context) -> anyhow::Result<NodeStatus> {
286 if self.children.is_empty() {
287 return Ok(NodeStatus::Success);
288 }
289
290 for (i, child) in self.children.iter_mut().enumerate() {
291 if self.child_statuses[i].is_running() {
292 let status = child.tick(ctx).await?;
293 self.child_statuses[i] = status;
294 debug!("Parallel: child {} status: {}", i + 1, status);
295 }
296 }
297
298 let success_count = self.child_statuses.iter().filter(|s| s.is_success()).count();
299 let failure_count = self.child_statuses.iter().filter(|s| s.is_failure()).count();
300 let _running_count = self.child_statuses.iter().filter(|s| s.is_running()).count();
301
302 match self.policy {
303 ParallelPolicy::RequireAll => {
304 if failure_count > 0 {
305 debug!("Parallel (RequireAll): at least one child failed");
306 Ok(NodeStatus::Failure)
307 } else if success_count == self.children.len() {
308 debug!("Parallel (RequireAll): all children succeeded");
309 Ok(NodeStatus::Success)
310 } else {
311 Ok(NodeStatus::Running)
312 }
313 }
314 ParallelPolicy::RequireOne => {
315 if success_count > 0 {
316 debug!("Parallel (RequireOne): at least one child succeeded");
317 Ok(NodeStatus::Success)
318 } else if failure_count == self.children.len() {
319 debug!("Parallel (RequireOne): all children failed");
320 Ok(NodeStatus::Failure)
321 } else {
322 Ok(NodeStatus::Running)
323 }
324 }
325 ParallelPolicy::RequireN(n) => {
326 if success_count >= n {
327 debug!("Parallel (RequireN({})): {} children succeeded", n, success_count);
328 Ok(NodeStatus::Success)
329 } else if (self.children.len() - failure_count) < n {
330 debug!("Parallel (RequireN({})): not enough children can succeed", n);
331 Ok(NodeStatus::Failure)
332 } else {
333 Ok(NodeStatus::Running)
334 }
335 }
336 }
337 }
338
339 crate::reset_child_statuses_field!(child_statuses);
340
341 fn name(&self) -> &str {
342 "parallel"
343 }
344
345 fn children_mut(&mut self) -> Vec<&mut dyn BehaviorNode> {
346 crate::boxed_children_mut(self.children.iter_mut())
347 }
348}
349
350#[cfg(test)]
351mod tests {
352 use super::*;
353
354 #[derive(Debug, Default)]
357 struct DummyBehavior;
358
359 #[async_trait]
360 impl BehaviorNode for DummyBehavior {
361 async fn tick(&mut self, _ctx: &Context) -> anyhow::Result<NodeStatus> {
362 unimplemented!("not exercised by these tests")
363 }
364 }
365
366 #[test]
367 fn sequence_children_mut_exposes_every_child() {
368 let mut node = SequenceNode::new(vec![Box::new(DummyBehavior), Box::new(DummyBehavior)]);
369 assert_eq!(node.children_mut().len(), 2);
370 }
371
372 #[tokio::test]
373 async fn sequence_reset_rewinds_current_index() {
374 let mut node = SequenceNode::new(vec![Box::new(DummyBehavior)]);
375 node.current_index = 1;
376
377 node.reset().await.unwrap();
378
379 assert_eq!(node.current_index, 0);
380 }
381
382 #[test]
383 fn selector_children_mut_exposes_every_child() {
384 let mut node = SelectOrNode::new(vec![Box::new(DummyBehavior), Box::new(DummyBehavior)]);
385 assert_eq!(node.children_mut().len(), 2);
386 }
387
388 #[tokio::test]
389 async fn selector_reset_rewinds_current_index() {
390 let mut node = SelectOrNode::new(vec![Box::new(DummyBehavior)]);
391 node.current_index = 1;
392
393 node.reset().await.unwrap();
394
395 assert_eq!(node.current_index, 0);
396 }
397
398 #[test]
399 fn parallel_children_mut_exposes_every_child() {
400 let mut node = ParallelNode::new(
401 vec![Box::new(DummyBehavior), Box::new(DummyBehavior)],
402 ParallelPolicy::RequireAll,
403 );
404 assert_eq!(node.children_mut().len(), 2);
405 }
406
407 #[tokio::test]
408 async fn parallel_reset_clears_child_statuses_back_to_running() {
409 let mut node = ParallelNode::new(vec![Box::new(DummyBehavior)], ParallelPolicy::RequireAll);
410 node.child_statuses[0] = NodeStatus::Success;
411
412 node.reset().await.unwrap();
413
414 assert_eq!(node.child_statuses[0], NodeStatus::Running);
415 }
416}