use std::time::Instant;
use crate::SequenceExhausted;
use crate::prng::WyRand;
use crate::types::Ulid;
#[derive(Debug, Clone)]
pub struct UlidGenerator {
epoch: Instant,
unix_base_ms: u64,
rng: WyRand,
last_ts_ms: u64,
last_rand_hi: u16,
last_rand_lo: u64,
}
impl UlidGenerator {
#[inline]
pub fn new(epoch: Instant, unix_base_ms: u64, seed: u64) -> Self {
Self {
epoch,
unix_base_ms,
rng: WyRand::new(seed),
last_ts_ms: u64::MAX,
last_rand_hi: 0,
last_rand_lo: 0,
}
}
pub fn from_entropy(epoch: Instant, unix_base_ms: u64) -> Self {
Self {
epoch,
unix_base_ms,
rng: WyRand::from_entropy(),
last_ts_ms: u64::MAX,
last_rand_hi: 0,
last_rand_lo: 0,
}
}
#[inline]
pub fn next(&mut self, now: Instant) -> Ulid {
let offset_ms = now.duration_since(self.epoch).as_millis() as u64;
let ts_ms = self.unix_base_ms.wrapping_add(offset_ms);
let (rand_hi, rand_lo) = if ts_ms == self.last_ts_ms {
let (new_lo, carry) = self.last_rand_lo.overflowing_add(1);
let new_hi = if carry {
self.last_rand_hi.wrapping_add(1)
} else {
self.last_rand_hi
};
self.last_rand_hi = new_hi;
self.last_rand_lo = new_lo;
(new_hi, new_lo)
} else {
self.last_ts_ms = ts_ms;
let rand_hi = (self.rng.next_u64() & 0xFFFF) as u16;
let rand_lo = self.rng.next_u64();
self.last_rand_hi = rand_hi;
self.last_rand_lo = rand_lo;
(rand_hi, rand_lo)
};
Ulid::from_raw(ts_ms, rand_hi, rand_lo)
}
#[inline]
pub fn try_next(&mut self, now: Instant) -> Result<Ulid, SequenceExhausted> {
let offset_ms = now.duration_since(self.epoch).as_millis() as u64;
let ts_ms = self.unix_base_ms.wrapping_add(offset_ms);
let (rand_hi, rand_lo) = if ts_ms == self.last_ts_ms {
let (new_lo, carry) = self.last_rand_lo.overflowing_add(1);
let new_hi = if carry {
let (hi, hi_carry) = self.last_rand_hi.overflowing_add(1);
if hi_carry {
return Err(SequenceExhausted {
tick: ts_ms,
max_sequence: u64::MAX,
});
}
hi
} else {
self.last_rand_hi
};
self.last_rand_hi = new_hi;
self.last_rand_lo = new_lo;
(new_hi, new_lo)
} else {
self.last_ts_ms = ts_ms;
let rand_hi = (self.rng.next_u64() & 0xFFFF) as u16;
let rand_lo = self.rng.next_u64();
self.last_rand_hi = rand_hi;
self.last_rand_lo = rand_lo;
(rand_hi, rand_lo)
};
Ok(Ulid::from_raw(ts_ms, rand_hi, rand_lo))
}
#[inline]
pub fn epoch(&self) -> Instant {
self.epoch
}
#[inline]
pub const fn unix_base_ms(&self) -> u64 {
self.unix_base_ms
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
fn test_generator() -> UlidGenerator {
let epoch = Instant::now();
let unix_base = 1_700_000_000_000u64;
UlidGenerator::new(epoch, unix_base, 42)
}
#[test]
fn basic_generation() {
let epoch = Instant::now();
let unix_base = 1_700_000_000_000u64;
let mut generator = UlidGenerator::new(epoch, unix_base, 42);
let ulid = generator.next(epoch);
assert_eq!(ulid.len(), 26);
for c in ulid.as_str().chars() {
assert!(
c.is_ascii_digit() || c.is_ascii_uppercase(),
"Invalid char: {}",
c
);
}
}
#[test]
fn deterministic_with_seed() {
let epoch = Instant::now();
let unix_base = 1_700_000_000_000u64;
let mut gen1 = UlidGenerator::new(epoch, unix_base, 42);
let mut gen2 = UlidGenerator::new(epoch, unix_base, 42);
assert_eq!(gen1.next(epoch).as_str(), gen2.next(epoch).as_str());
}
#[test]
fn timestamp_encoded() {
let epoch = Instant::now();
let unix_base = 1_700_000_000_000u64;
let mut generator = UlidGenerator::new(epoch, unix_base, 42);
let ulid = generator.next(epoch);
assert_eq!(ulid.timestamp_ms(), unix_base);
let later = epoch + Duration::from_millis(100);
let mut gen2 = UlidGenerator::new(epoch, unix_base, 42);
let ulid2 = gen2.next(later);
assert_eq!(ulid2.timestamp_ms(), unix_base + 100);
}
#[test]
fn monotonic_within_ms() {
let mut generator = test_generator();
let epoch = generator.epoch();
let ulid1 = generator.next(epoch);
let ulid2 = generator.next(epoch);
let ulid3 = generator.next(epoch);
assert!(ulid1.as_str() < ulid2.as_str());
assert!(ulid2.as_str() < ulid3.as_str());
}
#[test]
fn random_roundtrip() {
let epoch = Instant::now();
let unix_base = 1_700_000_000_000u64;
let mut generator = UlidGenerator::new(epoch, unix_base, 42);
let ulid = generator.next(epoch);
let (rand_hi, rand_lo) = ulid.random();
let reconstructed = Ulid::from_raw(unix_base, rand_hi, rand_lo);
assert_eq!(ulid.as_str(), reconstructed.as_str());
}
#[test]
fn time_ordering() {
let mut generator = test_generator();
let epoch = generator.epoch();
let mut ulids = Vec::new();
for i in 0..100 {
let now = epoch + Duration::from_millis(i);
ulids.push(generator.next(now));
}
for i in 1..ulids.len() {
assert!(ulids[i].as_str() > ulids[i - 1].as_str());
}
}
#[test]
fn from_entropy_works() {
let epoch = Instant::now();
let unix_base = 1_700_000_000_000u64;
let mut generator = UlidGenerator::from_entropy(epoch, unix_base);
let ulid = generator.next(epoch);
assert_eq!(ulid.len(), 26);
}
}