use core::{cmp, marker::PhantomData};
use portable_atomic::{AtomicU64, Ordering};
#[cfg(feature = "tracing")]
use tracing::instrument;
use crate::{
generator::{Poll, Result, SnowflakeGenerator},
id::SnowflakeId,
time::TimeSource,
};
pub struct AtomicSnowflakeGenerator<ID, T>
where
ID: SnowflakeId<Ty = u64>,
T: TimeSource<ID::Ty>,
{
#[cfg(feature = "cache-padded")]
state: crossbeam_utils::CachePadded<AtomicU64>,
#[cfg(not(feature = "cache-padded"))]
state: AtomicU64,
time: T,
_id: PhantomData<ID>,
}
impl<ID, T> AtomicSnowflakeGenerator<ID, T>
where
ID: SnowflakeId<Ty = u64>,
T: TimeSource<ID::Ty>,
{
pub fn new(machine_id: ID::Ty, time: T) -> Self {
Self::from_components(ID::ZERO, machine_id, ID::ZERO, time)
}
pub fn from_components(
timestamp: ID::Ty,
machine_id: ID::Ty,
sequence: ID::Ty,
time: T,
) -> Self {
let initial = ID::from_components(timestamp, machine_id, sequence);
Self {
#[cfg(feature = "cache-padded")]
state: crossbeam_utils::CachePadded::new(AtomicU64::new(initial.to_raw())),
#[cfg(not(feature = "cache-padded"))]
state: AtomicU64::new(initial.to_raw()),
time,
_id: PhantomData,
}
}
#[cfg_attr(feature = "tracing", instrument(level = "trace", skip(self, f)))]
pub fn next_id(&self, mut f: impl FnMut(ID::Ty)) -> ID {
loop {
match self.poll_id() {
Poll::Ready { id } => break id,
Poll::Pending { yield_for } => f(yield_for),
}
}
}
#[cfg_attr(feature = "tracing", instrument(level = "trace", skip(self)))]
pub fn poll_id(&self) -> Poll<ID> {
let now = self.time.current_millis();
let current_raw = self.state.load(Ordering::Relaxed);
let current_id = ID::from_raw(current_raw);
let current_ts = current_id.timestamp();
let next_id = match now.cmp(¤t_ts) {
cmp::Ordering::Equal => {
if current_id.has_sequence_room() {
current_id.increment_sequence()
} else {
return Poll::Pending { yield_for: ID::ONE };
}
}
cmp::Ordering::Greater => current_id.rollover_to_timestamp(now),
cmp::Ordering::Less => {
return Self::cold_clock_behind(now, current_ts);
}
};
let next_raw = next_id.to_raw();
if self
.state
.compare_exchange(current_raw, next_raw, Ordering::Relaxed, Ordering::Relaxed)
.is_ok()
{
Poll::Ready { id: next_id }
} else {
Poll::Pending {
yield_for: ID::ZERO,
}
}
}
#[cold]
#[inline(never)]
fn cold_clock_behind(now: ID::Ty, current_ts: ID::Ty) -> Poll<ID> {
let yield_for = current_ts - now;
debug_assert!(yield_for >= ID::ZERO);
Poll::Pending { yield_for }
}
}
impl<ID, T> SnowflakeGenerator<ID, T> for AtomicSnowflakeGenerator<ID, T>
where
ID: SnowflakeId<Ty = u64>,
T: TimeSource<u64>,
{
type Err = core::convert::Infallible;
fn new(machine_id: ID::Ty, time: T) -> Self {
Self::new(machine_id, time)
}
fn next_id(&self, f: impl FnMut(ID::Ty)) -> ID {
self.next_id(f)
}
fn try_next_id(&self, f: impl FnMut(ID::Ty)) -> Result<ID, Self::Err> {
Ok(self.next_id(f))
}
fn poll_id(&self) -> Poll<ID> {
self.poll_id()
}
fn try_poll_id(&self) -> Result<Poll<ID>, Self::Err> {
Ok(self.poll_id())
}
}