alpha2_extensions/progress.rs
1//! Progress a provider reports while one call works through many frames.
2//!
3//! A host that watches its threads for calls that never return (a decode
4//! shard's heartbeat) cannot tell a long call that keeps going from one that
5//! is stuck. An animated source's open decodes every frame once, and a
6//! request for an earlier frame decodes forward from the file's start: each
7//! frame done is progress. The watch is the calling thread's own, as the
8//! native decoders' is, so a provider needs no handle to report it.
9
10use std::cell::RefCell;
11
12thread_local! {
13 static WATCH: RefCell<Option<Box<dyn Fn()>>> = const { RefCell::new(None) };
14}
15
16/// Calls `progressed` on this thread each time a provider call it makes
17/// gets further, until the returned watch drops.
18pub fn watch_progress(progressed: impl Fn() + 'static) -> ProgressWatch {
19 let previous = WATCH.with(|watch| watch.replace(Some(Box::new(progressed))));
20 ProgressWatch {
21 previous,
22 thread: std::marker::PhantomData,
23 }
24}
25
26/// Ends a [`watch_progress`] watch, restoring the one before it.
27pub struct ProgressWatch {
28 previous: Option<Box<dyn Fn()>>,
29 /// Makes the watch `!Send`: it must drop on the thread that set it.
30 thread: std::marker::PhantomData<*const ()>,
31}
32
33impl Drop for ProgressWatch {
34 fn drop(&mut self) {
35 let previous = self.previous.take();
36 WATCH.with(|watch| {
37 watch.replace(previous);
38 });
39 }
40}
41
42/// The call in progress got further: a frame was decoded.
43#[cfg(feature = "animated-image")]
44pub(crate) fn progressed() {
45 WATCH.with(|watch| {
46 if let Some(progressed) = watch.borrow().as_ref() {
47 progressed();
48 }
49 });
50}