qubit_id/snowflake/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//! Classic 41/10/12 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::DEFAULT_QUBIT_EPOCH_MILLIS;
24use super::time_slice::TimeSlice;
25use crate::{
26 IdError,
27 IdGenerator,
28};
29
30const TIMESTAMP_BITS: u8 = 41;
31const NODE_BITS: u8 = 10;
32const SEQUENCE_BITS: u8 = 12;
33const MAX_NODE_ID: u64 = (1_u64 << NODE_BITS) - 1;
34
35/// Classic Snowflake generator using 41 timestamp, 10 node, and 12 sequence bits.
36pub struct SnowflakeGenerator {
37 node_id: u64,
38 epoch: SystemTime,
39 clock: Arc<dyn Fn() -> SystemTime + Send + Sync>,
40 state: Mutex<TimeSlice>,
41}
42
43impl SnowflakeGenerator {
44 /// Creates a classic Snowflake generator with the default Qubit epoch.
45 ///
46 /// # Parameters
47 /// - `node_id`: Node identifier in `0..=1023`.
48 ///
49 /// # Returns
50 /// A configured generator.
51 ///
52 /// # Errors
53 /// Returns [`IdError::NodeOutOfRange`] when `node_id` does not fit in 10 bits.
54 pub fn new(node_id: u64) -> Result<Self, IdError> {
55 Self::with_epoch(
56 node_id,
57 UNIX_EPOCH + Duration::from_millis(DEFAULT_QUBIT_EPOCH_MILLIS),
58 )
59 }
60
61 /// Creates a classic Snowflake generator with an explicit epoch.
62 ///
63 /// # Parameters
64 /// - `node_id`: Node identifier in `0..=1023`.
65 /// - `epoch`: Timestamp origin.
66 ///
67 /// # Returns
68 /// A configured generator using the system clock.
69 ///
70 /// # Errors
71 /// Returns [`IdError::NodeOutOfRange`] when `node_id` does not fit in 10 bits.
72 pub fn with_epoch(node_id: u64, epoch: SystemTime) -> Result<Self, IdError> {
73 Self::with_clock(node_id, epoch, SystemTime::now)
74 }
75
76 /// Creates a classic Snowflake generator with an explicit clock.
77 ///
78 /// # Parameters
79 /// - `node_id`: Node identifier in `0..=1023`.
80 /// - `epoch`: Timestamp origin.
81 /// - `clock`: Function returning the current time.
82 ///
83 /// # Returns
84 /// A configured generator.
85 ///
86 /// # Errors
87 /// Returns [`IdError::NodeOutOfRange`] when `node_id` does not fit in 10 bits.
88 pub fn with_clock<F>(node_id: u64, epoch: SystemTime, clock: F) -> Result<Self, IdError>
89 where
90 F: Fn() -> SystemTime + Send + Sync + 'static,
91 {
92 if node_id > MAX_NODE_ID {
93 return Err(IdError::NodeOutOfRange {
94 node_id,
95 max: MAX_NODE_ID,
96 });
97 }
98 Ok(Self {
99 node_id,
100 epoch,
101 clock: Arc::new(clock),
102 state: Mutex::new(TimeSlice::new(0)),
103 })
104 }
105
106 /// Returns the configured node identifier.
107 ///
108 /// # Returns
109 /// Node identifier.
110 pub const fn node_id(&self) -> u64 {
111 self.node_id
112 }
113
114 /// Returns the configured epoch.
115 ///
116 /// # Returns
117 /// Timestamp origin.
118 pub const fn epoch(&self) -> SystemTime {
119 self.epoch
120 }
121
122 /// Returns the maximum representable timestamp.
123 ///
124 /// # Returns
125 /// Maximum timestamp in milliseconds since the epoch.
126 pub const fn max_timestamp(&self) -> u64 {
127 (1_u64 << TIMESTAMP_BITS) - 1
128 }
129
130 /// Returns the maximum representable sequence.
131 ///
132 /// # Returns
133 /// Maximum sequence number.
134 pub const fn max_sequence(&self) -> u64 {
135 (1_u64 << SEQUENCE_BITS) - 1
136 }
137
138 /// Composes an ID from timestamp and sequence parts.
139 ///
140 /// # Parameters
141 /// - `timestamp`: Milliseconds elapsed since the epoch.
142 /// - `sequence`: Sequence value inside the timestamp millisecond.
143 ///
144 /// # Returns
145 /// Encoded ID.
146 ///
147 /// # Errors
148 /// Returns [`IdError::TimestampOverflow`] or [`IdError::SequenceOverflow`]
149 /// when a part does not fit the classic Snowflake layout.
150 pub fn compose(&self, timestamp: u64, sequence: u64) -> Result<u64, IdError> {
151 if timestamp > self.max_timestamp() {
152 return Err(IdError::TimestampOverflow {
153 timestamp,
154 max: self.max_timestamp(),
155 });
156 }
157 if sequence > self.max_sequence() {
158 return Err(IdError::SequenceOverflow {
159 sequence,
160 max: self.max_sequence(),
161 });
162 }
163 Ok((timestamp << (NODE_BITS + SEQUENCE_BITS)) | (self.node_id << SEQUENCE_BITS) | sequence)
164 }
165
166 /// Extracts the timestamp part from an ID.
167 ///
168 /// # Parameters
169 /// - `id`: ID generated by this layout.
170 ///
171 /// # Returns
172 /// Milliseconds elapsed since the epoch.
173 pub const fn extract_timestamp(&self, id: u64) -> u64 {
174 id >> (NODE_BITS + SEQUENCE_BITS)
175 }
176
177 /// Extracts the node identifier from an ID.
178 ///
179 /// # Parameters
180 /// - `id`: ID generated by this layout.
181 ///
182 /// # Returns
183 /// Node identifier.
184 pub const fn extract_node_id(&self, id: u64) -> u64 {
185 (id >> SEQUENCE_BITS) & MAX_NODE_ID
186 }
187
188 /// Extracts the sequence number from an ID.
189 ///
190 /// # Parameters
191 /// - `id`: ID generated by this layout.
192 ///
193 /// # Returns
194 /// Sequence number.
195 pub const fn extract_sequence(&self, id: u64) -> u64 {
196 id & ((1_u64 << SEQUENCE_BITS) - 1)
197 }
198
199 /// Converts a clock time into milliseconds since the epoch.
200 ///
201 /// # Parameters
202 /// - `time`: Time to convert.
203 ///
204 /// # Returns
205 /// Milliseconds elapsed since the epoch.
206 ///
207 /// # Errors
208 /// Returns [`IdError::TimeBeforeEpoch`] when `time` is before the epoch.
209 fn timestamp_for(&self, time: SystemTime) -> Result<u64, IdError> {
210 let elapsed = time
211 .duration_since(self.epoch)
212 .map_err(|_| IdError::TimeBeforeEpoch)?;
213 let timestamp = elapsed.as_millis();
214 if timestamp > u128::from(self.max_timestamp()) {
215 return Err(IdError::TimestampOverflow {
216 timestamp: u64::try_from(timestamp).unwrap_or(u64::MAX),
217 max: self.max_timestamp(),
218 });
219 }
220 Ok(timestamp as u64)
221 }
222
223 /// Reads the current timestamp from the configured clock.
224 ///
225 /// # Returns
226 /// Current timestamp in milliseconds.
227 ///
228 /// # Errors
229 /// Returns [`IdError::TimeBeforeEpoch`] when the clock is before the epoch.
230 fn current_timestamp(&self) -> Result<u64, IdError> {
231 self.timestamp_for((self.clock)())
232 }
233
234 /// Waits until the clock reaches a later millisecond.
235 ///
236 /// # Parameters
237 /// - `last_timestamp`: Millisecond that exhausted its sequence range.
238 ///
239 /// # Returns
240 /// First observed millisecond greater than `last_timestamp`.
241 ///
242 /// # Errors
243 /// Returns [`IdError::TimeBeforeEpoch`] when the clock is before the epoch.
244 fn wait_for_next_timestamp(&self, last_timestamp: u64) -> Result<u64, IdError> {
245 let mut timestamp = self.current_timestamp()?;
246 while timestamp <= last_timestamp {
247 thread::sleep(Duration::from_millis(1));
248 timestamp = self.current_timestamp()?;
249 }
250 Ok(timestamp)
251 }
252}
253
254impl IdGenerator<u64> for SnowflakeGenerator {
255 type Error = IdError;
256
257 /// Generates the next classic Snowflake ID.
258 fn next_id(&self) -> Result<u64, Self::Error> {
259 let mut state = self
260 .state
261 .lock()
262 .expect("generator state mutex should not be poisoned");
263 let mut timestamp = self.current_timestamp()?;
264
265 if state.timestamp > timestamp {
266 return Err(IdError::ClockMovedBackwards {
267 last_timestamp: state.timestamp,
268 current_timestamp: timestamp,
269 skew_millis: state.timestamp - timestamp,
270 max_skew_millis: 0,
271 });
272 }
273
274 let sequence = if timestamp == state.timestamp {
275 let next_sequence = state.sequence + 1;
276 if next_sequence > self.max_sequence() {
277 drop(state);
278 timestamp = self.wait_for_next_timestamp(timestamp)?;
279 let mut state = self
280 .state
281 .lock()
282 .expect("generator state mutex should not be poisoned");
283 state.timestamp = timestamp;
284 state.sequence = 0;
285 return self.compose(timestamp, 0);
286 }
287 next_sequence
288 } else {
289 0
290 };
291
292 state.timestamp = timestamp;
293 state.sequence = sequence;
294 self.compose(timestamp, sequence)
295 }
296}