usecoroutine.rs 4.5 KB

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