Skip to main content

beetry_node/control/
fallback.rs

1use beetry_core::{Node, NonEmptyNodes, TickStatus};
2
3use crate::{Indices, control::RunningNodesAborter};
4
5/// Control node that tries children in order until one succeeds.
6///
7/// `Fallback` ticks children in order on each tick:
8///
9/// - returns [`TickStatus::Success`] as soon as a child succeeds
10/// - returns [`TickStatus::Running`] as soon as a child is still running
11/// - returns [`TickStatus::Failure`] only if every child fails on the same tick
12///
13/// This variant does not remember which child was previously running, so the
14/// next tick starts again from the first child.
15pub struct Fallback {
16    nodes: NonEmptyNodes,
17    aborter: RunningNodesAborter,
18}
19
20impl Fallback {
21    #[must_use]
22    pub fn new(nodes: NonEmptyNodes) -> Self {
23        Self {
24            nodes,
25            aborter: RunningNodesAborter::new(),
26        }
27    }
28}
29
30impl Node for Fallback {
31    fn tick(&mut self) -> TickStatus {
32        let aborter = &mut self.aborter;
33        for idx in self.nodes.indices() {
34            let node = &mut self.nodes[idx];
35            match node.tick() {
36                TickStatus::Failure => {
37                    aborter.untrack(idx);
38                }
39                TickStatus::Running => {
40                    aborter.abort_if_other_running(&mut self.nodes, idx);
41                    aborter.track(idx);
42                    return TickStatus::Running;
43                }
44                TickStatus::Success => {
45                    aborter.abort_if_other_running(&mut self.nodes, idx);
46                    return TickStatus::Success;
47                }
48            }
49        }
50        TickStatus::Failure
51    }
52
53    fn abort(&mut self) {
54        self.aborter.clear();
55        for node in &mut self.nodes {
56            node.abort();
57        }
58    }
59
60    fn reset(&mut self) {
61        self.aborter.clear();
62        for node in &mut self.nodes {
63            node.reset();
64        }
65    }
66}
67
68#[cfg(test)]
69mod tests {
70    use beetry_core::{Node, TickStatus};
71
72    use super::*;
73    use crate::mock_test::{boxed, mock_returns};
74
75    #[test]
76    fn success_with_first_success() {
77        let nodes = NonEmptyNodes::from([
78            boxed(mock_returns([TickStatus::Failure])),
79            boxed(mock_returns([TickStatus::Success])),
80            boxed(mock_returns([])),
81        ]);
82        let mut fb = Fallback::new(nodes);
83
84        assert_eq!(fb.tick(), TickStatus::Success);
85    }
86
87    #[test]
88    fn running_with_first_running() {
89        let nodes = NonEmptyNodes::from([
90            boxed(mock_returns([TickStatus::Failure])),
91            boxed(mock_returns([TickStatus::Running])),
92            boxed(mock_returns([])),
93        ]);
94        let mut fb = Fallback::new(nodes);
95        assert_eq!(fb.tick(), TickStatus::Running);
96    }
97
98    #[test]
99    fn failure_with_all_failed() {
100        let nodes = NonEmptyNodes::from([
101            boxed(mock_returns([TickStatus::Failure])),
102            boxed(mock_returns([TickStatus::Failure])),
103        ]);
104        let mut fb = Fallback::new(nodes);
105
106        assert_eq!(fb.tick(), TickStatus::Failure);
107    }
108
109    #[test]
110    fn resets_running() {
111        let m1 = mock_returns([
112            TickStatus::Failure,
113            TickStatus::Failure,
114            TickStatus::Running,
115        ]);
116        let mut m2 = mock_returns([TickStatus::Failure, TickStatus::Running]);
117        let mut m3 = mock_returns([TickStatus::Running]);
118        m2.expect_abort().once().return_const(());
119        m3.expect_abort().once().return_const(());
120
121        let nodes = NonEmptyNodes::from([boxed(m1), boxed(m2), boxed(m3)]);
122        let mut fb = Fallback::new(nodes);
123
124        assert_eq!(fb.tick(), TickStatus::Running);
125        assert_eq!(fb.tick(), TickStatus::Running);
126        assert_eq!(fb.tick(), TickStatus::Running);
127    }
128}