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}