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}