usecoroutine.rs 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  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) -> &Coroutine<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(Coroutine { 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<&Coroutine<M>> {
  77. cx.use_hook(|| cx.consume_context::<Coroutine<M>>())
  78. .as_ref()
  79. }
  80. pub struct Coroutine<T> {
  81. tx: UnboundedSender<T>,
  82. task: TaskId,
  83. }
  84. // for use in futures
  85. impl<T> Clone for Coroutine<T> {
  86. fn clone(&self) -> Self {
  87. Self {
  88. tx: self.tx.clone(),
  89. task: self.task,
  90. }
  91. }
  92. }
  93. impl<T> Coroutine<T> {
  94. /// Get the ID of this coroutine
  95. #[must_use]
  96. pub fn task_id(&self) -> TaskId {
  97. self.task
  98. }
  99. /// Send a message to the coroutine
  100. pub fn send(&self, msg: T) {
  101. let _ = self.tx.unbounded_send(msg);
  102. }
  103. }
  104. #[cfg(test)]
  105. mod tests {
  106. #![allow(unused)]
  107. use super::*;
  108. use dioxus_core::prelude::*;
  109. use futures_channel::mpsc::unbounded;
  110. use futures_util::StreamExt;
  111. fn app(cx: Scope, name: String) -> Element {
  112. let task = use_coroutine(cx, |mut rx: UnboundedReceiver<i32>| async move {
  113. while let Some(msg) = rx.next().await {
  114. println!("got message: {}", msg);
  115. }
  116. });
  117. let task2 = use_coroutine(cx, view_task);
  118. let task3 = use_coroutine(cx, |rx| complex_task(rx, 10));
  119. todo!()
  120. }
  121. async fn view_task(mut rx: UnboundedReceiver<i32>) {
  122. while let Some(msg) = rx.next().await {
  123. println!("got message: {}", msg);
  124. }
  125. }
  126. enum Actions {
  127. CloseAll,
  128. OpenAll,
  129. }
  130. async fn complex_task(mut rx: UnboundedReceiver<Actions>, name: i32) {
  131. while let Some(msg) = rx.next().await {
  132. match msg {
  133. Actions::CloseAll => todo!(),
  134. Actions::OpenAll => todo!(),
  135. }
  136. }
  137. }
  138. }