Skip to main content

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}