use crate::{Error, Nulid, Result};
use std::sync::Mutex;
use std::sync::atomic::{AtomicU64, Ordering};
pub trait Clock: Send + Sync {
fn now_nanos(&self) -> Result<u128>;
}
#[derive(Debug, Clone, Copy, Default)]
pub struct SystemClock;
impl Clock for SystemClock {
fn now_nanos(&self) -> Result<u128> {
crate::time::now_nanos()
}
}
#[derive(Debug)]
pub struct MockClock {
nanos: AtomicU64,
}
impl MockClock {
#[must_use]
pub const fn new(initial_nanos: u64) -> Self {
Self {
nanos: AtomicU64::new(initial_nanos),
}
}
#[must_use]
pub fn get(&self) -> u64 {
self.nanos.load(Ordering::SeqCst)
}
pub fn set(&self, nanos: u64) {
self.nanos.store(nanos, Ordering::SeqCst);
}
#[allow(clippy::cast_possible_truncation)]
pub fn advance(&self, duration: core::time::Duration) {
self.nanos
.fetch_add(duration.as_nanos() as u64, Ordering::SeqCst);
}
#[allow(clippy::cast_possible_truncation)]
pub fn regress(&self, duration: core::time::Duration) {
self.nanos
.fetch_sub(duration.as_nanos() as u64, Ordering::SeqCst);
}
}
impl Default for MockClock {
fn default() -> Self {
Self {
nanos: AtomicU64::new(0),
}
}
}
impl Clock for MockClock {
fn now_nanos(&self) -> Result<u128> {
Ok(u128::from(self.nanos.load(Ordering::SeqCst)))
}
}
impl Clock for &MockClock {
fn now_nanos(&self) -> Result<u128> {
Ok(u128::from(self.nanos.load(Ordering::SeqCst)))
}
}
pub trait Rng: Send + Sync {
fn random_u64(&self) -> u64;
}
#[derive(Debug, Clone, Copy, Default)]
pub struct CryptoRng;
impl Rng for CryptoRng {
fn random_u64(&self) -> u64 {
rand::random::<u64>()
}
}
pub struct SeededRng {
rng: Mutex<rand::rngs::StdRng>,
}
impl SeededRng {
#[must_use]
pub fn new(seed: u64) -> Self {
use rand::SeedableRng;
Self {
rng: Mutex::new(rand::rngs::StdRng::seed_from_u64(seed)),
}
}
}
impl core::fmt::Debug for SeededRng {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("SeededRng").finish_non_exhaustive()
}
}
impl Rng for SeededRng {
#[allow(clippy::expect_used)]
fn random_u64(&self) -> u64 {
use rand::RngCore;
self.rng
.lock()
.expect("SeededRng mutex poisoned")
.next_u64()
}
}
impl Rng for &SeededRng {
#[allow(clippy::expect_used)]
fn random_u64(&self) -> u64 {
use rand::RngCore;
self.rng
.lock()
.expect("SeededRng mutex poisoned")
.next_u64()
}
}
#[derive(Debug)]
pub struct SequentialRng {
counter: AtomicU64,
}
impl SequentialRng {
#[must_use]
pub const fn new() -> Self {
Self {
counter: AtomicU64::new(0),
}
}
#[must_use]
pub const fn starting_at(value: u64) -> Self {
Self {
counter: AtomicU64::new(value),
}
}
}
impl Default for SequentialRng {
fn default() -> Self {
Self {
counter: AtomicU64::new(0),
}
}
}
impl Rng for SequentialRng {
fn random_u64(&self) -> u64 {
self.counter.fetch_add(1, Ordering::SeqCst)
}
}
impl Rng for &SequentialRng {
fn random_u64(&self) -> u64 {
self.counter.fetch_add(1, Ordering::SeqCst)
}
}
pub trait NodeId: Send + Sync + Default + Copy {
fn get(&self) -> Option<u16>;
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct NoNodeId;
impl NodeId for NoNodeId {
#[inline]
fn get(&self) -> Option<u16> {
None
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct WithNodeId(u16);
impl WithNodeId {
#[must_use]
pub const fn new(node_id: u16) -> Self {
Self(node_id)
}
#[must_use]
pub const fn value(&self) -> u16 {
self.0
}
}
impl NodeId for WithNodeId {
#[inline]
fn get(&self) -> Option<u16> {
Some(self.0)
}
}
pub struct Generator<C: Clock = SystemClock, R: Rng = CryptoRng, N: NodeId = NoNodeId> {
clock: C,
rng: R,
node_id: N,
state: Mutex<Option<Nulid>>,
}
impl Generator<SystemClock, CryptoRng, NoNodeId> {
#[must_use]
pub const fn new() -> Self {
Self {
clock: SystemClock,
rng: CryptoRng,
node_id: NoNodeId,
state: Mutex::new(None),
}
}
}
impl Default for Generator<SystemClock, CryptoRng, NoNodeId> {
fn default() -> Self {
Self::new()
}
}
impl Generator<SystemClock, CryptoRng, WithNodeId> {
#[must_use]
pub const fn with_node_id(node_id: u16) -> Self {
Self {
clock: SystemClock,
rng: CryptoRng,
node_id: WithNodeId::new(node_id),
state: Mutex::new(None),
}
}
}
impl<C: Clock, R: Rng, N: NodeId> Generator<C, R, N> {
pub fn with_deps(clock: C, rng: R) -> Self {
Self {
clock,
rng,
node_id: N::default(),
state: Mutex::new(None),
}
}
pub const fn with_deps_and_node_id(clock: C, rng: R, node_id: N) -> Self {
Self {
clock,
rng,
node_id,
state: Mutex::new(None),
}
}
pub fn generate(&self) -> Result<Nulid> {
let timestamp = self.clock.now_nanos()?;
let random_bits = self.node_id.get().map_or_else(
|| self.rng.random_u64() & ((1u64 << 60) - 1),
|node_id| {
let random_44 = self.rng.random_u64() & ((1u64 << 44) - 1);
(u64::from(node_id) << 44) | random_44
},
);
let candidate = Nulid::from_nanos(timestamp, random_bits);
let mut state = self.state.lock().map_err(|_| Error::MutexPoisoned)?;
let result = match *state {
None => {
*state = Some(candidate);
Ok(candidate)
}
Some(last_id) => {
if candidate > last_id {
*state = Some(candidate);
Ok(candidate)
} else {
let incremented = last_id.increment().ok_or(Error::Overflow)?;
*state = Some(incremented);
Ok(incremented)
}
}
};
drop(state);
result
}
#[must_use]
pub fn last(&self) -> Option<Nulid> {
self.state.lock().ok().and_then(|s| *s)
}
pub fn reset(&self) {
if let Ok(mut state) = self.state.lock() {
*state = None;
}
}
#[must_use]
pub fn node_id(&self) -> Option<u16> {
self.node_id.get()
}
}
pub type DefaultGenerator = Generator<SystemClock, CryptoRng, NoNodeId>;
pub type DistributedGenerator = Generator<SystemClock, CryptoRng, WithNodeId>;
#[cfg(test)]
mod tests {
use super::*;
use core::time::Duration;
use std::sync::Arc;
use std::thread;
#[test]
fn test_new_generator() {
let generator = Generator::new();
assert!(generator.last().is_none());
assert_eq!(generator.node_id(), None);
}
#[test]
fn test_first_generation() {
let generator = Generator::new();
let id = generator.generate().unwrap();
assert!(id.nanos() > 0);
assert_eq!(generator.last(), Some(id));
}
#[test]
fn test_monotonic_ordering() {
let generator = Generator::new();
let id1 = generator.generate().unwrap();
let id2 = generator.generate().unwrap();
let id3 = generator.generate().unwrap();
assert!(id1 < id2);
assert!(id2 < id3);
}
#[test]
fn test_multiple_generations() {
let generator = Generator::new();
let mut ids = Vec::new();
for _ in 0..100 {
ids.push(generator.generate().unwrap());
}
for i in 1..ids.len() {
assert!(
ids[i - 1] < ids[i],
"IDs not strictly increasing at index {i}"
);
}
}
#[test]
fn test_rapid_generation() {
let generator = Generator::new();
let mut ids = Vec::new();
for _ in 0..1000 {
ids.push(generator.generate().unwrap());
}
for i in 1..ids.len() {
assert_ne!(ids[i - 1], ids[i]);
assert!(ids[i - 1] < ids[i]);
}
}
#[test]
fn test_reset() {
let generator = Generator::new();
let _ = generator.generate().unwrap();
assert!(generator.last().is_some());
generator.reset();
assert!(generator.last().is_none());
}
#[test]
fn test_string_representation_sorted() {
let generator = Generator::new();
let id1 = generator.generate().unwrap();
let id2 = generator.generate().unwrap();
let s1 = id1.to_string();
let s2 = id2.to_string();
assert!(s1 < s2);
}
#[test]
fn test_default() {
let generator = Generator::default();
let id = generator.generate().unwrap();
assert!(id.nanos() > 0);
}
#[test]
fn test_last_tracking() {
let generator = Generator::new();
assert!(generator.last().is_none());
let id1 = generator.generate().unwrap();
assert_eq!(generator.last(), Some(id1));
let id2 = generator.generate().unwrap();
assert_eq!(generator.last(), Some(id2));
assert_ne!(generator.last(), Some(id1));
}
#[test]
fn test_concurrent_safety() {
let generator = Arc::new(Generator::new());
let mut handles = vec![];
for _ in 0..10 {
let gen_clone = Arc::clone(&generator);
let handle = thread::spawn(move || {
let mut ids = Vec::new();
for _ in 0..10 {
ids.push(gen_clone.generate().unwrap());
}
ids
});
handles.push(handle);
}
let mut all_ids = Vec::new();
for handle in handles {
all_ids.extend(handle.join().unwrap());
}
all_ids.sort();
let original_len = all_ids.len();
all_ids.dedup();
assert_eq!(all_ids.len(), original_len);
}
#[test]
fn test_mock_clock_basic() {
let clock = MockClock::new(1_000_000_000);
assert_eq!(clock.get(), 1_000_000_000);
assert_eq!(clock.now_nanos().unwrap(), 1_000_000_000);
}
#[test]
fn test_mock_clock_set() {
let clock = MockClock::new(0);
clock.set(999);
assert_eq!(clock.get(), 999);
}
#[test]
fn test_mock_clock_advance() {
let clock = MockClock::new(1_000_000_000);
clock.advance(Duration::from_millis(100));
assert_eq!(clock.get(), 1_100_000_000);
}
#[test]
fn test_mock_clock_regress() {
let clock = MockClock::new(1_000_000_000);
clock.regress(Duration::from_millis(100));
assert_eq!(clock.get(), 900_000_000);
}
#[test]
fn test_seeded_rng_reproducible() {
let rng1 = SeededRng::new(42);
let rng2 = SeededRng::new(42);
let vals1: Vec<u64> = (0..10).map(|_| rng1.random_u64()).collect();
let vals2: Vec<u64> = (0..10).map(|_| rng2.random_u64()).collect();
assert_eq!(vals1, vals2);
}
#[test]
fn test_seeded_rng_different_seeds() {
let rng1 = SeededRng::new(42);
let rng2 = SeededRng::new(43);
let val1 = rng1.random_u64();
let val2 = rng2.random_u64();
assert_ne!(val1, val2);
}
#[test]
fn test_sequential_rng() {
let rng = SequentialRng::new();
assert_eq!(rng.random_u64(), 0);
assert_eq!(rng.random_u64(), 1);
assert_eq!(rng.random_u64(), 2);
}
#[test]
fn test_sequential_rng_starting_at() {
let rng = SequentialRng::starting_at(100);
assert_eq!(rng.random_u64(), 100);
assert_eq!(rng.random_u64(), 101);
}
#[test]
fn test_no_node_id() {
let n = NoNodeId;
assert_eq!(n.get(), None);
}
#[test]
fn test_with_node_id() {
let n = WithNodeId::new(42);
assert_eq!(n.get(), Some(42));
assert_eq!(n.value(), 42);
}
#[test]
fn test_node_id_max_valid() {
let n = WithNodeId::new(65535);
assert_eq!(n.get(), Some(65535));
}
#[test]
fn test_no_node_id_is_zst() {
assert_eq!(core::mem::size_of::<NoNodeId>(), 0);
}
#[test]
fn test_with_node_id_size() {
assert_eq!(core::mem::size_of::<WithNodeId>(), 2);
}
#[test]
fn test_generator_with_mock_clock() {
let clock = MockClock::new(1_000_000_000);
let rng = SeededRng::new(42);
let generator = Generator::<_, _, NoNodeId>::with_deps(&clock, &rng);
let id1 = generator.generate().unwrap();
assert!(id1.nanos() > 0);
let id2 = generator.generate().unwrap();
assert!(id2 > id1);
}
#[test]
fn test_clock_regression_preserves_monotonicity() {
let clock = MockClock::new(1_000_000_000);
let rng = SeededRng::new(42);
let generator = Generator::<_, _, NoNodeId>::with_deps(&clock, &rng);
let id1 = generator.generate().unwrap();
clock.regress(Duration::from_millis(100));
let id2 = generator.generate().unwrap();
assert!(id2 > id1, "Clock regression must not break monotonicity");
assert_eq!(id2.as_u128(), id1.as_u128() + 1);
}
#[test]
fn test_clock_stall_maintains_ordering() {
let clock = MockClock::new(1_000_000_000);
let rng = SequentialRng::new(); let generator = Generator::<_, _, NoNodeId>::with_deps(&clock, &rng);
let ids: Vec<Nulid> = (0..100).map(|_| generator.generate().unwrap()).collect();
for i in 1..ids.len() {
assert!(ids[i] > ids[i - 1], "ID {} not > ID {}", i, i - 1);
}
}
#[test]
fn test_reproducible_sequence() {
fn generate_sequence(seed: u64) -> Vec<Nulid> {
let clock = MockClock::new(1_000_000_000);
let rng = SeededRng::new(seed);
let generator = Generator::<_, _, NoNodeId>::with_deps(&clock, &rng);
(0..10)
.map(|i| {
clock.set(1_000_000_000 + i * 1000);
generator.generate().unwrap()
})
.collect()
}
let run1 = generate_sequence(42);
let run2 = generate_sequence(42);
assert_eq!(run1, run2, "Seeded generator must be reproducible");
}
#[test]
fn test_distributed_no_collision() {
let clock = MockClock::new(1_000_000_000);
let rng1 = SeededRng::new(42);
let rng2 = SeededRng::new(42);
let gen1 = Generator::with_deps_and_node_id(&clock, &rng1, WithNodeId::new(1));
let gen2 = Generator::with_deps_and_node_id(&clock, &rng2, WithNodeId::new(2));
let id1 = gen1.generate().unwrap();
let id2 = gen2.generate().unwrap();
assert_ne!(id1, id2, "Different nodes must produce different IDs");
}
#[test]
fn test_clock_oscillation() {
let clock = MockClock::new(1000);
let rng = SeededRng::new(42);
let generator = Generator::<_, _, NoNodeId>::with_deps(&clock, &rng);
let mut ids = Vec::new();
for i in 0..20 {
clock.set(if i % 2 == 0 { 1000 } else { 900 });
ids.push(generator.generate().unwrap());
}
for i in 1..ids.len() {
assert!(
ids[i] > ids[i - 1],
"Oscillating clock must not break ordering"
);
}
}
#[test]
fn test_large_clock_jump_forward() {
let clock = MockClock::new(1_000_000_000);
let rng = SeededRng::new(42);
let generator = Generator::<_, _, NoNodeId>::with_deps(&clock, &rng);
let id1 = generator.generate().unwrap();
clock.set(2_000_000_000_000);
let id2 = generator.generate().unwrap();
assert!(id2 > id1, "Forward clock jump should produce larger ID");
assert!(id2.nanos() > id1.nanos());
}
#[test]
fn test_generator_node_id() {
let gen1 = Generator::new();
assert_eq!(gen1.node_id(), None);
let gen2 = Generator::<SystemClock, CryptoRng, WithNodeId>::with_node_id(42);
assert_eq!(gen2.node_id(), Some(42));
}
#[test]
fn test_node_id_embedded_in_nulid() {
let clock = MockClock::new(1_000_000_000);
let rng = SequentialRng::new();
let generator = Generator::with_deps_and_node_id(&clock, &rng, WithNodeId::new(0x123));
let id = generator.generate().unwrap();
let random = id.random();
#[allow(clippy::cast_possible_truncation)]
let extracted_node_id = (random >> 44) as u16;
assert_eq!(extracted_node_id, 0x123);
}
#[test]
fn test_increment_same_timestamp() {
let clock = MockClock::new(1_000_000_000);
let rng = SequentialRng::starting_at(1000);
let generator = Generator::<_, _, NoNodeId>::with_deps(&clock, &rng);
let _id1 = generator.generate().unwrap();
generator.reset();
let rng2 = SequentialRng::starting_at(100); let gen2 = Generator::<_, _, NoNodeId>::with_deps(&clock, &rng2);
let _ = gen2.generate().unwrap();
let clock3 = MockClock::new(1_000_000_000);
let rng3 = SequentialRng::new(); let gen3 = Generator::<_, _, NoNodeId>::with_deps(&clock3, &rng3);
let first = gen3.generate().unwrap(); let second = gen3.generate().unwrap();
assert!(second > first);
}
}