Skip to main content

pebble/ecs/
promise.rs

1/// The result of polling a [`Promise<T>`].
2pub enum PromiseState<T> {
3    /// Not resolved yet — poll again next tick.
4    Pending,
5    /// Resolved. Only returned once, ever.
6    Ready(T),
7    /// The matching [`Fulfiller`] was dropped without fulfilling — this
8    /// promise will never resolve.
9    Disconnected,
10}
11
12/// A one-off async result you poll each tick — e.g. GPU backend
13/// acquisition, [`Buffer::read`](crate::graphics::pipeline::buffers::Buffer::read).
14/// Not a resource, not registered anywhere — a plain value you store
15/// wherever fits (a [`Local`](crate::ecs::local::Local), a field on your
16/// own resource/component).
17pub struct Promise<T> {
18    rx: oneshot::Receiver<T>,
19}
20
21// oneshot::Receiver<T> is Send (given T: Send) but not Sync — it uses a raw
22// pointer internally and only guarantees safety for a single consumer, not
23// concurrent access through a shared reference. This engine runs systems
24// one at a time on a single thread, so a Promise is never actually touched
25// concurrently. Needed so Promise<T> can be stored in a Local<T>/resource,
26// both of which require Send + Sync.
27unsafe impl<T> Sync for Promise<T> {}
28
29impl<T> Promise<T> {
30    /// Creates a paired [`Fulfiller<T>`]/`Promise<T>` — whoever produces
31    /// the value calls `fulfiller.fulfill(value)`, whoever needs it polls
32    /// the `Promise` each tick.
33    pub fn new() -> (Fulfiller<T>, Promise<T>) {
34        let (tx, rx) = oneshot::channel();
35        (Fulfiller { tx }, Promise { rx })
36    }
37
38    /// Checks whether this has resolved yet. Non-blocking, safe to call
39    /// every tick.
40    pub fn poll(&self) -> PromiseState<T> {
41        match self.rx.try_recv() {
42            Ok(value) => PromiseState::Ready(value),
43            Err(oneshot::TryRecvError::Empty) => PromiseState::Pending,
44            Err(oneshot::TryRecvError::Disconnected) => PromiseState::Disconnected,
45        }
46    }
47}
48
49/// The producing half of a [`Promise`], from [`Promise::new`].
50pub struct Fulfiller<T> {
51    tx: oneshot::Sender<T>,
52}
53
54impl<T> Fulfiller<T> {
55    /// Resolves the matching `Promise` with `value`.
56    pub fn fulfill(self, value: T) {
57        let _ = self.tx.send(value);
58    }
59}
60
61#[cfg(test)]
62mod tests {
63    use super::*;
64
65    #[test]
66    fn poll_is_pending_before_a_fulfill_and_ready_after() {
67        let (fulfiller, promise) = Promise::new();
68
69        assert!(matches!(promise.poll(), PromiseState::Pending));
70
71        fulfiller.fulfill(42);
72
73        assert!(matches!(promise.poll(), PromiseState::Ready(42)));
74    }
75
76    #[test]
77    fn poll_is_disconnected_once_the_fulfiller_is_dropped_without_fulfilling() {
78        let (fulfiller, promise) = Promise::<i32>::new();
79
80        drop(fulfiller);
81
82        assert!(matches!(promise.poll(), PromiseState::Disconnected));
83    }
84}