use std::time::Duration;
use qubit_clock::MonotonicClock;
use qubit_clock::MonotonicInstant;
use super::TimeBudgetError;
#[derive(Debug)]
pub struct TimeBudget<R, C> {
resource: R,
clock: C,
started_at: MonotonicInstant,
deadline: MonotonicInstant,
}
impl<R, C: MonotonicClock> TimeBudget<R, C> {
#[must_use = "the time budget result must be handled"]
pub fn for_duration(resource: R, clock: C, duration: Duration) -> Result<Self, TimeBudgetError<R>> {
let started_at = clock.now();
let deadline = match started_at.checked_add(duration) {
Ok(deadline) => deadline,
Err(source) => {
return Err(TimeBudgetError::Clock { resource, source });
}
};
Ok(Self {
resource,
clock,
started_at,
deadline,
})
}
#[must_use = "the time budget result must be handled"]
pub fn until(resource: R, clock: C, deadline: MonotonicInstant) -> Result<Self, TimeBudgetError<R>> {
if let Err(source) = deadline.validate_domain(clock.domain()) {
return Err(TimeBudgetError::Clock { resource, source });
}
let started_at = clock.now();
Ok(Self {
resource,
clock,
started_at,
deadline,
})
}
}
impl<R, C> TimeBudget<R, C> {
#[must_use = "the elapsed-time result must be handled"]
#[inline(always)]
pub const fn resource(&self) -> &R {
&self.resource
}
#[must_use = "inspect the construction instant"]
#[inline(always)]
pub const fn started_at(&self) -> MonotonicInstant {
self.started_at
}
#[must_use = "inspect the deadline instant"]
#[inline(always)]
pub const fn deadline(&self) -> MonotonicInstant {
self.deadline
}
}
impl<R, C: MonotonicClock> TimeBudget<R, C> {
#[inline]
#[must_use = "the remaining-time result must be handled"]
pub fn is_expired(&self) -> bool {
self.clock.now() >= self.deadline
}
}
impl<R: Clone, C: MonotonicClock> TimeBudget<R, C> {
#[must_use = "the time budget check result must be handled"]
pub fn elapsed(&self) -> Result<Duration, TimeBudgetError<R>> {
self.clock
.now()
.duration_since(self.started_at)
.map_err(|source| TimeBudgetError::Clock {
resource: self.resource.clone(),
source,
})
}
#[must_use = "the time budget check result must be handled"]
pub fn remaining(&self) -> Result<Duration, TimeBudgetError<R>> {
let now = self.clock.now();
if now >= self.deadline {
Ok(Duration::ZERO)
} else {
self.deadline
.duration_since(now)
.map_err(|source| TimeBudgetError::Clock {
resource: self.resource.clone(),
source,
})
}
}
#[must_use = "the time budget check result must be handled"]
pub fn check(&self) -> Result<(), TimeBudgetError<R>> {
let now = self.clock.now();
if now >= self.deadline {
Err(TimeBudgetError::Expired {
resource: self.resource.clone(),
deadline: self.deadline,
now,
})
} else {
Ok(())
}
}
#[must_use = "the time budget check result must be handled"]
pub fn check_after(&self, duration: Duration) -> Result<(), TimeBudgetError<R>> {
let now = self.clock.now();
if now >= self.deadline {
return Err(TimeBudgetError::Expired {
resource: self.resource.clone(),
deadline: self.deadline,
now,
});
}
let end = now.checked_add(duration).map_err(|source| TimeBudgetError::Clock {
resource: self.resource.clone(),
source,
})?;
if end >= self.deadline {
Err(TimeBudgetError::WouldExpire {
resource: self.resource.clone(),
deadline: self.deadline,
now,
requested: duration,
})
} else {
Ok(())
}
}
}