beetry_node/control/
fallback.rs1use beetry_core::{Node, NonEmptyNodes, TickStatus};
2
3use crate::{Indices, control::RunningNodesAborter};
4
5pub 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}