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