dioxus_hooks/use_coroutine.rs
1use crate::{use_context_provider, use_future, UseFuture};
2use dioxus_core::prelude::{consume_context, use_hook};
3use dioxus_core::Task;
4use dioxus_signals::*;
5pub use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender};
6use std::future::Future;
7
8/// Maintain a handle over a future that can be paused, resumed, and canceled.
9///
10/// This is an upgraded form of [`crate::use_future()`] with an integrated channel system.
11/// Specifically, the coroutine generated here comes with an [`futures_channel::mpsc::UnboundedSender`]
12/// built into it - saving you the hassle of building your own.
13///
14/// Additionally, coroutines are automatically injected as shared contexts, so
15/// downstream components can tap into a coroutine's channel and send messages
16/// into a singular async event loop.
17///
18/// This makes it effective for apps that need to interact with an event loop or
19/// some asynchronous code without thinking too hard about state.
20///
21/// ## Global State
22///
23/// Typically, writing apps that handle concurrency properly can be difficult,
24/// so the intention of this hook is to make it easy to join and poll async tasks
25/// concurrently in a centralized place. You'll find that you can have much better
26/// control over your app's state if you centralize your async actions, even under
27/// the same concurrent context. This makes it easier to prevent undeseriable
28/// states in your UI while various async tasks are already running.
29///
30/// This hook is especially powerful when combined with Fermi. We can store important
31/// global data in a coroutine, and then access display-level values from the rest
32/// of our app through atoms.
33///
34/// ## UseCallback instead
35///
36/// However, you must plan out your own concurrency and synchronization. If you
37/// don't care about actions in your app being synchronized, you can use [`crate::use_callback()`]
38/// hook to spawn multiple tasks and run them concurrently.
39///
40/// ### Notice
41/// In order to use ``rx.next().await``, you will need to extend the ``Stream`` trait (used by ``UnboundedReceiver``)
42/// by adding the ``futures-util`` crate as a dependency and adding ``StreamExt`` into scope via ``use futures_util::stream::StreamExt;``
43///
44/// ## Example
45///
46/// ```rust, no_run
47/// # use dioxus::prelude::*;
48/// use futures_util::StreamExt;
49/// enum Action {
50/// Start,
51/// Stop,
52/// }
53///
54/// let chat_client = use_coroutine(|mut rx: UnboundedReceiver<Action>| async move {
55/// while let Some(action) = rx.next().await {
56/// match action {
57/// Action::Start => {}
58/// Action::Stop => {},
59/// }
60/// }
61/// });
62///
63///
64/// rsx! {
65/// button {
66/// onclick: move |_| chat_client.send(Action::Start),
67/// "Start Chat Service"
68/// }
69/// };
70/// ```
71#[doc = include_str!("../docs/rules_of_hooks.md")]
72pub fn use_coroutine<M, G, F>(mut init: G) -> Coroutine<M>
73where
74 M: 'static,
75 G: FnMut(UnboundedReceiver<M>) -> F + 'static,
76 F: Future<Output = ()> + 'static,
77{
78 let mut tx_copy_value = use_hook(|| CopyValue::new(None));
79
80 let future = use_future(move || {
81 let (tx, rx) = futures_channel::mpsc::unbounded();
82 tx_copy_value.set(Some(tx));
83 init(rx)
84 });
85
86 use_context_provider(|| Coroutine {
87 tx: tx_copy_value,
88 future,
89 })
90}
91
92/// Get a handle to a coroutine higher in the tree
93/// Analogous to use_context_provider and use_context,
94/// but used for coroutines specifically
95/// See the docs for [`use_coroutine`] for more details.
96#[doc = include_str!("../docs/rules_of_hooks.md")]
97#[must_use]
98pub fn use_coroutine_handle<M: 'static>() -> Coroutine<M> {
99 use_hook(consume_context::<Coroutine<M>>)
100}
101
102pub struct Coroutine<T: 'static> {
103 tx: CopyValue<Option<UnboundedSender<T>>>,
104 future: UseFuture,
105}
106
107impl<T> Coroutine<T> {
108 /// Get the underlying task handle
109 pub fn task(&self) -> Task {
110 self.future.task()
111 }
112
113 /// Send a message to the coroutine
114 pub fn send(&self, msg: T) {
115 let _ = self.tx.read().as_ref().unwrap().unbounded_send(msg);
116 }
117
118 pub fn tx(&self) -> UnboundedSender<T> {
119 self.tx.read().as_ref().unwrap().clone()
120 }
121
122 /// Restart this coroutine
123 pub fn restart(&mut self) {
124 self.future.restart();
125 }
126}
127
128// manual impl since deriving doesn't work with generics
129impl<T> Copy for Coroutine<T> {}
130
131impl<T> Clone for Coroutine<T> {
132 fn clone(&self) -> Self {
133 *self
134 }
135}
136
137impl<T> PartialEq for Coroutine<T> {
138 fn eq(&self, other: &Self) -> bool {
139 self.tx == other.tx && self.future == other.future
140 }
141}