123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163 |
- use dioxus_core::{ScopeState, TaskId};
- pub use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender};
- use std::future::Future;
- /// Maintain a handle over a future that can be paused, resumed, and canceled.
- ///
- /// This is an upgraded form of [`use_future`] with an integrated channel system.
- /// Specifically, the coroutine generated here comes with an [`UnboundedChannel`]
- /// built into it - saving you the hassle of building your own.
- ///
- /// Addititionally, coroutines are automatically injected as shared contexts, so
- /// downstream components can tap into a coroutine's channel and send messages
- /// into a singular async event loop.
- ///
- /// This makes it effective for apps that need to interact with an event loop or
- /// some asynchronous code without thinking too hard about state.
- ///
- /// ## Global State
- ///
- /// Typically, writing apps that handle concurrency properly can be difficult,
- /// so the intention of this hook is to make it easy to join and poll async tasks
- /// concurrently in a centralized place. You'll find that you can have much better
- /// control over your app's state if you centralize your async actions, even under
- /// the same concurrent context. This makes it easier to prevent undeseriable
- /// states in your UI while various async tasks are already running.
- ///
- /// This hook is especially powerful when combined with Fermi. We can store important
- /// global data in a coroutine, and then access display-level values from the rest
- /// of our app through atoms.
- ///
- /// ## UseCallback instead
- ///
- /// However, you must plan out your own concurrency and synchronization. If you
- /// don't care about actions in your app being synchronized, you can use [`use_callback`]
- /// hook to spawn multiple tasks and run them concurrently.
- ///
- /// ### Notice
- /// In order to use ``rx.next().await``, you will need to extend the ``Stream`` trait (used by ``UnboundedReceiver``)
- /// by adding the ``futures-util`` crate as a dependency and adding ``StreamExt`` into scope via ``use futures_util::stream::StreamExt;``
- ///
- /// ## Example
- ///
- /// ```rust, ignore
- /// enum Action {
- /// Start,
- /// Stop,
- /// }
- ///
- /// let chat_client = use_coroutine(cx, |rx: UnboundedReceiver<Action>| async move {
- /// while let Some(action) = rx.next().await {
- /// match action {
- /// Action::Start => {}
- /// Action::Stop => {},
- /// }
- /// }
- /// });
- ///
- ///
- /// cx.render(rsx!{
- /// button {
- /// onclick: move |_| chat_client.send(Action::Start),
- /// "Start Chat Service"
- /// }
- /// })
- /// ```
- pub fn use_coroutine<M, G, F>(cx: &ScopeState, init: G) -> &Coroutine<M>
- where
- M: 'static,
- G: FnOnce(UnboundedReceiver<M>) -> F,
- F: Future<Output = ()> + 'static,
- {
- cx.use_hook(|| {
- let (tx, rx) = futures_channel::mpsc::unbounded();
- let task = cx.push_future(init(rx));
- cx.provide_context(Coroutine { tx, task })
- })
- }
- /// Get a handle to a coroutine higher in the tree
- ///
- /// See the docs for [`use_coroutine`] for more details.
- pub fn use_coroutine_handle<M: 'static>(cx: &ScopeState) -> Option<&Coroutine<M>> {
- cx.use_hook(|| cx.consume_context::<Coroutine<M>>())
- .as_ref()
- }
- pub struct Coroutine<T> {
- tx: UnboundedSender<T>,
- task: TaskId,
- }
- // for use in futures
- impl<T> Clone for Coroutine<T> {
- fn clone(&self) -> Self {
- Self {
- tx: self.tx.clone(),
- task: self.task,
- }
- }
- }
- impl<T> Coroutine<T> {
- /// Get the ID of this coroutine
- #[must_use]
- pub fn task_id(&self) -> TaskId {
- self.task
- }
- /// Send a message to the coroutine
- pub fn send(&self, msg: T) {
- let _ = self.tx.unbounded_send(msg);
- }
- }
- impl<T> PartialEq for Coroutine<T> {
- fn eq(&self, other: &Self) -> bool {
- self.task == other.task
- }
- }
- #[cfg(test)]
- mod tests {
- #![allow(unused)]
- use super::*;
- use dioxus_core::prelude::*;
- use futures_channel::mpsc::unbounded;
- use futures_util::StreamExt;
- fn app(cx: Scope, name: String) -> Element {
- let task = use_coroutine(cx, |mut rx: UnboundedReceiver<i32>| async move {
- while let Some(msg) = rx.next().await {
- println!("got message: {msg}");
- }
- });
- let task2 = use_coroutine(cx, view_task);
- let task3 = use_coroutine(cx, |rx| complex_task(rx, 10));
- todo!()
- }
- async fn view_task(mut rx: UnboundedReceiver<i32>) {
- while let Some(msg) = rx.next().await {
- println!("got message: {msg}");
- }
- }
- enum Actions {
- CloseAll,
- OpenAll,
- }
- async fn complex_task(mut rx: UnboundedReceiver<Actions>, name: i32) {
- while let Some(msg) = rx.next().await {
- match msg {
- Actions::CloseAll => todo!(),
- Actions::OpenAll => todo!(),
- }
- }
- }
- }
|