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(
92 mode,
93 precision,
94 host,
95 epoch,
96 DEFAULT_MAX_SKEW_MILLIS,
97 SystemTime::now,
98 )
99 }
100
101 /// Creates a generator with an explicit clock.
102 ///
103 /// This constructor is useful for deterministic tests and for embedding the
104 /// generator in systems that already provide a clock abstraction.
105 ///
106 /// # Parameters
107 /// - `mode`: ID ordering mode.
108 /// - `precision`: Timestamp precision.
109 /// - `host`: Host identifier in `0..=511`.
110 /// - `epoch`: Timestamp origin.
111 /// - `max_skew_millis`: Maximum tolerated backwards clock movement in
112 /// milliseconds.
113 /// - `clock`: Function returning the current time.
114 ///
115 /// # Returns
116 /// A configured generator.
117 ///
118 /// # Errors
119 /// Returns [`IdError::HostOutOfRange`] when `host` is invalid.
120 pub fn with_clock<F>(
121 mode: IdMode,
122 precision: TimestampPrecision,
123 host: u64,
124 epoch: SystemTime,
125 max_skew_millis: u64,
126 clock: F,
127 ) -> Result<Self, IdError>
128 where
129 F: Fn() -> SystemTime + Send + Sync + 'static,
130 {
131 Ok(Self {
132 builder: QubitSnowflakeBuilder::new(mode, precision, host)?,
133 epoch,
134 max_skew_millis,
135 clock: Arc::new(clock),
136 state: Mutex::new(TimeSlice::new(0)),
137 })
138 }
139
140 /// Returns the Qubit bit builder.
141 ///
142 /// # Returns
143 /// Builder used to compose and inspect generated IDs.
144 pub const fn builder(&self) -> &QubitSnowflakeBuilder {
145 &self.builder
146 }
147
148 /// Returns the configured epoch.
149 ///
150 /// # Returns
151 /// Timestamp origin.
152 pub const fn epoch(&self) -> SystemTime {
153 self.epoch
154 }
155
156 /// Generates an ID for an explicit time and sequence.
157 ///
158 /// # Parameters
159 /// - `time`: Time to encode.
160 /// - `sequence`: Sequence value inside the encoded time slice.
161 ///
162 /// # Returns
163 /// Encoded ID.
164 ///
165 /// # Errors
166 /// Returns [`IdError::TimeBeforeEpoch`] if `time` is before the configured
167 /// epoch. Returns builder validation errors if the computed timestamp or
168 /// provided sequence does not fit.
169 pub fn generate_at(&self, time: SystemTime, sequence: u64) -> Result<u64, IdError> {
170 let timestamp = self.timestamp_for(time)?;
171 self.builder.build(timestamp, sequence)
172 }
173
174 /// Converts a time value into a precision-aware timestamp.
175 ///
176 /// # Parameters
177 /// - `time`: Time to convert.
178 ///
179 /// # Returns
180 /// Elapsed timestamp in the configured precision.
181 ///
182 /// # Errors
183 /// Returns [`IdError::TimeBeforeEpoch`] when `time` is before the epoch.
184 fn timestamp_for(&self, time: SystemTime) -> Result<u64, IdError> {
185 let elapsed = time
186 .duration_since(self.epoch)
187 .map_err(|_| IdError::TimeBeforeEpoch)?;
188 let timestamp = elapsed.as_millis() / u128::from(self.builder.precision().divisor_millis());
189 if timestamp > u128::from(self.builder.max_timestamp()) {
190 return Err(IdError::TimestampOverflow {
191 timestamp: u64::try_from(timestamp).unwrap_or(u64::MAX),
192 max: self.builder.max_timestamp(),
193 });
194 }
195 Ok(timestamp as u64)
196 }
197
198 /// Reads the current timestamp from the configured clock.
199 ///
200 /// # Returns
201 /// Current timestamp in the configured precision.
202 ///
203 /// # Errors
204 /// Returns [`IdError::TimeBeforeEpoch`] when the clock is before the epoch.
205 fn current_timestamp(&self) -> Result<u64, IdError> {
206 self.timestamp_for((self.clock)())
207 }
208
209 /// Waits until the clock reaches a later timestamp.
210 ///
211 /// # Parameters
212 /// - `last_timestamp`: Timestamp that has exhausted its sequence range.
213 ///
214 /// # Returns
215 /// First observed timestamp greater than `last_timestamp`.
216 ///
217 /// # Errors
218 /// Returns [`IdError::TimeBeforeEpoch`] when the clock is before the epoch.
219 fn wait_for_next_timestamp(&self, last_timestamp: u64) -> Result<u64, IdError> {
220 let mut timestamp = self.current_timestamp()?;
221 while timestamp <= last_timestamp {
222 thread::sleep(Duration::from_millis(
223 self.builder.precision().wait_duration_millis(),
224 ));
225 timestamp = self.current_timestamp()?;
226 }
227 Ok(timestamp)
228 }
229}
230
231impl IdGenerator<u64> for QubitSnowflakeGenerator {
232 type Error = IdError;
233
234 /// Generates the next Qubit snowflake ID.
235 fn next_id(&self) -> Result<u64, Self::Error> {
236 loop {
237 let mut state = self
238 .state
239 .lock()
240 .expect("generator state mutex should not be poisoned");
241 let mut timestamp = self.current_timestamp()?;
242
243 if state.timestamp > timestamp {
244 let skew = state.timestamp - timestamp;
245 let skew_millis = skew * self.builder.precision().divisor_millis();
246 if skew_millis > self.max_skew_millis {
247 return Err(IdError::ClockMovedBackwards {
248 last_timestamp: state.timestamp,
249 current_timestamp: timestamp,
250 skew_millis,
251 max_skew_millis: self.max_skew_millis,
252 });
253 }
254 drop(state);
255 thread::sleep(Duration::from_millis(skew_millis));
256 continue;
257 }
258
259 let sequence = if timestamp == state.timestamp {
260 let next_sequence = (state.sequence + 1) & self.builder.max_sequence();
261 if next_sequence == 0 {
262 drop(state);
263 timestamp = self.wait_for_next_timestamp(timestamp)?;
264 let mut state = self
265 .state
266 .lock()
267 .expect("generator state mutex should not be poisoned");
268 state.timestamp = timestamp;
269 state.sequence = 0;
270 return self.builder.build(timestamp, 0);
271 }
272 next_sequence
273 } else {
274 0
275 };
276
277 state.timestamp = timestamp;
278 state.sequence = sequence;
279 return self.builder.build(timestamp, sequence);
280 }
281 }
282}