From 36ae7ec12e0900558f1d6e56f026cdaca1c7712c Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 19 Feb 2024 14:59:58 -0600 Subject: [PATCH 01/19] wrap fullstack rebuilds in block in place --- packages/fullstack/src/render.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/fullstack/src/render.rs b/packages/fullstack/src/render.rs index ea179207f..adb38ac4c 100644 --- a/packages/fullstack/src/render.rs +++ b/packages/fullstack/src/render.rs @@ -10,6 +10,7 @@ use std::future::Future; use std::sync::Arc; use std::sync::RwLock; use tokio::task::JoinHandle; +use tokio::task::block_in_place; use crate::prelude::*; use dioxus_lib::prelude::*; @@ -64,7 +65,7 @@ impl SsrRendererPool { let prev_context = SERVER_CONTEXT.with(|ctx| ctx.replace(server_context)); // poll the future, which may call server_context() tracing::info!("Rebuilding vdom"); - vdom.rebuild(&mut NoOpMutations); + block_in_place(|| vdom.rebuild(&mut NoOpMutations)); vdom.wait_for_suspense().await; tracing::info!("Suspense resolved"); // after polling the future, we need to restore the context @@ -124,7 +125,7 @@ impl SsrRendererPool { .with(|ctx| ctx.replace(Box::new(server_context))); // poll the future, which may call server_context() tracing::info!("Rebuilding vdom"); - vdom.rebuild(&mut NoOpMutations); + block_in_place(|| vdom.rebuild(&mut NoOpMutations)); vdom.wait_for_suspense().await; tracing::info!("Suspense resolved"); // after polling the future, we need to restore the context From f53969896317ed96aec8224e5e197e3592e45951 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 26 Feb 2024 11:46:01 -0600 Subject: [PATCH 02/19] add debug information for signal subscriptions --- Cargo.lock | 11 ++++++ packages/core/src/diff/component.rs | 6 +-- packages/core/src/scopes.rs | 19 ++++++++- packages/core/src/virtual_dom.rs | 2 +- packages/hooks/src/use_effect.rs | 8 ++-- packages/signals/src/reactive_context.rs | 49 +++++++++++++++++++++++- packages/signals/src/signal.rs | 14 ++++++- 7 files changed, 96 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 52f5a173a..28babd636 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2429,6 +2429,7 @@ name = "dioxus-examples" version = "0.4.3" dependencies = [ "dioxus", + "dioxus-logger", "dioxus-ssr", "form_urlencoded", "futures-util", @@ -2614,6 +2615,16 @@ dependencies = [ "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]] name = "dioxus-mobile" version = "0.4.3" diff --git a/packages/core/src/diff/component.rs b/packages/core/src/diff/component.rs index 6d4ab005e..651823c5c 100644 --- a/packages/core/src/diff/component.rs +++ b/packages/core/src/diff/component.rs @@ -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 // This also implicitly drops the new props since they're not used if old_props.memoize(new_props.props()) { - tracing::trace!( - "Memoized props for component {:#?} ({})", - scope_id, - old_scope.state().name - ); + tracing::trace!("Memoized props for component {:#?}", scope_id,); return; } diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index 632b42fa3..b34187472 100644 --- a/packages/core/src/scopes.rs +++ b/packages/core/src/scopes.rs @@ -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 for any logic that relies on these IDs to properly update. #[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); +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 { /// The root ScopeId. /// diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 2de7f0514..9e78f53db 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -373,7 +373,7 @@ impl VirtualDom { return; }; - tracing::trace!("Marking scope {:?} ({}) as dirty", id, scope.name); + tracing::trace!("Marking scope {:?} as dirty", id); self.dirty_scopes.insert(DirtyScope { height: scope.height(), id, diff --git a/packages/hooks/src/use_effect.rs b/packages/hooks/src/use_effect.rs index a3d16f420..7811d5171 100644 --- a/packages/hooks/src/use_effect.rs +++ b/packages/hooks/src/use_effect.rs @@ -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. /// /// 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) { // let mut run_effect = use_hook(|| CopyValue::new(true)); // use_hook_did_run(move |did_run| run_effect.set(did_run)); + let location = std::panic::Location::caller(); + use_hook(|| { spawn(async move { - let rc = ReactiveContext::new(); - + let rc = ReactiveContext::new_with_origin(location); loop { // Wait for the dom the be finished with sync work - flush_sync().await; + // flush_sync().await; // Run the effect rc.run_in(&mut callback); diff --git a/packages/signals/src/reactive_context.rs b/packages/signals/src/reactive_context.rs index fdbaf6429..98b611bb1 100644 --- a/packages/signals/src/reactive_context.rs +++ b/packages/signals/src/reactive_context.rs @@ -22,20 +22,47 @@ thread_local! { static CURRENT: RefCell> = 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 { + #[track_caller] fn default() -> Self { - Self::new_for_scope(None) + Self::new_for_scope(None, std::panic::Location::caller()) } } impl ReactiveContext { /// Create a new reactive context + #[track_caller] pub fn new() -> Self { 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 - pub(crate) fn new_for_scope(scope: Option) -> Self { + #[allow(unused)] + pub(crate) fn new_for_scope( + scope: Option, + origin: &'static std::panic::Location<'static>, + ) -> Self { let (tx, rx) = flume::unbounded(); let mut scope_subscribers = FxHashSet::default(); @@ -49,6 +76,8 @@ impl ReactiveContext { self_: None, update_any: schedule_update_any(), receiver: rx, + #[cfg(debug_assertions)] + origin, }; let mut self_ = Self { @@ -87,6 +116,7 @@ impl ReactiveContext { // Otherwise, create a new context at the current scope Some(provide_context(ReactiveContext::new_for_scope( 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 pub fn mark_dirty(&self) -> bool { 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 { (self_read.update_any)(scope); } @@ -148,4 +189,8 @@ struct Inner { // Futures will call .changed().await sender: flume::Sender<()>, receiver: flume::Receiver<()>, + + // Debug information for signal subscriptions + #[cfg(debug_assertions)] + origin: &'static std::panic::Location<'static>, } diff --git a/packages/signals/src/signal.rs b/packages/signals/src/signal.rs index 44de51082..faa39009b 100644 --- a/packages/signals/src/signal.rs +++ b/packages/signals/src/signal.rs @@ -202,6 +202,7 @@ impl>> Readable for Signal { let inner = self.inner.try_read()?; if let Some(reactive_context) = ReactiveContext::current() { + tracing::trace!("Subscribing to the reactive context {}", reactive_context); inner.subscribers.lock().unwrap().insert(reactive_context); } @@ -244,7 +245,11 @@ impl>> Writable for Signal { let borrow = S::map_mut(inner, |v| &mut v.value); Write { 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 DerefMut for Write { struct SignalSubscriberDrop>> { signal: Signal, + #[cfg(debug_assertions)] + origin: &'static std::panic::Location<'static>, } impl>> Drop for SignalSubscriberDrop { fn drop(&mut self) { + #[cfg(debug_assertions)] + tracing::trace!( + "Write on signal at {:?} finished, updating subscribers", + self.origin + ); self.signal.update_subscribers(); } } From 277e4677e9d426334c3710afe4127e46e9243628 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 26 Feb 2024 11:49:05 -0600 Subject: [PATCH 03/19] fix clippy --- Cargo.lock | 11 ----------- packages/core/src/scopes.rs | 2 +- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 28babd636..52f5a173a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2429,7 +2429,6 @@ name = "dioxus-examples" version = "0.4.3" dependencies = [ "dioxus", - "dioxus-logger", "dioxus-ssr", "form_urlencoded", "futures-util", @@ -2615,16 +2614,6 @@ dependencies = [ "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]] name = "dioxus-mobile" version = "0.4.3" diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index b34187472..03c438978 100644 --- a/packages/core/src/scopes.rs +++ b/packages/core/src/scopes.rs @@ -1,7 +1,7 @@ use crate::{ any_props::BoxedAnyProps, nodes::RenderReturn, runtime::Runtime, scope_context::Scope, }; -use std::{cell::Ref, fmt::Debug, rc::Rc}; +use std::{cell::Ref, rc::Rc}; /// A component's unique identifier. /// From d3b9b764e09610d1eb3b86524209fc0a8a868299 Mon Sep 17 00:00:00 2001 From: marc2332 Date: Mon, 26 Feb 2024 21:46:00 +0100 Subject: [PATCH 04/19] fix: Update `dioxus-router` docs --- packages/router/src/hooks/use_navigator.rs | 4 ++-- packages/router/src/hooks/use_route.rs | 8 ++------ packages/router/src/utils/use_router_internal.rs | 4 ++-- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/packages/router/src/hooks/use_navigator.rs b/packages/router/src/hooks/use_navigator.rs index 57a3bd3b8..1207b1fb2 100644 --- a/packages/router/src/hooks/use_navigator.rs +++ b/packages/router/src/hooks/use_navigator.rs @@ -2,7 +2,7 @@ use dioxus_lib::prelude::{try_consume_context, use_hook}; use crate::prelude::{Navigator, RouterContext}; -/// A hook that provides access to the navigator to change the router history. Unlike [`use_router`], this hook will not cause a rerender when the current route changes +/// A hook that provides access to the navigator to change the router history. /// /// > The Routable macro will define a version of this hook with an explicit type. /// @@ -26,7 +26,7 @@ use crate::prelude::{Navigator, RouterContext}; /// /// #[component] /// fn Index() -> Element { -/// let navigator = use_navigator(&cx); +/// let navigator = use_navigator(); /// /// rsx! { /// button { diff --git a/packages/router/src/hooks/use_route.rs b/packages/router/src/hooks/use_route.rs index 7a9942bd1..d0c66ebfb 100644 --- a/packages/router/src/hooks/use_route.rs +++ b/packages/router/src/hooks/use_route.rs @@ -5,12 +5,8 @@ use crate::utils::use_router_internal::use_router_internal; /// /// > The Routable macro will define a version of this hook with an explicit type. /// -/// # Return values -/// - None, when not called inside a [`Link`] component. -/// - Otherwise the current route. -/// /// # Panic -/// - When the calling component is not nested within a [`Link`] component during a debug build. +/// - When the calling component is not nested within a [`Router`] component. /// /// # Example /// ```rust @@ -49,7 +45,7 @@ pub fn use_route() -> R { match use_router_internal() { Some(r) => r.current(), None => { - panic!("`use_route` must have access to a parent router") + panic!("`use_route` must be called in a descendant of a Router component") } } } diff --git a/packages/router/src/utils/use_router_internal.rs b/packages/router/src/utils/use_router_internal.rs index 441063cbe..04853233c 100644 --- a/packages/router/src/utils/use_router_internal.rs +++ b/packages/router/src/utils/use_router_internal.rs @@ -8,10 +8,10 @@ use crate::prelude::*; /// single component, but not recommended. Multiple subscriptions will be discarded. /// /// # Return values -/// - [`None`], when the current component isn't a descendant of a [`Link`] component. +/// - [`None`], when the current component isn't a descendant of a [`Router`] component. /// - Otherwise [`Some`]. pub(crate) fn use_router_internal() -> Option { - let router = use_hook(consume_context::); + let router = try_consume_context::()?; let id = current_scope_id().expect("use_router_internal called outside of a component"); use_drop({ to_owned![router]; From 97478bcb13eeeb6f8f9287113a6196997e59b207 Mon Sep 17 00:00:00 2001 From: marc2332 Date: Mon, 26 Feb 2024 21:50:16 +0100 Subject: [PATCH 05/19] fix unrelated to dioxus-router --- packages/core-macro/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core-macro/src/lib.rs b/packages/core-macro/src/lib.rs index a4626b65a..a469eb04c 100644 --- a/packages/core-macro/src/lib.rs +++ b/packages/core-macro/src/lib.rs @@ -167,7 +167,7 @@ pub(crate) const COMPONENT_ARG_CASE_CHECK_OFF: &str = "no_case_check"; /// #[warn(non_snake_case)] /// #[inline(always)] /// fn __dx_inner_comp(props: GreetPersonProps>e) -> Element { -/// let GreetPersonProps { person } = &cx.props; +/// let GreetPersonProps { person } = props; /// { /// rsx! { "hello, {person}" } /// } From df0d45be486fe9f966bb8fad8ec71c7648bee92e Mon Sep 17 00:00:00 2001 From: andrey Date: Tue, 27 Feb 2024 10:15:38 +0800 Subject: [PATCH 06/19] added better documentation and code snipppets for the use_resource, use_future, use_effect and use_context hooks --- packages/hooks/src/use_context.rs | 27 +++++++++++++++++++++++++++ packages/hooks/src/use_coroutine.rs | 3 ++- packages/hooks/src/use_effect.rs | 18 +++++++++++++++--- packages/hooks/src/use_future.rs | 24 ++++++++++++++++++++++++ packages/hooks/src/use_resource.rs | 26 +++++++++++++++++++++++++- packages/signals/src/impls.rs | 1 + packages/signals/src/signal.rs | 4 ++-- packages/signals/tests/create.rs | 3 +++ 8 files changed, 99 insertions(+), 7 deletions(-) diff --git a/packages/hooks/src/use_context.rs b/packages/hooks/src/use_context.rs index ac3bb62ea..5deafd417 100644 --- a/packages/hooks/src/use_context.rs +++ b/packages/hooks/src/use_context.rs @@ -14,6 +14,18 @@ pub fn try_use_context() -> Option { /// Consume some context in the tree, providing a sharable handle to the value /// /// Does not regenerate the value if the value is changed at the parent. +/// ```rust +/// fn Parent() -> Element { +/// use_context_provider(|| Theme::Dark); +/// rsx! { Child {} } +/// } +/// #[component] +/// fn Child() -> Element { +/// //gets context provided by parent element with use_context_provider +/// let user_theme = use_context::(); +/// rsx! { "user using dark mode: {user_theme == Theme::Dark}" } +/// } +/// ``` #[must_use] pub fn use_context() -> T { use_hook(|| consume_context::()) @@ -22,6 +34,21 @@ pub fn use_context() -> T { /// Provide some context via the tree and return a reference to it /// /// Once the context has been provided, it is immutable. Mutations should be done via interior mutability. +/// Context can be read by any child components of the context provider, and is a solution to prop +/// drilling, using a context provider with a Signal inside is a good way to provide global/shared +/// state in your app: +/// ```rust +///fn app() -> Element { +/// use_context_provider(|| Signal::new(0)); +/// rsx! { Child {} } +///} +// This component does read from the signal, so when the signal changes it will rerun +///#[component] +///fn Child() -> Element { +/// let signal: Signal = use_context(); +/// rsx! { "{signal}" } +///} +/// ``` pub fn use_context_provider(f: impl FnOnce() -> T) -> T { use_hook(|| { let val = f(); diff --git a/packages/hooks/src/use_coroutine.rs b/packages/hooks/src/use_coroutine.rs index 06f342ceb..4834c5431 100644 --- a/packages/hooks/src/use_coroutine.rs +++ b/packages/hooks/src/use_coroutine.rs @@ -93,7 +93,8 @@ where } /// Get a handle to a coroutine higher in the tree -/// +/// Analagous to use_context_provider and use_context, +/// but used for coroutines specifically /// See the docs for [`use_coroutine`] for more details. #[must_use] pub fn use_coroutine_handle() -> Coroutine { diff --git a/packages/hooks/src/use_effect.rs b/packages/hooks/src/use_effect.rs index a3d16f420..97a603b21 100644 --- a/packages/hooks/src/use_effect.rs +++ b/packages/hooks/src/use_effect.rs @@ -1,10 +1,22 @@ use dioxus_core::prelude::*; use dioxus_signals::ReactiveContext; -/// Create a new effect. The effect will be run immediately and whenever any signal it reads changes. -/// The signal will be owned by the current component and will be dropped when the component is dropped. -/// +/// use_effect will subscribe to any changes in the signal values it captures +/// effects will always run after first mount and then whenever the signal values change /// If the use_effect call was skipped due to an early return, the effect will no longer activate. +/// ```rust +/// fn app() -> Element { +/// let mut count = use_signal(|| 0); +/// //the effect runs again each time count changes +/// use_effect(move || println!("Count changed to {count}")); +/// +/// rsx! { +/// h1 { "High-Five counter: {count}" } +/// button { onclick: move |_| count += 1, "Up high!" } +/// button { onclick: move |_| count -= 1, "Down low!" } +/// } +/// } +/// ``` pub fn use_effect(mut callback: impl FnMut() + 'static) { // let mut run_effect = use_hook(|| CopyValue::new(true)); // use_hook_did_run(move |did_run| run_effect.set(did_run)); diff --git a/packages/hooks/src/use_future.rs b/packages/hooks/src/use_future.rs index 4d69879aa..d2619f624 100644 --- a/packages/hooks/src/use_future.rs +++ b/packages/hooks/src/use_future.rs @@ -12,6 +12,30 @@ use std::future::Future; /// /// The future is spawned on the next call to `flush_sync` which means that it will not run on the server. /// To run a future on the server, you should use `spawn` directly. +/// use_future assumes your future will never complete - **it won't return a value**. +/// If you want to return a value, use `use_resource` instead. +/// ```rust +/// fn app() -> Element { +/// let mut count = use_signal(|| 0); +/// let mut running = use_signal(|| true); +/// // use_future will spawn an infinitely running future that can be started and stopped +/// use_future(move || async move { +/// loop { +/// if running() { +/// count += 1; +/// } +/// tokio::time::sleep(Duration::from_millis(400)).await; +/// } +/// }); +/// rsx! { +/// div { +/// h1 { "Current count: {count}" } +/// button { onclick: move |_| running.toggle(), "Start/Stop the count"} +/// button { onclick: move |_| count.set(0), "Reset the count" } +/// } +/// } +/// } +/// ``` pub fn use_future(mut future: impl FnMut() -> F + 'static) -> UseFuture where F: Future + 'static, diff --git a/packages/hooks/src/use_resource.rs b/packages/hooks/src/use_resource.rs index 222b62e7e..389697d74 100644 --- a/packages/hooks/src/use_resource.rs +++ b/packages/hooks/src/use_resource.rs @@ -10,8 +10,32 @@ use futures_util::{future, pin_mut, FutureExt}; use std::future::Future; /// A memo that resolve to a value asynchronously. +/// Unlike `use_future`, `use_resource` runs on the **server** +/// See [`Resource`] for more details. +/// ```rust +///fn app() -> Element { +/// let country = use_signal(|| WeatherLocation { +/// city: "Berlin".to_string(), +/// country: "Germany".to_string(), +/// coordinates: (52.5244, 13.4105) +/// }); /// -/// This runs on the server +/// let current_weather = //run a future inside the use_resource hook +/// use_resource(move || async move { get_weather(&country.read().clone()).await }); +/// +/// rsx! { +/// //the value of the future can be polled to +/// //conditionally render elements based off if the future +/// //finished (Some(Ok(_)), errored Some(Err(_)), +/// //or is still finishing (None) +/// match current_weather.value() { +/// Some(Ok(weather)) => WeatherElement { weather }, +/// Some(Err(e)) => p { "Loading weather failed, {e}" } +/// None => p { "Loading..." } +/// } +/// } +///} +/// ``` #[must_use = "Consider using `cx.spawn` to run a future without reading its value"] pub fn use_resource(future: impl Fn() -> F + 'static) -> Resource where diff --git a/packages/signals/src/impls.rs b/packages/signals/src/impls.rs index 3d120b54e..9cc790a48 100644 --- a/packages/signals/src/impls.rs +++ b/packages/signals/src/impls.rs @@ -5,6 +5,7 @@ use crate::write::Writable; use crate::{GlobalMemo, GlobalSignal, MappedSignal, ReadOnlySignal, SignalData}; use generational_box::{AnyStorage, Storage}; +use std::ops::Index; use std::{ fmt::{Debug, Display}, ops::{Add, Div, Mul, Sub}, diff --git a/packages/signals/src/signal.rs b/packages/signals/src/signal.rs index 44de51082..3d2ea96a2 100644 --- a/packages/signals/src/signal.rs +++ b/packages/signals/src/signal.rs @@ -10,8 +10,8 @@ use generational_box::{AnyStorage, Storage, SyncStorage, UnsyncStorage}; use std::{ any::Any, collections::HashSet, - ops::{Deref, DerefMut}, - sync::Mutex, + ops::{Deref, DerefMut, Index, IndexMut}, + sync::{Arc, Mutex}, }; /// Creates a new Signal. Signals are a Copy state management solution with automatic dependency tracking. diff --git a/packages/signals/tests/create.rs b/packages/signals/tests/create.rs index 7b17310b6..f9caddd2e 100644 --- a/packages/signals/tests/create.rs +++ b/packages/signals/tests/create.rs @@ -1,5 +1,8 @@ #![allow(unused, non_upper_case_globals, non_snake_case)] +use std::collections::HashMap; +use std::sync::Arc; + use dioxus::prelude::*; use dioxus_core::ElementId; use dioxus_core::NoOpMutations; From 24ebc1e628642659c93f470d78eb3748f60c4193 Mon Sep 17 00:00:00 2001 From: andrey Date: Tue, 27 Feb 2024 10:32:55 +0800 Subject: [PATCH 07/19] added better documentation and code snipppets for the use_resource, use_future, use_effect and use_context hooks --- packages/signals/src/impls.rs | 1 - packages/signals/src/signal.rs | 4 ++-- packages/signals/tests/create.rs | 3 --- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/signals/src/impls.rs b/packages/signals/src/impls.rs index 9cc790a48..3d120b54e 100644 --- a/packages/signals/src/impls.rs +++ b/packages/signals/src/impls.rs @@ -5,7 +5,6 @@ use crate::write::Writable; use crate::{GlobalMemo, GlobalSignal, MappedSignal, ReadOnlySignal, SignalData}; use generational_box::{AnyStorage, Storage}; -use std::ops::Index; use std::{ fmt::{Debug, Display}, ops::{Add, Div, Mul, Sub}, diff --git a/packages/signals/src/signal.rs b/packages/signals/src/signal.rs index 3d2ea96a2..44de51082 100644 --- a/packages/signals/src/signal.rs +++ b/packages/signals/src/signal.rs @@ -10,8 +10,8 @@ use generational_box::{AnyStorage, Storage, SyncStorage, UnsyncStorage}; use std::{ any::Any, collections::HashSet, - ops::{Deref, DerefMut, Index, IndexMut}, - sync::{Arc, Mutex}, + ops::{Deref, DerefMut}, + sync::Mutex, }; /// Creates a new Signal. Signals are a Copy state management solution with automatic dependency tracking. diff --git a/packages/signals/tests/create.rs b/packages/signals/tests/create.rs index f9caddd2e..7b17310b6 100644 --- a/packages/signals/tests/create.rs +++ b/packages/signals/tests/create.rs @@ -1,8 +1,5 @@ #![allow(unused, non_upper_case_globals, non_snake_case)] -use std::collections::HashMap; -use std::sync::Arc; - use dioxus::prelude::*; use dioxus_core::ElementId; use dioxus_core::NoOpMutations; From 5a73147d69be36368d92c79f0643124c15b2aa6a Mon Sep 17 00:00:00 2001 From: andrey Date: Tue, 27 Feb 2024 10:36:05 +0800 Subject: [PATCH 08/19] added better documentation and code snipppets for the use_resource, use_future, use_effect and use_context hooks --- packages/hooks/src/use_context.rs | 7 +++++-- packages/hooks/src/use_effect.rs | 2 +- packages/hooks/src/use_future.rs | 8 ++++---- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/packages/hooks/src/use_context.rs b/packages/hooks/src/use_context.rs index 5deafd417..27c586e4f 100644 --- a/packages/hooks/src/use_context.rs +++ b/packages/hooks/src/use_context.rs @@ -45,8 +45,11 @@ pub fn use_context() -> T { // This component does read from the signal, so when the signal changes it will rerun ///#[component] ///fn Child() -> Element { -/// let signal: Signal = use_context(); -/// rsx! { "{signal}" } +/// let signal: Signal = use_context(); +/// rsx! { +/// button { onclick: move |_| signal += 1, "increment context" } +/// p {"{signal}"} +/// } ///} /// ``` pub fn use_context_provider(f: impl FnOnce() -> T) -> T { diff --git a/packages/hooks/src/use_effect.rs b/packages/hooks/src/use_effect.rs index 97a603b21..0028bc7c5 100644 --- a/packages/hooks/src/use_effect.rs +++ b/packages/hooks/src/use_effect.rs @@ -1,7 +1,7 @@ use dioxus_core::prelude::*; use dioxus_signals::ReactiveContext; -/// use_effect will subscribe to any changes in the signal values it captures +/// `use_effect` will subscribe to any changes in the signal values it captures /// effects will always run after first mount and then whenever the signal values change /// If the use_effect call was skipped due to an early return, the effect will no longer activate. /// ```rust diff --git a/packages/hooks/src/use_future.rs b/packages/hooks/src/use_future.rs index d2619f624..a4f6ed214 100644 --- a/packages/hooks/src/use_future.rs +++ b/packages/hooks/src/use_future.rs @@ -8,17 +8,17 @@ use dioxus_signals::*; use dioxus_signals::{Readable, Writable}; use std::future::Future; -/// A hook that allows you to spawn a future -/// +/// A hook that allows you to spawn a future. +/// This future will **not** run on the server /// The future is spawned on the next call to `flush_sync` which means that it will not run on the server. /// To run a future on the server, you should use `spawn` directly. -/// use_future assumes your future will never complete - **it won't return a value**. +/// `use_future` assumes your future will never complete - **it won't return a value**. /// If you want to return a value, use `use_resource` instead. /// ```rust /// fn app() -> Element { /// let mut count = use_signal(|| 0); /// let mut running = use_signal(|| true); -/// // use_future will spawn an infinitely running future that can be started and stopped +/// // `use_future` will spawn an infinitely running future that can be started and stopped /// use_future(move || async move { /// loop { /// if running() { From 5dad13ac74f0877b246ba90de6924b22286640fe Mon Sep 17 00:00:00 2001 From: Florian de Gaulejac Date: Tue, 27 Feb 2024 09:16:42 +0100 Subject: [PATCH 09/19] Restore image element under svg element --- packages/html/src/elements.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/html/src/elements.rs b/packages/html/src/elements.rs index c5e5e3ab5..6d2338ccd 100644 --- a/packages/html/src/elements.rs +++ b/packages/html/src/elements.rs @@ -1616,7 +1616,7 @@ builder_constructors! { // /// Build a // /// [``](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/image) // /// element. - // image "http://www.w3.org/2000/svg" {}; + image "http://www.w3.org/2000/svg" {}; /// Build a /// [``](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/line) From f7209e123f3c3d49405f50f930f9eba4a77cf1f6 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 27 Feb 2024 10:36:45 -0600 Subject: [PATCH 10/19] restore the docs for the image element --- packages/html/src/elements.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/html/src/elements.rs b/packages/html/src/elements.rs index 6d2338ccd..5275fa4f6 100644 --- a/packages/html/src/elements.rs +++ b/packages/html/src/elements.rs @@ -1613,9 +1613,9 @@ builder_constructors! { /// element. hatchpath "http://www.w3.org/2000/svg" {}; - // /// Build a - // /// [``](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/image) - // /// element. + /// Build a + /// [``](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/image) + /// element. image "http://www.w3.org/2000/svg" {}; /// Build a From fe839ab3475c526e1991efb0b54253f8785ed926 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 27 Feb 2024 15:15:12 -0600 Subject: [PATCH 11/19] Move some global variables in the interpreter into locals in the interpreter --- packages/interpreter/src/interpreter.js | 8 +++---- .../interpreter/src/sledgehammer_bindings.rs | 23 ++++++++----------- 2 files changed, 12 insertions(+), 19 deletions(-) diff --git a/packages/interpreter/src/interpreter.js b/packages/interpreter/src/interpreter.js index 5887828c7..63ee6d630 100644 --- a/packages/interpreter/src/interpreter.js +++ b/packages/interpreter/src/interpreter.js @@ -232,16 +232,14 @@ this.LoadChild = function (array) { this.listeners = new ListenerMap(); this.nodes = []; this.stack = []; -this.root; this.templates = {}; -this.els = null; this.end = null; this.AppendChildren = function (id, many) { - this.root = this.nodes[id]; - this.els = this.stack.splice(this.stack.length - many); + let root = this.nodes[id]; + let els = this.stack.splice(this.stack.length - many); for (let k = 0; k < many; k++) { - this.root.appendChild(this.els[k]); + root.appendChild(els[k]); } } diff --git a/packages/interpreter/src/sledgehammer_bindings.rs b/packages/interpreter/src/sledgehammer_bindings.rs index 18e4b9df5..1fbdc9823 100644 --- a/packages/interpreter/src/sledgehammer_bindings.rs +++ b/packages/interpreter/src/sledgehammer_bindings.rs @@ -80,9 +80,7 @@ mod js { this.listeners = new ListenerMap(); this.nodes = []; this.stack = []; - this.root = null; this.templates = {}; - this.els = null; this.save_template = function(nodes, tmpl_id) { this.templates[tmpl_id] = nodes; } @@ -131,15 +129,15 @@ mod js { } this.AppendChildren = function (id, many){ let root = this.nodes[id]; - this.els = this.stack.splice(this.stack.length-many); + let els = this.stack.splice(this.stack.length-many); for (let k = 0; k < many; k++) { - root.appendChild(this.els[k]); + root.appendChild(els[k]); } } "#; fn mount_to_root() { - "{this.AppendChildren(this.root, this.stack.length-1);}" + "{this.AppendChildren(this.listeners.root, this.stack.length-1);}" } fn push_root(root: u32) { "{this.stack.push(this.nodes[$root$]);}" @@ -151,7 +149,7 @@ mod js { "{this.stack.pop();}" } fn replace_with(id: u32, n: u16) { - "{const root = this.nodes[$id$]; this.els = this.stack.splice(this.stack.length-$n$); if (root.listening) { this.listeners.removeAllNonBubbling(root); } root.replaceWith(...this.els);}" + "{const root = this.nodes[$id$]; let els = this.stack.splice(this.stack.length-$n$); if (root.listening) { this.listeners.removeAllNonBubbling(root); } root.replaceWith(...els);}" } fn insert_after(id: u32, n: u16) { "{this.nodes[$id$].after(...this.stack.splice(this.stack.length-$n$));}" @@ -228,7 +226,7 @@ mod js { }"# } fn replace_placeholder(ptr: u32, len: u8, n: u16) { - "{this.els = this.stack.splice(this.stack.length - $n$); let node = this.LoadChild($ptr$, $len$); node.replaceWith(...this.els);}" + "{els = this.stack.splice(this.stack.length - $n$); let node = this.LoadChild($ptr$, $len$); node.replaceWith(...els);}" } fn load_template(tmpl_id: u16, index: u16, id: u32) { "{let node = this.templates[$tmpl_id$][$index$].cloneNode(true); this.nodes[$id$] = node; this.stack.push(node);}" @@ -270,9 +268,6 @@ pub mod binary_protocol { const JS_FILE: &str = "./src/interpreter.js"; const JS_FILE: &str = "./src/common.js"; - fn mount_to_root() { - "{this.AppendChildren(this.root, this.stack.length-1);}" - } fn push_root(root: u32) { "{this.stack.push(this.nodes[$root$]);}" } @@ -282,9 +277,9 @@ pub mod binary_protocol { fn append_children_to_top(many: u16) { "{ let root = this.stack[this.stack.length-many-1]; - this.els = this.stack.splice(this.stack.length-many); + let els = this.stack.splice(this.stack.length-many); for (let k = 0; k < many; k++) { - root.appendChild(this.els[k]); + root.appendChild(els[k]); } }" } @@ -292,7 +287,7 @@ pub mod binary_protocol { "{this.stack.pop();}" } fn replace_with(id: u32, n: u16) { - "{let root = this.nodes[$id$]; this.els = this.stack.splice(this.stack.length-$n$); if (root.listening) { this.listeners.removeAllNonBubbling(root); } root.replaceWith(...this.els);}" + "{let root = this.nodes[$id$]; let els = this.stack.splice(this.stack.length-$n$); if (root.listening) { this.listeners.removeAllNonBubbling(root); } root.replaceWith(...els);}" } fn insert_after(id: u32, n: u16) { "{this.nodes[$id$].after(...this.stack.splice(this.stack.length-$n$));}" @@ -406,7 +401,7 @@ pub mod binary_protocol { }"# } fn replace_placeholder(array: &[u8], n: u16) { - "{this.els = this.stack.splice(this.stack.length - $n$); let node = this.LoadChild($array$); node.replaceWith(...this.els);}" + "{let els = this.stack.splice(this.stack.length - $n$); let node = this.LoadChild($array$); node.replaceWith(...els);}" } fn load_template(tmpl_id: u16, index: u16, id: u32) { "{let node = this.templates[$tmpl_id$][$index$].cloneNode(true); this.nodes[$id$] = node; this.stack.push(node);}" From 70136b22ea2d9aeeaf2bbbaa260608194589373b Mon Sep 17 00:00:00 2001 From: andrey Date: Wed, 28 Feb 2024 11:42:04 +0800 Subject: [PATCH 12/19] Fix error in examples/future.rs comments, use_futures inline docs comments, also added clarification that Signal.read() and Singal() are the same thing --- examples/future.rs | 4 ++-- examples/seven_guis/cells.rs | 22 +++++++++++++++++ examples/seven_guis/circle-drawer.rs | 22 +++++++++++++++++ examples/seven_guis/counter.rs | 25 ++++++++++++++++++++ examples/seven_guis/crud.rs | 22 +++++++++++++++++ examples/seven_guis/flight-booker.rs | 22 +++++++++++++++++ examples/seven_guis/temperature-converter.rs | 22 +++++++++++++++++ examples/seven_guis/timer.rs | 22 +++++++++++++++++ packages/hooks/src/use_context.rs | 2 +- packages/hooks/src/use_future.rs | 2 +- packages/signals/src/read.rs | 4 +++- 11 files changed, 164 insertions(+), 5 deletions(-) create mode 100644 examples/seven_guis/cells.rs create mode 100644 examples/seven_guis/circle-drawer.rs create mode 100644 examples/seven_guis/counter.rs create mode 100644 examples/seven_guis/crud.rs create mode 100644 examples/seven_guis/flight-booker.rs create mode 100644 examples/seven_guis/temperature-converter.rs create mode 100644 examples/seven_guis/timer.rs diff --git a/examples/future.rs b/examples/future.rs index 130d526b2..76b6c8e7f 100644 --- a/examples/future.rs +++ b/examples/future.rs @@ -1,7 +1,7 @@ //! A simple example that shows how to use the use_future hook to run a background task. //! -//! use_future assumes your future will never complete - it won't return a value. -//! If you want to return a value, use use_resource instead. +//! use_future won't return a value, analagous to use_effect. +//! If you want to return a value from a future, use use_resource instead. use dioxus::prelude::*; use std::time::Duration; diff --git a/examples/seven_guis/cells.rs b/examples/seven_guis/cells.rs new file mode 100644 index 000000000..ee33c2230 --- /dev/null +++ b/examples/seven_guis/cells.rs @@ -0,0 +1,22 @@ +//! The simplest example of a Dioxus app. +//! +//! In this example we: +//! - import a number of important items from the prelude (launch, Element, rsx, div, etc.) +//! - define a main function that calls the launch function with our app function +//! - define an app function that returns a div element with the text "Hello, world!" +//! +//! The `launch` function is the entry point for all Dioxus apps. It takes a function that returns an Element. This function +//! calls "launch" on the currently-configured renderer you have. So if the `web` feature is enabled, it will launch a web +//! app, and if the `desktop` feature is enabled, it will launch a desktop app. + +use dioxus::prelude::*; + +fn main() { + launch(app); +} + +fn app() -> Element { + rsx! { + div { "Hello, world!" } + } +} diff --git a/examples/seven_guis/circle-drawer.rs b/examples/seven_guis/circle-drawer.rs new file mode 100644 index 000000000..ee33c2230 --- /dev/null +++ b/examples/seven_guis/circle-drawer.rs @@ -0,0 +1,22 @@ +//! The simplest example of a Dioxus app. +//! +//! In this example we: +//! - import a number of important items from the prelude (launch, Element, rsx, div, etc.) +//! - define a main function that calls the launch function with our app function +//! - define an app function that returns a div element with the text "Hello, world!" +//! +//! The `launch` function is the entry point for all Dioxus apps. It takes a function that returns an Element. This function +//! calls "launch" on the currently-configured renderer you have. So if the `web` feature is enabled, it will launch a web +//! app, and if the `desktop` feature is enabled, it will launch a desktop app. + +use dioxus::prelude::*; + +fn main() { + launch(app); +} + +fn app() -> Element { + rsx! { + div { "Hello, world!" } + } +} diff --git a/examples/seven_guis/counter.rs b/examples/seven_guis/counter.rs new file mode 100644 index 000000000..12fafb4f9 --- /dev/null +++ b/examples/seven_guis/counter.rs @@ -0,0 +1,25 @@ +//! The simplest example of a Dioxus app. +//! +//! In this example we: +//! - import a number of important items from the prelude (launch, Element, rsx, div, etc.) +//! - define a main function that calls the launch function with our app function +//! - define an app function that returns a div element with the text "Hello, world!" +//! +//! The `launch` function is the entry point for all Dioxus apps. It takes a function that returns an Element. This function +//! calls "launch" on the currently-configured renderer you have. So if the `web` feature is enabled, it will launch a web +//! app, and if the `desktop` feature is enabled, it will launch a desktop app. + +use dioxus::prelude::*; + +fn main() { + launch(app); +} + +fn app() -> Element { + let counter = use_signal(|| 0usize); + + rsx! { + button { onclick: move || counter += 1, "Increment Counter" } + p { "{counter}" } + } +} diff --git a/examples/seven_guis/crud.rs b/examples/seven_guis/crud.rs new file mode 100644 index 000000000..ee33c2230 --- /dev/null +++ b/examples/seven_guis/crud.rs @@ -0,0 +1,22 @@ +//! The simplest example of a Dioxus app. +//! +//! In this example we: +//! - import a number of important items from the prelude (launch, Element, rsx, div, etc.) +//! - define a main function that calls the launch function with our app function +//! - define an app function that returns a div element with the text "Hello, world!" +//! +//! The `launch` function is the entry point for all Dioxus apps. It takes a function that returns an Element. This function +//! calls "launch" on the currently-configured renderer you have. So if the `web` feature is enabled, it will launch a web +//! app, and if the `desktop` feature is enabled, it will launch a desktop app. + +use dioxus::prelude::*; + +fn main() { + launch(app); +} + +fn app() -> Element { + rsx! { + div { "Hello, world!" } + } +} diff --git a/examples/seven_guis/flight-booker.rs b/examples/seven_guis/flight-booker.rs new file mode 100644 index 000000000..ee33c2230 --- /dev/null +++ b/examples/seven_guis/flight-booker.rs @@ -0,0 +1,22 @@ +//! The simplest example of a Dioxus app. +//! +//! In this example we: +//! - import a number of important items from the prelude (launch, Element, rsx, div, etc.) +//! - define a main function that calls the launch function with our app function +//! - define an app function that returns a div element with the text "Hello, world!" +//! +//! The `launch` function is the entry point for all Dioxus apps. It takes a function that returns an Element. This function +//! calls "launch" on the currently-configured renderer you have. So if the `web` feature is enabled, it will launch a web +//! app, and if the `desktop` feature is enabled, it will launch a desktop app. + +use dioxus::prelude::*; + +fn main() { + launch(app); +} + +fn app() -> Element { + rsx! { + div { "Hello, world!" } + } +} diff --git a/examples/seven_guis/temperature-converter.rs b/examples/seven_guis/temperature-converter.rs new file mode 100644 index 000000000..ee33c2230 --- /dev/null +++ b/examples/seven_guis/temperature-converter.rs @@ -0,0 +1,22 @@ +//! The simplest example of a Dioxus app. +//! +//! In this example we: +//! - import a number of important items from the prelude (launch, Element, rsx, div, etc.) +//! - define a main function that calls the launch function with our app function +//! - define an app function that returns a div element with the text "Hello, world!" +//! +//! The `launch` function is the entry point for all Dioxus apps. It takes a function that returns an Element. This function +//! calls "launch" on the currently-configured renderer you have. So if the `web` feature is enabled, it will launch a web +//! app, and if the `desktop` feature is enabled, it will launch a desktop app. + +use dioxus::prelude::*; + +fn main() { + launch(app); +} + +fn app() -> Element { + rsx! { + div { "Hello, world!" } + } +} diff --git a/examples/seven_guis/timer.rs b/examples/seven_guis/timer.rs new file mode 100644 index 000000000..ee33c2230 --- /dev/null +++ b/examples/seven_guis/timer.rs @@ -0,0 +1,22 @@ +//! The simplest example of a Dioxus app. +//! +//! In this example we: +//! - import a number of important items from the prelude (launch, Element, rsx, div, etc.) +//! - define a main function that calls the launch function with our app function +//! - define an app function that returns a div element with the text "Hello, world!" +//! +//! The `launch` function is the entry point for all Dioxus apps. It takes a function that returns an Element. This function +//! calls "launch" on the currently-configured renderer you have. So if the `web` feature is enabled, it will launch a web +//! app, and if the `desktop` feature is enabled, it will launch a desktop app. + +use dioxus::prelude::*; + +fn main() { + launch(app); +} + +fn app() -> Element { + rsx! { + div { "Hello, world!" } + } +} diff --git a/packages/hooks/src/use_context.rs b/packages/hooks/src/use_context.rs index 27c586e4f..9194dbd2f 100644 --- a/packages/hooks/src/use_context.rs +++ b/packages/hooks/src/use_context.rs @@ -42,7 +42,7 @@ pub fn use_context() -> T { /// use_context_provider(|| Signal::new(0)); /// rsx! { Child {} } ///} -// This component does read from the signal, so when the signal changes it will rerun +/// // This component does read from the signal, so when the signal changes it will rerun ///#[component] ///fn Child() -> Element { /// let signal: Signal = use_context(); diff --git a/packages/hooks/src/use_future.rs b/packages/hooks/src/use_future.rs index a4f6ed214..b2276147b 100644 --- a/packages/hooks/src/use_future.rs +++ b/packages/hooks/src/use_future.rs @@ -12,7 +12,7 @@ use std::future::Future; /// This future will **not** run on the server /// The future is spawned on the next call to `flush_sync` which means that it will not run on the server. /// To run a future on the server, you should use `spawn` directly. -/// `use_future` assumes your future will never complete - **it won't return a value**. +/// `use_future` **won't return a value**. /// If you want to return a value, use `use_resource` instead. /// ```rust /// fn app() -> Element { diff --git a/packages/signals/src/read.rs b/packages/signals/src/read.rs index 63de45047..7effa39f7 100644 --- a/packages/signals/src/read.rs +++ b/packages/signals/src/read.rs @@ -39,7 +39,9 @@ pub trait Readable { MappedSignal::new(try_read, peek) } - /// Get the current value of the state. If this is a signal, this will subscribe the current scope to the signal. If the value has been dropped, this will panic. + /// Get the current value of the state. If this is a signal, this will subscribe the current scope to the signal. + /// If the value has been dropped, this will panic. Calling this on a Signal is the same as + /// using the signal() syntax to read and subscribe to its value #[track_caller] fn read(&self) -> ReadableRef { self.try_read().unwrap() From c9f04ac3d0340a8a42edf5305430293b2c744291 Mon Sep 17 00:00:00 2001 From: andrey Date: Wed, 28 Feb 2024 11:45:08 +0800 Subject: [PATCH 13/19] remove things that werent meant to be in last commit --- examples/seven_guis/cells.rs | 22 ----------------- examples/seven_guis/circle-drawer.rs | 22 ----------------- examples/seven_guis/counter.rs | 25 -------------------- examples/seven_guis/crud.rs | 22 ----------------- examples/seven_guis/flight-booker.rs | 22 ----------------- examples/seven_guis/temperature-converter.rs | 22 ----------------- examples/seven_guis/timer.rs | 22 ----------------- 7 files changed, 157 deletions(-) delete mode 100644 examples/seven_guis/cells.rs delete mode 100644 examples/seven_guis/circle-drawer.rs delete mode 100644 examples/seven_guis/counter.rs delete mode 100644 examples/seven_guis/crud.rs delete mode 100644 examples/seven_guis/flight-booker.rs delete mode 100644 examples/seven_guis/temperature-converter.rs delete mode 100644 examples/seven_guis/timer.rs diff --git a/examples/seven_guis/cells.rs b/examples/seven_guis/cells.rs deleted file mode 100644 index ee33c2230..000000000 --- a/examples/seven_guis/cells.rs +++ /dev/null @@ -1,22 +0,0 @@ -//! The simplest example of a Dioxus app. -//! -//! In this example we: -//! - import a number of important items from the prelude (launch, Element, rsx, div, etc.) -//! - define a main function that calls the launch function with our app function -//! - define an app function that returns a div element with the text "Hello, world!" -//! -//! The `launch` function is the entry point for all Dioxus apps. It takes a function that returns an Element. This function -//! calls "launch" on the currently-configured renderer you have. So if the `web` feature is enabled, it will launch a web -//! app, and if the `desktop` feature is enabled, it will launch a desktop app. - -use dioxus::prelude::*; - -fn main() { - launch(app); -} - -fn app() -> Element { - rsx! { - div { "Hello, world!" } - } -} diff --git a/examples/seven_guis/circle-drawer.rs b/examples/seven_guis/circle-drawer.rs deleted file mode 100644 index ee33c2230..000000000 --- a/examples/seven_guis/circle-drawer.rs +++ /dev/null @@ -1,22 +0,0 @@ -//! The simplest example of a Dioxus app. -//! -//! In this example we: -//! - import a number of important items from the prelude (launch, Element, rsx, div, etc.) -//! - define a main function that calls the launch function with our app function -//! - define an app function that returns a div element with the text "Hello, world!" -//! -//! The `launch` function is the entry point for all Dioxus apps. It takes a function that returns an Element. This function -//! calls "launch" on the currently-configured renderer you have. So if the `web` feature is enabled, it will launch a web -//! app, and if the `desktop` feature is enabled, it will launch a desktop app. - -use dioxus::prelude::*; - -fn main() { - launch(app); -} - -fn app() -> Element { - rsx! { - div { "Hello, world!" } - } -} diff --git a/examples/seven_guis/counter.rs b/examples/seven_guis/counter.rs deleted file mode 100644 index 12fafb4f9..000000000 --- a/examples/seven_guis/counter.rs +++ /dev/null @@ -1,25 +0,0 @@ -//! The simplest example of a Dioxus app. -//! -//! In this example we: -//! - import a number of important items from the prelude (launch, Element, rsx, div, etc.) -//! - define a main function that calls the launch function with our app function -//! - define an app function that returns a div element with the text "Hello, world!" -//! -//! The `launch` function is the entry point for all Dioxus apps. It takes a function that returns an Element. This function -//! calls "launch" on the currently-configured renderer you have. So if the `web` feature is enabled, it will launch a web -//! app, and if the `desktop` feature is enabled, it will launch a desktop app. - -use dioxus::prelude::*; - -fn main() { - launch(app); -} - -fn app() -> Element { - let counter = use_signal(|| 0usize); - - rsx! { - button { onclick: move || counter += 1, "Increment Counter" } - p { "{counter}" } - } -} diff --git a/examples/seven_guis/crud.rs b/examples/seven_guis/crud.rs deleted file mode 100644 index ee33c2230..000000000 --- a/examples/seven_guis/crud.rs +++ /dev/null @@ -1,22 +0,0 @@ -//! The simplest example of a Dioxus app. -//! -//! In this example we: -//! - import a number of important items from the prelude (launch, Element, rsx, div, etc.) -//! - define a main function that calls the launch function with our app function -//! - define an app function that returns a div element with the text "Hello, world!" -//! -//! The `launch` function is the entry point for all Dioxus apps. It takes a function that returns an Element. This function -//! calls "launch" on the currently-configured renderer you have. So if the `web` feature is enabled, it will launch a web -//! app, and if the `desktop` feature is enabled, it will launch a desktop app. - -use dioxus::prelude::*; - -fn main() { - launch(app); -} - -fn app() -> Element { - rsx! { - div { "Hello, world!" } - } -} diff --git a/examples/seven_guis/flight-booker.rs b/examples/seven_guis/flight-booker.rs deleted file mode 100644 index ee33c2230..000000000 --- a/examples/seven_guis/flight-booker.rs +++ /dev/null @@ -1,22 +0,0 @@ -//! The simplest example of a Dioxus app. -//! -//! In this example we: -//! - import a number of important items from the prelude (launch, Element, rsx, div, etc.) -//! - define a main function that calls the launch function with our app function -//! - define an app function that returns a div element with the text "Hello, world!" -//! -//! The `launch` function is the entry point for all Dioxus apps. It takes a function that returns an Element. This function -//! calls "launch" on the currently-configured renderer you have. So if the `web` feature is enabled, it will launch a web -//! app, and if the `desktop` feature is enabled, it will launch a desktop app. - -use dioxus::prelude::*; - -fn main() { - launch(app); -} - -fn app() -> Element { - rsx! { - div { "Hello, world!" } - } -} diff --git a/examples/seven_guis/temperature-converter.rs b/examples/seven_guis/temperature-converter.rs deleted file mode 100644 index ee33c2230..000000000 --- a/examples/seven_guis/temperature-converter.rs +++ /dev/null @@ -1,22 +0,0 @@ -//! The simplest example of a Dioxus app. -//! -//! In this example we: -//! - import a number of important items from the prelude (launch, Element, rsx, div, etc.) -//! - define a main function that calls the launch function with our app function -//! - define an app function that returns a div element with the text "Hello, world!" -//! -//! The `launch` function is the entry point for all Dioxus apps. It takes a function that returns an Element. This function -//! calls "launch" on the currently-configured renderer you have. So if the `web` feature is enabled, it will launch a web -//! app, and if the `desktop` feature is enabled, it will launch a desktop app. - -use dioxus::prelude::*; - -fn main() { - launch(app); -} - -fn app() -> Element { - rsx! { - div { "Hello, world!" } - } -} diff --git a/examples/seven_guis/timer.rs b/examples/seven_guis/timer.rs deleted file mode 100644 index ee33c2230..000000000 --- a/examples/seven_guis/timer.rs +++ /dev/null @@ -1,22 +0,0 @@ -//! The simplest example of a Dioxus app. -//! -//! In this example we: -//! - import a number of important items from the prelude (launch, Element, rsx, div, etc.) -//! - define a main function that calls the launch function with our app function -//! - define an app function that returns a div element with the text "Hello, world!" -//! -//! The `launch` function is the entry point for all Dioxus apps. It takes a function that returns an Element. This function -//! calls "launch" on the currently-configured renderer you have. So if the `web` feature is enabled, it will launch a web -//! app, and if the `desktop` feature is enabled, it will launch a desktop app. - -use dioxus::prelude::*; - -fn main() { - launch(app); -} - -fn app() -> Element { - rsx! { - div { "Hello, world!" } - } -} From 1404009edc4c6a6a2fbac4cd39bb5f6b6498d4b4 Mon Sep 17 00:00:00 2001 From: Andrey <146410045+agreyyy@users.noreply.github.com> Date: Wed, 28 Feb 2024 03:48:52 +0000 Subject: [PATCH 14/19] touchups --- packages/hooks/src/use_future.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/hooks/src/use_future.rs b/packages/hooks/src/use_future.rs index b2276147b..53a67087c 100644 --- a/packages/hooks/src/use_future.rs +++ b/packages/hooks/src/use_future.rs @@ -13,7 +13,7 @@ use std::future::Future; /// The future is spawned on the next call to `flush_sync` which means that it will not run on the server. /// To run a future on the server, you should use `spawn` directly. /// `use_future` **won't return a value**. -/// If you want to return a value, use `use_resource` instead. +/// If you want to return a value from a future, use `use_resource` instead. /// ```rust /// fn app() -> Element { /// let mut count = use_signal(|| 0); From 55f308363dfcf8868b16420b5593d2f95f3a3913 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 29 Feb 2024 10:39:02 -0600 Subject: [PATCH 15/19] expose non-blocking functions to read and write to the fullstack request --- packages/fullstack/src/render.rs | 2 +- packages/fullstack/src/server_context.rs | 28 +++++++++++++++++++++--- packages/router/src/router_cfg.rs | 2 +- 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/packages/fullstack/src/render.rs b/packages/fullstack/src/render.rs index adb38ac4c..42b6395ee 100644 --- a/packages/fullstack/src/render.rs +++ b/packages/fullstack/src/render.rs @@ -9,8 +9,8 @@ use dioxus_ssr::{ use std::future::Future; use std::sync::Arc; use std::sync::RwLock; -use tokio::task::JoinHandle; use tokio::task::block_in_place; +use tokio::task::JoinHandle; use crate::prelude::*; use dioxus_lib::prelude::*; diff --git a/packages/fullstack/src/server_context.rs b/packages/fullstack/src/server_context.rs index a2cbfeb55..7ddc67f3b 100644 --- a/packages/fullstack/src/server_context.rs +++ b/packages/fullstack/src/server_context.rs @@ -84,14 +84,36 @@ mod server_fn_impl { /// Get the request that triggered: /// - The initial SSR render if called from a ScopeState or ServerFn /// - The server function to be called if called from a server function after the initial render - pub fn request_parts(&self) -> tokio::sync::RwLockReadGuard<'_, http::request::Parts> { + pub async fn request_parts( + &self, + ) -> tokio::sync::RwLockReadGuard<'_, http::request::Parts> { + self.parts.read().await + } + + /// Get the request that triggered: + /// - The initial SSR render if called from a ScopeState or ServerFn + /// - The server function to be called if called from a server function after the initial render + pub fn request_parts_blocking( + &self, + ) -> tokio::sync::RwLockReadGuard<'_, http::request::Parts> { self.parts.blocking_read() } /// Get the request that triggered: /// - The initial SSR render if called from a ScopeState or ServerFn /// - The server function to be called if called from a server function after the initial render - pub fn request_parts_mut(&self) -> tokio::sync::RwLockWriteGuard<'_, http::request::Parts> { + pub async fn request_parts_mut( + &self, + ) -> tokio::sync::RwLockWriteGuard<'_, http::request::Parts> { + self.parts.write().await + } + + /// Get the request that triggered: + /// - The initial SSR render if called from a ScopeState or ServerFn + /// - The server function to be called if called from a server function after the initial render + pub fn request_parts_mut_blocking( + &self, + ) -> tokio::sync::RwLockWriteGuard<'_, http::request::Parts> { self.parts.blocking_write() } @@ -239,6 +261,6 @@ impl< type Rejection = R; async fn from_request(req: &DioxusServerContext) -> Result { - Ok(I::from_request_parts(&mut req.request_parts_mut(), &()).await?) + Ok(I::from_request_parts(&mut *req.request_parts_mut().await, &()).await?) } } diff --git a/packages/router/src/router_cfg.rs b/packages/router/src/router_cfg.rs index 8ec762b14..f4e9177d3 100644 --- a/packages/router/src/router_cfg.rs +++ b/packages/router/src/router_cfg.rs @@ -134,7 +134,7 @@ where return Box::new(AnyHistoryProviderImplWrapper::new( MemoryHistory::::with_initial_path( dioxus_fullstack::prelude::server_context() - .request_parts() + .request_parts_blocking() .uri .to_string() .parse() From 9ae3d14e676c90c73a99f5cfb41ff85c7cbf4bc2 Mon Sep 17 00:00:00 2001 From: Jeremy Arnold Date: Thu, 29 Feb 2024 16:49:35 -0700 Subject: [PATCH 16/19] Add tracing to virtual_dom (#1949) Address #1161; Add tracing to virtual dom: - Added a dependency on [tracing-fluent-assertions](https://github.com/tobz/tracing-fluent-assertions) for test cases - Added a test case showing tracing example log ``` 2024-02-28T21:52:25.266474Z TRACE dioxus_core::scope_context: providing context alloc::rc::Rc (TypeId { t: 86211292795840746005296084033382154959 }) in app at packages/core/src/scope_context.rs:189 in dioxus_core::virtual_dom::VirtualDom::new 2024-02-28T21:52:25.266651Z TRACE dioxus_core::scope_context: looking for context generational_box::Owner (TypeId { t: 16187079142358387300491255137481079601 }) in app at packages/core/src/scope_context.rs:106 in dioxus_core::scope_arena::render with scope: app in dioxus_core::virtual_dom::VirtualDom::rebuild 2024-02-28T21:52:25.266662Z TRACE dioxus_core::scope_context: context generational_box::Owner (TypeId { t: 16187079142358387300491255137481079601 }) not found at packages/core/src/scope_context.rs:140 in dioxus_core::scope_arena::render with scope: app in dioxus_core::virtual_dom::VirtualDom::rebuild 2024-02-28T21:52:25.266671Z TRACE dioxus_core::scope_context: providing context generational_box::Owner (TypeId { t: 16187079142358387300491255137481079601 }) in app at packages/core/src/scope_context.rs:189 in dioxus_core::scope_arena::render with scope: app in dioxus_core::virtual_dom::VirtualDom::rebuild 2024-02-28T21:52:25.266713Z TRACE dioxus_core::scope_context: providing context dioxus_signals::reactive_context::ReactiveContext (TypeId { t: 86476382904980360086558195405008306005 }) in app at packages/core/src/scope_context.rs:189 in dioxus_core::scope_arena::render with scope: app in dioxus_core::virtual_dom::VirtualDom::rebuild 2024-02-28T21:52:25.266723Z TRACE dioxus_core::scope_context: looking for context generational_box::Owner (TypeId { t: 87785542931436862771411509553978749471 }) in app at packages/core/src/scope_context.rs:106 in dioxus_core::scope_arena::render with scope: app in dioxus_core::virtual_dom::VirtualDom::rebuild 2024-02-28T21:52:25.266730Z TRACE dioxus_core::scope_context: context generational_box::Owner (TypeId { t: 87785542931436862771411509553978749471 }) not found at packages/core/src/scope_context.rs:140 in dioxus_core::scope_arena::render with scope: app in dioxus_core::virtual_dom::VirtualDom::rebuild 2024-02-28T21:52:25.266769Z TRACE dioxus_core::scope_context: providing context generational_box::Owner (TypeId { t: 87785542931436862771411509553978749471 }) in app at packages/core/src/scope_context.rs:189 in dioxus_core::scope_arena::render with scope: app in dioxus_core::virtual_dom::VirtualDom::rebuild 2024-02-28T21:52:25.266828Z INFO dioxus_core::diff::node: creating template, self: VNode { vnode: VNodeInner { key: None, template: Cell { value: Template { name: "packages/core/tests/tracing.rs:60:5:1728", roots: [Element { tag: "div", namespace: None, attrs: [], children: [Element { tag: "button", namespace: None, attrs: [Dynamic { id: 0 }], children: [Text { text: "+" }] }, Element { tag: "button", namespace: None, attrs: [Dynamic { id: 1 }], children: [Text { text: "-" }] }, Element { tag: "ul", namespace: None, attrs: [], children: [Dynamic { id: 0 }] }] }], node_paths: [[0, 2, 0]], attr_paths: [[0, 0], [0, 1]] } }, dynamic_nodes: [Placeholder(VPlaceholder)], dynamic_attrs: [[Attribute { name: "onclick", value: Listener, namespace: None, volatile: false }], [Attribute { name: "onclick", value: Listener, namespace: None, volatile: false }]] }, mount: Cell { value: MountId(0) } }, mount: MountId(0) at packages/core/src/diff/node.rs:547 in dioxus_core::virtual_dom::VirtualDom::rebuild 2024-02-28T21:52:25.266896Z TRACE dioxus_core::virtual_dom: Calling 1 listeners at packages/core/src/virtual_dom.rs:695 in dioxus_core::virtual_dom::VirtualDom::handle_bubbling_event with parent: Some(ElementRef { path: ElementPath { path: [0, 0] }, mount: MountId(0) }), name: "click" in dioxus_core::virtual_dom::VirtualDom::handle_event with name: "click", data: Any { .. }, element: ElementId(2), bubbles: true 2024-02-28T21:52:25.266934Z TRACE dioxus_core::virtual_dom: Marking scope ScopeId(0) (app) as dirty at packages/core/src/virtual_dom.rs:379 in dioxus_core::virtual_dom::VirtualDom::process_events 2024-02-28T21:52:25.266997Z INFO dioxus_core::diff::node: creating template, self: VNode { vnode: VNodeInner { key: None, template: Cell { value: Template { name: "packages/core/tests/tracing.rs:71:37:2059", roots: [Dynamic { id: 0 }], node_paths: [[0]], attr_paths: [] } }, dynamic_nodes: [Component(VComponent { name: "ChildExample" })], dynamic_attrs: [] }, mount: Cell { value: MountId(1) } }, mount: MountId(1) at packages/core/src/diff/node.rs:547 in dioxus_core::virtual_dom::VirtualDom::render_immediate 2024-02-28T21:52:25.267026Z INFO dioxus_core::diff::node: creating template, self: VNode { vnode: VNodeInner { key: None, template: Cell { value: Template { name: "packages/core/tests/tracing.rs:81:5:2238", roots: [Element { tag: "li", namespace: None, attrs: [Dynamic { id: 0 }], children: [DynamicText { id: 0 }] }], node_paths: [[0, 0]], attr_paths: [[0]] } }, dynamic_nodes: [Text(VText { value: "0" })], dynamic_attrs: [[Attribute { name: "onmouseover", value: Listener, namespace: None, volatile: false }]] }, mount: Cell { value: MountId(2) } }, mount: MountId(2) at packages/core/src/diff/node.rs:547 in dioxus_core::virtual_dom::VirtualDom::render_immediate 2024-02-28T21:52:25.267061Z TRACE dioxus_core::virtual_dom: Calling 1 listeners at packages/core/src/virtual_dom.rs:695 in dioxus_core::virtual_dom::VirtualDom::handle_bubbling_event with parent: Some(ElementRef { path: ElementPath { path: [0, 0] }, mount: MountId(0) }), name: "click" in dioxus_core::virtual_dom::VirtualDom::handle_event with name: "click", data: Any { .. }, element: ElementId(2), bubbles: true 2024-02-28T21:52:25.267084Z TRACE dioxus_core::virtual_dom: Marking scope ScopeId(0) (app) as dirty at packages/core/src/virtual_dom.rs:379 in dioxus_core::virtual_dom::VirtualDom::process_events 2024-02-28T21:52:25.267131Z INFO dioxus_core::diff::node: creating template, self: VNode { vnode: VNodeInner { key: None, template: Cell { value: Template { name: "packages/core/tests/tracing.rs:71:37:2059", roots: [Dynamic { id: 0 }], node_paths: [[0]], attr_paths: [] } }, dynamic_nodes: [Component(VComponent { name: "ChildExample" })], dynamic_attrs: [] }, mount: Cell { value: MountId(3) } }, mount: MountId(3) at packages/core/src/diff/node.rs:547 in dioxus_core::virtual_dom::VirtualDom::render_immediate 2024-02-28T21:52:25.267157Z INFO dioxus_core::diff::node: creating template, self: VNode { vnode: VNodeInner { key: None, template: Cell { value: Template { name: "packages/core/tests/tracing.rs:81:5:2238", roots: [Element { tag: "li", namespace: None, attrs: [Dynamic { id: 0 }], children: [DynamicText { id: 0 }] }], node_paths: [[0, 0]], attr_paths: [[0]] } }, dynamic_nodes: [Text(VText { value: "1" })], dynamic_attrs: [[Attribute { name: "onmouseover", value: Listener, namespace: None, volatile: false }]] }, mount: Cell { value: MountId(4) } }, mount: MountId(4) at packages/core/src/diff/node.rs:547 in dioxus_core::virtual_dom::VirtualDom::render_immediate 2024-02-28T21:52:25.267206Z TRACE dioxus_core::virtual_dom: Calling 1 listeners at packages/core/src/virtual_dom.rs:695 in dioxus_core::virtual_dom::VirtualDom::handle_bubbling_event with parent: Some(ElementRef { path: ElementPath { path: [0, 0] }, mount: MountId(0) }), name: "click" in dioxus_core::virtual_dom::VirtualDom::handle_event with name: "click", data: Any { .. }, element: ElementId(2), bubbles: true 2024-02-28T21:52:25.267229Z TRACE dioxus_core::virtual_dom: Marking scope ScopeId(0) (app) as dirty at packages/core/src/virtual_dom.rs:379 in dioxus_core::virtual_dom::VirtualDom::process_events 2024-02-28T21:52:25.267274Z INFO dioxus_core::diff::node: creating template, self: VNode { vnode: VNodeInner { key: None, template: Cell { value: Template { name: "packages/core/tests/tracing.rs:71:37:2059", roots: [Dynamic { id: 0 }], node_paths: [[0]], attr_paths: [] } }, dynamic_nodes: [Component(VComponent { name: "ChildExample" })], dynamic_attrs: [] }, mount: Cell { value: MountId(5) } }, mount: MountId(5) at packages/core/src/diff/node.rs:547 in dioxus_core::virtual_dom::VirtualDom::render_immediate 2024-02-28T21:52:25.267297Z INFO dioxus_core::diff::node: creating template, self: VNode { vnode: VNodeInner { key: None, template: Cell { value: Template { name: "packages/core/tests/tracing.rs:81:5:2238", roots: [Element { tag: "li", namespace: None, attrs: [Dynamic { id: 0 }], children: [DynamicText { id: 0 }] }], node_paths: [[0, 0]], attr_paths: [[0]] } }, dynamic_nodes: [Text(VText { value: "2" })], dynamic_attrs: [[Attribute { name: "onmouseover", value: Listener, namespace: None, volatile: false }]] }, mount: Cell { value: MountId(6) } }, mount: MountId(6) at packages/core/src/diff/node.rs:547 in dioxus_core::virtual_dom::VirtualDom::render_immediate ``` --- Cargo.lock | 13 +++++ packages/core/Cargo.toml | 2 + packages/core/src/virtual_dom.rs | 41 ++++++++++++++-- packages/core/tests/tracing.rs | 82 ++++++++++++++++++++++++++++++++ 4 files changed, 134 insertions(+), 4 deletions(-) create mode 100644 packages/core/tests/tracing.rs diff --git a/Cargo.lock b/Cargo.lock index de7afb9eb..094de12a8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2361,6 +2361,8 @@ dependencies = [ "slab", "tokio", "tracing", + "tracing-fluent-assertions", + "tracing-subscriber", ] [[package]] @@ -9707,6 +9709,17 @@ dependencies = [ "valuable", ] +[[package]] +name = "tracing-fluent-assertions" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12de1a8c6bcfee614305e836308b596bbac831137a04c61f7e5b0b0bf2cfeaf6" +dependencies = [ + "tracing", + "tracing-core", + "tracing-subscriber", +] + [[package]] name = "tracing-futures" version = "0.2.5" diff --git a/packages/core/Cargo.toml b/packages/core/Cargo.toml index 01ed1847d..765f9b12f 100644 --- a/packages/core/Cargo.toml +++ b/packages/core/Cargo.toml @@ -20,9 +20,11 @@ slab = { workspace = true } futures-channel = { workspace = true } tracing = { workspace = true } serde = { version = "1", features = ["derive"], optional = true } +tracing-subscriber = "0.3.18" [dev-dependencies] tokio = { workspace = true, features = ["full"] } +tracing-fluent-assertions = "0.3.0" dioxus = { workspace = true } pretty_assertions = "1.3.0" rand = "0.8.5" diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 9e78f53db..b1a4e1203 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -19,6 +19,7 @@ use futures_util::StreamExt; use rustc_hash::{FxHashMap, FxHashSet}; use slab::Slab; use std::{any::Any, collections::BTreeSet, rc::Rc}; +use tracing::instrument; /// A virtual node system that progresses user events and diffs UI trees. /// @@ -303,6 +304,7 @@ impl VirtualDom { /// let mut dom = VirtualDom::new_from_root(VComponent::new(Example, SomeProps { name: "jane" }, "Example")); /// let mutations = dom.rebuild(); /// ``` + #[instrument(skip(root), level = "trace", name = "VirtualDom::new")] pub(crate) fn new_with_component(root: impl AnyProps + 'static) -> Self { let (tx, rx) = futures_channel::mpsc::unbounded(); @@ -345,6 +347,7 @@ impl VirtualDom { } /// Run a closure inside the dioxus runtime + #[instrument(skip(self, f), level = "trace", name = "VirtualDom::in_runtime")] pub fn in_runtime(&self, f: impl FnOnce() -> O) -> O { let _runtime = RuntimeGuard::new(self.runtime.clone()); f() @@ -373,7 +376,13 @@ impl VirtualDom { return; }; - tracing::trace!("Marking scope {:?} as dirty", id); + tracing::event!( + tracing::Level::TRACE, + "Marking scope {:?} ({}) as dirty", + id, + scope.name + ); + self.dirty_scopes.insert(DirtyScope { height: scope.height(), id, @@ -389,6 +398,7 @@ impl VirtualDom { /// It is up to the listeners themselves to mark nodes as dirty. /// /// If you have multiple events, you can call this method multiple times before calling "render_with_deadline" + #[instrument(skip(self), level = "trace", name = "VirtualDom::handle_event")] pub fn handle_event( &mut self, name: &str, @@ -422,12 +432,14 @@ impl VirtualDom { /// ```rust, ignore /// let dom = VirtualDom::new(app); /// ``` + #[instrument(skip(self), level = "trace", name = "VirtualDom::wait_for_work")] pub async fn wait_for_work(&mut self) { // And then poll the futures self.poll_tasks().await; } /// + #[instrument(skip(self), level = "trace", name = "VirtualDom::poll_tasks")] async fn poll_tasks(&mut self) { // Release the flush lock // This will cause all the flush wakers to immediately spring to life, which we will off with process_events @@ -461,6 +473,7 @@ impl VirtualDom { } /// Process all events in the queue until there are no more left + #[instrument(skip(self), level = "trace", name = "VirtualDom::process_events")] pub fn process_events(&mut self) { let _runtime = RuntimeGuard::new(self.runtime.clone()); @@ -478,7 +491,8 @@ impl VirtualDom { /// /// The caller must ensure that the template references the same dynamic attributes and nodes as the original template. /// - /// This will only replace the the parent template, not any nested templates. + /// This will only replace the parent template, not any nested templates. + #[instrument(skip(self), level = "trace", name = "VirtualDom::replace_template")] pub fn replace_template(&mut self, template: Template) { self.register_template_first_byte_index(template); // iterating a slab is very inefficient, but this is a rare operation that will only happen during development so it's fine @@ -518,7 +532,7 @@ impl VirtualDom { /// The mutations item expects the RealDom's stack to be the root of the application. /// /// Tasks will not be polled with this method, nor will any events be processed from the event queue. Instead, the - /// root component will be ran once and then diffed. All updates will flow out as mutations. + /// root component will be run once and then diffed. All updates will flow out as mutations. /// /// All state stored in components will be completely wiped away. /// @@ -533,6 +547,7 @@ impl VirtualDom { /// /// apply_edits(edits); /// ``` + #[instrument(skip(self, to), level = "trace", name = "VirtualDom::rebuild")] pub fn rebuild(&mut self, to: &mut impl WriteMutations) { self.flush_templates(to); let _runtime = RuntimeGuard::new(self.runtime.clone()); @@ -546,6 +561,7 @@ impl VirtualDom { /// Render whatever the VirtualDom has ready as fast as possible without requiring an executor to progress /// suspended subtrees. + #[instrument(skip(self, to), level = "trace", name = "VirtualDom::render_immediate")] pub fn render_immediate(&mut self, to: &mut impl WriteMutations) { self.flush_templates(to); @@ -584,7 +600,8 @@ impl VirtualDom { /// The mutations will be thrown out, so it's best to use this method for things like SSR that have async content /// /// We don't call "flush_sync" here since there's no sync work to be done. Futures will be progressed like usual, - /// however any futures wating on flush_sync will remain pending + /// however any futures waiting on flush_sync will remain pending + #[instrument(skip(self), level = "trace", name = "VirtualDom::wait_for_suspense")] pub async fn wait_for_suspense(&mut self) { loop { if self.suspended_scopes.is_empty() { @@ -605,6 +622,7 @@ impl VirtualDom { } /// Flush any queued template changes + #[instrument(skip(self, to), level = "trace", name = "VirtualDom::flush_templates")] fn flush_templates(&mut self, to: &mut impl WriteMutations) { for template in self.queued_templates.drain(..) { to.register_template(template); @@ -632,6 +650,11 @@ impl VirtualDom { | | | <-- no, broke early | <-- no, broke early */ + #[instrument( + skip(self, uievent), + level = "trace", + name = "VirtualDom::handle_bubbling_event" + )] fn handle_bubbling_event( &mut self, mut parent: Option, @@ -670,6 +693,11 @@ impl VirtualDom { // Now that we've accumulated all the parent attributes for the target element, call them in reverse order // We check the bubble state between each call to see if the event has been stopped from bubbling + tracing::event!( + tracing::Level::TRACE, + "Calling {} listeners", + listeners.len() + ); for listener in listeners.into_iter().rev() { if let AttributeValue::Listener(listener) = listener { self.runtime.rendering.set(false); @@ -688,6 +716,11 @@ impl VirtualDom { } /// Call an event listener in the simplest way possible without bubbling upwards + #[instrument( + skip(self, uievent), + level = "trace", + name = "VirtualDom::handle_non_bubbling_event" + )] fn handle_non_bubbling_event(&mut self, node: ElementRef, name: &str, uievent: Event) { let el_ref = &self.mounts[node.mount.0].node; let node_template = el_ref.template.get(); diff --git a/packages/core/tests/tracing.rs b/packages/core/tests/tracing.rs new file mode 100644 index 000000000..1fe04033d --- /dev/null +++ b/packages/core/tests/tracing.rs @@ -0,0 +1,82 @@ +use dioxus::html::SerializedHtmlEventConverter; +use dioxus::prelude::*; +use dioxus_core::ElementId; +use std::rc::Rc; +use tracing_fluent_assertions::{AssertionRegistry, AssertionsLayer}; +use tracing_subscriber::{layer::SubscriberExt, Registry}; + +#[test] +fn basic_tracing() { + // setup tracing + let assertion_registry = AssertionRegistry::default(); + let base_subscriber = Registry::default(); + // log to standard out for testing + let std_out_log = tracing_subscriber::fmt::layer().pretty(); + let subscriber = base_subscriber + .with(std_out_log) + .with(AssertionsLayer::new(&assertion_registry)); + tracing::subscriber::set_global_default(subscriber).unwrap(); + + let new_virtual_dom = assertion_registry + .build() + .with_name("VirtualDom::new") + .was_created() + .was_entered_exactly(1) + .was_closed() + .finalize(); + + let edited_virtual_dom = assertion_registry + .build() + .with_name("VirtualDom::rebuild") + .was_created() + .was_entered_exactly(1) + .was_closed() + .finalize(); + + set_event_converter(Box::new(SerializedHtmlEventConverter)); + let mut dom = VirtualDom::new(app); + + dom.rebuild(&mut dioxus_core::NoOpMutations); + + new_virtual_dom.assert(); + edited_virtual_dom.assert(); + + for _ in 0..3 { + dom.handle_event( + "click", + Rc::new(PlatformEventData::new(Box::::default())), + ElementId(2), + true, + ); + dom.process_events(); + _ = dom.render_immediate_to_vec(); + } +} + +fn app() -> Element { + let mut idx = use_signal(|| 0); + let onhover = |_| println!("go!"); + + rsx! { + div { + button { + onclick: move |_| { + idx += 1; + println!("Clicked"); + }, + "+" + } + button { onclick: move |_| idx -= 1, "-" } + ul { + {(0..idx()).map(|i| rsx! { + ChildExample { i: i, onhover: onhover } + })} + } + } + } +} + +#[component] +fn ChildExample(i: i32, onhover: EventHandler) -> Element { + rsx! { li { onmouseover: move |e| onhover.call(e), "{i}" } } +} From 8322b508a71822b7d4cb95fb5774ae1f1d549e96 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 4 Mar 2024 15:51:14 -0600 Subject: [PATCH 17/19] fix signals leaking memory --- packages/signals/src/copy_value.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/signals/src/copy_value.rs b/packages/signals/src/copy_value.rs index 6efe3c70f..ca318bf2c 100644 --- a/packages/signals/src/copy_value.rs +++ b/packages/signals/src/copy_value.rs @@ -93,7 +93,7 @@ fn current_owner, T>() -> Owner { } fn owner_in_scope, T>(scope: ScopeId) -> Owner { - match consume_context_from_scope(scope) { + match scope.has_context() { Some(rt) => rt, None => { let owner = S::owner(); From 568631e3318607aff2b8e860389403f19770b7a9 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 4 Mar 2024 16:02:19 -0600 Subject: [PATCH 18/19] test signal drops --- packages/signals/README.md | 19 +++++++++---------- packages/signals/src/signal.rs | 10 ++++------ packages/signals/tests/create.rs | 21 ++++++++++++++++----- 3 files changed, 29 insertions(+), 21 deletions(-) diff --git a/packages/signals/README.md b/packages/signals/README.md index 56dd8d47d..7a1f39f0d 100644 --- a/packages/signals/README.md +++ b/packages/signals/README.md @@ -36,18 +36,18 @@ use dioxus_signals::*; #[component] fn App() -> Element { // Because signal is never read in this component, this component will not rerun when the signal changes - let signal = use_signal(|| 0); + let mut signal = use_signal(|| 0); rsx! { button { onclick: move |_| { - *signal.write() += 1; + signal += 1; }, "Increase" } for id in 0..10 { Child { - signal: signal, + signal, } } } @@ -58,11 +58,10 @@ struct ChildProps { signal: Signal, } -#[component] -fn Child(cx: Scope) -> Element { +fn Child(props: ChildProps) -> Element { // This component does read from the signal, so when the signal changes it will rerun rsx! { - "{cx.props.signal}" + "{props.signal}" } } ``` @@ -85,7 +84,7 @@ fn App() -> Element { #[component] fn Child() -> Element { - let signal: Signal = *use_context(cx).unwrap(); + let signal: Signal = use_context(); // This component does read from the signal, so when the signal changes it will rerun rsx! { "{signal}" @@ -105,12 +104,12 @@ use dioxus_signals::*; #[component] fn App() -> Element { - let signal = use_signal(|| 0); - let doubled = use_memo(|| signal * 2); + let mut signal = use_signal(|| 0); + let doubled = use_memo(move || signal * 2); rsx! { button { - onclick: move |_| *signal.write() += 1, + onclick: move |_| signal += 1, "Increase" } Child { diff --git a/packages/signals/src/signal.rs b/packages/signals/src/signal.rs index faa39009b..b0a70d210 100644 --- a/packages/signals/src/signal.rs +++ b/packages/signals/src/signal.rs @@ -30,17 +30,15 @@ use std::{ /// } /// /// #[component] -/// fn Child(state: Signal) -> Element { -/// let state = *state; -/// -/// use_future( |()| async move { +/// fn Child(mut state: Signal) -> Element { +/// use_future(move || async move { /// // Because the signal is a Copy type, we can use it in an async block without cloning it. -/// *state.write() += 1; +/// state += 1; /// }); /// /// rsx! { /// button { -/// onclick: move |_| *state.write() += 1, +/// onclick: move |_| state += 1, /// "{state}" /// } /// } diff --git a/packages/signals/tests/create.rs b/packages/signals/tests/create.rs index 7b17310b6..b32da529c 100644 --- a/packages/signals/tests/create.rs +++ b/packages/signals/tests/create.rs @@ -56,6 +56,11 @@ fn deref_signal() { #[test] fn drop_signals() { + use std::sync::atomic::AtomicUsize; + use std::sync::atomic::Ordering; + + static SIGNAL_DROP_COUNT: AtomicUsize = AtomicUsize::new(0); + let mut dom = VirtualDom::new(|| { let generation = generation(); @@ -68,10 +73,18 @@ fn drop_signals() { }); fn Child() -> Element { - let signal = create_without_cx(); + struct TracksDrops; + + impl Drop for TracksDrops { + fn drop(&mut self) { + SIGNAL_DROP_COUNT.fetch_add(1, Ordering::Relaxed); + } + } + + use_signal(|| TracksDrops); rsx! { - "{signal}" + "" } } @@ -79,7 +92,5 @@ fn drop_signals() { dom.mark_dirty(ScopeId::ROOT); dom.render_immediate(&mut NoOpMutations); - fn create_without_cx() -> Signal { - Signal::new("hello world".to_string()) - } + assert_eq!(SIGNAL_DROP_COUNT.load(Ordering::Relaxed), 10); } From f39a2e5f98050363f4416a77284ec3a4ccb058b9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Mar 2024 16:24:29 -0600 Subject: [PATCH 19/19] Bump mio from 0.8.10 to 0.8.11 (#2001) Bumps [mio](https://github.com/tokio-rs/mio) from 0.8.10 to 0.8.11. - [Release notes](https://github.com/tokio-rs/mio/releases) - [Changelog](https://github.com/tokio-rs/mio/blob/master/CHANGELOG.md) - [Commits](https://github.com/tokio-rs/mio/compare/v0.8.10...v0.8.11) --- updated-dependencies: - dependency-name: mio dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 094de12a8..1d6e8ce04 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5926,9 +5926,9 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", "log",