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_point_handle::RunningProgressPointHandle,
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 [`RunningProgressPointHandle`] 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_point_handle = running_progress.point_handle();
61///
62/// completed.store(1, Ordering::Release);
63/// assert!(progress_point_handle.report());
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 point_handle(&self) -> RunningProgressPointHandle {
114 RunningProgressPointHandle::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}