main
Caleb Publish 0.2.0 cbe04a0 11h ago
//! The router's clock counts work done, not seconds passed.
//!
//! Every budget in the router (the run's limit, a connection's turn, a repair's share, a closing
//! pass's grant) used to be wall-clock time, so the same board routed differently depending on how
//! busy the machine was: on benchmark C one binary gave 301 joins in one run and 294 in another
//! (2026-11-06 and 11-09), and no change smaller than that could be measured. Here a "second" is a
//! fixed amount of search work, so the same input always gives the same board, on any machine and
//! under any load. `Instant` has the few methods of `std::time::Instant` the router uses, so the
//! budgets read as they did.
//!
//! What counts as work: one unit per search node expanded, a fixed charge per search started, and
//! one unit each time the clock is read, so a loop that only watches the clock still ends.

use std::cell::Cell;
use std::time::Duration;

/// Work units to one clock second. Measured 2026-11-09 on the development machine, each board
/// routed alone: benchmark C 164,090 units per wall second, benchmark B 239,069, piprobe_v13
/// 344,868 (a denser board spends longer on each search step). Set to benchmark C's, the board whose
/// run is bound by its limit, so its 790 s means the work it meant before; the smaller boards
/// finish before their limits. A fixed number on purpose: changing it changes every board's result.
pub const UNITS_PER_SECOND: f64 = 164_000.0;
/// What starting one search costs, in units: building its lines and tables.
pub const SEARCH_START: u64 = 200;

thread_local! {
    static WORK: Cell<u64> = const { Cell::new(0) };
}

pub fn tick(n: u64) {
    WORK.with(|w| w.set(w.get() + n));
}

pub fn units() -> u64 {
    WORK.with(|w| w.get())
}

#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
pub struct Instant(f64);

impl Instant {
    pub fn now() -> Instant {
        tick(1);
        Instant(units() as f64 / UNITS_PER_SECOND)
    }

    pub fn elapsed(&self) -> Duration {
        Instant::now().saturating_duration_since(*self)
    }

    /// The earlier of two instants.
    pub fn min(self, other: Instant) -> Instant {
        if other.0 < self.0 { other } else { self }
    }

    pub fn saturating_duration_since(&self, earlier: Instant) -> Duration {
        Duration::from_secs_f64((self.0 - earlier.0).max(0.0))
    }
}

impl std::ops::Add<Duration> for Instant {
    type Output = Instant;
    fn add(self, d: Duration) -> Instant {
        Instant(self.0 + d.as_secs_f64())
    }
}