qubit_atomic/atomic/atomic_count.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
11//! # Atomic Count
12//!
13//! Provides a non-negative atomic counter for values whose transitions are
14//! used as synchronization signals.
15//!
16
17use std::fmt;
18use std::sync::atomic::{
19 AtomicUsize as StdAtomicUsize,
20 Ordering,
21};
22
23/// A non-negative atomic counter with synchronization-oriented operations.
24///
25/// Use this type when the counter value is part of a concurrent state machine,
26/// such as active task counts, in-flight request counts, resource usage counts,
27/// or shutdown and termination checks. Its operations return the value after
28/// the update, which makes zero-transition logic straightforward.
29///
30/// For pure metrics, statistics, event counters, or ID generation, prefer the
31/// regular atomic integer types such as [`Atomic<usize>`](crate::Atomic).
32/// Those types keep arithmetic operations lightweight for pure counting.
33///
34/// This counter never wraps. Incrementing past [`usize::MAX`] panics, and
35/// decrementing below zero panics. Use [`try_add`](Self::try_add),
36/// [`try_dec`](Self::try_dec), or [`try_sub`](Self::try_sub) when overflow or
37/// underflow is a normal business outcome.
38///
39/// # Example
40///
41/// ```rust
42/// use qubit_atomic::AtomicCount;
43///
44/// let active_tasks = AtomicCount::zero();
45///
46/// active_tasks.inc();
47/// assert!(!active_tasks.is_zero());
48///
49/// if active_tasks.dec() == 0 {
50/// // The last active task finished; notify termination waiters here.
51/// }
52/// ```
53///
54#[repr(transparent)]
55pub struct AtomicCount {
56 /// Standard-library atomic storage for the non-negative counter value.
57 inner: StdAtomicUsize,
58}
59
60impl AtomicCount {
61 /// Creates a new non-negative atomic counter.
62 ///
63 /// # Parameters
64 ///
65 /// * `value` - The initial counter value.
66 ///
67 /// # Returns
68 ///
69 /// A counter initialized to `value`.
70 ///
71 /// # Example
72 ///
73 /// ```rust
74 /// use qubit_atomic::AtomicCount;
75 ///
76 /// let counter = AtomicCount::new(3);
77 /// assert_eq!(counter.get(), 3);
78 /// ```
79 #[inline]
80 pub const fn new(value: usize) -> Self {
81 Self {
82 inner: StdAtomicUsize::new(value),
83 }
84 }
85
86 /// Creates a new counter initialized to zero.
87 ///
88 /// # Returns
89 ///
90 /// A counter whose current value is zero.
91 ///
92 /// # Example
93 ///
94 /// ```rust
95 /// use qubit_atomic::AtomicCount;
96 ///
97 /// let counter = AtomicCount::zero();
98 /// assert!(counter.is_zero());
99 /// ```
100 #[inline]
101 pub const fn zero() -> Self {
102 Self::new(0)
103 }
104
105 /// Gets the current counter value.
106 ///
107 /// # Returns
108 ///
109 /// The current counter value.
110 ///
111 /// # Example
112 ///
113 /// ```rust
114 /// use qubit_atomic::AtomicCount;
115 ///
116 /// let counter = AtomicCount::new(7);
117 /// assert_eq!(counter.get(), 7);
118 /// ```
119 #[inline]
120 pub fn get(&self) -> usize {
121 self.inner.load(Ordering::Acquire)
122 }
123
124 /// Returns whether the current counter value is zero.
125 ///
126 /// # Returns
127 ///
128 /// `true` if the current value is zero, otherwise `false`.
129 ///
130 /// # Example
131 ///
132 /// ```rust
133 /// use qubit_atomic::AtomicCount;
134 ///
135 /// let counter = AtomicCount::zero();
136 /// assert!(counter.is_zero());
137 /// ```
138 #[inline]
139 pub fn is_zero(&self) -> bool {
140 self.get() == 0
141 }
142
143 /// Returns whether the current counter value is greater than zero.
144 ///
145 /// # Returns
146 ///
147 /// `true` if the current value is greater than zero, otherwise `false`.
148 ///
149 /// # Example
150 ///
151 /// ```rust
152 /// use qubit_atomic::AtomicCount;
153 ///
154 /// let counter = AtomicCount::new(1);
155 /// assert!(counter.is_positive());
156 /// ```
157 #[inline]
158 pub fn is_positive(&self) -> bool {
159 self.get() > 0
160 }
161
162 /// Increments the counter by one and returns the new value.
163 ///
164 /// # Returns
165 ///
166 /// The counter value after the increment.
167 ///
168 /// # Panics
169 ///
170 /// Panics if the increment would overflow [`usize::MAX`].
171 ///
172 /// # Example
173 ///
174 /// ```rust
175 /// use qubit_atomic::AtomicCount;
176 ///
177 /// let counter = AtomicCount::zero();
178 /// assert_eq!(counter.inc(), 1);
179 /// ```
180 #[inline]
181 pub fn inc(&self) -> usize {
182 self.add(1)
183 }
184
185 /// Adds `delta` to the counter and returns the new value.
186 ///
187 /// # Parameters
188 ///
189 /// * `delta` - The amount to add.
190 ///
191 /// # Returns
192 ///
193 /// The counter value after the addition.
194 ///
195 /// # Panics
196 ///
197 /// Panics if the addition would overflow [`usize::MAX`].
198 ///
199 /// # Example
200 ///
201 /// ```rust
202 /// use qubit_atomic::AtomicCount;
203 ///
204 /// let counter = AtomicCount::new(2);
205 /// assert_eq!(counter.add(3), 5);
206 /// ```
207 #[inline]
208 pub fn add(&self, delta: usize) -> usize {
209 self.try_add(delta).expect("atomic counter overflow")
210 }
211
212 /// Tries to add `delta` to the counter.
213 ///
214 /// # Parameters
215 ///
216 /// * `delta` - The amount to add.
217 ///
218 /// # Returns
219 ///
220 /// `Some(new_value)` if the addition succeeds, or `None` if it would
221 /// overflow [`usize::MAX`]. On `None`, the counter is left unchanged.
222 ///
223 /// # Example
224 ///
225 /// ```rust
226 /// use qubit_atomic::AtomicCount;
227 ///
228 /// let counter = AtomicCount::new(2);
229 /// assert_eq!(counter.try_add(3), Some(5));
230 /// ```
231 #[inline]
232 pub fn try_add(&self, delta: usize) -> Option<usize> {
233 self.try_update(|current| current.checked_add(delta))
234 }
235
236 /// Decrements the counter by one and returns the new value.
237 ///
238 /// This method is useful for detecting the transition to zero:
239 ///
240 /// ```rust
241 /// use qubit_atomic::AtomicCount;
242 ///
243 /// let counter = AtomicCount::new(1);
244 /// if counter.dec() == 0 {
245 /// // This call consumed the final counted item.
246 /// }
247 /// ```
248 ///
249 /// # Returns
250 ///
251 /// The counter value after the decrement.
252 ///
253 /// # Panics
254 ///
255 /// Panics if the current value is zero.
256 #[inline]
257 pub fn dec(&self) -> usize {
258 self.try_dec().expect("atomic counter underflow")
259 }
260
261 /// Tries to decrement the counter by one.
262 ///
263 /// # Returns
264 ///
265 /// `Some(new_value)` if the decrement succeeds, or `None` if the current
266 /// value is zero. On `None`, the counter is left unchanged.
267 ///
268 /// # Example
269 ///
270 /// ```rust
271 /// use qubit_atomic::AtomicCount;
272 ///
273 /// let counter = AtomicCount::new(1);
274 /// assert_eq!(counter.try_dec(), Some(0));
275 /// assert_eq!(counter.try_dec(), None);
276 /// ```
277 #[inline]
278 pub fn try_dec(&self) -> Option<usize> {
279 self.try_sub(1)
280 }
281
282 /// Subtracts `delta` from the counter and returns the new value.
283 ///
284 /// # Parameters
285 ///
286 /// * `delta` - The amount to subtract.
287 ///
288 /// # Returns
289 ///
290 /// The counter value after the subtraction.
291 ///
292 /// # Panics
293 ///
294 /// Panics if the subtraction would make the counter negative.
295 ///
296 /// # Example
297 ///
298 /// ```rust
299 /// use qubit_atomic::AtomicCount;
300 ///
301 /// let counter = AtomicCount::new(5);
302 /// assert_eq!(counter.sub(2), 3);
303 /// ```
304 #[inline]
305 pub fn sub(&self, delta: usize) -> usize {
306 self.try_sub(delta).expect("atomic counter underflow")
307 }
308
309 /// Tries to subtract `delta` from the counter.
310 ///
311 /// # Parameters
312 ///
313 /// * `delta` - The amount to subtract.
314 ///
315 /// # Returns
316 ///
317 /// `Some(new_value)` if the subtraction succeeds, or `None` if it would
318 /// make the counter negative. On `None`, the counter is left unchanged.
319 ///
320 /// # Example
321 ///
322 /// ```rust
323 /// use qubit_atomic::AtomicCount;
324 ///
325 /// let counter = AtomicCount::new(3);
326 /// assert_eq!(counter.try_sub(2), Some(1));
327 /// assert_eq!(counter.try_sub(2), None);
328 /// ```
329 #[inline]
330 pub fn try_sub(&self, delta: usize) -> Option<usize> {
331 self.try_update(|current| current.checked_sub(delta))
332 }
333
334 /// Applies a checked update with synchronization semantics.
335 ///
336 /// # Parameters
337 ///
338 /// * `update` - A function that maps the current value to the next value,
339 /// or returns `None` to reject the update.
340 ///
341 /// # Returns
342 ///
343 /// `Some(new_value)` if the update succeeds, or `None` if `update`
344 /// rejects the current value. A rejected update leaves the counter
345 /// unchanged.
346 #[inline]
347 fn try_update<F>(&self, update: F) -> Option<usize>
348 where
349 F: Fn(usize) -> Option<usize>,
350 {
351 let mut current = self.get();
352 loop {
353 let next = update(current)?;
354 match self.inner.compare_exchange_weak(
355 current,
356 next,
357 Ordering::AcqRel,
358 Ordering::Acquire,
359 ) {
360 Ok(_) => return Some(next),
361 Err(actual) => current = actual,
362 }
363 }
364 }
365}
366
367impl Default for AtomicCount {
368 /// Creates a zero-valued atomic counter.
369 ///
370 /// # Returns
371 ///
372 /// A counter whose current value is zero.
373 #[inline]
374 fn default() -> Self {
375 Self::zero()
376 }
377}
378
379impl From<usize> for AtomicCount {
380 /// Converts an initial counter value into an [`AtomicCount`].
381 ///
382 /// # Parameters
383 ///
384 /// * `value` - The initial counter value.
385 ///
386 /// # Returns
387 ///
388 /// A counter initialized to `value`.
389 #[inline]
390 fn from(value: usize) -> Self {
391 Self::new(value)
392 }
393}
394
395impl fmt::Debug for AtomicCount {
396 /// Formats the current counter value for debugging.
397 ///
398 /// # Parameters
399 ///
400 /// * `f` - The formatter receiving the debug representation.
401 ///
402 /// # Returns
403 ///
404 /// A formatting result from the formatter.
405 #[inline]
406 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
407 f.debug_struct("AtomicCount")
408 .field("value", &self.get())
409 .finish()
410 }
411}
412
413impl fmt::Display for AtomicCount {
414 /// Formats the current counter value with decimal display formatting.
415 ///
416 /// # Parameters
417 ///
418 /// * `f` - The formatter receiving the displayed value.
419 ///
420 /// # Returns
421 ///
422 /// A formatting result from the formatter.
423 #[inline]
424 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
425 write!(f, "{}", self.get())
426 }
427}