pool.rs 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. use crate::LiveViewError;
  2. use dioxus_core::prelude::*;
  3. use dioxus_html::HtmlEvent;
  4. use futures_util::{pin_mut, SinkExt, StreamExt};
  5. use std::time::Duration;
  6. use tokio_util::task::LocalPoolHandle;
  7. #[derive(Clone)]
  8. pub struct LiveViewPool {
  9. pub(crate) pool: LocalPoolHandle,
  10. }
  11. impl Default for LiveViewPool {
  12. fn default() -> Self {
  13. Self::new()
  14. }
  15. }
  16. impl LiveViewPool {
  17. pub fn new() -> Self {
  18. LiveViewPool {
  19. pool: LocalPoolHandle::new(16),
  20. }
  21. }
  22. pub async fn launch(
  23. &self,
  24. ws: impl LiveViewSocket,
  25. app: fn(Scope<()>) -> Element,
  26. ) -> Result<(), LiveViewError> {
  27. self.launch_with_props(ws, app, ()).await
  28. }
  29. pub async fn launch_with_props<T: Send + 'static>(
  30. &self,
  31. ws: impl LiveViewSocket,
  32. app: fn(Scope<T>) -> Element,
  33. props: T,
  34. ) -> Result<(), LiveViewError> {
  35. match self.pool.spawn_pinned(move || run(app, props, ws)).await {
  36. Ok(Ok(_)) => Ok(()),
  37. Ok(Err(e)) => Err(e),
  38. Err(_) => Err(LiveViewError::SendingFailed),
  39. }
  40. }
  41. }
  42. /// A LiveViewSocket is a Sink and Stream of Strings that Dioxus uses to communicate with the client
  43. ///
  44. /// Most websockets from most HTTP frameworks can be converted into a LiveViewSocket using the appropriate adapter.
  45. ///
  46. /// You can also convert your own socket into a LiveViewSocket by implementing this trait. This trait is an auto trait,
  47. /// meaning that as long as your type implements Stream and Sink, you can use it as a LiveViewSocket.
  48. ///
  49. /// For example, the axum implementation is a really small transform:
  50. ///
  51. /// ```rust, ignore
  52. /// pub fn axum_socket(ws: WebSocket) -> impl LiveViewSocket {
  53. /// ws.map(transform_rx)
  54. /// .with(transform_tx)
  55. /// .sink_map_err(|_| LiveViewError::SendingFailed)
  56. /// }
  57. ///
  58. /// fn transform_rx(message: Result<Message, axum::Error>) -> Result<String, LiveViewError> {
  59. /// message
  60. /// .map_err(|_| LiveViewError::SendingFailed)?
  61. /// .into_text()
  62. /// .map_err(|_| LiveViewError::SendingFailed)
  63. /// }
  64. ///
  65. /// async fn transform_tx(message: String) -> Result<Message, axum::Error> {
  66. /// Ok(Message::Text(message))
  67. /// }
  68. /// ```
  69. pub trait LiveViewSocket:
  70. SinkExt<String, Error = LiveViewError>
  71. + StreamExt<Item = Result<String, LiveViewError>>
  72. + Send
  73. + 'static
  74. {
  75. }
  76. impl<S> LiveViewSocket for S where
  77. S: SinkExt<String, Error = LiveViewError>
  78. + StreamExt<Item = Result<String, LiveViewError>>
  79. + Send
  80. + 'static
  81. {
  82. }
  83. /// The primary event loop for the VirtualDom waiting for user input
  84. ///
  85. /// This function makes it easy to integrate Dioxus LiveView with any socket-based framework.
  86. ///
  87. /// As long as your framework can provide a Sink and Stream of Strings, you can use this function.
  88. ///
  89. /// You might need to transform the error types of the web backend into the LiveView error type.
  90. pub async fn run<T>(
  91. app: Component<T>,
  92. props: T,
  93. ws: impl LiveViewSocket,
  94. ) -> Result<(), LiveViewError>
  95. where
  96. T: Send + 'static,
  97. {
  98. let mut vdom = VirtualDom::new_with_props(app, props);
  99. // todo: use an efficient binary packed format for this
  100. let edits = serde_json::to_string(&vdom.rebuild()).unwrap();
  101. // pin the futures so we can use select!
  102. pin_mut!(ws);
  103. // send the initial render to the client
  104. ws.send(edits).await?;
  105. // desktop uses this wrapper struct thing around the actual event itself
  106. // this is sorta driven by tao/wry
  107. #[derive(serde::Deserialize)]
  108. struct IpcMessage {
  109. params: HtmlEvent,
  110. }
  111. loop {
  112. tokio::select! {
  113. // poll any futures or suspense
  114. _ = vdom.wait_for_work() => {}
  115. evt = ws.next() => {
  116. match evt {
  117. Some(Ok(evt)) => {
  118. if let Ok(IpcMessage { params }) = serde_json::from_str::<IpcMessage>(&evt) {
  119. vdom.handle_event(&params.name, params.data.into_any(), params.element, params.bubbles);
  120. }
  121. }
  122. // log this I guess? when would we get an error here?
  123. Some(Err(_e)) => {},
  124. None => return Ok(()),
  125. }
  126. }
  127. }
  128. let edits = vdom
  129. .render_with_deadline(tokio::time::sleep(Duration::from_millis(10)))
  130. .await;
  131. ws.send(serde_json::to_string(&edits).unwrap()).await?;
  132. }
  133. }