use crate::clock::Clock;
use crate::quality::{ClockSource, SyncQuality};
use crate::timestamp::{Timestamp, WallTime};
#[cfg(not(target_has_atomic = "64"))]
use core::cell::Cell;
#[cfg(target_has_atomic = "64")]
use core::sync::atomic::{AtomicI64, AtomicU64, Ordering};
use core::time::Duration;
#[cfg(not(target_has_atomic = "64"))]
use critical_section::Mutex;
#[derive(Debug)]
struct MonoCell {
#[cfg(target_has_atomic = "64")]
ns: AtomicU64,
#[cfg(not(target_has_atomic = "64"))]
ns: Mutex<Cell<u64>>,
}
impl MonoCell {
const fn new(start_ns: u64) -> Self {
Self {
#[cfg(target_has_atomic = "64")]
ns: AtomicU64::new(start_ns),
#[cfg(not(target_has_atomic = "64"))]
ns: Mutex::new(Cell::new(start_ns)),
}
}
fn saturating_add(&self, dt: u64) {
#[cfg(target_has_atomic = "64")]
{
let mut cur = self.ns.load(Ordering::Relaxed);
loop {
let next = cur.saturating_add(dt);
match self
.ns
.compare_exchange_weak(cur, next, Ordering::AcqRel, Ordering::Relaxed)
{
Ok(_) => return,
Err(seen) => cur = seen,
}
}
}
#[cfg(not(target_has_atomic = "64"))]
{
critical_section::with(|cs| {
let c = self.ns.borrow(cs);
c.set(c.get().saturating_add(dt));
});
}
}
fn load(&self) -> u64 {
#[cfg(target_has_atomic = "64")]
{
self.ns.load(Ordering::Acquire)
}
#[cfg(not(target_has_atomic = "64"))]
{
critical_section::with(|cs| self.ns.borrow(cs).get())
}
}
}
#[derive(Debug)]
struct WallCell {
#[cfg(target_has_atomic = "64")]
ns: AtomicI64,
#[cfg(not(target_has_atomic = "64"))]
ns: Mutex<Cell<i64>>,
}
impl WallCell {
const fn new(start_ns: i64) -> Self {
Self {
#[cfg(target_has_atomic = "64")]
ns: AtomicI64::new(start_ns),
#[cfg(not(target_has_atomic = "64"))]
ns: Mutex::new(Cell::new(start_ns)),
}
}
fn saturating_add(&self, dt: i64) {
#[cfg(target_has_atomic = "64")]
{
let mut cur = self.ns.load(Ordering::Relaxed);
loop {
let next = cur.saturating_add(dt);
match self
.ns
.compare_exchange_weak(cur, next, Ordering::AcqRel, Ordering::Relaxed)
{
Ok(_) => return,
Err(seen) => cur = seen,
}
}
}
#[cfg(not(target_has_atomic = "64"))]
{
critical_section::with(|cs| {
let c = self.ns.borrow(cs);
c.set(c.get().saturating_add(dt));
});
}
}
fn load(&self) -> i64 {
#[cfg(target_has_atomic = "64")]
{
self.ns.load(Ordering::Acquire)
}
#[cfg(not(target_has_atomic = "64"))]
{
critical_section::with(|cs| self.ns.borrow(cs).get())
}
}
}
#[derive(Debug)]
pub struct MockClock {
mono: MonoCell,
wall: WallCell,
}
impl MockClock {
#[must_use]
pub fn start_at(mono_ns: u64, wall_ns: u64) -> Self {
Self {
mono: MonoCell::new(mono_ns),
wall: WallCell::new(clamp_i64(wall_ns)),
}
}
#[must_use]
pub fn new() -> Self {
Self::start_at(0, 0)
}
pub fn advance(&self, dt: Duration) {
let ns = u64::try_from(dt.as_nanos()).unwrap_or(u64::MAX);
self.mono.saturating_add(ns);
}
pub fn advance_nanos(&self, ns: u64) {
self.mono.saturating_add(ns);
}
pub fn step_wall(&self, offset_ns: i64) {
self.wall.saturating_add(offset_ns);
}
#[must_use]
pub fn wall_nanos(&self) -> u64 {
let ns = self.wall.load();
u64::try_from(ns).unwrap_or(0)
}
#[must_use]
pub fn mono_nanos(&self) -> u64 {
self.mono.load()
}
}
impl Default for MockClock {
fn default() -> Self {
Self::new()
}
}
impl Clock for MockClock {
fn now(&self) -> Timestamp {
Timestamp {
wall: WallTime::from_unix_nanos(self.wall_nanos()),
mono: self.mono_nanos(),
}
}
fn quality(&self) -> SyncQuality {
SyncQuality::Fallback
}
fn source(&self) -> ClockSource {
ClockSource::Mock
}
}
#[allow(clippy::cast_possible_wrap)]
const fn clamp_i64(wall_ns: u64) -> i64 {
if wall_ns > i64::MAX as u64 {
i64::MAX
} else {
wall_ns as i64
}
}
#[cfg(test)]
mod tests {
use super::{MockClock, WallTime};
use crate::clock::Clock;
use crate::quality::{ClockSource, SyncQuality};
use core::time::Duration;
#[test]
fn mock_sequence_is_reproducible() {
let run = || {
let c = MockClock::start_at(1_000, 5_000);
c.advance(Duration::from_millis(2));
c.advance_nanos(500);
c.step_wall(-1_234);
c.now()
};
let a = run();
let b = run();
assert_eq!(a, b);
assert_eq!(a.mono, 2_000_500 + 1_000);
assert_eq!(a.wall, WallTime::from_unix_nanos(5_000 - 1_234));
}
#[test]
fn mock_step_wall_independent_of_mono() {
let c = MockClock::start_at(0, 1_000_000_000);
c.advance_nanos(10);
c.step_wall(-2_000_000_000); let t = c.now();
assert_eq!(t.mono, 10);
assert_eq!(t.wall.unix_nanos(), 0); assert_ne!(t.wall.unix_nanos(), t.mono);
}
#[test]
fn mock_saturates_at_bounds() {
let c = MockClock::start_at(u64::MAX - 5, 0);
c.advance_nanos(100);
assert_eq!(c.mono_nanos(), u64::MAX);
c.step_wall(i64::MIN);
c.step_wall(i64::MIN);
assert_eq!(c.wall_nanos(), 0);
}
#[test]
fn mock_reports_source_and_quality() {
let c = MockClock::new();
assert_eq!(c.source(), ClockSource::Mock);
assert_eq!(c.quality(), SyncQuality::Fallback);
assert_eq!(c.now().mono, 0);
}
}