use std::{
thread,
time::{
Duration,
Instant,
},
};
use crate::{
model::{
ProgressCounter,
ProgressEvent,
ProgressEventBuilder,
ProgressMetric,
ProgressPhase,
ProgressSchema,
ProgressStage,
},
reporter::ProgressReporter,
running::{
RunningProgressGuard,
RunningProgressLoop,
},
};
pub struct Progress<'a> {
reporter: &'a dyn ProgressReporter,
schema: ProgressSchema,
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, schema: ProgressSchema) -> Self {
Self::from_start(reporter, report_interval, schema, Instant::now())
}
#[inline]
pub fn single_metric(
reporter: &'a dyn ProgressReporter,
report_interval: Duration,
metric_id: &str,
metric_name: &str,
) -> Self {
Self::new(
reporter,
report_interval,
ProgressSchema::new(vec![ProgressMetric::new(metric_id, metric_name)]),
)
}
#[inline]
fn from_start(
reporter: &'a dyn ProgressReporter,
report_interval: Duration,
schema: ProgressSchema,
started_at: Instant,
) -> Self {
Self {
reporter,
schema,
started_at,
report_interval,
next_running_at: next_instant(started_at, report_interval),
stage: None,
}
}
#[inline]
#[must_use]
pub fn with_stage(mut self, stage: ProgressStage) -> Self {
self.stage = Some(stage);
self
}
#[inline]
#[must_use]
pub fn without_stage(mut self) -> Self {
self.stage = None;
self
}
#[inline]
pub fn event_builder(&self) -> ProgressEventBuilder {
self.event_builder_with_elapsed(self.elapsed())
}
#[inline]
pub fn report_started<F>(&self, configure: F) -> ProgressEvent
where
F: FnOnce(ProgressEventBuilder) -> ProgressEventBuilder,
{
self.report_with_elapsed(ProgressPhase::Started, Duration::ZERO, configure)
}
pub fn report_running<F>(&mut self, configure: F) -> ProgressEvent
where
F: FnOnce(ProgressEventBuilder) -> ProgressEventBuilder,
{
let now = Instant::now();
let event = self.report_with_elapsed(
ProgressPhase::Running,
now.saturating_duration_since(self.started_at),
configure,
);
self.next_running_at = next_instant(now, self.report_interval);
event
}
pub fn report_running_if_due<F>(&mut self, configure: F) -> Option<ProgressEvent>
where
F: FnOnce(ProgressEventBuilder) -> ProgressEventBuilder,
{
let now = Instant::now();
if now < self.next_running_at {
return None;
}
let event = self.report_with_elapsed(
ProgressPhase::Running,
now.saturating_duration_since(self.started_at),
configure,
);
self.next_running_at = next_instant(now, self.report_interval);
Some(event)
}
#[inline]
pub fn report_finished<F>(&self, configure: F) -> ProgressEvent
where
F: FnOnce(ProgressEventBuilder) -> ProgressEventBuilder,
{
self.report_with_elapsed(ProgressPhase::Finished, self.elapsed(), configure)
}
#[inline]
pub fn report_failed<F>(&self, configure: F) -> ProgressEvent
where
F: FnOnce(ProgressEventBuilder) -> ProgressEventBuilder,
{
self.report_with_elapsed(ProgressPhase::Failed, self.elapsed(), configure)
}
#[inline]
pub fn report_canceled<F>(&self, configure: F) -> ProgressEvent
where
F: FnOnce(ProgressEventBuilder) -> ProgressEventBuilder,
{
self.report_with_elapsed(ProgressPhase::Canceled, self.elapsed(), configure)
}
pub fn spawn_running_reporter<'scope, 'env, F>(
&self,
scope: &'scope thread::Scope<'scope, 'env>,
snapshot: F,
) -> RunningProgressGuard<'scope>
where
'a: 'scope,
F: FnMut() -> Vec<ProgressCounter> + Send + 'scope,
{
RunningProgressLoop::spawn_scoped(scope, self.fork_for_running(), snapshot)
}
fn report_with_elapsed<F>(&self, phase: ProgressPhase, elapsed: Duration, configure: F) -> ProgressEvent
where
F: FnOnce(ProgressEventBuilder) -> ProgressEventBuilder,
{
let event = configure(self.event_builder_with_elapsed(elapsed).phase(phase)).build();
self.reporter.report(&event);
event
}
#[inline]
pub const fn schema(&self) -> &ProgressSchema {
&self.schema
}
#[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,
schema: self.schema.clone(),
started_at: self.started_at,
report_interval: self.report_interval,
next_running_at: self.next_running_at,
stage: self.stage.clone(),
}
}
fn event_builder_with_elapsed(&self, elapsed: Duration) -> ProgressEventBuilder {
let builder = ProgressEvent::builder(self.schema.clone()).elapsed(elapsed);
match self.stage.clone() {
Some(stage) => builder.stage(stage),
None => builder,
}
}
}
fn next_instant(base: Instant, interval: Duration) -> Instant {
base.checked_add(interval).unwrap_or(base)
}