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