Skip to main content

barber/
progress_bar.rs

1use std::sync::{
2    Arc,
3    atomic::{AtomicUsize, Ordering},
4    mpsc::channel,
5};
6
7use crate::{ProgressEvent, ProgressHandle, ProgressRenderer, spawn_progress_thread};
8
9/// A thread safe, thread stable progress bar, abstracted for any frontend
10#[derive(Clone)]
11pub struct ProgressBar {
12    handle: Arc<ProgressHandle>,
13}
14
15impl ProgressBar {
16    /// Runs a custom closure for this progress bar.
17    ///
18    /// # Panics
19    ///
20    /// Panics if the internal `GLOBAL_SENDER` thread cannot be communicated with. This usually signals critical failure somewhere
21    ///
22    /// # Notes
23    ///
24    /// If the input closure panics, it will not kill the thread, it will instead log the error and return
25    pub fn custom<F>(&self, f: F)
26    where
27        F: FnOnce() + Send + 'static,
28    {
29        if self.handle.renderer.__is_null() {
30            return;
31        }
32
33        self.handle
34            .sender
35            .send(ProgressEvent::Custom {
36                f: Box::new(f),
37                _handle: self.handle.clone(),
38            })
39            .expect("Progress handler thread died.");
40    }
41
42    /// Waits until the flush is processed, allowing you to wait until all previous events have been processed
43    ///
44    /// # Panics
45    ///
46    /// Panics if the internal `GLOBAL_SENDER` thread cannot be communicated with. This usually signals critical failure somewhere
47    pub fn flush(&self) {
48        if self.handle.renderer.__is_null() {
49            return;
50        }
51
52        let (tx, rx) = channel();
53        self.handle
54            .sender
55            .send(ProgressEvent::Flush {
56                done: tx,
57                _handle: self.handle.clone(),
58            })
59            .expect("Progress handler thread died.");
60        let _ = rx.recv();
61    }
62
63    /// Gets the max value of the internal `ProgressHandle`
64    ///
65    /// # Notes
66    ///
67    /// While this will return the correct max value at time of call, In multi-threaded contexts, this max may have already changed by the time it has been used
68    pub fn get_max(&self) -> usize {
69        self.handle.max.load(Ordering::SeqCst)
70    }
71
72    /// Gets the value of the internal `ProgressHandle`
73    ///
74    /// # Notes
75    ///
76    /// While this will return the correct value at time of call, In multi-threaded contexts, this value may have already changed by the time it has been used
77    pub fn get_value(&self) -> usize {
78        self.handle.value.load(Ordering::SeqCst)
79    }
80
81    /// Increments the value of a progress bar by 1
82    ///
83    /// # Panics
84    ///
85    /// Panics if the internal `GLOBAL_SENDER` thread cannot be communicated with. This usually signals critical failure somewhere
86    pub fn increment(&self) {
87        if self.handle.renderer.__is_null() {
88            return;
89        }
90
91        self.handle
92            .sender
93            .send(ProgressEvent::Increment {
94                handle: self.handle.clone(),
95            })
96            .expect("Progress handler thread died.");
97    }
98
99    /// Creates a new progress bar instance using the renderer
100    ///
101    /// This progress bar can safely be shared and updated between threads
102    pub fn new(value: usize, max: usize, renderer: Arc<dyn ProgressRenderer>) -> Self {
103        let (tx, rx) = channel();
104
105        let handle = Arc::new(ProgressHandle {
106            value: AtomicUsize::new(value),
107            max: AtomicUsize::new(max),
108            renderer: renderer.clone(),
109            sender: tx,
110        });
111
112        if !renderer.__is_null() {
113            spawn_progress_thread(rx);
114            renderer.on_start(value, max);
115        }
116
117        Self { handle }
118    }
119
120    /// Sets the max-value of the progress bar
121    ///
122    /// # Panics
123    ///
124    /// Panics if the internal `GLOBAL_SENDER` thread cannot be communicated with. This usually signals critical failure somewhere
125    pub fn set_max(&self, max: usize) {
126        if self.handle.renderer.__is_null() {
127            return;
128        }
129
130        self.handle
131            .sender
132            .send(ProgressEvent::SetMax {
133                handle: self.handle.clone(),
134                max,
135            })
136            .expect("Progress handler thread died.");
137    }
138
139    /// Sets the value of the progress bar
140    ///
141    /// # Panics
142    ///
143    /// Panics if the internal `GLOBAL_SENDER` thread cannot be communicated with. This usually signals critical failure somewhere
144    pub fn set_value(&self, value: usize) {
145        if self.handle.renderer.__is_null() {
146            return;
147        }
148
149        self.handle
150            .sender
151            .send(ProgressEvent::SetValue {
152                handle: self.handle.clone(),
153                value,
154            })
155            .expect("Progress handler thread died.");
156    }
157
158    /// Forces the [`ProgressRenderer`] to upate with the new values
159    ///
160    /// # Panics
161    ///
162    /// Panics if the progress bars handling thread cannot be communicated with.
163    pub fn update(&self) {
164        if self.handle.renderer.__is_null() {
165            return;
166        }
167
168        self.handle
169            .sender
170            .send(ProgressEvent::Update {
171                handle: self.handle.clone(),
172            })
173            .expect("Progress handler thread died.")
174    }
175
176    /// Checks if the the handling thread is alive by sending a check to it and seeing if it returns [`Ok`]
177    ///
178    /// # Notes
179    ///
180    /// This function will correctly check if the handling thread is alive at the time of calling, but can not guarantee the thread will stay alive after calling or that it receives the check
181    pub fn is_alive(&self) -> bool {
182        if self.handle.renderer.__is_null() {
183            return false;
184        }
185
186        self.handle.sender.send(ProgressEvent::Check).is_ok()
187    }
188
189    /// Calls the [`ProgressRenderer::set_label()`] function for the held [`ProgressRenderer`]
190    pub fn set_label(&self, label: &str) {
191        self.handle.renderer.set_label(label);
192    }
193
194    /// Tells the handling thread to call the [`ProgressRenderer::on_notify()`] function for the held [`ProgressRenderer`]
195    ///
196    /// # Panics
197    ///
198    /// Panics if the progress bars handling thread cannot be communicated with.
199    pub fn notify(&self, message: impl ToString) {
200        if self.handle.renderer.__is_null() {
201            return;
202        }
203
204        self.handle
205            .sender
206            .send(ProgressEvent::Notify {
207                handle: self.handle.clone(),
208                msg: message.to_string(),
209            })
210            .expect("Progress handler thread died.")
211    }
212}