use std::sync::Arc;
use std::time::{SystemTime, UNIX_EPOCH};
pub trait IdGenerator: Send + Sync + std::fmt::Debug {
fn next_id(&self) -> String;
}
pub trait Clock: Send + Sync + std::fmt::Debug {
fn now_ms(&self) -> u64;
}
#[derive(Debug, Clone)]
pub struct HostEnv {
pub id_generator: Arc<dyn IdGenerator>,
pub clock: Arc<dyn Clock>,
}
impl HostEnv {
pub fn new(id_generator: Arc<dyn IdGenerator>, clock: Arc<dyn Clock>) -> Self {
Self {
id_generator,
clock,
}
}
pub fn system() -> Self {
Self {
id_generator: Arc::new(SystemIdGenerator),
clock: Arc::new(SystemClock),
}
}
pub fn next_id(&self) -> String {
self.id_generator.next_id()
}
pub fn now_ms(&self) -> u64 {
self.clock.now_ms()
}
}
impl Default for HostEnv {
fn default() -> Self {
Self::system()
}
}
#[derive(Debug, Default, Clone, Copy)]
pub struct SystemIdGenerator;
impl IdGenerator for SystemIdGenerator {
fn next_id(&self) -> String {
uuid::Uuid::new_v4().to_string()
}
}
#[derive(Debug, Default, Clone, Copy)]
pub struct SystemClock;
impl Clock for SystemClock {
fn now_ms(&self) -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_millis() as u64)
.unwrap_or(0)
}
}
#[derive(Debug, Default)]
pub struct SequentialIdGenerator {
prefix: String,
counter: std::sync::atomic::AtomicU64,
}
impl SequentialIdGenerator {
pub fn new(prefix: impl Into<String>) -> Self {
Self {
prefix: prefix.into(),
counter: std::sync::atomic::AtomicU64::new(0),
}
}
}
impl IdGenerator for SequentialIdGenerator {
fn next_id(&self) -> String {
let n = self
.counter
.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
if self.prefix.is_empty() {
n.to_string()
} else {
format!("{}-{}", self.prefix, n)
}
}
}
#[derive(Debug)]
pub struct FixedClock {
now_ms: std::sync::atomic::AtomicU64,
}
impl FixedClock {
pub fn new(now_ms: u64) -> Self {
Self {
now_ms: std::sync::atomic::AtomicU64::new(now_ms),
}
}
pub fn set(&self, now_ms: u64) -> u64 {
self.now_ms
.swap(now_ms, std::sync::atomic::Ordering::SeqCst)
}
pub fn advance(&self, delta_ms: u64) {
self.now_ms
.fetch_add(delta_ms, std::sync::atomic::Ordering::SeqCst);
}
}
impl Clock for FixedClock {
fn now_ms(&self) -> u64 {
self.now_ms.load(std::sync::atomic::Ordering::SeqCst)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn system_host_env_produces_nonempty_ids_and_increasing_time() {
let env = HostEnv::system();
let a = env.next_id();
let b = env.next_id();
assert!(!a.is_empty());
assert!(!b.is_empty());
assert_ne!(a, b);
let t1 = env.now_ms();
std::thread::sleep(std::time::Duration::from_millis(2));
let t2 = env.now_ms();
assert!(t2 >= t1);
}
#[test]
fn sequential_id_generator_is_deterministic() {
let gen = SequentialIdGenerator::new("run");
assert_eq!(gen.next_id(), "run-0");
assert_eq!(gen.next_id(), "run-1");
assert_eq!(gen.next_id(), "run-2");
}
#[test]
fn fixed_clock_is_controllable() {
let clock = FixedClock::new(1000);
assert_eq!(clock.now_ms(), 1000);
clock.advance(500);
assert_eq!(clock.now_ms(), 1500);
assert_eq!(clock.set(0), 1500);
assert_eq!(clock.now_ms(), 0);
}
}