atomic_progress/builder.rs
1//! Fluent interface for constructing [`Progress`] instances.
2//!
3//! While simple progress bars can be created via [`Progress::new_pb`] or [`Progress::new_spinner`],
4//! the [`ProgressBuilder`] allows for complex initialization scenarios.
5//!
6//! # Key Features
7//!
8//! * **Shared State:** You can inject existing `Arc<Atomic...>` instances. This is vital
9//! for scenarios where multiple distinct parts of an application need to update or view
10//! the *exact same* underlying counter (e.g., a global download byte counter shared by
11//! multiple workers), or you need to integrate with a foreign crate/system.
12//! * **Time Travel:** Allows explicitly setting the `start` time, useful for resuming
13//! previously paused tasks or synchronizing start times across a batch of jobs.
14
15use std::sync::{
16 Arc,
17 atomic::{AtomicBool, AtomicU64},
18};
19
20use compact_str::CompactString;
21use parking_lot::RwLock;
22use web_time::Instant;
23
24use crate::progress::{Cold, Progress, ProgressType};
25
26/// A builder pattern for constructing complex [`Progress`] instances.
27///
28/// Use this when you need fine-grained control over initialization, such as setting
29/// a specific start time, or injecting existing `Arc<Atomic...>` instances for shared
30/// state management.
31#[derive(Default)]
32pub struct ProgressBuilder {
33 kind: ProgressType,
34 name: CompactString,
35 start: Option<Instant>,
36 total: u64,
37 // Optional shared atomic references
38 atomic_pos: Option<Arc<AtomicU64>>,
39 atomic_total: Option<Arc<AtomicU64>>,
40 atomic_finished: Option<Arc<AtomicBool>>,
41}
42
43impl ProgressBuilder {
44 /// Starts building a generic Progress Bar (known total).
45 #[must_use]
46 pub fn new_bar(name: impl Into<CompactString>, total: impl Into<u64>) -> Self {
47 Self {
48 kind: ProgressType::Bar,
49 name: name.into(),
50 total: total.into(),
51 ..Default::default()
52 }
53 }
54
55 /// Starts building a Spinner (indeterminate total).
56 #[must_use]
57 pub fn new_spinner(name: impl Into<CompactString>) -> Self {
58 Self {
59 kind: ProgressType::Spinner,
60 name: name.into(),
61 ..Default::default()
62 }
63 }
64
65 /// Sets a pre-existing atomic counter for position.
66 ///
67 /// This allows multiple `Progress` instances or external systems to share/update
68 /// the same position state.
69 #[must_use]
70 pub fn with_atomic_pos(mut self, atomic_pos: Arc<AtomicU64>) -> Self {
71 self.atomic_pos = Some(atomic_pos);
72 self
73 }
74
75 /// Sets a pre-existing atomic counter for the total.
76 #[must_use]
77 pub fn with_atomic_total(mut self, atomic_total: Arc<AtomicU64>) -> Self {
78 self.atomic_total = Some(atomic_total);
79 self
80 }
81
82 /// Sets a pre-existing atomic boolean for the finished state.
83 #[must_use]
84 pub fn with_atomic_finished(mut self, atomic_finished: Arc<AtomicBool>) -> Self {
85 self.atomic_finished = Some(atomic_finished);
86 self
87 }
88
89 /// Sets the start time explicitly.
90 #[must_use]
91 pub const fn with_start_time(mut self, start: Instant) -> Self {
92 self.start = Some(start);
93 self
94 }
95
96 /// Sets the start time to `Instant::now()`.
97 #[must_use]
98 pub fn with_start_time_now(self) -> Self {
99 self.with_start_time(Instant::now())
100 }
101
102 /// Consumes the builder and returns the constructed [`Progress`] instance.
103 #[must_use]
104 pub fn build(self) -> Progress {
105 Progress {
106 kind: self.kind,
107 start: self.start,
108 cold: Arc::new(RwLock::new(Cold {
109 name: self.name,
110 stopped: None,
111 error: None,
112 })),
113 item: Arc::new(RwLock::new(CompactString::default())),
114 // Use provided atomics or create new ones
115 position: self
116 .atomic_pos
117 .unwrap_or_else(|| Arc::new(AtomicU64::new(0))),
118 total: self
119 .atomic_total
120 .unwrap_or_else(|| Arc::new(AtomicU64::new(self.total))),
121 finished: self
122 .atomic_finished
123 .unwrap_or_else(|| Arc::new(AtomicBool::new(false))),
124 }
125 }
126}
127
128#[cfg(test)]
129mod tests {
130 use std::sync::{Arc, atomic::AtomicU64};
131
132 use super::ProgressBuilder;
133
134 /// Shared State Injection
135 /// Creates two Progress handles that point to the SAME atomic counter.
136 /// This is a critical feature for distributed/shared progress tracking.
137 #[test]
138 fn test_shared_atomics() {
139 let shared_pos = Arc::new(AtomicU64::new(0));
140
141 let p1 = ProgressBuilder::new_bar("worker_1", 100u64)
142 .with_atomic_pos(shared_pos.clone())
143 .build();
144
145 let p2 = ProgressBuilder::new_bar("worker_2", 100u64)
146 .with_atomic_pos(shared_pos) // Same Arc
147 .build();
148
149 // Worker 1 updates progress
150 p1.inc(10u64);
151
152 // Worker 2 sees the update immediately (atomic load)
153 assert_eq!(
154 p2.get_pos(),
155 10,
156 "p2 should see p1's updates via shared atomic"
157 );
158 }
159}