qubit_id/snowflake/qubit_snowflake_generator.rs
1/*******************************************************************************
2 *
3 * Copyright (c) 2026 Haixing Hu.
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 *
7 * Licensed under the Apache License, Version 2.0.
8 *
9 ******************************************************************************/
10//! Qubit snowflake generator.
11
12use std::sync::{
13 Arc,
14 Mutex,
15};
16use std::thread;
17use std::time::{
18 Duration,
19 SystemTime,
20 UNIX_EPOCH,
21};
22
23use super::constants::{
24 DEFAULT_MAX_SKEW_MILLIS,
25 DEFAULT_QUBIT_EPOCH_MILLIS,
26};
27use super::time_slice::TimeSlice;
28use super::{
29 IdMode,
30 QubitSnowflakeBuilder,
31 TimestampPrecision,
32};
33use crate::{
34 IdError,
35 IdGenerator,
36};
37
38/// Qubit Snowflake generator.
39///
40/// This generator uses the Qubit fixed-header layout, including mode and
41/// precision bits. The default constructor uses sequential mode, second
42/// precision, host `0`, and epoch `2018-12-02T00:00:00Z`.
43pub struct QubitSnowflakeGenerator {
44 builder: QubitSnowflakeBuilder,
45 epoch: SystemTime,
46 max_skew_millis: u64,
47 clock: Arc<dyn Fn() -> SystemTime + Send + Sync>,
48 state: Mutex<TimeSlice>,
49}
50
51impl QubitSnowflakeGenerator {
52 /// Creates a generator with Qubit defaults.
53 ///
54 /// # Parameters
55 /// - `host`: Host identifier in `0..=511`.
56 ///
57 /// # Returns
58 /// A configured generator.
59 ///
60 /// # Errors
61 /// Returns [`IdError::HostOutOfRange`] when `host` does not fit in the host
62 /// field.
63 pub fn new(host: u64) -> Result<Self, IdError> {
64 Self::with_options(
65 IdMode::Sequential,
66 TimestampPrecision::Second,
67 host,
68 UNIX_EPOCH + Duration::from_millis(DEFAULT_QUBIT_EPOCH_MILLIS),
69 )
70 }
71
72 /// Creates a generator with an explicit layout and epoch.
73 ///
74 /// # Parameters
75 /// - `mode`: ID ordering mode.
76 /// - `precision`: Timestamp precision.
77 /// - `host`: Host identifier in `0..=511`.
78 /// - `epoch`: Timestamp origin.
79 ///
80 /// # Returns
81 /// A configured generator using the system clock.
82 ///
83 /// # Errors
84 /// Returns [`IdError::HostOutOfRange`] when `host` is invalid.
85 pub fn with_options(
86 mode: IdMode,
87 precision: TimestampPrecision,
88 host: u64,
89 epoch: SystemTime,
90 ) -> Result<Self, IdError> {
91 Self::with_clock(mode, precision, host, epoch, DEFAULT_MAX_SKEW_MILLIS, SystemTime::now)
92 }
93
94 /// Creates a generator with an explicit clock.
95 ///
96 /// This constructor is useful for deterministic tests and for embedding the
97 /// generator in systems that already provide a clock abstraction.
98 ///
99 /// # Parameters
100 /// - `mode`: ID ordering mode.
101 /// - `precision`: Timestamp precision.
102 /// - `host`: Host identifier in `0..=511`.
103 /// - `epoch`: Timestamp origin.
104 /// - `max_skew_millis`: Maximum tolerated backwards clock movement in
105 /// milliseconds.
106 /// - `clock`: Function returning the current time.
107 ///
108 /// # Returns
109 /// A configured generator.
110 ///
111 /// # Errors
112 /// Returns [`IdError::HostOutOfRange`] when `host` is invalid.
113 pub fn with_clock<F>(
114 mode: IdMode,
115 precision: TimestampPrecision,
116 host: u64,
117 epoch: SystemTime,
118 max_skew_millis: u64,
119 clock: F,
120 ) -> Result<Self, IdError>
121 where
122 F: Fn() -> SystemTime + Send + Sync + 'static,
123 {
124 Ok(Self {
125 builder: QubitSnowflakeBuilder::new(mode, precision, host)?,
126 epoch,
127 max_skew_millis,
128 clock: Arc::new(clock),
129 state: Mutex::new(TimeSlice::new(0)),
130 })
131 }
132
133 /// Returns the Qubit bit builder.
134 ///
135 /// # Returns
136 /// Builder used to compose and inspect generated IDs.
137 pub const fn builder(&self) -> &QubitSnowflakeBuilder {
138 &self.builder
139 }
140
141 /// Returns the configured epoch.
142 ///
143 /// # Returns
144 /// Timestamp origin.
145 pub const fn epoch(&self) -> SystemTime {
146 self.epoch
147 }
148
149 /// Generates an ID for an explicit time and sequence.
150 ///
151 /// # Parameters
152 /// - `time`: Time to encode.
153 /// - `sequence`: Sequence value inside the encoded time slice.
154 ///
155 /// # Returns
156 /// Encoded ID.
157 ///
158 /// # Errors
159 /// Returns [`IdError::TimeBeforeEpoch`] if `time` is before the configured
160 /// epoch. Returns builder validation errors if the computed timestamp or
161 /// provided sequence does not fit.
162 pub fn generate_at(&self, time: SystemTime, sequence: u64) -> Result<u64, IdError> {
163 let timestamp = self.timestamp_for(time)?;
164 self.builder.build(timestamp, sequence)
165 }
166
167 /// Converts a time value into a precision-aware timestamp.
168 ///
169 /// # Parameters
170 /// - `time`: Time to convert.
171 ///
172 /// # Returns
173 /// Elapsed timestamp in the configured precision.
174 ///
175 /// # Errors
176 /// Returns [`IdError::TimeBeforeEpoch`] when `time` is before the epoch.
177 fn timestamp_for(&self, time: SystemTime) -> Result<u64, IdError> {
178 let elapsed = time.duration_since(self.epoch).map_err(|_| IdError::TimeBeforeEpoch)?;
179 let timestamp = elapsed.as_millis() / u128::from(self.builder.precision().divisor_millis());
180 if timestamp > u128::from(self.builder.max_timestamp()) {
181 return Err(IdError::TimestampOverflow {
182 timestamp: u64::try_from(timestamp).unwrap_or(u64::MAX),
183 max: self.builder.max_timestamp(),
184 });
185 }
186 Ok(timestamp as u64)
187 }
188
189 /// Reads the current timestamp from the configured clock.
190 ///
191 /// # Returns
192 /// Current timestamp in the configured precision.
193 ///
194 /// # Errors
195 /// Returns [`IdError::TimeBeforeEpoch`] when the clock is before the epoch.
196 fn current_timestamp(&self) -> Result<u64, IdError> {
197 self.timestamp_for((self.clock)())
198 }
199
200 /// Waits until the clock reaches a later timestamp.
201 ///
202 /// # Parameters
203 /// - `last_timestamp`: Timestamp that has exhausted its sequence range.
204 ///
205 /// # Returns
206 /// First observed timestamp greater than `last_timestamp`.
207 ///
208 /// # Errors
209 /// Returns [`IdError::TimeBeforeEpoch`] when the clock is before the epoch.
210 fn wait_for_next_timestamp(&self, last_timestamp: u64) -> Result<u64, IdError> {
211 let mut timestamp = self.current_timestamp()?;
212 while timestamp <= last_timestamp {
213 thread::sleep(Duration::from_millis(self.builder.precision().wait_duration_millis()));
214 timestamp = self.current_timestamp()?;
215 }
216 Ok(timestamp)
217 }
218}
219
220impl IdGenerator<u64> for QubitSnowflakeGenerator {
221 type Error = IdError;
222
223 /// Generates the next Qubit snowflake ID.
224 fn next_id(&self) -> Result<u64, Self::Error> {
225 loop {
226 let mut state = self.state.lock().expect("generator state mutex should not be poisoned");
227 let mut timestamp = self.current_timestamp()?;
228
229 if state.timestamp > timestamp {
230 let skew = state.timestamp - timestamp;
231 let skew_millis = skew * self.builder.precision().divisor_millis();
232 if skew_millis > self.max_skew_millis {
233 return Err(IdError::ClockMovedBackwards {
234 last_timestamp: state.timestamp,
235 current_timestamp: timestamp,
236 skew_millis,
237 max_skew_millis: self.max_skew_millis,
238 });
239 }
240 drop(state);
241 thread::sleep(Duration::from_millis(skew_millis));
242 continue;
243 }
244
245 let sequence = if timestamp == state.timestamp {
246 let next_sequence = (state.sequence + 1) & self.builder.max_sequence();
247 if next_sequence == 0 {
248 drop(state);
249 timestamp = self.wait_for_next_timestamp(timestamp)?;
250 let mut state = self.state.lock().expect("generator state mutex should not be poisoned");
251 state.timestamp = timestamp;
252 state.sequence = 0;
253 return self.builder.build(timestamp, 0);
254 }
255 next_sequence
256 } else {
257 0
258 };
259
260 state.timestamp = timestamp;
261 state.sequence = sequence;
262 return self.builder.build(timestamp, sequence);
263 }
264 }
265}