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