add debug information for signal subscriptions
This commit is contained in:
1 parent
c0f2e8391c
commit
f539698963
7 files changed
+96
-13
No files matched your search
Generated
+11
@@ -2429,6 +2429,7 @@ name = "dioxus-examples"
|
|||||||
version = "0.4.3"
|
version = "0.4.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"dioxus",
|
"dioxus",
|
||||||
|
"dioxus-logger",
|
||||||
"dioxus-ssr",
|
"dioxus-ssr",
|
||||||
"form_urlencoded",
|
"form_urlencoded",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
@@ -2614,6 +2615,16 @@ dependencies = [
|
|||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dioxus-logger"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3d7cbab0b5519060fe9e14b3c21e3f2329b8386cd905618f78c7b929cd00cf54"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
"web-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dioxus-mobile"
|
name = "dioxus-mobile"
|
||||||
version = "0.4.3"
|
version = "0.4.3"
|
||||||
|
|||||||
@@ -79,11 +79,7 @@ impl VNode {
|
|||||||
// The target ScopeState still has the reference to the old props, so there's no need to update anything
|
// The target ScopeState still has the reference to the old props, so there's no need to update anything
|
||||||
// This also implicitly drops the new props since they're not used
|
// This also implicitly drops the new props since they're not used
|
||||||
if old_props.memoize(new_props.props()) {
|
if old_props.memoize(new_props.props()) {
|
||||||
tracing::trace!(
|
tracing::trace!("Memoized props for component {:#?}", scope_id,);
|
||||||
"Memoized props for component {:#?} ({})",
|
|
||||||
scope_id,
|
|
||||||
old_scope.state().name
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,9 +9,26 @@ use std::{cell::Ref, fmt::Debug, rc::Rc};
|
|||||||
/// time. We do try and guarantee that between calls to `wait_for_work`, no ScopeIds will be recycled in order to give
|
/// time. We do try and guarantee that between calls to `wait_for_work`, no ScopeIds will be recycled in order to give
|
||||||
/// time for any logic that relies on these IDs to properly update.
|
/// time for any logic that relies on these IDs to properly update.
|
||||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)]
|
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
pub struct ScopeId(pub usize);
|
pub struct ScopeId(pub usize);
|
||||||
|
|
||||||
|
impl std::fmt::Debug for ScopeId {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let mut builder = f.debug_tuple("ScopeId");
|
||||||
|
let mut builder = builder.field(&self.0);
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
{
|
||||||
|
if let Some(name) = Runtime::current()
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|rt| rt.get_state(*self))
|
||||||
|
{
|
||||||
|
builder = builder.field(&name.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
builder.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ScopeId {
|
impl ScopeId {
|
||||||
/// The root ScopeId.
|
/// The root ScopeId.
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -373,7 +373,7 @@ impl VirtualDom {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
tracing::trace!("Marking scope {:?} ({}) as dirty", id, scope.name);
|
tracing::trace!("Marking scope {:?} as dirty", id);
|
||||||
self.dirty_scopes.insert(DirtyScope {
|
self.dirty_scopes.insert(DirtyScope {
|
||||||
height: scope.height(),
|
height: scope.height(),
|
||||||
id,
|
id,
|
||||||
|
|||||||
@@ -5,17 +5,19 @@ use dioxus_signals::ReactiveContext;
|
|||||||
/// The signal will be owned by the current component and will be dropped when the component is dropped.
|
/// The signal will be owned by the current component and will be dropped when the component is dropped.
|
||||||
///
|
///
|
||||||
/// If the use_effect call was skipped due to an early return, the effect will no longer activate.
|
/// If the use_effect call was skipped due to an early return, the effect will no longer activate.
|
||||||
|
#[track_caller]
|
||||||
pub fn use_effect(mut callback: impl FnMut() + 'static) {
|
pub fn use_effect(mut callback: impl FnMut() + 'static) {
|
||||||
// let mut run_effect = use_hook(|| CopyValue::new(true));
|
// let mut run_effect = use_hook(|| CopyValue::new(true));
|
||||||
// use_hook_did_run(move |did_run| run_effect.set(did_run));
|
// use_hook_did_run(move |did_run| run_effect.set(did_run));
|
||||||
|
|
||||||
|
let location = std::panic::Location::caller();
|
||||||
|
|
||||||
use_hook(|| {
|
use_hook(|| {
|
||||||
spawn(async move {
|
spawn(async move {
|
||||||
let rc = ReactiveContext::new();
|
let rc = ReactiveContext::new_with_origin(location);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
// Wait for the dom the be finished with sync work
|
// Wait for the dom the be finished with sync work
|
||||||
flush_sync().await;
|
// flush_sync().await;
|
||||||
|
|
||||||
// Run the effect
|
// Run the effect
|
||||||
rc.run_in(&mut callback);
|
rc.run_in(&mut callback);
|
||||||
|
|||||||
@@ -22,20 +22,47 @@ thread_local! {
|
|||||||
static CURRENT: RefCell<Vec<ReactiveContext>> = const { RefCell::new(vec![]) };
|
static CURRENT: RefCell<Vec<ReactiveContext>> = const { RefCell::new(vec![]) };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for ReactiveContext {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let read = self.inner.read();
|
||||||
|
match read.scope_subscriber {
|
||||||
|
Some(scope) => write!(f, "ReactiveContext for scope {:?}", scope),
|
||||||
|
None => {
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
return write!(f, "ReactiveContext created at {}", read.origin);
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
write!(f, "ReactiveContext")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for ReactiveContext {
|
impl Default for ReactiveContext {
|
||||||
|
#[track_caller]
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::new_for_scope(None)
|
Self::new_for_scope(None, std::panic::Location::caller())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ReactiveContext {
|
impl ReactiveContext {
|
||||||
/// Create a new reactive context
|
/// Create a new reactive context
|
||||||
|
#[track_caller]
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self::default()
|
Self::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a new reactive context with a location for debugging purposes
|
||||||
|
/// This is useful for reactive contexts created within closures
|
||||||
|
pub fn new_with_origin(origin: &'static std::panic::Location<'static>) -> Self {
|
||||||
|
Self::new_for_scope(None, origin)
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a new reactive context that may update a scope
|
/// Create a new reactive context that may update a scope
|
||||||
pub(crate) fn new_for_scope(scope: Option<ScopeId>) -> Self {
|
#[allow(unused)]
|
||||||
|
pub(crate) fn new_for_scope(
|
||||||
|
scope: Option<ScopeId>,
|
||||||
|
origin: &'static std::panic::Location<'static>,
|
||||||
|
) -> Self {
|
||||||
let (tx, rx) = flume::unbounded();
|
let (tx, rx) = flume::unbounded();
|
||||||
|
|
||||||
let mut scope_subscribers = FxHashSet::default();
|
let mut scope_subscribers = FxHashSet::default();
|
||||||
@@ -49,6 +76,8 @@ impl ReactiveContext {
|
|||||||
self_: None,
|
self_: None,
|
||||||
update_any: schedule_update_any(),
|
update_any: schedule_update_any(),
|
||||||
receiver: rx,
|
receiver: rx,
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
origin,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut self_ = Self {
|
let mut self_ = Self {
|
||||||
@@ -87,6 +116,7 @@ impl ReactiveContext {
|
|||||||
// Otherwise, create a new context at the current scope
|
// Otherwise, create a new context at the current scope
|
||||||
Some(provide_context(ReactiveContext::new_for_scope(
|
Some(provide_context(ReactiveContext::new_for_scope(
|
||||||
current_scope_id(),
|
current_scope_id(),
|
||||||
|
std::panic::Location::caller(),
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,6 +138,17 @@ impl ReactiveContext {
|
|||||||
/// Returns true if the context was marked as dirty, or false if the context has been dropped
|
/// Returns true if the context was marked as dirty, or false if the context has been dropped
|
||||||
pub fn mark_dirty(&self) -> bool {
|
pub fn mark_dirty(&self) -> bool {
|
||||||
if let Ok(self_read) = self.inner.try_read() {
|
if let Ok(self_read) = self.inner.try_read() {
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
{
|
||||||
|
if let Some(scope) = self_read.scope_subscriber {
|
||||||
|
tracing::trace!("Marking reactive context for scope {:?} as dirty", scope);
|
||||||
|
} else {
|
||||||
|
tracing::trace!(
|
||||||
|
"Marking reactive context created at {} as dirty",
|
||||||
|
self_read.origin
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
if let Some(scope) = self_read.scope_subscriber {
|
if let Some(scope) = self_read.scope_subscriber {
|
||||||
(self_read.update_any)(scope);
|
(self_read.update_any)(scope);
|
||||||
}
|
}
|
||||||
@@ -148,4 +189,8 @@ struct Inner {
|
|||||||
// Futures will call .changed().await
|
// Futures will call .changed().await
|
||||||
sender: flume::Sender<()>,
|
sender: flume::Sender<()>,
|
||||||
receiver: flume::Receiver<()>,
|
receiver: flume::Receiver<()>,
|
||||||
|
|
||||||
|
// Debug information for signal subscriptions
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
origin: &'static std::panic::Location<'static>,
|
||||||
}
|
}
|
||||||
@@ -202,6 +202,7 @@ impl<T, S: Storage<SignalData<T>>> Readable for Signal<T, S> {
|
|||||||
let inner = self.inner.try_read()?;
|
let inner = self.inner.try_read()?;
|
||||||
|
|
||||||
if let Some(reactive_context) = ReactiveContext::current() {
|
if let Some(reactive_context) = ReactiveContext::current() {
|
||||||
|
tracing::trace!("Subscribing to the reactive context {}", reactive_context);
|
||||||
inner.subscribers.lock().unwrap().insert(reactive_context);
|
inner.subscribers.lock().unwrap().insert(reactive_context);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -244,7 +245,11 @@ impl<T: 'static, S: Storage<SignalData<T>>> Writable for Signal<T, S> {
|
|||||||
let borrow = S::map_mut(inner, |v| &mut v.value);
|
let borrow = S::map_mut(inner, |v| &mut v.value);
|
||||||
Write {
|
Write {
|
||||||
write: borrow,
|
write: borrow,
|
||||||
drop_signal: Box::new(SignalSubscriberDrop { signal: *self }),
|
drop_signal: Box::new(SignalSubscriberDrop {
|
||||||
|
signal: *self,
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
origin: std::panic::Location::caller(),
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -344,10 +349,17 @@ impl<T: ?Sized, S: AnyStorage> DerefMut for Write<T, S> {
|
|||||||
|
|
||||||
struct SignalSubscriberDrop<T: 'static, S: Storage<SignalData<T>>> {
|
struct SignalSubscriberDrop<T: 'static, S: Storage<SignalData<T>>> {
|
||||||
signal: Signal<T, S>,
|
signal: Signal<T, S>,
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
origin: &'static std::panic::Location<'static>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: 'static, S: Storage<SignalData<T>>> Drop for SignalSubscriberDrop<T, S> {
|
impl<T: 'static, S: Storage<SignalData<T>>> Drop for SignalSubscriberDrop<T, S> {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
tracing::trace!(
|
||||||
|
"Write on signal at {:?} finished, updating subscribers",
|
||||||
|
self.origin
|
||||||
|
);
|
||||||
self.signal.update_subscribers();
|
self.signal.update_subscribers();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user