ixa/
progress.rs

1#![allow(clippy::cast_possible_truncation)]
2#![allow(clippy::cast_sign_loss)]
3//! Provides functions to set up and update a progress bar.
4//!
5//! A progress bar has a label, a maximum progress value, and its current progress, which
6//! starts at zero. The maximum and current progress values are constrained to be of type
7//! `usize`. However, convenience methods are provided for the common case of a progress bar
8//! for the timeline that take `f64` time values and rounds them to nearest integers for you.
9//!
10//! Only one progress bar can be active at a time. If you try to set a second progress bar, the
11//! new progress bar will replace this first. This is useful if you want to track the progress
12//! of a simulation in multiple phases. Keep in mind, however, that if you define a timeline
13//! progress bar, the `Context` will try to update it in its event loop with the current time,
14//! which might not be what you want if you have replaced the progress bar with a new one.
15//!
16//! # Timeline Progress Bar
17//!
18//! ```ignore
19//! /// Initialize the progress bar with the maximum time until the simulation ends.
20//! pub fn init_timeline_progress_bar(max_time: f64);
21//! /// Updates the progress bar with the current time. Finalizes the progress bar when
22//! /// `current_time >= max_time`.
23//! pub fn update_timeline_progress(mut current_time: f64);
24//! ```
25//!
26//! # Custom Progress Bar
27//!
28//! If the timeline is not a good indication of progress for your simulation, you can set up a
29//! custom progress bar.
30//!
31//! ```ignore
32//! /// Initializes a custom progress bar with the given label and max value.
33//! pub fn init_custom_progress_bar(label: &str, max_value: usize);
34//!
35//! /// Updates the current value of the custom progress bar.
36//! pub fn update_custom_progress(current_value: usize);
37//!
38//! /// Increments the custom progress bar by 1. Use this if you don't want to keep track of the
39//! /// current value.
40//! pub fn increment_custom_progress()
41//! ```
42//!
43//! # Custom Example: People Infected
44//!
45//! Suppose you want a progress bar that tracks how much of the population has been infected (or
46//! infected and then recovered). You first initialize a custom progress bar before executing
47//! the simulation.
48//!
49//! ```ignore
50//! use crate::progress_bar::{init_custom_progress_bar};
51//!
52//! init_custom_progress_bar("People Infected", POPULATION_SIZE);
53//! ```
54//!
55//! To update the progress bar, we need to listen to the infection status property change event.
56//!
57//! ```ignore
58//! use crate::progress_bar::{increment_custom_progress};
59//!
60//! // You might already have this event defined for other purposes.
61//! pub type InfectionStatusEvent = PersonPropertyChangeEvent<InfectionStatus>;
62//!
63//! // This will handle the status change event, updating the progress bar
64//! // if there is a new infection.
65//! fn handle_infection_status_change(context: &mut Context, event: InfectionStatusEvent) {
66//!   // We only increment the progress bar when a new infection occurs.
67//!   if (InfectionStatusValue::Susceptible, InfectionStatusValue::Infected)
68//!       == (event.previous, event.current)
69//!   {
70//!     increment_custom_progress();
71//!   }
72//! }
73//!
74//! // Be sure to subscribe to the event when you initialize the context.
75//! pub fn init(context: &mut Context) -> Result<(), IxaError> {
76//!     // ... other initialization code ...
77//!     context.subscribe_to_event::<InfectionStatusEvent>(handle_infection_status_change);
78//!     // ...
79//!     Ok(())
80//! }
81//! ```
82//!
83
84use crate::log::{trace, warn};
85use progress_bar::{
86    finalize_progress_bar, inc_progress_bar, init_progress_bar, set_progress_bar_action,
87    set_progress_bar_progress, Color, Style,
88};
89// Requires at least rustc@1.70
90use std::sync::OnceLock;
91
92/// We want to store the original `f64` max value, not the `usize` we initialized the progress
93/// bar with.
94pub(crate) static MAX_TIME: OnceLock<f64> = OnceLock::new();
95
96/// Initialize the progress bar with the maximum time until the simulation ends.
97pub fn init_timeline_progress_bar(max_time: f64) {
98    trace!(
99        "initializing timeline progress bar with max time {}",
100        max_time
101    );
102    MAX_TIME
103        .set(max_time)
104        .expect("Timeline progress already initialized");
105    init_progress_bar(max_time.round() as usize);
106    set_progress_bar_action("Time", Color::Blue, Style::Bold);
107}
108
109/// Updates the timeline progress bar with the current time.
110pub(crate) fn update_timeline_progress(mut current_time: f64) {
111    if let Some(max_time) = MAX_TIME.get() {
112        if current_time >= *max_time {
113            current_time = *max_time;
114        }
115        set_progress_bar_progress(current_time.round() as usize);
116        // It's possible that `progress.round() == max_time.round()` but `progress < max_time`.
117        // We only finalize if they are equal as floats.
118        #[allow(clippy::float_cmp)]
119        if current_time == *max_time {
120            finalize_progress_bar();
121        }
122    } else {
123        warn!("attempted to update timeline progress bar before it was initialized");
124    }
125}
126
127/// Initializes a custom progress bar with the given label and max value.
128///
129/// Note: If you attempt to set two progress bars, the second progress bar will replace the first.
130pub fn init_custom_progress_bar(label: &str, max_value: usize) {
131    trace!(
132        "initializing custom progress bar with label {} and max value {}",
133        label,
134        max_value
135    );
136    init_progress_bar(max_value);
137    set_progress_bar_action(label, Color::Blue, Style::Bold);
138}
139
140/// Updates the current value of the custom progress bar.
141pub fn update_custom_progress(current_value: usize) {
142    set_progress_bar_progress(current_value);
143}
144
145/// Increments the custom progress bar by 1. Use this if you don't want to keep track of the
146/// current value.
147pub fn increment_custom_progress() {
148    inc_progress_bar();
149}