coven_foundation/
progress.rs1use crate::clock::ClockRef;
5use std::future::Future;
6use std::time::Duration;
7use tokio::sync::watch;
8
9#[derive(Clone, Copy, Debug, PartialEq, Eq)]
14pub struct Progress<S> {
15 pub step: S,
17 pub done: u64,
19 pub total: u64,
23}
24
25pub const REPORT_INTERVAL: Duration = Duration::from_millis(250);
28
29pub struct ProgressReport<S> {
34 sender: watch::Sender<Option<Progress<S>>>,
35 clock: ClockRef,
36 current: Option<Progress<S>>,
37 sent: Duration,
39}
40
41impl<S: Clone + PartialEq> ProgressReport<S> {
42 pub fn new(sender: watch::Sender<Option<Progress<S>>>, clock: ClockRef) -> Self {
44 let sent = clock.elapsed();
45 Self { sender, clock, current: None, sent }
46 }
47
48 pub fn of(channel: &ProgressChannel<S>, clock: ClockRef) -> Self {
51 Self::new(channel.0.clone(), clock)
52 }
53
54 pub fn unwatched(clock: ClockRef) -> Self {
57 Self::new(watch::channel(None).0, clock)
58 }
59
60 pub fn begin(&mut self, step: S, total: u64) {
64 debug_assert!(self.current.as_ref().is_none_or(|current| current.step != step));
65 self.send(Progress { step, done: 0, total });
66 }
67
68 pub fn restart(&mut self) {
71 self.current = None;
72 }
73
74 pub fn done(&mut self, done: u64) {
76 let Some(current) = self.current.clone() else {
77 debug_assert!(false, "progress is reported within a step");
78 return;
79 };
80 debug_assert!(current.done <= done && done <= current.total);
81 let finished = done == current.total;
82 self.update(Progress { done, ..current }, finished);
83 }
84
85 pub fn advance(&mut self) {
87 let done = self.current.as_ref().map_or(0, |current| current.done + 1);
88 self.done(done);
89 }
90
91 pub fn grow(&mut self, total: u64) {
94 let Some(current) = self.current.clone() else {
95 debug_assert!(false, "progress is reported within a step");
96 return;
97 };
98 debug_assert!(current.total <= total);
99 self.update(Progress { total, ..current }, false);
100 }
101
102 pub fn relay(&mut self, progress: Progress<S>) {
107 match &self.current {
108 Some(current) if current.step == progress.step => {
109 let finished = progress.done == progress.total;
110 self.update(progress, finished);
111 }
112 _ => self.send(progress),
113 }
114 }
115
116 fn update(&mut self, next: Progress<S>, finished: bool) {
119 if finished || self.clock.elapsed() >= self.sent + REPORT_INTERVAL {
120 self.send(next);
121 } else {
122 self.current = Some(next);
123 }
124 }
125
126 fn send(&mut self, progress: Progress<S>) {
127 self.current = Some(progress.clone());
128 self.sent = self.clock.elapsed();
129 self.sender.send_replace(Some(progress));
131 }
132}
133
134#[derive(Clone)]
137pub struct ProgressChannel<S>(watch::Sender<Option<Progress<S>>>);
138
139impl<S: Clone + PartialEq> ProgressChannel<S> {
140 pub fn new() -> Self {
142 Self(watch::channel(None).0)
143 }
144
145 pub fn watch(&self) -> ProgressWatch<S> {
147 ProgressWatch(self.0.subscribe())
148 }
149}
150
151pub struct ProgressWatch<S>(watch::Receiver<Option<Progress<S>>>);
154
155impl<S: Clone + PartialEq> ProgressWatch<S> {
156 pub fn forward<T>(
160 self,
161 work: impl Future<Output = T>,
162 report: impl Fn(Progress<S>),
163 ) -> impl Future<Output = T> {
164 forward(self.0, work, report)
165 }
166}
167
168pub async fn forward<S: Clone + PartialEq, T>(
175 mut progress: watch::Receiver<Option<Progress<S>>>,
176 work: impl Future<Output = T>,
177 report: impl Fn(Progress<S>),
178) -> T {
179 let mut reported = None;
180 let mut deliver = |newest: Option<Progress<S>>| {
181 if newest != reported {
182 if let Some(value) = newest.clone() {
183 reported = newest;
184 report(value);
185 }
186 }
187 };
188 let mut work = std::pin::pin!(work);
189 loop {
190 tokio::select! {
191 biased;
192 output = &mut work => {
193 let newest = progress.borrow_and_update();
195 let unseen = newest.has_changed().then(|| newest.clone());
196 drop(newest);
197 if let Some(newest) = unseen {
198 deliver(newest);
199 }
200 return output;
201 }
202 changed = progress.changed() => match changed {
203 Ok(()) => {
204 let newest = progress.borrow_and_update().clone();
205 deliver(newest);
206 }
207 Err(_closed) => return work.await,
210 },
211 }
212 }
213}
214
215#[cfg(feature = "test-utils")]
218type OnRecord<S> = Box<dyn Fn(&Progress<S>) + Send + Sync>;
219
220#[cfg(feature = "test-utils")]
226pub struct RecordingClock<S> {
227 clock: ClockRef,
228 watch: std::sync::Mutex<Option<watch::Receiver<Option<Progress<S>>>>>,
229 seen: std::sync::Mutex<Vec<Progress<S>>>,
230 on_record: std::sync::OnceLock<OnRecord<S>>,
231}
232
233#[cfg(feature = "test-utils")]
234impl<S: Clone + PartialEq> RecordingClock<S> {
235 pub fn new(clock: ClockRef) -> Self {
237 Self {
238 clock,
239 watch: std::sync::Mutex::new(None),
240 seen: std::sync::Mutex::new(Vec::new()),
241 on_record: std::sync::OnceLock::new(),
242 }
243 }
244
245 pub fn watch(&self, watch: watch::Receiver<Option<Progress<S>>>) {
251 self.record();
252 *self.watch.lock().unwrap() = Some(watch);
253 }
254
255 pub fn watch_progress(&self, progress: ProgressWatch<S>) {
258 self.watch(progress.0);
259 }
260
261 pub fn on_record(&self, hook: impl Fn(&Progress<S>) + Send + Sync + 'static) {
265 assert!(self.on_record.set(Box::new(hook)).is_ok(), "a recording clock has one hook");
266 }
267
268 pub fn seen(&self) -> Vec<Progress<S>> {
270 self.record();
271 self.seen.lock().unwrap().clone()
272 }
273
274 fn record(&self) {
275 let recorded = {
276 let mut seen = self.seen.lock().unwrap();
279 let Some(newest) =
280 self.watch.lock().unwrap().as_ref().map(|watch| watch.borrow().clone())
281 else {
282 return;
283 };
284 match newest.filter(|newest| seen.last() != Some(newest)) {
285 Some(newest) => {
286 seen.push(newest.clone());
287 newest
288 }
289 None => return,
290 }
291 };
292 if let Some(hook) = self.on_record.get() {
293 hook(&recorded);
294 }
295 }
296}
297
298#[cfg(feature = "test-utils")]
299impl<S: Clone + PartialEq + Send + Sync> crate::clock::Clock for RecordingClock<S> {
300 fn now(&self) -> std::time::SystemTime {
301 self.clock.now()
302 }
303 fn elapsed(&self) -> Duration {
304 self.record();
305 self.clock.elapsed()
306 }
307 fn deadline(&self, duration: Duration) -> std::pin::Pin<Box<dyn Future<Output = ()> + Send>> {
308 self.clock.deadline(duration)
309 }
310 fn wait_out(&self, wait: Duration) -> std::pin::Pin<Box<dyn Future<Output = ()> + Send>> {
311 self.clock.wait_out(wait)
312 }
313 fn sleep_until(
314 &self,
315 at: std::time::SystemTime,
316 ) -> std::pin::Pin<Box<dyn Future<Output = Result<(), crate::clock::WallTimerError>> + Send>>
317 {
318 self.clock.sleep_until(at)
319 }
320}
321
322#[cfg(test)]
323#[path = "progress_tests.rs"]
324mod tests;