use crate::types::{Key, TypeName, Value};
use core::cmp::Ordering;
use core::fmt;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct HybridLogicalClock(u64);
const LOGICAL_BITS: u32 = 16;
const LOGICAL_MASK: u64 = (1 << LOGICAL_BITS) - 1; const PHYSICAL_SHIFT: u32 = LOGICAL_BITS;
#[cfg(feature = "std")]
fn wall_clock_ms() -> u64 {
#[allow(clippy::cast_possible_truncation)]
let ms = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or(std::time::Duration::ZERO)
.as_millis() as u64;
ms
}
#[cfg(not(feature = "std"))]
fn wall_clock_ms() -> u64 {
0
}
impl HybridLogicalClock {
pub const ZERO: Self = Self(0);
pub const MIN: Self = Self(0);
pub const MAX: Self = Self(u64::MAX);
#[must_use]
pub fn now() -> Self {
Self(wall_clock_ms() << PHYSICAL_SHIFT)
}
#[must_use]
pub fn from_raw(raw: u64) -> Self {
Self(raw)
}
#[must_use]
pub fn to_raw(self) -> u64 {
self.0
}
#[must_use]
pub fn physical_ms(self) -> u64 {
self.0 >> PHYSICAL_SHIFT
}
#[must_use]
pub fn logical(self) -> u16 {
#[allow(clippy::cast_possible_truncation)]
let l = (self.0 & LOGICAL_MASK) as u16;
l
}
#[must_use]
pub fn from_wall_ns(ns: u64) -> Self {
let ms = ns / 1_000_000;
Self(ms << PHYSICAL_SHIFT)
}
#[must_use]
pub fn from_parts(physical_ms: u64, logical: u16) -> Self {
debug_assert!(
physical_ms < (1u64 << 48),
"physical_ms {physical_ms} exceeds 48-bit HLC capacity (max {})",
(1u64 << 48) - 1
);
Self((physical_ms << PHYSICAL_SHIFT) | u64::from(logical))
}
#[must_use]
pub fn tick(self) -> Self {
if self.logical() < u16::MAX {
Self(self.0 + 1)
} else {
Self((self.physical_ms() + 1) << PHYSICAL_SHIFT)
}
}
#[must_use]
pub fn advance(self) -> Self {
let now_ms = wall_clock_ms();
let self_ms = self.physical_ms();
if now_ms > self_ms {
Self(now_ms << PHYSICAL_SHIFT)
} else {
self.tick()
}
}
#[must_use]
pub fn merge(self, other: Self) -> Self {
let now_ms = wall_clock_ms();
let self_ms = self.physical_ms();
let other_ms = other.physical_ms();
let max_physical = now_ms.max(self_ms).max(other_ms);
if max_physical == now_ms && now_ms > self_ms && now_ms > other_ms {
Self(now_ms << PHYSICAL_SHIFT)
} else if self_ms == other_ms && self_ms == max_physical {
let max_logical = self.logical().max(other.logical());
if max_logical < u16::MAX {
Self::from_parts(max_physical, max_logical + 1)
} else {
Self((max_physical + 1) << PHYSICAL_SHIFT)
}
} else if self_ms == max_physical {
self.tick()
} else if other_ms == max_physical {
other.tick()
} else if self_ms == now_ms {
self.tick()
} else if other_ms == now_ms {
other.tick()
} else {
Self(now_ms << PHYSICAL_SHIFT)
}
}
}
impl fmt::Debug for HybridLogicalClock {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"HLC(physical={}ms, logical={})",
self.physical_ms(),
self.logical()
)
}
}
impl fmt::Display for HybridLogicalClock {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}:{}", self.physical_ms(), self.logical())
}
}
impl Value for HybridLogicalClock {
type SelfType<'a>
= HybridLogicalClock
where
Self: 'a;
type AsBytes<'a>
= [u8; 8]
where
Self: 'a;
fn fixed_width() -> Option<usize> {
Some(8)
}
#[allow(clippy::big_endian_bytes)]
fn from_bytes<'a>(data: &'a [u8]) -> Self::SelfType<'a>
where
Self: 'a,
{
Self(u64::from_be_bytes(data[..8].try_into().unwrap()))
}
#[allow(clippy::big_endian_bytes)]
fn as_bytes<'a, 'b: 'a>(value: &'a Self::SelfType<'b>) -> Self::AsBytes<'a>
where
Self: 'b,
{
value.0.to_be_bytes()
}
fn type_name() -> TypeName {
TypeName::internal("redb::temporal::HLC")
}
}
impl Key for HybridLogicalClock {
fn compare(data1: &[u8], data2: &[u8]) -> Ordering {
data1[..8].cmp(&data2[..8])
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn hlc_physical_and_logical_extraction() {
let hlc = HybridLogicalClock::from_parts(1000, 42);
assert_eq!(hlc.physical_ms(), 1000);
assert_eq!(hlc.logical(), 42);
}
#[test]
fn hlc_tick_increments_logical() {
let hlc = HybridLogicalClock::from_parts(1000, 0);
let ticked = hlc.tick();
assert_eq!(ticked.physical_ms(), 1000);
assert_eq!(ticked.logical(), 1);
}
#[test]
fn hlc_tick_overflow_advances_physical() {
let hlc = HybridLogicalClock::from_parts(1000, u16::MAX);
let ticked = hlc.tick();
assert_eq!(ticked.physical_ms(), 1001);
assert_eq!(ticked.logical(), 0);
}
#[test]
fn hlc_ordering_is_causal() {
let a = HybridLogicalClock::from_parts(100, 0);
let b = HybridLogicalClock::from_parts(100, 1);
let c = HybridLogicalClock::from_parts(101, 0);
assert!(a < b);
assert!(b < c);
assert!(a < c);
}
#[test]
fn hlc_from_wall_ns() {
let ns = 1_700_000_000_000_000_000u64; let hlc = HybridLogicalClock::from_wall_ns(ns);
assert_eq!(hlc.physical_ms(), 1_700_000_000_000);
assert_eq!(hlc.logical(), 0);
}
#[test]
fn hlc_now_is_monotonic() {
let a = HybridLogicalClock::now();
let b = HybridLogicalClock::now();
assert!(b >= a);
}
#[test]
fn hlc_advance_monotonic() {
let old = HybridLogicalClock::from_parts(0, 0);
let advanced = old.advance();
assert!(advanced > old);
}
#[test]
fn hlc_merge_produces_greater() {
let a = HybridLogicalClock::from_parts(1000, 5);
let b = HybridLogicalClock::from_parts(1000, 10);
let merged = a.merge(b);
assert!(merged > a);
assert!(merged > b);
}
#[test]
fn hlc_raw_roundtrip() {
let hlc = HybridLogicalClock::from_parts(123456, 789);
let raw = hlc.to_raw();
let recovered = HybridLogicalClock::from_raw(raw);
assert_eq!(hlc, recovered);
}
#[test]
fn hlc_key_trait_roundtrip() {
let hlc = HybridLogicalClock::from_parts(42000, 7);
let bytes = HybridLogicalClock::as_bytes(&hlc);
let recovered = HybridLogicalClock::from_bytes(bytes.as_ref());
assert_eq!(hlc, recovered);
}
#[test]
fn hlc_key_compare() {
let a = HybridLogicalClock::from_parts(100, 5);
let b = HybridLogicalClock::from_parts(200, 0);
let a_bytes = HybridLogicalClock::as_bytes(&a);
let b_bytes = HybridLogicalClock::as_bytes(&b);
assert_eq!(
HybridLogicalClock::compare(a_bytes.as_ref(), b_bytes.as_ref()),
Ordering::Less
);
}
}