use std::{
thread,
time::{
Duration,
Instant,
},
};
use crate::{
model::{
ProgressCounters,
ProgressEvent,
ProgressPhase,
ProgressStage,
},
reporter::ProgressReporter,
running::{
RunningProgressGuard,
RunningProgressLoop,
},
};
pub struct Progress<'a> {
reporter: &'a dyn ProgressReporter,
started_at: Instant,
report_interval: Duration,
next_running_at: Instant,
stage: Option<ProgressStage>,
}
impl<'a> Progress<'a> {
#[inline]
pub fn new(reporter: &'a dyn ProgressReporter, report_interval: Duration) -> Self {
Self::from_start(reporter, report_interval, Instant::now())
}
#[inline]
fn from_start(
reporter: &'a dyn ProgressReporter,
report_interval: Duration,
started_at: Instant,
) -> Self {
Self {
reporter,
started_at,
report_interval,
next_running_at: next_instant(started_at, report_interval),
stage: None,
}
}
#[inline]
pub fn with_stage(mut self, stage: ProgressStage) -> Self {
self.stage = Some(stage);
self
}
#[inline]
pub fn without_stage(mut self) -> Self {
self.stage = None;
self
}
#[inline]
pub fn report_started(&self, counters: ProgressCounters) -> ProgressEvent {
self.report_with_elapsed(ProgressPhase::Started, counters, Duration::ZERO)
}
pub fn report_running(&mut self, counters: ProgressCounters) -> ProgressEvent {
let now = Instant::now();
let event = self.report_with_elapsed(
ProgressPhase::Running,
counters,
now.saturating_duration_since(self.started_at),
);
self.next_running_at = next_instant(now, self.report_interval);
event
}
pub fn report_running_if_due(&mut self, counters: ProgressCounters) -> Option<ProgressEvent> {
let now = Instant::now();
if now < self.next_running_at {
return None;
}
let event = self.report_with_elapsed(
ProgressPhase::Running,
counters,
now.saturating_duration_since(self.started_at),
);
self.next_running_at = next_instant(now, self.report_interval);
Some(event)
}
#[inline]
pub fn report_finished(&self, counters: ProgressCounters) -> ProgressEvent {
self.report_with_elapsed(ProgressPhase::Finished, counters, self.elapsed())
}
#[inline]
pub fn report_failed(&self, counters: ProgressCounters) -> ProgressEvent {
self.report_with_elapsed(ProgressPhase::Failed, counters, self.elapsed())
}
#[inline]
pub fn report_canceled(&self, counters: ProgressCounters) -> ProgressEvent {
self.report_with_elapsed(ProgressPhase::Canceled, counters, self.elapsed())
}
pub fn spawn_running_reporter<'scope, 'env, F>(
&self,
scope: &'scope thread::Scope<'scope, 'env>,
snapshot: F,
) -> RunningProgressGuard<'scope>
where
'a: 'scope,
F: FnMut() -> ProgressCounters + Send + 'scope,
{
RunningProgressLoop::spawn_scoped(scope, self.fork_for_running(), snapshot)
}
fn report_with_elapsed(
&self,
phase: ProgressPhase,
counters: ProgressCounters,
elapsed: Duration,
) -> ProgressEvent {
let event = self.event_with_elapsed(phase, counters, elapsed);
self.reporter.report(&event);
event
}
#[inline]
pub fn elapsed(&self) -> Duration {
self.started_at.elapsed()
}
#[inline]
pub const fn started_at(&self) -> Instant {
self.started_at
}
#[inline]
pub const fn report_interval(&self) -> Duration {
self.report_interval
}
#[inline]
pub const fn stage(&self) -> Option<&ProgressStage> {
self.stage.as_ref()
}
fn fork_for_running(&self) -> Self {
Self {
reporter: self.reporter,
started_at: self.started_at,
report_interval: self.report_interval,
next_running_at: self.next_running_at,
stage: self.stage.clone(),
}
}
fn event_with_elapsed(
&self,
phase: ProgressPhase,
counters: ProgressCounters,
elapsed: Duration,
) -> ProgressEvent {
let event = ProgressEvent::from_phase(phase, counters, elapsed);
match self.stage.clone() {
Some(stage) => event.with_stage(stage),
None => event,
}
}
}
fn next_instant(base: Instant, interval: Duration) -> Instant {
base.checked_add(interval).unwrap_or(base)
}