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}