use std::cmp::Ordering;
use std::hint::spin_loop;
use std::sync::atomic::{AtomicU64, Ordering as AtomicOrdering};
use std::time::{SystemTime, UNIX_EPOCH};
const EPOCH: u128 = 1_767_225_600_000;
const SEQUENCE_BITS: u32 = 22;
const SEQUENCE_MASK: u64 = (1 << SEQUENCE_BITS) - 1;
#[derive(Debug)]
pub struct SnowflakeGenerator {
last_state: AtomicU64,
}
impl SnowflakeGenerator {
pub fn new() -> Self {
let now_ms = unix_timestamp_now_ms();
let initial_id = Snowflake::new(now_ms, 0);
Self {
last_state: AtomicU64::new(initial_id.0),
}
}
pub fn generate(&self) -> Snowflake {
let mut current_bits = self.last_state.load(AtomicOrdering::Relaxed);
loop {
let last_ts = current_bits >> SEQUENCE_BITS;
let last_seq = current_bits & SEQUENCE_MASK;
let now_ms = unix_timestamp_now_ms();
let now_ts = u64::try_from(now_ms.saturating_sub(EPOCH))
.expect("Timestamp exceeds u64 capacity");
let next_bits = match now_ts.cmp(&last_ts) {
Ordering::Greater => now_ts << SEQUENCE_BITS,
Ordering::Equal => {
let next_seq = last_seq + 1;
if next_seq > SEQUENCE_MASK {
spin_loop();
current_bits = self.last_state.load(AtomicOrdering::Relaxed);
continue;
}
(last_ts << SEQUENCE_BITS) | next_seq
}
Ordering::Less => {
spin_loop();
current_bits = self.last_state.load(AtomicOrdering::Relaxed);
continue;
}
};
match self.last_state.compare_exchange_weak(
current_bits,
next_bits,
AtomicOrdering::SeqCst,
AtomicOrdering::Relaxed,
) {
Ok(_) => return Snowflake(next_bits),
Err(fresh_bits) => current_bits = fresh_bits,
}
}
}
}
#[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Snowflake(pub u64);
impl Snowflake {
fn new(timestamp: u128, sequence: u64) -> Self {
let shifted = u64::try_from(timestamp - EPOCH).expect("Timestamp overflow") << SEQUENCE_BITS;
Snowflake(shifted | (sequence & SEQUENCE_MASK))
}
pub fn extract_unix_timestamp(&self) -> u128 {
u128::from(self.0 >> SEQUENCE_BITS) + EPOCH
}
}
impl From<Snowflake> for u64 {
fn from(value: Snowflake) -> Self {
value.0
}
}
pub fn unix_timestamp_now_ms() -> u128 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("Time went backwards")
.as_millis()
}