use_coroutine.rs 4.5 KB

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