//! The clock this surface commits against.
//!
//! Normally wall time. But a snapshot's address has its timestamp hashed into
//! it — see `jac_object::encode::snapshot_id` — so replaying a stored history
//! against wall time produces different addresses for the same work. That is
//! not a cosmetic difference: it means the ids written to disk describe
//! something the engine can no longer reproduce, which makes them decorative.
//!
//! So loading a repo drives this clock from the recorded timestamps instead.
//! Every snapshot is then committed at the instant it actually happened, the
//! addresses come out identical, and `store::load` can *prove* the round trip
//! rather than assert it.
//!
//! Once a replay runs out of scripted instants it falls back to wall time,
//! which is what any write after the load should get.
use std::collections::VecDeque;
use std::sync::Mutex;
use jac_core::{Clock, SystemClock, Timestamp};
/// Wall time, or a scripted sequence for replaying a stored history.
/// A mutex rather than a cell: this lives inside the shared application state,
/// which axum requires to be `Sync`. It is held for a pop and nothing else —
/// never across an await.
#[derive(Debug, Default)]
pub struct HostClock {
/// Instants to hand out, oldest first. Empty means wall time.
scripted: Mutex<VecDeque<i64>>,
}
impl HostClock {
/// Wall time.
#[must_use]
pub const fn system() -> Self {
Self {
scripted: Mutex::new(VecDeque::new()),
}
}
/// Hands out `instants` in order, then falls back to wall time.
#[must_use]
pub fn replaying(instants: impl IntoIterator<Item = i64>) -> Self {
Self {
scripted: Mutex::new(instants.into_iter().collect()),
}
}
/// How many scripted instants remain — a replay that ends early left
/// snapshots to be stamped with wall time, which is worth noticing.
#[must_use]
pub fn remaining(&self) -> usize {
self.scripted.lock().map_or(0, |q| q.len())
}
}
impl Clock for HostClock {
fn now(&self) -> Timestamp {
// A poisoned lock falls back to wall time rather than panicking: a
// clock that brings the surface down over bookkeeping is worse than a
// clock that is briefly wrong.
self.scripted.lock().map_or_else(
|_| SystemClock.now(),
|mut queue| {
queue
.pop_front()
.map_or_else(|| SystemClock.now(), Timestamp::from_millis)
},
)
}
}