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