reactive_context.rs 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. use dioxus_core::prelude::{
  2. current_scope_id, has_context, provide_context, schedule_update_any, ScopeId,
  3. };
  4. use futures_channel::mpsc::UnboundedReceiver;
  5. use generational_box::SyncStorage;
  6. use std::{cell::RefCell, hash::Hash};
  7. use crate::{CopyValue, Writable};
  8. /// A context for signal reads and writes to be directed to
  9. ///
  10. /// When a signal calls .read(), it will look for the current ReactiveContext to read from.
  11. /// If it doesn't find it, then it will try and insert a context into the nearest component scope via context api.
  12. ///
  13. /// When the ReactiveContext drops, it will remove itself from the associated contexts attached to signal
  14. #[derive(Clone, Copy, PartialEq, Eq)]
  15. pub struct ReactiveContext {
  16. inner: CopyValue<Inner, SyncStorage>,
  17. }
  18. thread_local! {
  19. static CURRENT: RefCell<Vec<ReactiveContext>> = const { RefCell::new(vec![]) };
  20. }
  21. impl std::fmt::Display for ReactiveContext {
  22. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  23. #[cfg(debug_assertions)]
  24. {
  25. use crate::Readable;
  26. if let Ok(read) = self.inner.try_read() {
  27. if let Some(scope) = read.scope {
  28. return write!(f, "ReactiveContext(for scope: {:?})", scope);
  29. }
  30. return write!(f, "ReactiveContext created at {}", read.origin);
  31. }
  32. }
  33. write!(f, "ReactiveContext")
  34. }
  35. }
  36. impl ReactiveContext {
  37. /// Create a new reactive context
  38. #[track_caller]
  39. pub fn new() -> (Self, UnboundedReceiver<()>) {
  40. Self::new_with_origin(std::panic::Location::caller())
  41. }
  42. /// Create a new reactive context with a location for debugging purposes
  43. /// This is useful for reactive contexts created within closures
  44. pub fn new_with_origin(
  45. origin: &'static std::panic::Location<'static>,
  46. ) -> (Self, UnboundedReceiver<()>) {
  47. let (tx, rx) = futures_channel::mpsc::unbounded();
  48. let callback = move || {
  49. let _ = tx.unbounded_send(());
  50. };
  51. let _self = Self::new_with_callback(callback, current_scope_id().unwrap(), origin);
  52. (_self, rx)
  53. }
  54. /// Create a new reactive context that may update a scope. When any signal that this context subscribes to changes, the callback will be run
  55. pub fn new_with_callback(
  56. callback: impl FnMut() + Send + Sync + 'static,
  57. scope: ScopeId,
  58. #[allow(unused)] origin: &'static std::panic::Location<'static>,
  59. ) -> Self {
  60. let inner = Inner {
  61. self_: None,
  62. update: Box::new(callback),
  63. #[cfg(debug_assertions)]
  64. origin,
  65. #[cfg(debug_assertions)]
  66. scope: None,
  67. };
  68. let mut self_ = Self {
  69. inner: CopyValue::new_maybe_sync_in_scope(inner, scope),
  70. };
  71. self_.inner.write().self_ = Some(self_);
  72. self_
  73. }
  74. /// Get the current reactive context
  75. ///
  76. /// If this was set manually, then that value will be returned.
  77. ///
  78. /// If there's no current reactive context, then a new one will be created for the current scope and returned.
  79. pub fn current() -> Option<Self> {
  80. let cur = CURRENT.with(|current| current.borrow().last().cloned());
  81. // If we're already inside a reactive context, then return that
  82. if let Some(cur) = cur {
  83. return Some(cur);
  84. }
  85. // If we're rendering, then try and use the reactive context attached to this component
  86. if !dioxus_core::vdom_is_rendering() {
  87. return None;
  88. }
  89. if let Some(cx) = has_context() {
  90. return Some(cx);
  91. }
  92. let update_any = schedule_update_any();
  93. let scope_id = current_scope_id().unwrap();
  94. let update_scope = move || {
  95. tracing::trace!("Marking scope {:?} as dirty", scope_id);
  96. update_any(scope_id)
  97. };
  98. // Otherwise, create a new context at the current scope
  99. #[allow(unused_mut)]
  100. let mut reactive_context = ReactiveContext::new_with_callback(
  101. update_scope,
  102. scope_id,
  103. std::panic::Location::caller(),
  104. );
  105. #[cfg(debug_assertions)]
  106. {
  107. // Associate the reactive context with the current scope for debugging
  108. reactive_context.inner.write().scope = Some(scope_id);
  109. }
  110. Some(provide_context(reactive_context))
  111. }
  112. /// Run this function in the context of this reactive context
  113. ///
  114. /// This will set the current reactive context to this context for the duration of the function.
  115. /// You can then get information about the current subscriptions.
  116. pub fn run_in<O>(&self, f: impl FnOnce() -> O) -> O {
  117. CURRENT.with(|current| current.borrow_mut().push(*self));
  118. let out = f();
  119. CURRENT.with(|current| current.borrow_mut().pop());
  120. out
  121. }
  122. /// Marks this reactive context as dirty
  123. ///
  124. /// If there's a scope associated with this context, then it will be marked as dirty too
  125. ///
  126. /// Returns true if the context was marked as dirty, or false if the context has been dropped
  127. pub fn mark_dirty(&self) -> bool {
  128. if let Ok(mut self_write) = self.inner.try_write_unchecked() {
  129. #[cfg(debug_assertions)]
  130. {
  131. tracing::trace!(
  132. "Marking reactive context created at {} as dirty",
  133. self_write.origin
  134. );
  135. }
  136. (self_write.update)();
  137. true
  138. } else {
  139. false
  140. }
  141. }
  142. /// Get the scope that inner CopyValue is associated with
  143. pub fn origin_scope(&self) -> ScopeId {
  144. self.inner.origin_scope()
  145. }
  146. }
  147. impl Hash for ReactiveContext {
  148. fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
  149. self.inner.id().hash(state);
  150. }
  151. }
  152. struct Inner {
  153. self_: Option<ReactiveContext>,
  154. // Futures will call .changed().await
  155. update: Box<dyn FnMut() + Send + Sync>,
  156. // Debug information for signal subscriptions
  157. #[cfg(debug_assertions)]
  158. origin: &'static std::panic::Location<'static>,
  159. #[cfg(debug_assertions)]
  160. // The scope that this reactive context is associated with
  161. scope: Option<ScopeId>,
  162. }