From 952a91d5408aaf789b496f11d01c3b3f7fcf9059 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Thu, 1 Jul 2021 14:14:59 -0400 Subject: [PATCH] wip --- .nojekyll | 0 README.md | 6 +-- docs/main-concepts/12-signals.md | 2 +- examples/listener.rs | 2 +- notes/ARCHITECTURE.md | 47 ++++++++++++++++++++++ notes/TODO.md | 2 +- packages/core-macro/src/lib.rs | 9 ++++- packages/core-macro/src/rsx/mod.rs | 2 +- packages/core/src/debug_renderer.rs | 8 ++-- packages/core/src/events.rs | 4 +- packages/core/src/lib.rs | 3 +- packages/core/src/nodebuilder.rs | 42 ++++++++++---------- packages/core/src/nodes.rs | 4 +- packages/core/src/signals.rs | 1 + packages/core/src/util.rs | 1 - packages/core/src/virtual_dom.rs | 8 ++-- packages/html-namespace/examples/poc.rs | 8 ++-- packages/web/examples/anim.rs | 53 +++++++++++++++++++++++++ 18 files changed, 154 insertions(+), 48 deletions(-) create mode 100644 .nojekyll create mode 100644 packages/core/src/signals.rs create mode 100644 packages/web/examples/anim.rs diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 00000000..e69de29b diff --git a/README.md b/README.md index 23487c87..2934e2b2 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Dioxus is a portable, performant, and ergonomic framework for building cross-pla ```rust fn Example(cx: Context<()>) -> VNode { - let mut name = use_signal(cx, || "..?"); + let name = use_state(cx, || "..?"); cx.render(rsx! { h1 { "Hello, {name}" } @@ -81,8 +81,6 @@ If you know React, then you already know Dioxus. ## Why? ---- - TypeScript is a great addition to JavaScript, but comes with a lot of tweaking flags, a slight performance hit, and an uneven ecosystem where some of the most important packages are not properly typed. TypeScript provides a lot of great benefits to JS projects, but comes with its own "tax" that can slow down dev teams. Rust can be seen as a step up from TypeScript, supporting: - static types for _all_ libraries @@ -93,7 +91,7 @@ TypeScript is a great addition to JavaScript, but comes with a lot of tweaking f - integrated documentation - inline built-in unit/integration testing - best-in-class error handling -- simple and fast build system +- simple and fast build system (compared to webpack!) - powerful standard library (no need for lodash or underscore) - include_str! for integrating html/css/svg templates directly - various macros (`html!`, `rsx!`) for fast template iteration diff --git a/docs/main-concepts/12-signals.md b/docs/main-concepts/12-signals.md index 21bec60e..0ad05916 100644 --- a/docs/main-concepts/12-signals.md +++ b/docs/main-concepts/12-signals.md @@ -45,7 +45,7 @@ fn Comp(cx: Context<()>) -> VNode { Many experienced React developers will just say "this is bad design" - but we consider it to be a pit of failure, rather than a pit of success! That's why signals exist - to push you in a more performant (and ergonomic) direction. Signals let us directly bind values to their final place in the VirtualDOM. Whenever the signal value is updated, Dioxus will only the DOM nodes where that signal is used. Signals are built into Dioxus, so we can directly bind attributes of elements to their updates. -We can use signals generated a two-way binding between data and the input box. Our text input is now just a two-line component! +We can use signals to generate a two-way binding between data and the input box. Our text input is now just a two-line component! ```rust fn Comp(cx: Context<()>) -> VNode { diff --git a/examples/listener.rs b/examples/listener.rs index 32239db0..0e22f866 100644 --- a/examples/listener.rs +++ b/examples/listener.rs @@ -16,7 +16,7 @@ static Example: FC<()> = |cx| { )) }; -pub fn render<'src, 'a, F: for<'b> FnOnce(&'b NodeCtx<'src>) -> VNode<'src> + 'src + 'a, P>( +pub fn render<'src, 'a, F: for<'b> FnOnce(&'b NodeFactory<'src>) -> VNode<'src> + 'src + 'a, P>( cx: &'a Context<'src, P>, lazy_nodes: LazyNodes<'src, F>, ) -> VNode<'src> { diff --git a/notes/ARCHITECTURE.md b/notes/ARCHITECTURE.md index e69de29b..6a81927d 100644 --- a/notes/ARCHITECTURE.md +++ b/notes/ARCHITECTURE.md @@ -0,0 +1,47 @@ +# Signals + +Signals provide a way of forcing updates directly through Dioxus without having to go through the diffing phase. + +When diffing is too slow for your use-case, signals can be faster. Signals run at a higher priority than regular diffing, acting as a hint to Dioxus that a signal update needs to take precedence over a subtree update. This can be useful in real-time systems where getting data from a websocket to the screen ASAP is extremely important. + +- High +- Medium +- Low + +## Signals: + +Producer -> Receiver + +- The Dioxus VirtualDOM provides built-in receivers for signals. +- Elements themselves act as receivers. +- Any use of a signal schedules the current element and its children for updates. +- Attributes are valid receivers +- Text nodes are valid receivers +- Receivers may not be passed into child components (must be de-referenced) +- When receivers are derefed in a component's properties, the props will be updated in place and the component will re-render with the new value. + +```rust +let sig = use_signal(|| 0); + +// any updates to the signal will cause the child to re-render completely +Comp { + prop: *sig +} +``` + +Using 3 separate signals + +```rust +let width = use_signal(|| 0); + +cx.request_next_frame(move |frame| async { + sig1 += 1; + frame.again(); +}) + +div { + h2 { "{sig1}" } + h3 { "{sig2}" } + h4 { "{sig3}" } +} +``` diff --git a/notes/TODO.md b/notes/TODO.md index 74963669..b32bde78 100644 --- a/notes/TODO.md +++ b/notes/TODO.md @@ -1,4 +1,4 @@ -- [] Move the builder API onto NodeCtx +- [] Move the builder API onto NodeFactory - [] Transition away from names and towards compile-time safe tags - [] Fix diffing of fragments - [] Properly integrate memoization to prevent safety issues with children diff --git a/packages/core-macro/src/lib.rs b/packages/core-macro/src/lib.rs index 2943f4ee..6b75ec36 100644 --- a/packages/core-macro/src/lib.rs +++ b/packages/core-macro/src/lib.rs @@ -21,7 +21,14 @@ pub fn html(s: TokenStream) -> TokenStream { } /// The html! macro makes it easy for developers to write jsx-style markup in their components. -/// We aim to keep functional parity with html templates. +/// ``` +/// rsx! { +/// div { +/// class: "some special class" +/// h1 { "Children too" } +/// } +/// } +/// ``` #[proc_macro] pub fn rsx(s: TokenStream) -> TokenStream { match syn::parse::(s) { diff --git a/packages/core-macro/src/rsx/mod.rs b/packages/core-macro/src/rsx/mod.rs index 44b6584b..7a4579bf 100644 --- a/packages/core-macro/src/rsx/mod.rs +++ b/packages/core-macro/src/rsx/mod.rs @@ -105,7 +105,7 @@ impl ToTokens for RsxRender { }), // Otherwise we just build the LazyNode wrapper None => out_tokens.append_all(quote! { - dioxus::prelude::LazyNodes::new(move |__cx|{ + dioxus::prelude::LazyNodes::new(move |__cx: &NodeFactory|{ let bump = &__cx.bump(); #inner }) diff --git a/packages/core/src/debug_renderer.rs b/packages/core/src/debug_renderer.rs index 13486bb4..9ae811af 100644 --- a/packages/core/src/debug_renderer.rs +++ b/packages/core/src/debug_renderer.rs @@ -51,7 +51,7 @@ impl DebugRenderer { // Does not handle children or lifecycles and will always fail the test if they show up in the rhs pub fn compare<'a, F>(&self, other: LazyNodes<'a, F>) -> Result<()> where - F: for<'b> FnOnce(&'b NodeCtx<'a>) -> VNode<'a> + 'a, + F: for<'b> FnOnce(&'b NodeFactory<'a>) -> VNode<'a> + 'a, { Ok(()) } @@ -60,7 +60,7 @@ impl DebugRenderer { // Ignores listeners and children components pub fn compare_full<'a, F>(&self, other: LazyNodes<'a, F>) -> Result<()> where - F: for<'b> FnOnce(&'b NodeCtx<'a>) -> VNode<'a> + 'a, + F: for<'b> FnOnce(&'b NodeFactory<'a>) -> VNode<'a> + 'a, { Ok(()) } @@ -71,7 +71,7 @@ impl DebugRenderer { pub fn render_nodes<'a, F>(&self, other: LazyNodes<'a, F>) -> Result<()> where - F: for<'b> FnOnce(&'b NodeCtx<'a>) -> VNode<'a> + 'a, + F: for<'b> FnOnce(&'b NodeFactory<'a>) -> VNode<'a> + 'a, { Ok(()) } @@ -86,7 +86,7 @@ impl DebugVNodeSource { } fn render_nodes(&self) -> VNode { - // let cx = NodeCtx + // let cx = NodeFactory todo!() } } diff --git a/packages/core/src/events.rs b/packages/core/src/events.rs index ab7d966e..931a53d9 100644 --- a/packages/core/src/events.rs +++ b/packages/core/src/events.rs @@ -67,7 +67,7 @@ pub mod on { use crate::{ builder::ElementBuilder, - builder::NodeCtx, + builder::NodeFactory, innerlude::{Attribute, Listener, RealDomNode, VNode}, }; use std::cell::Cell; @@ -79,7 +79,7 @@ pub mod on { $( $( pub fn $name<'a>( - c: &'_ NodeCtx<'a>, + c: &'_ NodeFactory<'a>, callback: impl Fn(Rc) + 'a, ) -> Listener<'a> { let bump = &c.bump(); diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index 81f1d6b1..e6bf5953 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -20,6 +20,7 @@ pub mod events; // Manages the synthetic event API pub mod hooks; // Built-in hooks pub mod nodebuilder; // Logic for building VNodes with a direct syntax pub mod nodes; // Logic for the VNodes +pub mod signals; pub mod virtual_dom; // Most fun logic starts here, manages the lifecycle and suspense pub mod builder { @@ -57,7 +58,7 @@ pub mod prelude { pub use crate::nodebuilder::LazyNodes; pub use crate::nodebuilder::ChildrenList; - pub use crate::nodebuilder::NodeCtx; + pub use crate::nodebuilder::NodeFactory; // pub use nodes::iterables::IterableNodes; /// This type alias is an internal way of abstracting over the static functions that represent components. pub use crate::innerlude::FC; diff --git a/packages/core/src/nodebuilder.rs b/packages/core/src/nodebuilder.rs index e09076b6..dc467ba4 100644 --- a/packages/core/src/nodebuilder.rs +++ b/packages/core/src/nodebuilder.rs @@ -29,7 +29,7 @@ where Attributes: 'a + AsRef<[Attribute<'a>]>, Children: 'a + AsRef<[VNode<'a>]>, { - cx: &'b NodeCtx<'a>, + cx: &'b NodeFactory<'a>, key: NodeKey<'a>, tag_name: &'static str, // tag_name: &'a str, @@ -71,7 +71,7 @@ impl<'a, 'b> /// let my_element_builder = ElementBuilder::new(&b, tag_name); /// # fn flip_coin() -> bool { true } /// ``` - pub fn new(cx: &'b NodeCtx<'a>, tag_name: &'static str) -> Self { + pub fn new(cx: &'b NodeFactory<'a>, tag_name: &'static str) -> Self { let bump = cx.bump(); ElementBuilder { cx, @@ -532,20 +532,20 @@ impl<'a> IntoIterator for VNode<'a> { } } impl<'a> IntoVNode<'a> for VNode<'a> { - fn into_vnode(self, cx: &NodeCtx<'a>) -> VNode<'a> { + fn into_vnode(self, cx: &NodeFactory<'a>) -> VNode<'a> { self } } impl<'a> IntoVNode<'a> for &VNode<'a> { - fn into_vnode(self, cx: &NodeCtx<'a>) -> VNode<'a> { + fn into_vnode(self, cx: &NodeFactory<'a>) -> VNode<'a> { // cloning is cheap since vnodes are just references into bump arenas self.clone() } } pub trait IntoVNode<'a> { - fn into_vnode(self, cx: &NodeCtx<'a>) -> VNode<'a>; + fn into_vnode(self, cx: &NodeFactory<'a>) -> VNode<'a>; } pub trait VNodeBuilder<'a, G>: IntoIterator @@ -555,7 +555,7 @@ where } impl<'a, F> VNodeBuilder<'a, LazyNodes<'a, F>> for LazyNodes<'a, F> where - F: for<'b> FnOnce(&'b NodeCtx<'a>) -> VNode<'a> + 'a + F: for<'b> FnOnce(&'b NodeFactory<'a>) -> VNode<'a> + 'a { } @@ -564,7 +564,7 @@ impl<'a, F> VNodeBuilder<'a, LazyNodes<'a, F>> for LazyNodes<'a, F> where // This is a bit of a hack to implement the IntoVNode trait for closure types. pub struct LazyNodes<'a, G> where - G: for<'b> FnOnce(&'b NodeCtx<'a>) -> VNode<'a> + 'a, + G: for<'b> FnOnce(&'b NodeFactory<'a>) -> VNode<'a> + 'a, { inner: G, _p: std::marker::PhantomData<&'a ()>, @@ -572,7 +572,7 @@ where impl<'a, G> LazyNodes<'a, G> where - G: for<'b> FnOnce(&'b NodeCtx<'a>) -> VNode<'a> + 'a, + G: for<'b> FnOnce(&'b NodeFactory<'a>) -> VNode<'a> + 'a, { pub fn new(f: G) -> Self { Self { @@ -589,9 +589,9 @@ where // rsx! { {nodes } } impl<'a, G> IntoVNode<'a> for LazyNodes<'a, G> where - G: for<'b> FnOnce(&'b NodeCtx<'a>) -> VNode<'a> + 'a, + G: for<'b> FnOnce(&'b NodeFactory<'a>) -> VNode<'a> + 'a, { - fn into_vnode(self, cx: &NodeCtx<'a>) -> VNode<'a> { + fn into_vnode(self, cx: &NodeFactory<'a>) -> VNode<'a> { (self.inner)(cx) } } @@ -599,7 +599,7 @@ where // Required because anything that enters brackets in the rsx! macro needs to implement IntoIterator impl<'a, G> IntoIterator for LazyNodes<'a, G> where - G: for<'b> FnOnce(&'b NodeCtx<'a>) -> VNode<'a> + 'a, + G: for<'b> FnOnce(&'b NodeFactory<'a>) -> VNode<'a> + 'a, { type Item = Self; type IntoIter = std::iter::Once; @@ -609,7 +609,7 @@ where } impl<'a> IntoVNode<'a> for () { - fn into_vnode(self, cx: &NodeCtx<'a>) -> VNode<'a> { + fn into_vnode(self, cx: &NodeFactory<'a>) -> VNode<'a> { todo!(); VNode::Suspended { real: Cell::new(RealDomNode::empty()), @@ -618,7 +618,7 @@ impl<'a> IntoVNode<'a> for () { } impl<'a> IntoVNode<'a> for Option<()> { - fn into_vnode(self, cx: &NodeCtx<'a>) -> VNode<'a> { + fn into_vnode(self, cx: &NodeFactory<'a>) -> VNode<'a> { todo!(); VNode::Suspended { real: Cell::new(RealDomNode::empty()), @@ -684,7 +684,7 @@ pub fn attr<'a>(name: &'static str, value: &'a str) -> Attribute<'a> { } pub fn virtual_child<'a, T: Properties + 'a>( - cx: &NodeCtx<'a>, + cx: &NodeFactory<'a>, f: FC, props: T, key: Option<&'a str>, // key: NodeKey<'a>, @@ -702,7 +702,7 @@ pub fn virtual_child<'a, T: Properties + 'a>( } pub fn vfragment<'a>( - cx: &NodeCtx<'a>, + cx: &NodeFactory<'a>, key: Option<&'a str>, // key: NodeKey<'a>, children: &'a [VNode<'a>], ) -> VNode<'a> { @@ -710,12 +710,12 @@ pub fn vfragment<'a>( } pub struct ChildrenList<'a, 'b> { - cx: &'b NodeCtx<'a>, + cx: &'b NodeFactory<'a>, children: bumpalo::collections::Vec<'a, VNode<'a>>, } impl<'a, 'b> ChildrenList<'a, 'b> { - pub fn new(cx: &'b NodeCtx<'a>) -> Self { + pub fn new(cx: &'b NodeFactory<'a>) -> Self { Self { cx, children: bumpalo::collections::Vec::new_in(cx.bump()), @@ -735,16 +735,16 @@ impl<'a, 'b> ChildrenList<'a, 'b> { } } -// NodeCtx is used to build VNodes in the component's memory space. +// NodeFactory is used to build VNodes in the component's memory space. // This struct adds metadata to the final VNode about listeners, attributes, and children #[derive(Clone)] -pub struct NodeCtx<'a> { +pub struct NodeFactory<'a> { pub scope_ref: &'a Scope, pub listener_id: Cell, // pub listener_id: RefCell, } -impl<'a> NodeCtx<'a> { +impl<'a> NodeFactory<'a> { #[inline] pub fn bump(&self) -> &'a bumpalo::Bump { &self.scope_ref.cur_frame().bump @@ -771,7 +771,7 @@ impl<'a> NodeCtx<'a> { } use std::fmt::Debug; -impl Debug for NodeCtx<'_> { +impl Debug for NodeFactory<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { Ok(()) } diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 239f0422..411fa8a1 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -7,7 +7,7 @@ use crate::{ arena::ScopeArena, events::VirtualEvent, innerlude::{Context, Properties, Scope, ScopeIdx, FC}, - nodebuilder::{text3, NodeCtx}, + nodebuilder::{text3, NodeFactory}, virtual_dom::RealDomNode, }; use bumpalo::Bump; @@ -336,7 +336,7 @@ impl<'a> VComponent<'a> { /// /// If the CanMemo is `false`, then the macro will call the backup method which always defaults to "false" pub fn new( - cx: &NodeCtx<'a>, + cx: &NodeFactory<'a>, component: FC

, props: P, key: Option<&'a str>, diff --git a/packages/core/src/signals.rs b/packages/core/src/signals.rs new file mode 100644 index 00000000..de846de3 --- /dev/null +++ b/packages/core/src/signals.rs @@ -0,0 +1 @@ +//! Support for jumping out of the diff engine diff --git a/packages/core/src/util.rs b/packages/core/src/util.rs index d5c10a0c..73e75e7b 100644 --- a/packages/core/src/util.rs +++ b/packages/core/src/util.rs @@ -42,7 +42,6 @@ impl<'a> RealDom<'a> for DebugDom { realnode: RealDomNode, ) { } - fn remove_event_listener(&mut self, event: &str) {} fn set_text(&mut self, text: &str) {} diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 43d3b733..9241c99a 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -13,7 +13,7 @@ //! - The [`Scope`] object for mangning component lifecycle //! - The [`ActiveFrame`] object for managing the Scope`s microheap //! - The [`Context`] object for exposing VirtualDOM API to components -//! - The [`NodeCtx`] object for lazyily exposing the `Context` API to the nodebuilder API +//! - The [`NodeFactory`] object for lazyily exposing the `Context` API to the nodebuilder API //! - The [`Hook`] object for exposing state management in components. //! //! This module includes just the barebones for a complete VirtualDOM API. @@ -677,11 +677,11 @@ pub trait Scoped<'src>: Sized { /// cx.render(lazy_tree) /// } ///``` - fn render<'a, F: for<'b> FnOnce(&'b NodeCtx<'src>) -> VNode<'src> + 'src + 'a>( + fn render<'a, F: for<'b> FnOnce(&'b NodeFactory<'src>) -> VNode<'src> + 'src + 'a>( self, lazy_nodes: LazyNodes<'src, F>, ) -> VNode<'src> { - lazy_nodes.into_vnode(&NodeCtx { + lazy_nodes.into_vnode(&NodeFactory { scope_ref: self.get_scope(), listener_id: 0.into(), }) @@ -883,7 +883,7 @@ Any function prefixed with "use" should not be called conditionally. pub struct SuspendedContext {} impl SuspendedContext { - pub fn render<'a, 'src, F: for<'b> FnOnce(&'b NodeCtx<'src>) -> VNode<'src> + 'src + 'a>( + pub fn render<'a, 'src, F: for<'b> FnOnce(&'b NodeFactory<'src>) -> VNode<'src> + 'src + 'a>( self, lazy_nodes: LazyNodes<'src, F>, ) -> VNode<'src> { diff --git a/packages/html-namespace/examples/poc.rs b/packages/html-namespace/examples/poc.rs index acd882e1..501fe318 100644 --- a/packages/html-namespace/examples/poc.rs +++ b/packages/html-namespace/examples/poc.rs @@ -8,11 +8,11 @@ //! //! -struct NodeCtx {} +struct NodeFactory {} -struct div<'a>(&NodeCtx); +struct div<'a>(&NodeFactory); impl<'a> div<'a> { - fn new(cx: &NodeCtx) -> Self { + fn new(cx: &NodeFactory) -> Self { div(cx) } } @@ -21,7 +21,7 @@ fn main() {} fn factory( // this is your mom - cx: &NodeCtx, + cx: &NodeFactory, ) { div::new(cx); rsx! { diff --git a/packages/web/examples/anim.rs b/packages/web/examples/anim.rs new file mode 100644 index 00000000..2f9f3f8e --- /dev/null +++ b/packages/web/examples/anim.rs @@ -0,0 +1,53 @@ +fn main() { + // render the + let transition = move |cx, (width, height)| {}; + + cx.render(rsx! { + div { + Transition { + start: (0, 5), + stop: (10, 10), + render: transition + } + + Transition { + start: (0, 5), + stop: (10, 10), + render: move |cx, (width, height)| { + // + cx.render(rsx!{ + div { + style { + width: width, + width: height + } + } + }) + } + } + } + }) +} + +// use signals to directly update values outside of the diffing phase +fn signal_based(cx: ()) { + const InitPos: (i32, i32) = (0, 0); + const EndPos: (i32, i32) = (100, 200); + + let spring = use_spring(cx, move |spring| spring.from(InitPos).to(EndPos)); + + cx.render(rsx! { + div { + style { + width: spring.0, + width: spring.1 + } + button { "Reset" + onclick: move |_| spring.set(InitPos) + } + button { "Animate" + onclick: move |_| spring.set(EndPos) + } + } + }) +}