legume_numeric/matrix/progress.rs
1//! Workspace-wide progress-bar style and the single [`MULTI_PROGRESS`] that
2//! every bar registers with.
3//!
4//! This lives in `matrix-util` — the lowest common dependency of `data-beans`,
5//! `data-beans-alg`, `auxiliary-data`, and the binaries — so the whole
6//! workspace shares ONE style definition and ONE `MultiProgress`. That single
7//! `MultiProgress` is what lets `indicatif_log_bridge` (installed by
8//! `auxiliary_data::logging::init_logger`) interleave `log` output cleanly
9//! above the bars. Duplicating the bar/`MultiProgress` in another crate (as
10//! `graph-embedding-util` once did) silently spawns a second, *unbridged*
11//! `MultiProgress` whose bars corrupt the log output — so every crate must
12//! draw through this module. `auxiliary_data::logging` re-exports these names
13//! for backward compatibility.
14
15use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
16use std::sync::LazyLock;
17use std::time::Duration;
18
19/// The single global `MultiProgress` every workspace progress bar registers
20/// with, so `log` output routed through `indicatif_log_bridge` interleaves
21/// cleanly above the bars instead of corrupting them.
22pub static MULTI_PROGRESS: LazyLock<MultiProgress> = LazyLock::new(MultiProgress::new);
23
24/// Standard bounded-bar template: `[elapsed] bar pos/len (eta) msg`. The
25/// trailing `{msg}` is empty unless a caller sets one (e.g.
26/// `new_progress_bar(n).with_message("blocks")`).
27const BAR_TEMPLATE: &str = "[{elapsed_precise}] {bar:40.cyan/blue} {pos}/{len} ({eta}) {msg}";
28
29/// Tick frames shared by every [`new_spinner`].
30const SPINNER_TICKS: &str = "⠁⠂⠄⡀⢀⠠⠐⠈ ";
31
32/// Repaint cadence for both bars and spinners. Without a steady tick a bar
33/// only repaints when [`ProgressBar::inc`] fires, so work with a heavy-tailed
34/// per-item cost (one huge gene among thousands of small ones) leaves a frozen
35/// elapsed time on screen for minutes and reads as a hang.
36const STEADY_TICK: Duration = Duration::from_millis(200);
37
38/// Create a progress bar registered with the shared [`MULTI_PROGRESS`] and
39/// styled with the standard template. Attach a trailing label with
40/// [`ProgressBar::with_message`], e.g. `new_progress_bar(n).with_message("blocks")`.
41/// Repaints on the shared [`STEADY_TICK`] cadence.
42#[must_use]
43pub fn new_progress_bar(len: u64) -> ProgressBar {
44 let prog_bar = MULTI_PROGRESS.add(ProgressBar::new(len));
45 prog_bar.set_style(
46 ProgressStyle::with_template(BAR_TEMPLATE)
47 .unwrap()
48 .progress_chars("##-"),
49 );
50 prog_bar.enable_steady_tick(STEADY_TICK);
51 prog_bar
52}
53
54/// Create a spinner registered with the shared [`MULTI_PROGRESS`] for
55/// unbounded / streaming work (no known total). `template` is an indicatif
56/// spinner template (e.g. `"{spinner} streamed {pos} fragments ({per_sec})"`);
57/// the shared tick frames and the shared [`STEADY_TICK`] cadence are applied so
58/// the spinner animates and stays visually consistent across crates.
59#[must_use]
60pub fn new_spinner(template: &str) -> ProgressBar {
61 let prog_bar = MULTI_PROGRESS.add(ProgressBar::new_spinner());
62 prog_bar.set_style(
63 ProgressStyle::with_template(template)
64 .unwrap()
65 .tick_chars(SPINNER_TICKS),
66 );
67 prog_bar.enable_steady_tick(STEADY_TICK);
68 prog_bar
69}