Skip to main content

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