dioxus_hooks/
use_waker.rs

1use dioxus_core::use_hook;
2use dioxus_signals::{ReadableExt, Signal, WritableExt};
3use futures_channel::oneshot::{Canceled, Receiver, Sender};
4use futures_util::{future::Shared, FutureExt};
5
6/// A hook that provides a waker for other hooks to provide async/await capabilities.
7///
8/// This hook is a reactive wrapper over the `Shared<T>` future from the `futures` crate.
9/// It allows multiple awaiters to wait on the same value, similar to a broadcast channel from Tokio.
10///
11/// Calling `.await` on the waker will consume the waker, so you'll need to call `.wait()` on the
12/// source to get a new waker.
13pub fn use_waker<T: Clone + 'static>() -> UseWaker<T> {
14    // We use a oneshot channel to send the value to the awaiter.
15    // The shared future allows multiple awaiters to wait on the same value.
16    let (task_tx, task_rx) = use_hook(|| {
17        let (tx, rx) = futures_channel::oneshot::channel::<T>();
18        let shared = rx.shared();
19        (Signal::new(tx), Signal::new(shared))
20    });
21
22    UseWaker { task_tx, task_rx }
23}
24
25#[derive(Debug)]
26pub struct UseWaker<T: 'static> {
27    task_tx: Signal<Sender<T>>,
28    task_rx: Signal<Shared<Receiver<T>>>,
29}
30
31impl<T: Clone + 'static> UseWaker<T> {
32    /// Wake the current task with the provided value.
33    /// All awaiters will receive a clone of the value.
34    pub fn wake(&mut self, value: T) {
35        // We ignore the error because it means the task has already been woken.
36        let (tx, rx) = futures_channel::oneshot::channel::<T>();
37        let shared = rx.shared();
38
39        // Swap out the old sender and receiver with the new ones.
40        let tx = self.task_tx.replace(tx);
41        let _rx = self.task_rx.replace(shared);
42
43        // And then send out the oneshot value, waking up all awaiters.
44        let _ = tx.send(value);
45    }
46
47    /// Returns a future that resolves when the task is woken.
48    pub async fn wait(&self) -> Result<T, Canceled> {
49        self.task_rx.cloned().await
50    }
51}
52
53// Can await the waker to be woken.
54// We use `.peek()` here to avoid reacting to changes in the underlying task_rx which could lead
55// to an effect/future loop.
56impl<T: Clone + 'static> std::future::Future for UseWaker<T> {
57    type Output = Result<T, Canceled>;
58
59    fn poll(
60        self: std::pin::Pin<&mut Self>,
61        cx: &mut std::task::Context<'_>,
62    ) -> std::task::Poll<Self::Output> {
63        self.task_rx.peek().clone().poll_unpin(cx)
64    }
65}
66
67impl<T> Copy for UseWaker<T> {}
68impl<T> Clone for UseWaker<T> {
69    fn clone(&self) -> Self {
70        *self
71    }
72}