From 14b3fa72e46537b7783ff9b72246ab8e1325bb62 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sun, 15 Oct 2023 17:40:56 -0700 Subject: [PATCH 1/6] Change the semantics of exprs/for loops allocations strategy This commit adjusts how exprs and for loops are handled within rsx. This is a breaking change in terms of codegen, but has slight semantic changes as well. Now, when exprs/for loops are allocated, they are given a temporary. The temporary is elided to the <'a> lifetime of the bump, to satisfy the borrow checker. This fixes issues with signals where exprs/for loops mapping vecs out of RefCells would be caught up without a temporary lifetime. --- examples/signals.rs | 11 +++++++++++ packages/rsx/src/node.rs | 17 +++++++++++++---- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/examples/signals.rs b/examples/signals.rs index bc627fa8f..09c184d47 100644 --- a/examples/signals.rs +++ b/examples/signals.rs @@ -7,6 +7,7 @@ fn main() { fn app(cx: Scope) -> Element { let mut count = dioxus_signals::use_signal(cx, || 0); + let saved_values = dioxus_signals::use_signal(cx, || vec![0]); use_future!(cx, || async move { loop { @@ -19,9 +20,19 @@ fn app(cx: Scope) -> Element { h1 { "High-Five counter: {count}" } button { onclick: move |_| count += 1, "Up high!" } button { onclick: move |_| count -= 1, "Down low!" } + button { + onclick: move |_| saved_values.push(count.value()), + "Save this value" + } + // We can do boolean operations on the current signal value if count.value() > 5 { rsx!{ h2 { "High five!" } } } + + // We can cleanly map signals with iterators + for value in saved_values.read().iter() { + h3 { "Saved value: {value}" } + } }) } diff --git a/packages/rsx/src/node.rs b/packages/rsx/src/node.rs index 1bda081d9..9c2f7b330 100644 --- a/packages/rsx/src/node.rs +++ b/packages/rsx/src/node.rs @@ -125,7 +125,11 @@ impl ToTokens for BodyNode { __cx.text_node(#txt) }), BodyNode::RawExpr(exp) => tokens.append_all(quote! { - __cx.make_node(#exp) + { + use ::dioxus::core::IntoDynNode; + let ___nodes =(#exp).into_vnode(__cx); + ___nodes + } }), BodyNode::ForLoop(exp) => { let ForLoop { @@ -137,10 +141,15 @@ impl ToTokens for BodyNode { location: None, }; + // Signals expose an issue with temporary lifetimes + // We need to directly render out the nodes first to collapse their lifetime to <'a> + // And then we can return them into the dyn loop tokens.append_all(quote! { - __cx.make_node( - (#expr).into_iter().map(|#pat| { #renderer }) - ) + { + use ::dioxus::core::IntoDynNode; + let ___nodes =(#expr).into_iter().map(|#pat| { #renderer }).into_vnode(__cx); + ___nodes + } }) } BodyNode::IfChain(chain) => { From 741bfbc9ac27a9ec766f9e89e9a3255ac0bf3bad Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sun, 15 Oct 2023 17:50:23 -0700 Subject: [PATCH 2/6] Add toggle to signals example --- examples/signals.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/examples/signals.rs b/examples/signals.rs index 09c184d47..60c9af126 100644 --- a/examples/signals.rs +++ b/examples/signals.rs @@ -6,12 +6,15 @@ fn main() { } fn app(cx: Scope) -> Element { + let running = dioxus_signals::use_signal(cx, || true); let mut count = dioxus_signals::use_signal(cx, || 0); let saved_values = dioxus_signals::use_signal(cx, || vec![0]); use_future!(cx, || async move { loop { - count += 1; + if running.value() { + count += 1; + } tokio::time::sleep(Duration::from_millis(400)).await; } }); @@ -20,10 +23,8 @@ fn app(cx: Scope) -> Element { h1 { "High-Five counter: {count}" } button { onclick: move |_| count += 1, "Up high!" } button { onclick: move |_| count -= 1, "Down low!" } - button { - onclick: move |_| saved_values.push(count.value()), - "Save this value" - } + button { onclick: move |_| running.set(!running.value()), "Toggle counter" } + button { onclick: move |_| saved_values.push(count.value()), "Save this value" } // We can do boolean operations on the current signal value if count.value() > 5 { From 7e2164d6d6a142203b840a0f28eb18880f759268 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sun, 15 Oct 2023 17:52:01 -0700 Subject: [PATCH 3/6] Add toggle --- examples/signals.rs | 2 +- packages/signals/src/signal.rs | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/examples/signals.rs b/examples/signals.rs index 60c9af126..c16bfe927 100644 --- a/examples/signals.rs +++ b/examples/signals.rs @@ -23,7 +23,7 @@ fn app(cx: Scope) -> Element { h1 { "High-Five counter: {count}" } button { onclick: move |_| count += 1, "Up high!" } button { onclick: move |_| count -= 1, "Down low!" } - button { onclick: move |_| running.set(!running.value()), "Toggle counter" } + button { onclick: move |_| running.toggle(), "Toggle counter" } button { onclick: move |_| saved_values.push(count.value()), "Save this value" } // We can do boolean operations on the current signal value diff --git a/packages/signals/src/signal.rs b/packages/signals/src/signal.rs index c1ae076d9..3a0ad407d 100644 --- a/packages/signals/src/signal.rs +++ b/packages/signals/src/signal.rs @@ -262,6 +262,13 @@ impl Signal { } } +impl Signal { + /// Invert the boolean value of the signal. This will trigger an update on all subscribers. + pub fn toggle(&self) { + self.set(!self.value()); + } +} + impl PartialEq for Signal { fn eq(&self, other: &Self) -> bool { self.inner == other.inner From e819702fc516c79a813e0d82bad23998e572a49b Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 17 Oct 2023 15:42:21 -0700 Subject: [PATCH 4/6] Add into_vnode temporary logic for more things This commit adjusts how rsx! works, making it more forgiving with signals. Notably, we add the temporaries to if chains too. --- examples/signals.rs | 13 +++++++++++-- packages/core/src/lib.rs | 6 +++--- packages/rsx/src/node.rs | 14 +++++++++----- 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/examples/signals.rs b/examples/signals.rs index c16bfe927..559c3f5ce 100644 --- a/examples/signals.rs +++ b/examples/signals.rs @@ -8,8 +8,10 @@ fn main() { fn app(cx: Scope) -> Element { let running = dioxus_signals::use_signal(cx, || true); let mut count = dioxus_signals::use_signal(cx, || 0); - let saved_values = dioxus_signals::use_signal(cx, || vec![0]); + let saved_values = dioxus_signals::use_signal(cx, || vec![0.to_string()]); + // Signals can be used in async functions without an explicit clone since they're 'static and Copy + // Signals are backed by a runtime that is designed to deeply integrate with Dioxus apps use_future!(cx, || async move { loop { if running.value() { @@ -24,7 +26,7 @@ fn app(cx: Scope) -> Element { button { onclick: move |_| count += 1, "Up high!" } button { onclick: move |_| count -= 1, "Down low!" } button { onclick: move |_| running.toggle(), "Toggle counter" } - button { onclick: move |_| saved_values.push(count.value()), "Save this value" } + button { onclick: move |_| saved_values.push(count.value().to_string()), "Save this value" } // We can do boolean operations on the current signal value if count.value() > 5 { @@ -35,5 +37,12 @@ fn app(cx: Scope) -> Element { for value in saved_values.read().iter() { h3 { "Saved value: {value}" } } + + // We can also use the signal value as a slice + if let &[ref first, .., ref last] = saved_values.read().as_slice() { + rsx! { li { "single element: {first}, {last}" } } + } else { + rsx! { "No saved values" } + } }) } diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index ee557af2d..392774767 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -91,9 +91,9 @@ pub mod prelude { consume_context, consume_context_from_scope, current_scope_id, fc_to_builder, has_context, provide_context, provide_context_to_scope, provide_root_context, push_future, remove_future, schedule_update_any, spawn, spawn_forever, suspend, throw, AnyValue, - Component, Element, Event, EventHandler, Fragment, IntoAttributeValue, LazyNodes, - Properties, Runtime, RuntimeGuard, Scope, ScopeId, ScopeState, Scoped, TaskId, Template, - TemplateAttribute, TemplateNode, Throw, VNode, VirtualDom, + Component, Element, Event, EventHandler, Fragment, IntoAttributeValue, IntoDynNode, + LazyNodes, Properties, Runtime, RuntimeGuard, Scope, ScopeId, ScopeState, Scoped, TaskId, + Template, TemplateAttribute, TemplateNode, Throw, VNode, VirtualDom, }; } diff --git a/packages/rsx/src/node.rs b/packages/rsx/src/node.rs index 9c2f7b330..ed5e0bff4 100644 --- a/packages/rsx/src/node.rs +++ b/packages/rsx/src/node.rs @@ -126,8 +126,7 @@ impl ToTokens for BodyNode { }), BodyNode::RawExpr(exp) => tokens.append_all(quote! { { - use ::dioxus::core::IntoDynNode; - let ___nodes =(#exp).into_vnode(__cx); + let ___nodes = (#exp).into_vnode(__cx); ___nodes } }), @@ -146,7 +145,6 @@ impl ToTokens for BodyNode { // And then we can return them into the dyn loop tokens.append_all(quote! { { - use ::dioxus::core::IntoDynNode; let ___nodes =(#expr).into_iter().map(|#pat| { #renderer }).into_vnode(__cx); ___nodes } @@ -155,7 +153,10 @@ impl ToTokens for BodyNode { BodyNode::IfChain(chain) => { if is_if_chain_terminated(chain) { tokens.append_all(quote! { - __cx.make_node(#chain) + { + let ___nodes = (#chain).into_vnode(__cx); + ___nodes + } }); } else { let ExprIf { @@ -209,7 +210,10 @@ impl ToTokens for BodyNode { }); tokens.append_all(quote! { - __cx.make_node(#body) + { + let ___nodes = (#body).into_vnode(__cx); + ___nodes + } }); } } From 65e21d7874e9dfac7b2a5dcdec653500a954c057 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 17 Oct 2023 15:52:13 -0700 Subject: [PATCH 5/6] Change signals example a bit --- examples/signals.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/signals.rs b/examples/signals.rs index 559c3f5ce..49f8e2de1 100644 --- a/examples/signals.rs +++ b/examples/signals.rs @@ -27,6 +27,7 @@ fn app(cx: Scope) -> Element { button { onclick: move |_| count -= 1, "Down low!" } button { onclick: move |_| running.toggle(), "Toggle counter" } button { onclick: move |_| saved_values.push(count.value().to_string()), "Save this value" } + button { onclick: move |_| saved_values.write().clear(), "Clear saved values" } // We can do boolean operations on the current signal value if count.value() > 5 { @@ -40,7 +41,7 @@ fn app(cx: Scope) -> Element { // We can also use the signal value as a slice if let &[ref first, .., ref last] = saved_values.read().as_slice() { - rsx! { li { "single element: {first}, {last}" } } + rsx! { li { "First and last: {first}, {last}" } } } else { rsx! { "No saved values" } } From 9cdbf81a4bc8ae528db090173c84d63c07e850e8 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 17 Oct 2023 16:06:43 -0700 Subject: [PATCH 6/6] fix clippy lint --- examples/signals.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/signals.rs b/examples/signals.rs index 49f8e2de1..0faac0af4 100644 --- a/examples/signals.rs +++ b/examples/signals.rs @@ -40,7 +40,7 @@ fn app(cx: Scope) -> Element { } // We can also use the signal value as a slice - if let &[ref first, .., ref last] = saved_values.read().as_slice() { + if let [ref first, .., ref last] = saved_values.read().as_slice() { rsx! { li { "First and last: {first}, {last}" } } } else { rsx! { "No saved values" }