qubit_id/snowflake/sonyflake_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//! Sonyflake-style 63-bit ID 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::time_slice::TimeSlice;
24use crate::{
25 IdError,
26 IdGenerator,
27};
28
29const DEFAULT_BITS_SEQUENCE: u8 = 8;
30const DEFAULT_BITS_MACHINE: u8 = 16;
31const DEFAULT_TIME_UNIT_NANOS: u128 = 10_000_000;
32const MIN_TIME_UNIT_NANOS: u128 = 1_000_000;
33const DEFAULT_START_MILLIS: u64 = 1_735_689_600_000;
34
35/// Sonyflake-style generator using configurable time, sequence, and machine bits.
36///
37/// By default, the layout is compatible with Sonyflake's commonly documented
38/// allocation: 39 bits of time in 10 ms units, 8 sequence bits, and 16 machine
39/// bits. The sign bit is not used.
40pub struct SonyflakeGenerator {
41 bits_time: u8,
42 bits_sequence: u8,
43 bits_machine: u8,
44 time_unit: Duration,
45 start_time: SystemTime,
46 machine_id: u64,
47 clock: Arc<dyn Fn() -> SystemTime + Send + Sync>,
48 state: Mutex<TimeSlice>,
49}
50
51impl SonyflakeGenerator {
52 /// Creates a Sonyflake-style generator with default layout and epoch.
53 ///
54 /// # Parameters
55 /// - `machine_id`: Machine identifier in `0..=65535`.
56 ///
57 /// # Returns
58 /// A configured generator.
59 ///
60 /// # Errors
61 /// Returns [`IdError::MachineIdOutOfRange`] when `machine_id` does not fit
62 /// in the default 16-bit machine field.
63 pub fn new(machine_id: u64) -> Result<Self, IdError> {
64 Self::with_epoch(
65 machine_id,
66 UNIX_EPOCH + Duration::from_millis(DEFAULT_START_MILLIS),
67 )
68 }
69
70 /// Creates a Sonyflake-style generator with default layout and explicit epoch.
71 ///
72 /// # Parameters
73 /// - `machine_id`: Machine identifier in `0..=65535`.
74 /// - `start_time`: Start time used as elapsed-time origin.
75 ///
76 /// # Returns
77 /// A configured generator using the system clock.
78 ///
79 /// # Errors
80 /// Returns the same errors as [`SonyflakeGenerator::with_options`].
81 pub fn with_epoch(machine_id: u64, start_time: SystemTime) -> Result<Self, IdError> {
82 Self::with_options(
83 machine_id,
84 DEFAULT_BITS_SEQUENCE,
85 DEFAULT_BITS_MACHINE,
86 Duration::from_nanos(DEFAULT_TIME_UNIT_NANOS as u64),
87 start_time,
88 )
89 }
90
91 /// Creates a Sonyflake-style generator with explicit layout.
92 ///
93 /// Passing `0` for either bit length selects the Sonyflake default for that
94 /// field.
95 ///
96 /// # Parameters
97 /// - `machine_id`: Machine identifier.
98 /// - `bits_sequence`: Sequence bit length, or `0` for default.
99 /// - `bits_machine_id`: Machine bit length, or `0` for default.
100 /// - `time_unit`: Time unit; must be at least one millisecond.
101 /// - `start_time`: Start time used as elapsed-time origin.
102 ///
103 /// # Returns
104 /// A configured generator using the system clock.
105 ///
106 /// # Errors
107 /// Returns [`IdError::InvalidBitLength`] for invalid bit allocation,
108 /// [`IdError::InvalidTimeUnit`] for sub-millisecond time units,
109 /// [`IdError::StartTimeAhead`] when `start_time` is in the future, or
110 /// [`IdError::MachineIdOutOfRange`] when `machine_id` does not fit.
111 pub fn with_options(
112 machine_id: u64,
113 bits_sequence: u8,
114 bits_machine_id: u8,
115 time_unit: Duration,
116 start_time: SystemTime,
117 ) -> Result<Self, IdError> {
118 Self::with_clock(
119 machine_id,
120 bits_sequence,
121 bits_machine_id,
122 time_unit,
123 start_time,
124 SystemTime::now,
125 )
126 }
127
128 /// Creates a Sonyflake-style generator with an explicit clock.
129 ///
130 /// # Parameters
131 /// - `machine_id`: Machine identifier.
132 /// - `bits_sequence`: Sequence bit length, or `0` for default.
133 /// - `bits_machine_id`: Machine bit length, or `0` for default.
134 /// - `time_unit`: Time unit; must be at least one millisecond.
135 /// - `start_time`: Start time used as elapsed-time origin.
136 /// - `clock`: Function returning the current time.
137 ///
138 /// # Returns
139 /// A configured generator.
140 ///
141 /// # Errors
142 /// Returns the same validation errors as [`SonyflakeGenerator::with_options`].
143 pub fn with_clock<F>(
144 machine_id: u64,
145 bits_sequence: u8,
146 bits_machine_id: u8,
147 time_unit: Duration,
148 start_time: SystemTime,
149 clock: F,
150 ) -> Result<Self, IdError>
151 where
152 F: Fn() -> SystemTime + Send + Sync + 'static,
153 {
154 let bits_sequence = Self::normalize_bits("sequence", bits_sequence, DEFAULT_BITS_SEQUENCE)?;
155 let bits_machine = Self::normalize_bits("machine", bits_machine_id, DEFAULT_BITS_MACHINE)?;
156 let bits_time = 63_u8
157 .checked_sub(bits_sequence)
158 .and_then(|value| value.checked_sub(bits_machine))
159 .ok_or(IdError::InvalidBitLength {
160 name: "time",
161 bits: 0,
162 reason: "63 - sequence bits - machine bits must be at least 32",
163 })?;
164 if bits_time < 32 {
165 return Err(IdError::InvalidBitLength {
166 name: "time",
167 bits: bits_time,
168 reason: "time bit length must be at least 32",
169 });
170 }
171
172 let nanos = time_unit.as_nanos();
173 if nanos < MIN_TIME_UNIT_NANOS {
174 return Err(IdError::InvalidTimeUnit {
175 nanos,
176 min_nanos: MIN_TIME_UNIT_NANOS,
177 });
178 }
179
180 if start_time > clock() {
181 return Err(IdError::StartTimeAhead);
182 }
183
184 let max_machine_id = (1_u64 << bits_machine) - 1;
185 if machine_id > max_machine_id {
186 return Err(IdError::MachineIdOutOfRange {
187 machine_id,
188 max: max_machine_id,
189 });
190 }
191
192 Ok(Self {
193 bits_time,
194 bits_sequence,
195 bits_machine,
196 time_unit,
197 start_time,
198 machine_id,
199 clock: Arc::new(clock),
200 state: Mutex::new(TimeSlice::with_sequence(0, (1_u64 << bits_sequence) - 1)),
201 })
202 }
203
204 /// Normalizes and validates a Sonyflake bit length.
205 ///
206 /// # Parameters
207 /// - `name`: Name of the setting for diagnostics.
208 /// - `bits`: Provided bit length.
209 /// - `default_bits`: Default bit length used when `bits` is zero.
210 ///
211 /// # Returns
212 /// Normalized bit length.
213 ///
214 /// # Errors
215 /// Returns [`IdError::InvalidBitLength`] when the normalized value is 31 or
216 /// greater.
217 fn normalize_bits(name: &'static str, bits: u8, default_bits: u8) -> Result<u8, IdError> {
218 let normalized = if bits == 0 { default_bits } else { bits };
219 if normalized >= 31 {
220 return Err(IdError::InvalidBitLength {
221 name,
222 bits: normalized,
223 reason: "bit length must be less than 31",
224 });
225 }
226 Ok(normalized)
227 }
228
229 /// Returns the number of time bits.
230 ///
231 /// # Returns
232 /// Time bit length.
233 pub const fn bits_time(&self) -> u8 {
234 self.bits_time
235 }
236
237 /// Returns the number of sequence bits.
238 ///
239 /// # Returns
240 /// Sequence bit length.
241 pub const fn bits_sequence(&self) -> u8 {
242 self.bits_sequence
243 }
244
245 /// Returns the number of machine bits.
246 ///
247 /// # Returns
248 /// Machine bit length.
249 pub const fn bits_machine(&self) -> u8 {
250 self.bits_machine
251 }
252
253 /// Returns the maximum representable elapsed time unit.
254 ///
255 /// # Returns
256 /// Maximum elapsed time value.
257 pub const fn max_elapsed_time(&self) -> u64 {
258 (1_u64 << self.bits_time) - 1
259 }
260
261 /// Returns the maximum representable sequence.
262 ///
263 /// # Returns
264 /// Maximum sequence number.
265 pub const fn max_sequence(&self) -> u64 {
266 (1_u64 << self.bits_sequence) - 1
267 }
268
269 /// Returns the maximum representable machine identifier.
270 ///
271 /// # Returns
272 /// Maximum machine identifier.
273 pub const fn max_machine_id(&self) -> u64 {
274 (1_u64 << self.bits_machine) - 1
275 }
276
277 /// Composes a Sonyflake-style ID from explicit parts.
278 ///
279 /// # Parameters
280 /// - `elapsed_time`: Time units elapsed since the start time.
281 /// - `sequence`: Sequence value inside the time unit.
282 /// - `machine_id`: Machine identifier.
283 ///
284 /// # Returns
285 /// Encoded ID.
286 ///
287 /// # Errors
288 /// Returns range errors when any part does not fit the configured layout.
289 pub fn compose(
290 &self,
291 elapsed_time: u64,
292 sequence: u64,
293 machine_id: u64,
294 ) -> Result<u64, IdError> {
295 if elapsed_time > self.max_elapsed_time() {
296 return Err(IdError::TimestampOverflow {
297 timestamp: elapsed_time,
298 max: self.max_elapsed_time(),
299 });
300 }
301 if sequence > self.max_sequence() {
302 return Err(IdError::SequenceOverflow {
303 sequence,
304 max: self.max_sequence(),
305 });
306 }
307 if machine_id > self.max_machine_id() {
308 return Err(IdError::MachineIdOutOfRange {
309 machine_id,
310 max: self.max_machine_id(),
311 });
312 }
313 Ok((elapsed_time << (self.bits_sequence + self.bits_machine))
314 | (sequence << self.bits_machine)
315 | machine_id)
316 }
317
318 /// Extracts elapsed time from a Sonyflake-style ID.
319 ///
320 /// # Parameters
321 /// - `id`: ID generated by this layout.
322 ///
323 /// # Returns
324 /// Elapsed time units since the start time.
325 pub fn extract_elapsed_time(&self, id: u64) -> u64 {
326 id >> (self.bits_sequence + self.bits_machine)
327 }
328
329 /// Extracts sequence from a Sonyflake-style ID.
330 ///
331 /// # Parameters
332 /// - `id`: ID generated by this layout.
333 ///
334 /// # Returns
335 /// Sequence number.
336 pub fn extract_sequence(&self, id: u64) -> u64 {
337 let mask = ((1_u64 << self.bits_sequence) - 1) << self.bits_machine;
338 (id & mask) >> self.bits_machine
339 }
340
341 /// Extracts machine ID from a Sonyflake-style ID.
342 ///
343 /// # Parameters
344 /// - `id`: ID generated by this layout.
345 ///
346 /// # Returns
347 /// Machine identifier.
348 pub fn extract_machine_id(&self, id: u64) -> u64 {
349 id & ((1_u64 << self.bits_machine) - 1)
350 }
351
352 /// Converts a time value into elapsed Sonyflake units.
353 ///
354 /// # Parameters
355 /// - `time`: Time to convert.
356 ///
357 /// # Returns
358 /// Elapsed time units since the start time.
359 ///
360 /// # Errors
361 /// Returns [`IdError::TimeBeforeEpoch`] when `time` is before `start_time`.
362 fn elapsed_time_for(&self, time: SystemTime) -> Result<u64, IdError> {
363 let elapsed = time
364 .duration_since(self.start_time)
365 .map_err(|_| IdError::TimeBeforeEpoch)?;
366 let elapsed_units = elapsed.as_nanos() / self.time_unit.as_nanos();
367 if elapsed_units > u128::from(self.max_elapsed_time()) {
368 return Err(IdError::TimestampOverflow {
369 timestamp: u64::try_from(elapsed_units).unwrap_or(u64::MAX),
370 max: self.max_elapsed_time(),
371 });
372 }
373 Ok(elapsed_units as u64)
374 }
375
376 /// Reads the current elapsed time from the configured clock.
377 ///
378 /// # Returns
379 /// Current elapsed time units.
380 ///
381 /// # Errors
382 /// Returns [`IdError::TimeBeforeEpoch`] when the clock is before start time.
383 fn current_elapsed_time(&self) -> Result<u64, IdError> {
384 self.elapsed_time_for((self.clock)())
385 }
386}
387
388impl IdGenerator<u64> for SonyflakeGenerator {
389 type Error = IdError;
390
391 /// Generates the next Sonyflake-style ID.
392 fn next_id(&self) -> Result<u64, Self::Error> {
393 let mut state = self
394 .state
395 .lock()
396 .expect("generator state mutex should not be poisoned");
397 let current = self.current_elapsed_time()?;
398
399 if state.timestamp < current {
400 state.timestamp = current;
401 state.sequence = 0;
402 } else {
403 state.sequence = (state.sequence + 1) & self.max_sequence();
404 if state.sequence == 0 {
405 state.timestamp += 1;
406 let overtime = state.timestamp.saturating_sub(current);
407 drop(state);
408 thread::sleep(Duration::from_nanos(
409 (u128::from(overtime) * self.time_unit.as_nanos()) as u64,
410 ));
411 state = self
412 .state
413 .lock()
414 .expect("generator state mutex should not be poisoned");
415 }
416 }
417
418 self.compose(state.timestamp, state.sequence, self.machine_id)
419 }
420}