oxihuman_core/
sub_task.rs1#![allow(dead_code)]
4
5use std::collections::HashMap;
8
9#[allow(dead_code)]
11#[derive(Debug, Clone, PartialEq)]
12pub enum SubTaskStatus {
13 Pending,
14 Running,
15 Done,
16 Failed(String),
17 Skipped,
18}
19
20#[allow(dead_code)]
22#[derive(Debug, Clone)]
23pub struct SubTask {
24 pub name: String,
25 pub status: SubTaskStatus,
26 pub progress: f32,
27 pub weight: f32,
28}
29
30#[allow(dead_code)]
32pub struct SubTaskSet {
33 tasks: Vec<SubTask>,
34 name_index: HashMap<String, usize>,
35}
36
37#[allow(dead_code)]
38impl SubTaskSet {
39 pub fn new() -> Self {
40 Self {
41 tasks: Vec::new(),
42 name_index: HashMap::new(),
43 }
44 }
45
46 pub fn add(&mut self, name: &str, weight: f32) {
47 let idx = self.tasks.len();
48 self.tasks.push(SubTask {
49 name: name.to_string(),
50 status: SubTaskStatus::Pending,
51 progress: 0.0,
52 weight: weight.max(0.0),
53 });
54 self.name_index.insert(name.to_string(), idx);
55 }
56
57 pub fn set_running(&mut self, name: &str) -> bool {
58 if let Some(&idx) = self.name_index.get(name) {
59 self.tasks[idx].status = SubTaskStatus::Running;
60 true
61 } else {
62 false
63 }
64 }
65
66 pub fn set_progress(&mut self, name: &str, progress: f32) -> bool {
67 if let Some(&idx) = self.name_index.get(name) {
68 self.tasks[idx].progress = progress.clamp(0.0, 1.0);
69 true
70 } else {
71 false
72 }
73 }
74
75 pub fn set_done(&mut self, name: &str) -> bool {
76 if let Some(&idx) = self.name_index.get(name) {
77 self.tasks[idx].status = SubTaskStatus::Done;
78 self.tasks[idx].progress = 1.0;
79 true
80 } else {
81 false
82 }
83 }
84
85 pub fn set_failed(&mut self, name: &str, reason: &str) -> bool {
86 if let Some(&idx) = self.name_index.get(name) {
87 self.tasks[idx].status = SubTaskStatus::Failed(reason.to_string());
88 true
89 } else {
90 false
91 }
92 }
93
94 pub fn set_skipped(&mut self, name: &str) -> bool {
95 if let Some(&idx) = self.name_index.get(name) {
96 self.tasks[idx].status = SubTaskStatus::Skipped;
97 true
98 } else {
99 false
100 }
101 }
102
103 pub fn get(&self, name: &str) -> Option<&SubTask> {
104 self.name_index.get(name).map(|&i| &self.tasks[i])
105 }
106
107 pub fn overall_progress(&self) -> f32 {
109 let total_weight: f32 = self.tasks.iter().map(|t| t.weight).sum();
110 if total_weight <= 0.0 {
111 return 0.0;
112 }
113 let weighted: f32 = self.tasks.iter().map(|t| t.progress * t.weight).sum();
114 (weighted / total_weight).clamp(0.0, 1.0)
115 }
116
117 pub fn done_count(&self) -> usize {
118 self.tasks
119 .iter()
120 .filter(|t| t.status == SubTaskStatus::Done)
121 .count()
122 }
123
124 pub fn failed_count(&self) -> usize {
125 self.tasks
126 .iter()
127 .filter(|t| matches!(t.status, SubTaskStatus::Failed(_)))
128 .count()
129 }
130
131 pub fn pending_count(&self) -> usize {
132 self.tasks
133 .iter()
134 .filter(|t| t.status == SubTaskStatus::Pending)
135 .count()
136 }
137
138 pub fn task_count(&self) -> usize {
139 self.tasks.len()
140 }
141
142 pub fn all_done(&self) -> bool {
143 !self.tasks.is_empty()
144 && self
145 .tasks
146 .iter()
147 .all(|t| matches!(t.status, SubTaskStatus::Done | SubTaskStatus::Skipped))
148 }
149
150 pub fn has_failures(&self) -> bool {
151 self.tasks
152 .iter()
153 .any(|t| matches!(t.status, SubTaskStatus::Failed(_)))
154 }
155}
156
157impl Default for SubTaskSet {
158 fn default() -> Self {
159 Self::new()
160 }
161}
162
163pub fn new_sub_task_set() -> SubTaskSet {
164 SubTaskSet::new()
165}
166
167pub fn sts_add(set: &mut SubTaskSet, name: &str, weight: f32) {
168 set.add(name, weight);
169}
170
171pub fn sts_done(set: &mut SubTaskSet, name: &str) -> bool {
172 set.set_done(name)
173}
174
175pub fn sts_failed(set: &mut SubTaskSet, name: &str, reason: &str) -> bool {
176 set.set_failed(name, reason)
177}
178
179pub fn sts_overall(set: &SubTaskSet) -> f32 {
180 set.overall_progress()
181}
182
183#[cfg(test)]
184mod tests {
185 use super::*;
186
187 #[test]
188 fn empty_set() {
189 let s = new_sub_task_set();
190 assert_eq!(s.task_count(), 0);
191 }
192
193 #[test]
194 fn add_and_get() {
195 let mut s = new_sub_task_set();
196 sts_add(&mut s, "load", 1.0);
197 assert!(s.get("load").is_some());
198 assert_eq!(
199 s.get("load").expect("should succeed").status,
200 SubTaskStatus::Pending
201 );
202 }
203
204 #[test]
205 fn set_running() {
206 let mut s = new_sub_task_set();
207 sts_add(&mut s, "process", 1.0);
208 assert!(s.set_running("process"));
209 assert_eq!(
210 s.get("process").expect("should succeed").status,
211 SubTaskStatus::Running
212 );
213 }
214
215 #[test]
216 fn done_count() {
217 let mut s = new_sub_task_set();
218 sts_add(&mut s, "a", 1.0);
219 sts_add(&mut s, "b", 1.0);
220 sts_done(&mut s, "a");
221 assert_eq!(s.done_count(), 1);
222 }
223
224 #[test]
225 fn failed_count() {
226 let mut s = new_sub_task_set();
227 sts_add(&mut s, "task", 1.0);
228 sts_failed(&mut s, "task", "timeout");
229 assert_eq!(s.failed_count(), 1);
230 assert!(s.has_failures());
231 }
232
233 #[test]
234 fn overall_progress_even_weights() {
235 let mut s = new_sub_task_set();
236 sts_add(&mut s, "a", 1.0);
237 sts_add(&mut s, "b", 1.0);
238 sts_done(&mut s, "a");
239 let p = sts_overall(&s);
240 assert!((p - 0.5).abs() < 1e-6);
241 }
242
243 #[test]
244 fn all_done_when_all_completed() {
245 let mut s = new_sub_task_set();
246 sts_add(&mut s, "x", 1.0);
247 sts_done(&mut s, "x");
248 assert!(s.all_done());
249 }
250
251 #[test]
252 fn skipped_counts_as_done_for_all_done() {
253 let mut s = new_sub_task_set();
254 sts_add(&mut s, "opt", 0.5);
255 s.set_skipped("opt");
256 assert!(s.all_done());
257 }
258
259 #[test]
260 fn set_progress_clamps() {
261 let mut s = new_sub_task_set();
262 sts_add(&mut s, "t", 1.0);
263 s.set_progress("t", 2.0);
264 assert!((s.get("t").expect("should succeed").progress - 1.0).abs() < 1e-6);
265 }
266
267 #[test]
268 fn missing_task_returns_false() {
269 let mut s = new_sub_task_set();
270 assert!(!sts_done(&mut s, "ghost"));
271 }
272}