use_coroutine.rs 4.5 KB

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