Skip to main content

qubit_clock/monotonic/
clock_domain.rs

1// =============================================================================
2//    Copyright (c) 2025 - 2026 Haixing Hu.
3//
4//    SPDX-License-Identifier: Apache-2.0
5//
6//    Licensed under the Apache License, Version 2.0.
7// =============================================================================
8//! Defines unique monotonic clock domains.
9
10use std::fmt::Display;
11use std::fmt::Formatter;
12use std::num::NonZeroU64;
13use std::sync::atomic::AtomicU64;
14use std::sync::atomic::Ordering;
15
16/// Next unallocated clock domain identifier; zero marks exhaustion.
17static NEXT_CLOCK_DOMAIN: AtomicU64 = AtomicU64::new(1);
18
19/// Returns the allocator state following `identifier`, or `None` when zero
20/// already marks exhaustion.
21///
22/// The maximum identifier transitions to zero so it remains allocatable once
23/// without wrapping into a reused nonzero identifier.
24///
25/// # Parameters
26///
27/// * `identifier` - The current allocator state.
28///
29/// # Returns
30///
31/// The next allocator state, or `None` when `identifier` is already zero.
32#[inline(always)]
33pub(crate) fn next_identifier_state(identifier: u64) -> Option<u64> {
34    NonZeroU64::new(identifier).map(|identifier| identifier.get().wrapping_add(1))
35}
36
37/// Allocates an identifier from `next` without wrapping into a reused value.
38///
39/// The maximum `u64` value is returned once and atomically changes `next` to
40/// the terminal zero state. Calls made after that transition panic.
41///
42/// # Parameters
43///
44/// * `next` - Atomic allocator state to advance.
45///
46/// # Returns
47///
48/// A process-unique nonzero clock-domain identifier.
49///
50/// # Panics
51///
52/// Panics when the allocator has already reached its terminal zero state.
53#[must_use = "the allocated domain identifier must initialize a clock domain"]
54#[inline]
55fn allocate_clock_domain_identifier(next: &AtomicU64) -> u64 {
56    next.fetch_update(Ordering::Relaxed, Ordering::Relaxed, next_identifier_state)
57        .expect("monotonic clock domain identifiers exhausted")
58}
59
60/// Identifies one monotonic clock timeline within this process.
61///
62/// A domain is allocated by new() and is carried by every monotonic instant
63/// produced by its clock.
64///
65/// `ClockDomain` intentionally requires explicit allocation:
66///
67/// ```compile_fail
68/// use qubit_clock::ClockDomain;
69///
70/// let domain = ClockDomain::default();
71/// ```
72#[must_use = "clock domains should be retained to identify monotonic timelines"]
73#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
74pub struct ClockDomain(
75    /// Process-unique nonzero domain identifier.
76    u64,
77);
78
79impl ClockDomain {
80    /// Allocates a domain that is not reused within this process.
81    ///
82    /// Every representable nonzero identifier, including the final one, can be
83    /// allocated. After the final identifier is returned, later calls panic.
84    ///
85    /// # Returns
86    ///
87    /// A newly allocated process-unique clock domain.
88    ///
89    /// # Panics
90    ///
91    /// Panics if all representable nonzero domain identifiers have been
92    /// allocated rather than wrapping and reusing a prior identity.
93    #[allow(clippy::new_without_default)]
94    #[inline(always)]
95    pub fn new() -> Self {
96        Self(allocate_clock_domain_identifier(&NEXT_CLOCK_DOMAIN))
97    }
98}
99
100impl Display for ClockDomain {
101    /// Formats this domain for diagnostics.
102    ///
103    /// # Parameters
104    ///
105    /// * `formatter` - Destination formatter.
106    ///
107    /// # Returns
108    ///
109    /// `Ok(())` after the identifier is formatted.
110    ///
111    /// # Errors
112    ///
113    /// Returns [`std::fmt::Error`] when the formatter rejects the output.
114    #[inline(always)]
115    fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
116        let Self(identifier) = self;
117        identifier.fmt(formatter)
118    }
119}