usecoroutine.rs 4.3 KB

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