Skip to main content

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}