Skip to main content

beetry_node/control/
sequence.rs

1use beetry_core::{Node, NonEmptyNodes, TickStatus};
2
3use crate::{Indices, control::RunningNodesAborter};
4
5/// Control node that requires all children to succeed in order.
6///
7/// `Sequence` ticks children from left to right on each tick:
8///
9/// - returns [`TickStatus::Failure`] as soon as a child fails
10/// - returns [`TickStatus::Running`] as soon as a child is still running
11/// - returns [`TickStatus::Success`] only if every child succeeds on the same
12///   tick
13///
14/// This variant does not remember which child was previously running, so the
15/// next tick starts again from the first child.
16pub struct Sequence {
17    nodes: NonEmptyNodes,
18    aborter: RunningNodesAborter,
19}
20
21impl Sequence {
22    #[must_use]
23    pub fn new(nodes: impl Into<NonEmptyNodes>) -> Self {
24        Self {
25            nodes: nodes.into(),
26            aborter: RunningNodesAborter::new(),
27        }
28    }
29}
30
31impl Node for Sequence {
32    fn tick(&mut self) -> TickStatus {
33        let aborter = &mut self.aborter;
34        for idx in self.nodes.indices() {
35            let node = &mut self.nodes[idx];
36            match node.tick() {
37                TickStatus::Success => {
38                    aborter.untrack(idx);
39                }
40                TickStatus::Running => {
41                    aborter.abort_if_other_running(&mut self.nodes, idx);
42                    aborter.track(idx);
43                    return TickStatus::Running;
44                }
45
46                TickStatus::Failure => {
47                    aborter.abort_if_other_running(&mut self.nodes, idx);
48                    return TickStatus::Failure;
49                }
50            }
51        }
52        TickStatus::Success
53    }
54
55    fn abort(&mut self) {
56        self.aborter.clear();
57        for node in &mut self.nodes {
58            node.abort();
59        }
60    }
61
62    fn reset(&mut self) {
63        self.aborter.clear();
64        for node in &mut self.nodes {
65            node.reset();
66        }
67    }
68}
69
70/// Memory-based [`Sequence`] variant.
71///
72/// Unlike [`Sequence`], `MemorySequence` remembers the last running child and
73/// resumes from it on the next tick instead of restarting from the beginning.
74pub struct MemorySequence {
75    nodes: NonEmptyNodes,
76    running_idx: Option<usize>,
77}
78
79impl MemorySequence {
80    pub fn new(nodes: impl Into<NonEmptyNodes>) -> Self {
81        Self {
82            nodes: nodes.into(),
83            running_idx: None,
84        }
85    }
86}
87
88impl Node for MemorySequence {
89    fn tick(&mut self) -> TickStatus {
90        let start_idx = self.running_idx.take().unwrap_or(0);
91        for idx in start_idx..self.nodes.len().into() {
92            let node = &mut self.nodes[idx];
93            match node.tick() {
94                TickStatus::Success => {}
95                TickStatus::Running => {
96                    self.running_idx = Some(idx);
97                    return TickStatus::Running;
98                }
99                TickStatus::Failure => {
100                    return TickStatus::Failure;
101                }
102            }
103        }
104        TickStatus::Success
105    }
106
107    fn abort(&mut self) {
108        self.running_idx = None;
109        for node in &mut self.nodes {
110            node.abort();
111        }
112    }
113
114    fn reset(&mut self) {
115        self.running_idx = None;
116        for node in &mut self.nodes {
117            node.reset();
118        }
119    }
120}
121
122#[cfg(test)]
123mod tests {
124    use beetry_core::{Node, TickStatus};
125
126    use super::*;
127    use crate::mock_test::{boxed, mock_returns};
128
129    #[test]
130    fn success_with_all_success() {
131        let nodes = NonEmptyNodes::from([
132            boxed(mock_returns([TickStatus::Success])),
133            boxed(mock_returns([TickStatus::Success])),
134        ]);
135        let mut sq = Sequence::new(nodes);
136
137        assert_eq!(sq.tick(), TickStatus::Success);
138    }
139
140    #[test]
141    fn running_with_first_running() {
142        let nodes = NonEmptyNodes::from([
143            boxed(mock_returns([TickStatus::Success])),
144            boxed(mock_returns([TickStatus::Running])),
145            boxed(mock_returns([])),
146        ]);
147        let mut sq = Sequence::new(nodes);
148        assert_eq!(sq.tick(), TickStatus::Running);
149    }
150
151    #[test]
152    fn failure_with_first_failed() {
153        let nodes = NonEmptyNodes::from([
154            boxed(mock_returns([TickStatus::Success])),
155            boxed(mock_returns([TickStatus::Failure])),
156            boxed(mock_returns([])),
157        ]);
158        let mut sq = Sequence::new(nodes);
159
160        assert_eq!(sq.tick(), TickStatus::Failure);
161    }
162
163    #[test]
164    fn resets_running() {
165        let m1 = mock_returns([
166            TickStatus::Success,
167            TickStatus::Success,
168            TickStatus::Running,
169        ]);
170        let mut m2 = mock_returns([TickStatus::Success, TickStatus::Running]);
171        let mut m3 = mock_returns([TickStatus::Running]);
172        m2.expect_abort().once().return_const(());
173        m3.expect_abort().once().return_const(());
174
175        let nodes = NonEmptyNodes::from([boxed(m1), boxed(m2), boxed(m3)]);
176        let mut sq = Sequence::new(nodes);
177
178        assert_eq!(sq.tick(), TickStatus::Running);
179        assert_eq!(sq.tick(), TickStatus::Running);
180        assert_eq!(sq.tick(), TickStatus::Running);
181    }
182
183    #[test]
184    fn mem_sequence_resumes_from_running_child() {
185        let m1 = mock_returns([TickStatus::Success]);
186        let m2 = mock_returns([
187            TickStatus::Running,
188            TickStatus::Running,
189            TickStatus::Success,
190        ]);
191        let m3 = mock_returns([TickStatus::Success]);
192
193        let nodes = NonEmptyNodes::from([boxed(m1), boxed(m2), boxed(m3)]);
194        let mut msq = MemorySequence::new(nodes);
195
196        assert_eq!(msq.tick(), TickStatus::Running);
197        assert_eq!(msq.tick(), TickStatus::Running);
198        assert_eq!(msq.tick(), TickStatus::Success);
199    }
200
201    #[test]
202    fn mem_sequence_reset_clears_memory() {
203        let mut m1 = mock_returns([TickStatus::Success, TickStatus::Success]);
204        m1.expect_reset().once().return_const(());
205        let mut m2 = mock_returns([TickStatus::Running, TickStatus::Running]);
206        m2.expect_reset().once().return_const(());
207
208        let nodes = NonEmptyNodes::from([boxed(m1), boxed(m2)]);
209        let mut msq = MemorySequence::new(nodes);
210
211        assert_eq!(msq.tick(), TickStatus::Running);
212        msq.reset();
213        assert_eq!(msq.tick(), TickStatus::Running);
214    }
215}