use std::collections::VecDeque;
use std::sync::Arc;
use std::sync::atomic::AtomicUsize;
use std::time::Duration;
use indicatif::MultiProgress;
use indicatif::ProgressBar;
use indicatif::ProgressDrawTarget;
use indicatif::ProgressState;
use indicatif::style::ProgressStyle;
use tracing_core::Subscriber;
use tracing_core::span;
use tracing_subscriber::layer;
use tracing_subscriber::registry::LookupSpan;
use crate::IndicatifSpanContext;
#[derive(Clone)]
struct RequireDefault;
#[derive(Clone)]
pub struct TickSettings {
pub term_draw_hz: u8,
pub default_tick_interval: Option<Duration>,
pub footer_tick_interval: Option<Duration>,
#[doc(hidden)]
#[allow(private_interfaces)]
pub require_default: RequireDefault,
}
impl Default for TickSettings {
fn default() -> Self {
Self {
term_draw_hz: 20,
default_tick_interval: Some(Duration::from_millis(100)),
footer_tick_interval: None,
require_default: RequireDefault,
}
}
}
pub(crate) struct ProgressBarManager {
pub(crate) mp: MultiProgress,
active_progress_bars: u64,
max_progress_bars: u64,
pending_progress_bars: Arc<AtomicUsize>,
pending_spans: VecDeque<span::Id>,
footer_pb: Option<ProgressBar>,
tick_settings: TickSettings,
}
impl ProgressBarManager {
pub(crate) fn new(
max_progress_bars: u64,
footer_progress_style: Option<ProgressStyle>,
tick_settings: TickSettings,
) -> Self {
let mut s = Self {
mp: {
let mp = MultiProgress::new();
mp.set_draw_target(ProgressDrawTarget::stderr_with_hz(
tick_settings.term_draw_hz,
));
mp
},
active_progress_bars: 0,
max_progress_bars: 0,
pending_progress_bars: Arc::new(AtomicUsize::new(0)),
pending_spans: VecDeque::new(),
footer_pb: None,
tick_settings,
};
s.set_max_progress_bars(max_progress_bars, footer_progress_style);
s
}
pub(crate) fn set_max_progress_bars(
&mut self,
max_progress_bars: u64,
footer_style: Option<ProgressStyle>,
) {
self.max_progress_bars = max_progress_bars;
let pending_progress_bars = self.pending_progress_bars.clone();
self.footer_pb = footer_style.map(move |style| {
ProgressBar::hidden().with_style(style.with_key(
"pending_progress_bars",
move |_: &ProgressState, writer: &mut dyn std::fmt::Write| {
let _ = write!(
writer,
"{}",
pending_progress_bars.load(std::sync::atomic::Ordering::Acquire)
);
},
))
});
}
pub(crate) fn set_tick_settings(&mut self, tick_settings: TickSettings) {
self.mp.set_draw_target(ProgressDrawTarget::stderr_with_hz(
tick_settings.term_draw_hz,
));
self.tick_settings = tick_settings;
}
fn decrement_pending_pb(&mut self) {
let prev_val = self
.pending_progress_bars
.fetch_sub(1, std::sync::atomic::Ordering::AcqRel);
if let Some(footer_pb) = self.footer_pb.as_ref() {
if prev_val == 1 {
debug_assert!(
!footer_pb.is_hidden(),
"footer progress bar was hidden despite there being pending progress bars"
);
if self.tick_settings.footer_tick_interval.is_some() {
footer_pb.disable_steady_tick();
}
footer_pb.finish_and_clear();
self.mp.remove(footer_pb);
} else {
footer_pb.tick();
}
}
}
fn add_pending_pb(&mut self, span_id: &span::Id) {
let prev_val = self
.pending_progress_bars
.fetch_add(1, std::sync::atomic::Ordering::AcqRel);
self.pending_spans.push_back(span_id.clone());
if let Some(footer_pb) = self.footer_pb.as_ref() {
if prev_val == 0 {
debug_assert!(
footer_pb.is_hidden(),
"footer progress bar was not hidden despite there being no pending progress bars"
);
footer_pb.reset();
if let Some(tick_interval) = self.tick_settings.footer_tick_interval {
footer_pb.enable_steady_tick(tick_interval);
}
self.mp.add(footer_pb.clone());
}
footer_pb.tick();
}
}
pub(crate) fn show_progress_bar(
&mut self,
pb_span_ctx: &mut IndicatifSpanContext,
span_id: &span::Id,
) {
if self.active_progress_bars < self.max_progress_bars {
let Some(pb) = pb_span_ctx.progress_bar.take() else {
return;
};
let pb = match pb_span_ctx.parent_progress_bar {
Some(ref parent_pb) => self.mp.insert_after(parent_pb, pb),
None => {
if self
.footer_pb
.as_ref()
.map(|footer_pb| !footer_pb.is_hidden())
.unwrap_or(false)
{
self.mp.insert_from_back(1, pb)
} else {
self.mp.add(pb)
}
}
};
self.active_progress_bars += 1;
if let Some(tick_interval) = self.tick_settings.default_tick_interval {
pb.enable_steady_tick(tick_interval);
}
pb.tick();
pb_span_ctx.progress_bar = Some(pb);
} else {
self.add_pending_pb(span_id);
}
}
pub(crate) fn finish_progress_bar<S>(
&mut self,
pb_span_ctx: &mut IndicatifSpanContext,
ctx: &layer::Context<'_, S>,
) where
S: Subscriber + for<'a> LookupSpan<'a>,
{
let Some(pb) = pb_span_ctx.progress_bar.take() else {
return;
};
if pb.is_hidden() {
self.decrement_pending_pb();
return;
}
if let Some(finish_message) = pb_span_ctx.finish_message.take() {
pb.finish_with_message(finish_message);
} else {
pb.finish_and_clear();
self.mp.remove(&pb);
}
self.active_progress_bars -= 1;
loop {
let Some(span_id) = self.pending_spans.pop_front() else {
break;
};
match ctx.span(&span_id) {
Some(next_eligible_span) => {
let mut ext = next_eligible_span.extensions_mut();
let indicatif_span_ctx = ext
.get_mut::<IndicatifSpanContext>()
.expect("No IndicatifSpanContext found; this is a bug");
if indicatif_span_ctx.progress_bar.is_none() {
continue;
}
self.decrement_pending_pb();
self.show_progress_bar(indicatif_span_ctx, &span_id);
break;
}
None => {
continue;
}
}
}
}
}