use std::cell::Cell;
use std::time::{Duration, Instant};
pub const UNITS_PER_MS: u64 = 775_000;
thread_local! {
static EPOCH: Cell<Option<(Instant, u64)>> = const { Cell::new(None) };
static SPENT: Cell<u64> = const { Cell::new(0) };
}
#[must_use = "the meter is armed only while the guard is alive"]
pub struct Guard {
previous: Option<(Instant, u64)>,
}
impl Drop for Guard {
fn drop(&mut self) {
EPOCH.with(|c| c.set(self.previous));
}
}
pub fn arm(epoch: Instant) -> Guard {
let previous = EPOCH.with(Cell::get);
EPOCH.with(|c| c.set(Some((epoch, units_spent()))));
Guard { previous }
}
#[inline]
pub fn is_armed() -> bool {
EPOCH.with(Cell::get).is_some()
}
#[inline]
pub fn charge(units: u64) {
if is_armed() {
SPENT.with(|m| m.set(m.get().saturating_add(units)));
}
}
#[inline]
pub fn units_spent() -> u64 {
SPENT.with(Cell::get)
}
pub fn now() -> Instant {
match EPOCH.with(Cell::get) {
None => Instant::now(),
Some((epoch, mark)) => {
let ms = units_spent().saturating_sub(mark) / UNITS_PER_MS;
saturating_add_milliseconds(epoch, ms)
}
}
}
fn saturating_add_milliseconds(epoch: Instant, milliseconds: u64) -> Instant {
if let Some(result) = epoch.checked_add(Duration::from_millis(milliseconds)) {
return result;
}
let mut representable = 0;
let mut too_large = milliseconds;
while representable < too_large {
let candidate = representable + (too_large - representable).div_ceil(2);
if epoch
.checked_add(Duration::from_millis(candidate))
.is_some()
{
representable = candidate;
} else {
too_large = candidate - 1;
}
}
epoch
.checked_add(Duration::from_millis(representable))
.expect("adding zero milliseconds to an Instant is representable")
}
pub fn milliseconds_for_units(units: u64) -> u64 {
units / UNITS_PER_MS
}