qubit_progress/model/progress_counters.rs
1/*******************************************************************************
2 *
3 * Copyright (c) 2025 - 2026 Haixing Hu.
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 *
7 * Licensed under the Apache License, Version 2.0.
8 *
9 ******************************************************************************/
10/// Generic progress counters for a running operation.
11///
12/// # Examples
13///
14/// ```
15/// use qubit_progress::ProgressCounters;
16///
17/// let counters = ProgressCounters::new(Some(10))
18/// .with_completed_count(4)
19/// .with_active_count(2)
20/// .with_succeeded_count(3);
21///
22/// assert_eq!(counters.total_count(), Some(10));
23/// assert_eq!(counters.remaining_count(), Some(4));
24/// assert_eq!(counters.progress_percent(), Some(40.0));
25/// ```
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
27pub struct ProgressCounters {
28 /// Total work-unit count when known.
29 total_count: Option<usize>,
30 /// Completed work-unit count.
31 completed_count: usize,
32 /// Active work-unit count.
33 active_count: usize,
34 /// Successful work-unit count.
35 succeeded_count: usize,
36 /// Failed work-unit count.
37 failed_count: usize,
38}
39
40impl ProgressCounters {
41 /// Creates counters with a known or unknown total count.
42 ///
43 /// # Parameters
44 ///
45 /// * `total_count` - Total work-unit count, or `None` when unknown.
46 ///
47 /// # Returns
48 ///
49 /// Zeroed counters with the supplied total count.
50 #[inline]
51 pub const fn new(total_count: Option<usize>) -> Self {
52 Self {
53 total_count,
54 completed_count: 0,
55 active_count: 0,
56 succeeded_count: 0,
57 failed_count: 0,
58 }
59 }
60
61 /// Returns a copy configured with the known or unknown total count.
62 ///
63 /// # Parameters
64 ///
65 /// * `total_count` - Total work-unit count, or `None` when unknown.
66 ///
67 /// # Returns
68 ///
69 /// This counter set with `total_count` recorded.
70 #[inline]
71 pub const fn with_total_count(mut self, total_count: Option<usize>) -> Self {
72 self.total_count = total_count;
73 self
74 }
75
76 /// Returns a copy configured with the completed count.
77 ///
78 /// # Parameters
79 ///
80 /// * `completed_count` - Number of completed work units.
81 ///
82 /// # Returns
83 ///
84 /// This counter set with `completed_count` recorded.
85 #[inline]
86 pub const fn with_completed_count(mut self, completed_count: usize) -> Self {
87 self.completed_count = completed_count;
88 self
89 }
90
91 /// Returns a copy configured with the active count.
92 ///
93 /// # Parameters
94 ///
95 /// * `active_count` - Number of currently active work units.
96 ///
97 /// # Returns
98 ///
99 /// This counter set with `active_count` recorded.
100 #[inline]
101 pub const fn with_active_count(mut self, active_count: usize) -> Self {
102 self.active_count = active_count;
103 self
104 }
105
106 /// Returns a copy configured with the succeeded count.
107 ///
108 /// # Parameters
109 ///
110 /// * `succeeded_count` - Number of successful work units.
111 ///
112 /// # Returns
113 ///
114 /// This counter set with `succeeded_count` recorded.
115 #[inline]
116 pub const fn with_succeeded_count(mut self, succeeded_count: usize) -> Self {
117 self.succeeded_count = succeeded_count;
118 self
119 }
120
121 /// Returns a copy configured with the failed count.
122 ///
123 /// # Parameters
124 ///
125 /// * `failed_count` - Number of failed work units.
126 ///
127 /// # Returns
128 ///
129 /// This counter set with `failed_count` recorded.
130 #[inline]
131 pub const fn with_failed_count(mut self, failed_count: usize) -> Self {
132 self.failed_count = failed_count;
133 self
134 }
135
136 /// Returns the total work-unit count when known.
137 ///
138 /// # Returns
139 ///
140 /// `Some(total)` for known-total progress, or `None` for open-ended
141 /// progress.
142 #[inline]
143 pub const fn total_count(&self) -> Option<usize> {
144 self.total_count
145 }
146
147 /// Returns the completed work-unit count.
148 ///
149 /// # Returns
150 ///
151 /// The number of completed work units.
152 #[inline]
153 pub const fn completed_count(&self) -> usize {
154 self.completed_count
155 }
156
157 /// Returns the active work-unit count.
158 ///
159 /// # Returns
160 ///
161 /// The number of currently active work units.
162 #[inline]
163 pub const fn active_count(&self) -> usize {
164 self.active_count
165 }
166
167 /// Returns the successful work-unit count.
168 ///
169 /// # Returns
170 ///
171 /// The number of successful work units.
172 #[inline]
173 pub const fn succeeded_count(&self) -> usize {
174 self.succeeded_count
175 }
176
177 /// Returns the failed work-unit count.
178 ///
179 /// # Returns
180 ///
181 /// The number of failed work units.
182 #[inline]
183 pub const fn failed_count(&self) -> usize {
184 self.failed_count
185 }
186
187 /// Returns the remaining work-unit count when the total is known.
188 ///
189 /// # Returns
190 ///
191 /// `Some(total - completed - active)` using saturating arithmetic for
192 /// known-total progress, or `None` when the total is unknown.
193 #[inline]
194 pub const fn remaining_count(&self) -> Option<usize> {
195 match self.total_count {
196 Some(total_count) => Some(
197 total_count
198 .saturating_sub(self.completed_count)
199 .saturating_sub(self.active_count),
200 ),
201 None => None,
202 }
203 }
204
205 /// Returns completed progress as a fraction in `0.0..=1.0`.
206 ///
207 /// # Returns
208 ///
209 /// `Some(fraction)` for known-total progress, `Some(1.0)` when the known
210 /// total is zero, or `None` when the total is unknown.
211 #[inline]
212 pub fn progress_fraction(&self) -> Option<f64> {
213 self.total_count.map(|total_count| {
214 if total_count == 0 {
215 1.0
216 } else {
217 (self.completed_count as f64 / total_count as f64).clamp(0.0, 1.0)
218 }
219 })
220 }
221
222 /// Returns completed progress as a percentage in `0.0..=100.0`.
223 ///
224 /// # Returns
225 ///
226 /// `Some(percent)` for known-total progress, or `None` when the total is
227 /// unknown.
228 #[inline]
229 pub fn progress_percent(&self) -> Option<f64> {
230 self.progress_fraction().map(|fraction| fraction * 100.0)
231 }
232}