Skip to main content

valo_codec/
pending.rs

1//! The one completion primitive: a result on its way, usable as a future or by polling.
2use crate::DecodeError;
3use std::future::Future;
4use std::pin::Pin;
5use std::task::{Context, Poll, Waker};
6
7type Outcome<T> = Result<T, DecodeError>;
8type BoxedFuture<'a, T> = Pin<Box<dyn Future<Output = Outcome<T>> + 'a>>;
9
10/// Pending is a decode result on its way.
11///
12/// It is a `Future`: `.await` it on any single-threaded executor, or wrap it so its waker also
13/// wakes an event loop. A host that runs a frame loop instead calls [`try_take`](Self::try_take)
14/// once a frame. Dropping a `Pending` cancels the work if it has not started.
15///
16/// The lifetime is what a frame borrows from its [`Codec`](crate::Codec): opening an image
17/// answers a `Pending<'static, _>`, while a frame keeps the codec borrowed until it arrives, so
18/// one codec cannot be decoding two frames at once.
19///
20/// The type is `!Send` on purpose: the inline loader runs the decode inside `poll`, on the
21/// polling thread, and an opened codec belongs to that thread too.
22pub struct Pending<'a, T> {
23    future: Option<BoxedFuture<'a, T>>,
24}
25
26impl<'a, T> Pending<'a, T> {
27    pub(crate) fn from_future(future: impl Future<Output = Outcome<T>> + 'a) -> Self {
28        Self {
29            future: Some(Box::pin(future)),
30        }
31    }
32
33    /// `try_take` returns the result if it is ready, without blocking.
34    ///
35    /// With an inline loader the first call does the decode. Returns `None` while a worker is
36    /// still busy.
37    ///
38    /// # Panics
39    /// Panics if called again after it returned `Some`.
40    pub fn try_take(&mut self) -> Option<Outcome<T>> {
41        let mut context = Context::from_waker(Waker::noop());
42        match self.poll_inner(&mut context) {
43            Poll::Ready(result) => Some(result),
44            Poll::Pending => None,
45        }
46    }
47
48    fn poll_inner(&mut self, context: &mut Context<'_>) -> Poll<Outcome<T>> {
49        let future = self
50            .future
51            .as_mut()
52            .expect("Pending polled after it completed");
53        let result = future.as_mut().poll(context);
54        if result.is_ready() {
55            self.future = None;
56        }
57        result
58    }
59}
60
61impl<T> Future for Pending<'_, T> {
62    type Output = Outcome<T>;
63
64    fn poll(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Self::Output> {
65        self.get_mut().poll_inner(context)
66    }
67}
68
69impl<T> std::fmt::Debug for Pending<'_, T> {
70    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
71        f.debug_struct("Pending")
72            .field("completed", &self.future.is_none())
73            .finish()
74    }
75}
76
77#[cfg(all(feature = "worker", not(target_arch = "wasm32")))]
78pub(crate) use shared::{reply, Reply};
79
80/// The cross-thread half: a slot the worker fills and the caller's future waits on.
81#[cfg(all(feature = "worker", not(target_arch = "wasm32")))]
82mod shared {
83    use super::*;
84    use std::sync::{Arc, Mutex};
85
86    enum State<T> {
87        Waiting(Option<Waker>),
88        Ready(Outcome<T>),
89        Taken,
90        Cancelled,
91    }
92
93    struct Slot<T>(Mutex<State<T>>);
94
95    impl<T> Slot<T> {
96        fn lock(&self) -> std::sync::MutexGuard<'_, State<T>> {
97            self.0
98                .lock()
99                .unwrap_or_else(|poisoned| poisoned.into_inner())
100        }
101    }
102
103    /// `reply` makes a linked pair: the future the caller awaits and the reply a worker fills.
104    pub(crate) fn reply<T>() -> (impl Future<Output = Outcome<T>>, Reply<T>) {
105        let slot = Arc::new(Slot(Mutex::new(State::Waiting(None))));
106        (Awaiting(slot.clone()), Reply(slot))
107    }
108
109    struct Awaiting<T>(Arc<Slot<T>>);
110
111    impl<T> Future for Awaiting<T> {
112        type Output = Outcome<T>;
113
114        fn poll(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Self::Output> {
115            let mut state = self.0.lock();
116            match &mut *state {
117                State::Waiting(waker) => {
118                    *waker = Some(context.waker().clone());
119                    Poll::Pending
120                }
121                State::Ready(_) => match std::mem::replace(&mut *state, State::Taken) {
122                    State::Ready(result) => Poll::Ready(result),
123                    _ => unreachable!("matched Ready under the same lock"),
124                },
125                State::Taken => panic!("decode result taken twice"),
126                State::Cancelled => unreachable!("only a dropped future cancels"),
127            }
128        }
129    }
130
131    /// Dropping the future is the cancellation signal a worker reads before starting a job.
132    impl<T> Drop for Awaiting<T> {
133        fn drop(&mut self) {
134            let mut state = self.0.lock();
135            if matches!(*state, State::Waiting(_)) {
136                *state = State::Cancelled;
137            }
138        }
139    }
140
141    /// Reply is the worker's handle for delivering one result.
142    pub(crate) struct Reply<T>(Arc<Slot<T>>);
143
144    impl<T> Reply<T> {
145        /// `is_cancelled` is true once the caller stopped waiting; skip the work.
146        pub(crate) fn is_cancelled(&self) -> bool {
147            matches!(*self.0.lock(), State::Cancelled)
148        }
149
150        /// `send` delivers the result and wakes the caller, or hands it back if nobody waits.
151        pub(crate) fn send(self, result: Outcome<T>) -> Result<(), Outcome<T>> {
152            deliver(&self.0, result)
153        }
154    }
155
156    /// A reply dropped unsent — its job never ran because the worker went away — resolves the
157    /// caller with [`DecodeError::Closed`] rather than leaving it waiting forever.
158    impl<T> Drop for Reply<T> {
159        fn drop(&mut self) {
160            let _ = deliver(&self.0, Err(DecodeError::Closed));
161        }
162    }
163
164    /// Completes a waiting slot and wakes its poller; a slot nobody waits on refuses the result.
165    fn deliver<T>(slot: &Slot<T>, result: Outcome<T>) -> Result<(), Outcome<T>> {
166        let mut state = slot.lock();
167        let State::Waiting(waker) = &mut *state else {
168            return Err(result);
169        };
170        let waker = waker.take();
171        *state = State::Ready(result);
172        drop(state);
173        if let Some(waker) = waker {
174            waker.wake();
175        }
176        Ok(())
177    }
178}