Skip to main content

qubit_progress/running/
scoped_running_progress.rs

1/*******************************************************************************
2 *
3 *    Copyright (c) 2025 - 2026 Haixing Hu.
4 *
5 *    SPDX-License-Identifier: Apache-2.0
6 *
7 *    Licensed under the Apache License, Version 2.0.
8 *
9 ******************************************************************************/
10use std::{
11    panic::resume_unwind,
12    thread::ScopedJoinHandle,
13};
14
15use super::{
16    running_progress_notifier::RunningProgressNotifier,
17    running_progress_points::RunningProgressPoints,
18};
19
20/// Owns a scoped running progress reporter thread.
21///
22/// `ScopedRunningProgress` is a lifecycle guard for a reporter thread created
23/// by [`crate::RunningProgressLoop::spawn_scoped`]. Keep this guard on the
24/// coordinating thread, pass [`RunningProgressPoints`] clones to workers, and
25/// call [`Self::stop_and_join`] after worker execution completes.
26///
27/// # Examples
28///
29/// ```
30/// use std::{
31///     sync::{
32///         Arc,
33///         atomic::{
34///             AtomicUsize,
35///             Ordering,
36///         },
37///     },
38///     thread,
39///     time::Duration,
40/// };
41///
42/// use qubit_progress::{
43///     NoOpProgressReporter,
44///     Progress,
45///     ProgressCounters,
46///     RunningProgressLoop,
47/// };
48///
49/// let reporter = NoOpProgressReporter;
50/// let completed = Arc::new(AtomicUsize::new(0));
51///
52/// thread::scope(|scope| {
53///     let loop_completed = Arc::clone(&completed);
54///     let progress = Progress::new(&reporter, Duration::ZERO);
55///     let running_progress =
56///         RunningProgressLoop::spawn_scoped(scope, progress, move || {
57///             ProgressCounters::new(Some(1))
58///                 .with_completed_count(loop_completed.load(Ordering::Acquire))
59///         });
60///     let progress_points = running_progress.points();
61///
62///     completed.store(1, Ordering::Release);
63///     assert!(progress_points.running_point());
64///
65///     running_progress.stop_and_join();
66/// });
67/// ```
68///
69/// # Author
70///
71/// Haixing Hu
72pub struct ScopedRunningProgress<'scope> {
73    /// Notifier used to stop the reporter thread.
74    notifier: RunningProgressNotifier,
75    /// Scoped reporter thread handle.
76    progress_thread: ScopedJoinHandle<'scope, ()>,
77    /// Whether worker point notifications should wake the reporter loop.
78    report_points: bool,
79}
80
81impl<'scope> ScopedRunningProgress<'scope> {
82    /// Creates a scoped running progress guard.
83    ///
84    /// # Parameters
85    ///
86    /// * `notifier` - Notifier used to stop the reporter thread.
87    /// * `progress_thread` - Scoped reporter thread handle.
88    /// * `report_points` - Whether worker point notifications wake the loop.
89    ///
90    /// # Returns
91    ///
92    /// A guard owning the reporter thread lifecycle.
93    #[inline]
94    pub(crate) const fn new(
95        notifier: RunningProgressNotifier,
96        progress_thread: ScopedJoinHandle<'scope, ()>,
97        report_points: bool,
98    ) -> Self {
99        Self {
100            notifier,
101            progress_thread,
102            report_points,
103        }
104    }
105
106    /// Returns a worker-side running point handle.
107    ///
108    /// # Returns
109    ///
110    /// A cloneable handle that wakes the reporter loop for zero intervals and
111    /// becomes a no-op for positive intervals.
112    #[inline]
113    pub fn points(&self) -> RunningProgressPoints {
114        RunningProgressPoints::new(self.report_points.then(|| self.notifier.clone()))
115    }
116
117    /// Stops the reporter loop and joins the scoped reporter thread.
118    ///
119    /// # Panics
120    ///
121    /// Propagates any panic raised by the reporter thread.
122    #[inline]
123    pub fn stop_and_join(self) {
124        self.notifier.stop();
125        if let Err(payload) = self.progress_thread.join() {
126            resume_unwind(payload);
127        }
128    }
129}