use std::fmt;
use std::sync::{Arc, OnceLock};
use std::time::Duration;
use minstant::Instant;
type DurationCell = Arc<OnceLock<Duration>>;
#[derive(Default, Clone)]
pub struct GasDuration(GasDurationInner);
#[derive(Default, Clone)]
pub enum GasDurationInner {
#[default]
None,
Atomic(DurationCell),
Constant(Duration),
}
impl GasDuration {
pub fn get(&self) -> Option<&Duration> {
match &self.0 {
GasDurationInner::None => None,
GasDurationInner::Atomic(d) => d.get(),
GasDurationInner::Constant(d) => Some(d),
}
}
}
impl From<Duration> for GasDuration {
fn from(d: Duration) -> Self {
GasDuration(GasDurationInner::Constant(d))
}
}
impl fmt::Debug for GasDuration {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("GasDuration")
.field(&self.get() as &dyn fmt::Debug)
.finish()
}
}
pub type GasInstant = Instant;
#[derive(Debug)]
pub struct GasTimer(Option<GasTimerInner>);
#[derive(Debug)]
struct GasTimerInner {
start: GasInstant,
elapsed: DurationCell,
}
impl GasTimer {
pub fn start() -> GasInstant {
GasInstant::now()
}
pub fn empty() -> Self {
GasTimer(None)
}
pub fn new(duration: &mut GasDuration) -> Self {
debug_assert!(duration.get().is_none(), "GasCharge::elapsed already set!");
let cell = match &duration.0 {
GasDurationInner::None => {
let cell = DurationCell::default();
duration.0 = GasDurationInner::Atomic(cell.clone());
cell
}
GasDurationInner::Atomic(cell) if cell.get().is_none() => cell.clone(),
_ => return Self(None),
};
Self(Some(GasTimerInner {
start: Self::start(),
elapsed: cell,
}))
}
pub fn stop(self) {
if let Some(timer) = self.0 {
Self::set_elapsed(timer.elapsed, timer.start)
}
}
pub fn stop_with(self, start: GasInstant) {
if let Some(timer) = self.0 {
Self::set_elapsed(timer.elapsed, start)
}
}
fn set_elapsed(elapsed: Arc<OnceLock<Duration>>, start: GasInstant) {
elapsed
.set(start.elapsed())
.expect("GasCharge::elapsed already set!")
}
pub fn record<R, E>(self, result: Result<R, E>) -> Result<R, E> {
if result.is_ok() {
self.stop()
}
result
}
}