diff --git a/.vscode/spellright.dict b/.vscode/spellright.dict
index 8f1809d0..4d0fbe79 100644
--- a/.vscode/spellright.dict
+++ b/.vscode/spellright.dict
@@ -62,3 +62,4 @@ attr
derefed
Tokio
asynchronicity
+constified
diff --git a/README.md b/README.md
index 28589923..651c81d3 100644
--- a/README.md
+++ b/README.md
@@ -36,6 +36,28 @@ If you know React, then you already know Dioxus.
- Powerful and simple integrated state management
- Rust! (enums, static types, modules, efficiency)
+### Unique features:
+- Incredible inline documentation - supports hover and guides for all HTML elements, listeners, and events.
+- Templates are "constified" at compile time - nodes that don't change won't be unnecessarily diffed.
+- Rust's smart pointers make use_state extremely easy and fun to work with.
+- Starting a new app takes zero templates or special tools - get a new app running in just seconds.
+- Desktop apps running natively (no Electron!) in less than 10 lines of code.
+- Multiple flavors (raw, html, rsx) of templating to pick-and-choose for the type of component.
+- Extensible DSL through traits and impls plus support for WebComponents.
+- Simple-to-implement trait for new reconcilers.
+- Fast retained-mode server-side-renderer that works with buffered writers, files, and strings.
+- Compile-time correct inline CSS as well as global styling.
+- Integrated "signal" system allows for near-instant updates by skipping the diff algorithm entirely.
+- Built-in cooperative scheduler (IE React's fiber mechanism).
+- Built-in asynchronous scheduler for coroutines and tasks within components.
+- Built-in suspense scheduler for Rust's futures.
+- Custom bump-allocator backing for all components. Nearly 0 allocations for steady-state components.
+- Extremely fast diffing algorithm for the most complex of apps.
+- Runs natively on mobile and on desktop with no need for a 3rd party JS engine.
+- Drastically fewer runtime errors and crashes with first-class error handling.
+- Extremely powerful iterator and optional chaining integration for fast and robust apps.
+- The most ergonomic and powerful state management of any Rust UI toolkit.
+
## Get Started with...
diff --git a/examples/testbed.rs b/examples/testbed.rs
index 97c6b158..1d03ac7b 100644
--- a/examples/testbed.rs
+++ b/examples/testbed.rs
@@ -1,4 +1,10 @@
+use std::cell::Cell;
+
use dioxus::prelude::*;
+use dioxus_core::{
+ nodes::{NodeKey, VElement, VText},
+ RealDomNode,
+};
fn main() {
env_logger::init();
@@ -13,8 +19,18 @@ p {color: red;}
const Example: FC<()> = |cx| {
cx.render(rsx! {
- Child { }
- Child { }
+ Fragment {
+ Fragment {
+ Fragment {
+ "h1"
+ }
+ "h2"
+ }
+ "h3"
+ }
+ "h4"
+ div { "h5" }
+ Child {}
})
};
@@ -26,3 +42,22 @@ const Child: FC<()> = |cx| {
h1 {"4" }
))
};
+
+// this is a bad case that hurts our subtree memoization :(
+const AbTest: FC<()> = |cx| {
+ if 1 == 2 {
+ cx.render(rsx!(
+ h1 {"1"}
+ h1 {"2"}
+ h1 {"3"}
+ h1 {"4"}
+ ))
+ } else {
+ cx.render(rsx!(
+ h1 {"1"}
+ h1 {"2"}
+ h2 {"basd"}
+ h1 {"4"}
+ ))
+ }
+};
diff --git a/notes/ARCHITECTURE.md b/notes/ARCHITECTURE.md
index 6a81927d..46727de5 100644
--- a/notes/ARCHITECTURE.md
+++ b/notes/ARCHITECTURE.md
@@ -45,3 +45,8 @@ div {
h4 { "{sig3}" }
}
```
+
+
+## Subtree memoization
+
+The rsx! macro needs to be *really* smart. If it detects that no dynamics are pumped into the macro, then it opts to use the "const" flavors of the element build functions we know and love. This has to be done at build time rather than runtime since components may return basically anything. Using the const flavor enables is_static which encourages Dioxus to do a ptr compare instead of a value compare to short circuit through the diffing. Due to const folding in Rust, entire subtrees can be ruled out at compile time.
diff --git a/packages/core-macro/src/rsx/element.rs b/packages/core-macro/src/rsx/element.rs
index 65ba6ce2..a08fa129 100644
--- a/packages/core-macro/src/rsx/element.rs
+++ b/packages/core-macro/src/rsx/element.rs
@@ -17,6 +17,7 @@ pub struct Element {
attributes: Vec,
listeners: Vec,
children: Vec,
+ is_static: bool,
}
impl ToTokens for Element {
@@ -100,6 +101,7 @@ impl Parse for Element {
attributes,
children,
listeners,
+ is_static: false,
})
}
}
diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs
index 7bb5ea69..ee39f62b 100644
--- a/packages/core/src/diff.rs
+++ b/packages/core/src/diff.rs
@@ -66,7 +66,7 @@ use std::any::Any;
/// target-specific Node type as well as easily serializing the edits to be sent over a network or IPC connection.
pub trait RealDom<'a> {
// Navigation
- fn push_root(&mut self, root: RealDomNode);
+ fn push(&mut self, root: RealDomNode);
fn pop(&mut self);
// Add Nodes to the dom
@@ -168,7 +168,7 @@ where
}
if old.text != new.text {
- self.dom.push_root(old.dom_id.get());
+ self.dom.push(old.dom_id.get());
log::debug!("Text has changed {}, {}", old.text, new.text);
self.dom.set_text(new.text);
self.dom.pop();
@@ -183,7 +183,7 @@ where
//
// In Dioxus, this is less likely to occur unless through a fragment
if new.tag_name != old.tag_name || new.namespace != old.namespace {
- self.dom.push_root(old.dom_id.get());
+ self.dom.push(old.dom_id.get());
let meta = self.create(new_node);
self.dom.replace_with(meta.added_to_stack);
self.dom.pop();
@@ -194,7 +194,7 @@ where
new.dom_id.set(oldid);
// push it just in case
- self.dom.push_root(oldid);
+ self.dom.push(oldid);
self.diff_listeners(old.listeners, new.listeners);
self.diff_attr(old.attributes, new.attributes, new.namespace);
self.diff_children(old.children, new.children);
@@ -210,65 +210,48 @@ where
let scope_id = old.ass_scope.get();
new.ass_scope.set(scope_id);
- // new.mounted_root.set(old.mounted_root.get());
-
// make sure the component's caller function is up to date
let scope = self.components.try_get_mut(scope_id.unwrap()).unwrap();
- // .with_scope(scope_id.unwrap(), |scope| {
+
scope.caller = new.caller.clone();
// ack - this doesn't work on its own!
scope.update_children(new.children);
- // })
- // .unwrap();
-
// React doesn't automatically memoize, but we do.
- // The cost is low enough to make it worth checking
- // Rust produces fairly performant comparison methods, sometimes SIMD accelerated
- // let should_render = match old.comparator {
- // Some(comparator) => comparator(new),
- // None => true,
- // };
+ let should_render = match old.comparator {
+ Some(comparator) => comparator(new),
+ None => true,
+ };
+
+ if should_render {
+ scope.run_scope().unwrap();
+ self.diff_node(scope.old_frame(), scope.next_frame());
+ } else {
+ //
+ }
- // if should_render {
- // // self.dom.commit_traversal();
- // let f = self.components.try_get_mut(scope_id.unwrap()).unwrap();
- // self.components
- // .with_scope(scope_id.unwrap(), |f| {
- // log::debug!("running scope during diff {:#?}", scope_id);
- scope.run_scope().unwrap();
- self.diff_node(scope.old_frame(), scope.next_frame());
- // log::debug!("scope completed {:#?}", scope_id);
self.seen_nodes.insert(scope_id.unwrap());
- // })
- // .unwrap();
-
- // diff_machine.change_list.load_known_root(root_id);
- // run the scope
- //
- // } else {
- // log::error!("Memoized componented");
- // // Component has memoized itself and doesn't need to be re-rendered.
- // // We still need to make sure the child's props are up-to-date.
- // // Don't commit traversal
- // }
} else {
- // It's an entirely different component
+ // this seems to be a fairy common code path that we could
+ let mut old_iter = RealChildIterator::new(old_node, &self.components);
+ let first = old_iter
+ .next()
+ .expect("Components should generate a placeholder root");
- // A new component has shown up! We need to destroy the old node
+ // remove any leftovers
+ for to_remove in old_iter {
+ self.dom.push(to_remove);
+ self.dom.remove();
+ }
+
+ // seems like we could combine this into a single instruction....
+ self.dom.push(first);
+ let meta = self.create(new_node);
+ self.dom.replace_with(meta.added_to_stack);
+ self.dom.pop();
// Wipe the old one and plant the new one
- // self.dom.commit_traversal();
- // self.dom.replace_node_with(old.dom_id, new.dom_id);
- // self.create(new_node);
- log::warn!("creating and replacing...");
- self.create(new_node);
-
- // self.dom.replace_with();
- // self.create_and_repalce(new_node, old.mounted_root.get());
-
- // Now we need to remove the old scope and all of its descendents
let old_scope = old.ass_scope.get().unwrap();
self.destroy_scopes(old_scope);
}
@@ -278,7 +261,7 @@ where
// This is the case where options or direct vnodes might be used.
// In this case, it's faster to just skip ahead to their diff
if old.children.len() == 1 && new.children.len() == 1 {
- self.diff_node(old.children.get(0).unwrap(), new.children.get(0).unwrap());
+ self.diff_node(&old.children[0], &new.children[0]);
return;
}
@@ -300,6 +283,7 @@ where
) => {
// Choose the node to use as the placeholder for replacewith
let back_node = match old_node {
+ // We special case these two types to avoid allocating the small-vecs
VNode::Element(_) | VNode::Text(_) => old_node
.get_mounted_id(&self.components)
.expect("Element and text always have a real node"),
@@ -313,7 +297,7 @@ where
// remove any leftovers
for to_remove in old_iter {
- self.dom.push_root(to_remove);
+ self.dom.push(to_remove);
self.dom.remove();
}
@@ -322,9 +306,11 @@ where
};
// replace the placeholder or first node with the nodes generated from the "new"
- self.dom.push_root(back_node);
+ self.dom.push(back_node);
let meta = self.create(new_node);
self.dom.replace_with(meta.added_to_stack);
+
+ // todo use the is_static metadata to update this subtree
}
// TODO
@@ -1317,9 +1303,10 @@ enum KeyedPrefixResult {
struct RealChildIterator<'a> {
scopes: &'a SharedArena,
- // Heuristcally we should never bleed into 5 completely nested fragments/components
+ // Heuristcally we should never bleed into 4 completely nested fragments/components
// Smallvec lets us stack allocate our little stack machine so the vast majority of cases are sane
- stack: smallvec::SmallVec<[(u16, &'a VNode<'a>); 5]>,
+ // TODO: use const generics instead of the 4 estimation
+ stack: smallvec::SmallVec<[(u16, &'a VNode<'a>); 4]>,
}
impl<'a> RealChildIterator<'a> {
diff --git a/packages/core/src/serialize.rs b/packages/core/src/serialize.rs
index e28c6ae2..5233d1cb 100644
--- a/packages/core/src/serialize.rs
+++ b/packages/core/src/serialize.rs
@@ -18,6 +18,7 @@ pub enum DomEdit<'bump> {
PushRoot {
root: u64,
},
+ PopRoot,
AppendChildren {
many: u32,
},
diff --git a/packages/core/src/util.rs b/packages/core/src/util.rs
index 6be46c47..082483d6 100644
--- a/packages/core/src/util.rs
+++ b/packages/core/src/util.rs
@@ -62,7 +62,7 @@ impl RealDomNode {
pub fn new(id: u64) -> Self {
Self(id)
}
- pub fn empty() -> Self {
+ pub const fn empty() -> Self {
Self(u64::MIN)
}
}
@@ -86,7 +86,7 @@ impl DebugDom {
}
}
impl<'a> RealDom<'a> for DebugDom {
- fn push_root(&mut self, root: RealDomNode) {}
+ fn push(&mut self, root: RealDomNode) {}
fn pop(&mut self) {}
fn append_children(&mut self, many: u32) {}
diff --git a/packages/web/src/new.rs b/packages/web/src/new.rs
index 394431b1..1c181e7e 100644
--- a/packages/web/src/new.rs
+++ b/packages/web/src/new.rs
@@ -71,32 +71,46 @@ impl WebsysDom {
}
impl<'a> dioxus_core::diff::RealDom<'a> for WebsysDom {
- fn push_root(&mut self, root: RealDomNode) {
+ fn push(&mut self, root: RealDomNode) {
log::debug!("Called [push_root] {:?}", root);
let key: DefaultKey = KeyData::from_ffi(root.0).into();
let domnode = self.nodes.get(key).expect("Failed to pop know root");
self.stack.push(domnode.clone());
}
+ // drop the node off the stack
+ fn pop(&mut self) {
+ self.stack.pop();
+ }
fn append_children(&mut self, many: u32) {
log::debug!("Called [`append_child`]");
- let child = self.stack.pop();
- if child.dyn_ref::().is_some() {
- if self.last_node_was_text {
- let comment_node = self
- .document
- .create_comment("dioxus")
- .dyn_into::()
- .unwrap();
- self.stack.top().append_child(&comment_node).unwrap();
+ let mut root: Node = self
+ .stack
+ .list
+ .get(self.stack.list.len() - many as usize)
+ .unwrap()
+ .clone();
+
+ for _ in 0..many {
+ let child = self.stack.pop();
+
+ if child.dyn_ref::().is_some() {
+ if self.last_node_was_text {
+ let comment_node = self
+ .document
+ .create_comment("dioxus")
+ .dyn_into::()
+ .unwrap();
+ self.stack.top().append_child(&comment_node).unwrap();
+ }
+ self.last_node_was_text = true;
+ } else {
+ self.last_node_was_text = false;
}
- self.last_node_was_text = true;
- } else {
- self.last_node_was_text = false;
- }
- self.stack.top().append_child(&child).unwrap();
+ root.append_child(&child).unwrap();
+ }
}
fn replace_with(&mut self, many: u32) {
@@ -104,35 +118,31 @@ impl<'a> dioxus_core::diff::RealDom<'a> for WebsysDom {
let new_node = self.stack.pop();
let old_node = self.stack.pop();
- if old_node.has_type::() {
- old_node
- .dyn_ref::()
- .unwrap()
- .replace_with_with_node_1(&new_node)
- .unwrap();
- } else if old_node.has_type::() {
- old_node
- .dyn_ref::()
- .unwrap()
- .replace_with_with_node_1(&new_node)
- .unwrap();
- } else if old_node.has_type::() {
- old_node
- .dyn_ref::()
- .unwrap()
- .replace_with_with_node_1(&new_node)
- .unwrap();
- } else {
- panic!("Cannot replace node: {:?}", old_node);
+ // TODO: use different-sized replace withs
+ if many == 1 {
+ if old_node.has_type::() {
+ old_node
+ .dyn_ref::()
+ .unwrap()
+ .replace_with_with_node_1(&new_node)
+ .unwrap();
+ } else if old_node.has_type::() {
+ old_node
+ .dyn_ref::()
+ .unwrap()
+ .replace_with_with_node_1(&new_node)
+ .unwrap();
+ } else if old_node.has_type::() {
+ old_node
+ .dyn_ref::()
+ .unwrap()
+ .replace_with_with_node_1(&new_node)
+ .unwrap();
+ } else {
+ panic!("Cannot replace node: {:?}", old_node);
+ }
}
- // // poc to see if this is a valid solution
- // if let Some(id) = self.current_known {
- // // update mapping
- // self.known_roots.insert(id, new_node.clone());
- // self.current_known = None;
- // }
-
self.stack.push(new_node);
}
@@ -303,7 +313,7 @@ impl<'a> dioxus_core::diff::RealDom<'a> for WebsysDom {
#[derive(Debug, Default)]
pub struct Stack {
- list: Vec,
+ pub list: Vec,
}
impl Stack {
diff --git a/packages/webview/src/dom.rs b/packages/webview/src/dom.rs
index 79f7deae..7079eca3 100644
--- a/packages/webview/src/dom.rs
+++ b/packages/webview/src/dom.rs
@@ -31,9 +31,12 @@ impl WebviewDom<'_> {
}
}
impl<'bump> RealDom<'bump> for WebviewDom<'bump> {
- fn push_root(&mut self, root: RealDomNode) {
+ fn push(&mut self, root: RealDomNode) {
self.edits.push(PushRoot { root: root.0 });
}
+ fn pop(&mut self) {
+ self.edits.push(PopRoot {});
+ }
fn append_children(&mut self, many: u32) {
self.edits.push(AppendChildren { many });