Skip to main content

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}