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(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}