feat: enable children properly

This commit is contained in:
Jonathan Kelley committed 2021-12-21 00:46:10 -05:00
1 parent dbb981d500
commit b997b8ebbb
10 files changed
+128 -131

No files matched your search

+1 -1
View File
@@ -17,7 +17,7 @@ pub fn format_args_f(input: TokenStream) -> TokenStream {
.into()
}
#[proc_macro_derive(Props, attributes(builder))]
#[proc_macro_derive(Props, attributes(props))]
pub fn derive_typed_builder(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input = parse_macro_input!(input as syn::DeriveInput);
match props::impl_my_derive(&input) {
+19 -5
View File
@@ -3,8 +3,8 @@
//! However, it has been adopted to fit the Dioxus Props builder pattern.
//!
//! For dioxus, we make a few changes:
//! - automatically implement Into<Option> on the setters (IE the strip setter option)
//! - automatically implement a default of none for optional fields (those explicitly wrapped with Option<T>)
//! - [ ] automatically implement Into<Option> on the setters (IE the strip setter option)
//! - [ ] automatically implement a default of none for optional fields (those explicitly wrapped with Option<T>)
use proc_macro2::TokenStream;
@@ -194,6 +194,14 @@ mod field_info {
field_defaults: FieldBuilderAttr,
) -> Result<FieldInfo, Error> {
if let Some(ref name) = field.ident {
let mut builder_attr = field_defaults.with(&field.attrs)?;
// children field is automatically defaulted to None
if name == "children" {
builder_attr.default =
Some(syn::parse(quote!(Default::default()).into()).unwrap());
}
Ok(FieldInfo {
ordinal,
name,
@@ -202,7 +210,7 @@ mod field_info {
proc_macro2::Span::call_site(),
),
ty: &field.ty,
builder_attr: field_defaults.with(&field.attrs)?,
builder_attr,
})
} else {
Err(Error::new(field.span(), "Nameless field in struct"))
@@ -285,7 +293,7 @@ mod field_info {
pub fn with(mut self, attrs: &[syn::Attribute]) -> Result<Self, Error> {
let mut skip_tokens = None;
for attr in attrs {
if path_to_single_string(&attr.path).as_deref() != Some("builder") {
if path_to_single_string(&attr.path).as_deref() != Some("props") {
continue;
}
@@ -316,7 +324,7 @@ mod field_info {
if self.setter.skip && self.default.is_none() {
return Err(Error::new_spanned(
skip_tokens.unwrap(),
"#[builder(skip)] must be accompanied by default or default_code",
"#[props(skip)] must be accompanied by default or default_code",
));
}
@@ -325,6 +333,7 @@ mod field_info {
pub fn apply_meta(&mut self, expr: syn::Expr) -> Result<(), Error> {
match expr {
// #[props(default = "...")]
syn::Expr::Assign(assign) => {
let name = expr_to_single_string(&assign.left)
.ok_or_else(|| Error::new_spanned(&assign.left, "Expected identifier"))?;
@@ -356,6 +365,8 @@ mod field_info {
)),
}
}
// uh not sure
syn::Expr::Path(path) => {
let name = path_to_single_string(&path.path)
.ok_or_else(|| Error::new_spanned(&path, "Expected identifier"))?;
@@ -371,6 +382,8 @@ mod field_info {
)),
}
}
//
syn::Expr::Call(call) => {
let subsetting_name = if let syn::Expr::Path(path) = &*call.func {
path_to_single_string(&path.path)
@@ -398,6 +411,7 @@ mod field_info {
)),
}
}
syn::Expr::Unary(syn::ExprUnary {
op: syn::UnOp::Not(_),
expr,
@@ -0,0 +1,40 @@
#![allow(non_snake_case)]
use dioxus::prelude::*;
use dioxus_core as dioxus;
use dioxus_core_macro::*;
use dioxus_html as dioxus_elements;
fn main() {
let mut dom = VirtualDom::new(parent);
let edits = dom.rebuild();
dbg!(edits);
}
fn parent(cx: Scope<()>) -> Element {
let value = cx.use_hook(|_| String::new(), |f| f);
cx.render(rsx! {
div {
child(
name: value,
h1 {"hi"}
)
}
})
}
#[derive(Props)]
struct ChildProps<'a> {
name: &'a str,
children: Element<'a>,
}
fn child<'a>(cx: Scope<'a, ChildProps<'a>>) -> Element {
cx.render(rsx! {
div {
"it's nested {cx.props.name}"
{&cx.props.children}
}
})
}
+23 -25
View File
@@ -88,8 +88,6 @@
//! More info on how to improve this diffing algorithm:
//! - https://hacks.mozilla.org/2019/03/fast-bump-allocated-virtual-doms-with-rust-and-wasm/
use std::borrow::BorrowMut;
use crate::innerlude::*;
use fxhash::{FxHashMap, FxHashSet};
use smallvec::{smallvec, SmallVec};
@@ -295,7 +293,7 @@ impl<'bump> DiffState<'bump> {
for child in el.children.iter() {
num_on_stack += self.push_all_nodes(child);
}
self.mutations.push_root(el.dom_id.get().unwrap());
self.mutations.push_root(el.id.get().unwrap());
num_on_stack + 1
}
@@ -351,7 +349,7 @@ impl<'bump> DiffState<'bump> {
let real_id = self.scopes.reserve_node(node);
self.mutations.create_text_node(vtext.text, real_id);
vtext.dom_id.set(Some(real_id));
vtext.id.set(Some(real_id));
self.stack.add_child_count(1);
}
@@ -359,20 +357,20 @@ impl<'bump> DiffState<'bump> {
let real_id = self.scopes.reserve_node(node);
self.mutations.create_placeholder(real_id);
anchor.dom_id.set(Some(real_id));
anchor.id.set(Some(real_id));
self.stack.add_child_count(1);
}
fn create_element_node(&mut self, element: &'bump VElement<'bump>, node: &'bump VNode<'bump>) {
let VElement {
tag_name,
tag: tag_name,
listeners,
attributes,
children,
namespace,
dom_id,
parent_id,
id: dom_id,
parent: parent_id,
..
} = element;
@@ -479,9 +477,9 @@ impl<'bump> DiffState<'bump> {
}
(Element(old), Element(new)) => self.diff_element_nodes(old, new, old_node, new_node),
(Placeholder(old), Placeholder(new)) => {
if let Some(root) = old.dom_id.get() {
if let Some(root) = old.id.get() {
self.scopes.update_node(new_node, root);
new.dom_id.set(Some(root))
new.id.set(Some(root))
}
}
@@ -515,13 +513,13 @@ impl<'bump> DiffState<'bump> {
_old_node: &'bump VNode<'bump>,
new_node: &'bump VNode<'bump>,
) {
if let Some(root) = old.dom_id.get() {
if let Some(root) = old.id.get() {
if old.text != new.text {
self.mutations.set_text(new.text, root.as_u64());
}
self.scopes.update_node(new_node, root);
new.dom_id.set(Some(root));
new.id.set(Some(root));
}
}
@@ -532,13 +530,13 @@ impl<'bump> DiffState<'bump> {
old_node: &'bump VNode<'bump>,
new_node: &'bump VNode<'bump>,
) {
let root = old.dom_id.get().unwrap();
let root = old.id.get().unwrap();
// If the element type is completely different, the element needs to be re-rendered completely
// This is an optimization React makes due to how users structure their code
//
// This case is rather rare (typically only in non-keyed lists)
if new.tag_name != old.tag_name || new.namespace != old.namespace {
if new.tag != old.tag || new.namespace != old.namespace {
// maybe make this an instruction?
// issue is that we need the "vnode" but this method only has the velement
self.stack.push_nodes_created(0);
@@ -551,8 +549,8 @@ impl<'bump> DiffState<'bump> {
self.scopes.update_node(new_node, root);
new.dom_id.set(Some(root));
new.parent_id.set(old.parent_id.get());
new.id.set(Some(root));
new.parent.set(old.parent.get());
// todo: attributes currently rely on the element on top of the stack, but in theory, we only need the id of the
// element to modify its attributes.
@@ -1182,9 +1180,9 @@ impl<'bump> DiffState<'bump> {
loop {
match &search_node.take().unwrap() {
VNode::Text(t) => break t.dom_id.get(),
VNode::Element(t) => break t.dom_id.get(),
VNode::Placeholder(t) => break t.dom_id.get(),
VNode::Text(t) => break t.id.get(),
VNode::Element(t) => break t.id.get(),
VNode::Placeholder(t) => break t.id.get(),
VNode::Fragment(frag) => {
search_node = frag.children.last();
}
@@ -1209,9 +1207,9 @@ impl<'bump> DiffState<'bump> {
let scope_id = el.scope.get().unwrap();
search_node = Some(self.scopes.root_node(scope_id));
}
VNode::Text(t) => break t.dom_id.get(),
VNode::Element(t) => break t.dom_id.get(),
VNode::Placeholder(t) => break t.dom_id.get(),
VNode::Text(t) => break t.id.get(),
VNode::Element(t) => break t.id.get(),
VNode::Placeholder(t) => break t.id.get(),
}
}
}
@@ -1263,7 +1261,7 @@ impl<'bump> DiffState<'bump> {
match node {
VNode::Text(t) => {
// this check exists because our null node will be removed but does not have an ID
if let Some(id) = t.dom_id.get() {
if let Some(id) = t.id.get() {
self.scopes.collect_garbage(id);
if gen_muts {
@@ -1272,7 +1270,7 @@ impl<'bump> DiffState<'bump> {
}
}
VNode::Placeholder(a) => {
let id = a.dom_id.get().unwrap();
let id = a.id.get().unwrap();
self.scopes.collect_garbage(id);
if gen_muts {
@@ -1280,7 +1278,7 @@ impl<'bump> DiffState<'bump> {
}
}
VNode::Element(e) => {
let id = e.dom_id.get().unwrap();
let id = e.id.get().unwrap();
if gen_muts {
self.mutations.remove(id.as_u64());
-1
View File
@@ -16,7 +16,6 @@ pub(crate) mod innerlude {
pub use crate::mutations::*;
pub use crate::nodes::*;
pub use crate::scopes::*;
pub(crate) use crate::scopes::*;
pub use crate::virtual_dom::*;
/// An [`Element`] is a possibly-none [`VNode`] created by calling `render` on [`Scope`] or [`ScopeState`].
+27 -80
View File
@@ -141,9 +141,9 @@ impl<'src> VNode<'src> {
/// Returns None if the VNode is not mounted, or if the VNode cannot be presented by a mounted ID (Fragment/Component)
pub fn try_mounted_id(&self) -> Option<ElementId> {
match &self {
VNode::Text(el) => el.dom_id.get(),
VNode::Element(el) => el.dom_id.get(),
VNode::Placeholder(el) => el.dom_id.get(),
VNode::Text(el) => el.id.get(),
VNode::Element(el) => el.id.get(),
VNode::Placeholder(el) => el.id.get(),
VNode::Fragment(_) => None,
VNode::Component(_) => None,
}
@@ -176,7 +176,7 @@ impl Debug for VNode<'_> {
match &self {
VNode::Element(el) => s
.debug_struct("VNode::VElement")
.field("name", &el.tag_name)
.field("name", &el.tag)
.field("key", &el.key)
.field("attrs", &el.attributes)
.field("children", &el.children)
@@ -187,7 +187,6 @@ impl Debug for VNode<'_> {
write!(s, "VNode::VFragment {{ children: {:?} }}", frag.children)
}
VNode::Component(comp) => write!(s, "VNode::VComponent {{ fc: {:?}}}", comp.user_fc),
// VNode::Component(comp) => write!(s, "VNode::VComponent {{ fc: {:?}}}", comp.user_fc),
}
}
}
@@ -216,13 +215,13 @@ fn empty_cell() -> Cell<Option<ElementId>> {
/// A placeholder node only generated when Fragments don't have any children.
pub struct VPlaceholder {
pub dom_id: Cell<Option<ElementId>>,
pub id: Cell<Option<ElementId>>,
}
/// A bump-allocated string slice and metadata.
pub struct VText<'src> {
pub text: &'src str,
pub dom_id: Cell<Option<ElementId>>,
pub id: Cell<Option<ElementId>>,
pub is_static: bool,
}
@@ -238,11 +237,11 @@ pub struct VFragment<'src> {
/// An element like a "div" with children, listeners, and attributes.
pub struct VElement<'a> {
pub tag_name: &'static str,
pub tag: &'static str,
pub namespace: Option<&'static str>,
pub key: Option<&'a str>,
pub dom_id: Cell<Option<ElementId>>,
pub parent_id: Cell<Option<ElementId>>,
pub id: Cell<Option<ElementId>>,
pub parent: Cell<Option<ElementId>>,
pub listeners: &'a [Listener<'a>],
pub attributes: &'a [Attribute<'a>],
pub children: &'a [VNode<'a>],
@@ -251,11 +250,11 @@ pub struct VElement<'a> {
impl Debug for VElement<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("VElement")
.field("tag_name", &self.tag_name)
.field("tag_name", &self.tag)
.field("namespace", &self.namespace)
.field("key", &self.key)
.field("dom_id", &self.dom_id)
.field("parent_id", &self.parent_id)
.field("id", &self.id)
.field("parent", &self.parent)
.field("listeners", &self.listeners.len())
.field("attributes", &self.attributes)
.field("children", &self.children)
@@ -363,20 +362,13 @@ pub struct VComponent<'src> {
}
pub(crate) struct VComponentProps<P> {
// Props start local
// If they are static, then they can get promoted onto the heap
pub render_fn: Component<P>,
pub props: P,
pub memo: unsafe fn(&P, &P) -> bool,
pub props: P,
}
pub trait AnyProps {
fn as_ptr(&self) -> *const ();
fn release(&self);
fn replace_heap(&self, new: Box<dyn Any>);
// move the props from the bump arena onto the heap
fn promote(&self);
fn render<'a>(&'a self, bump: &'a ScopeState) -> Element<'a>;
unsafe fn memoize(&self, other: &dyn AnyProps) -> bool;
}
@@ -384,16 +376,9 @@ pub trait AnyProps {
impl<P> AnyProps for VComponentProps<P> {
fn as_ptr(&self) -> *const () {
&self.props as *const _ as *const ()
// // todo: maybe move this into an enum
// let heap_props = self.heap_props.borrow();
// if let Some(b) = heap_props.as_ref() {
// b.as_ref() as *const _ as *const ()
// } else {
// let local_props = self.local_props.borrow();
// local_props.as_ref().unwrap() as *const _ as *const ()
// }
}
// Safety:
// this will downcat the other ptr as our swallowed type!
// you *must* make this check *before* calling this method
// if your functions are not the same, then you will downcast a pointer into a different type (UB)
@@ -403,41 +388,9 @@ impl<P> AnyProps for VComponentProps<P> {
(self.memo)(real_us, real_other)
}
fn release(&self) {
panic!("don't release props anymore");
// if let Some(heap_props) = self.heap_props.borrow_mut().take() {
// dbg!("releasing heap");
// drop(heap_props)
// } else if let Some(local_props) = self.local_props.borrow_mut().take() {
// dbg!("releasing local");
// drop(local_props)
// } else {
// dbg!("trying to do a double release");
// }
}
fn promote(&self) {
panic!("don't promote props anymore");
// dbg!("promoting props");
// if let Some(props) = self.local_props.borrow_mut().take() {
// let mut heap_slot = self.heap_props.borrow_mut();
// *heap_slot = Some(Box::new(props));
// }
}
fn render<'a>(&'a self, scope: &'a ScopeState) -> Element<'a> {
let props: &P = &self.props;
let inline_props = unsafe { std::mem::transmute::<&P, &P>(props) };
(self.render_fn)(Scope {
scope,
props: inline_props,
})
}
fn replace_heap(&self, new: Box<dyn Any>) {
// let new = new.downcast::<P>().unwrap();
// self.heap_props.borrow_mut().replace(new);
let props = unsafe { std::mem::transmute::<&P, &P>(&self.props) };
(self.render_fn)(Scope { scope, props })
}
}
@@ -463,7 +416,7 @@ impl<'a> NodeFactory<'a> {
/// Directly pass in text blocks without the need to use the format_args macro.
pub fn static_text(&self, text: &'static str) -> VNode<'a> {
VNode::Text(self.bump.alloc(VText {
dom_id: empty_cell(),
id: empty_cell(),
text,
is_static: true,
}))
@@ -492,7 +445,7 @@ impl<'a> NodeFactory<'a> {
VNode::Text(self.bump.alloc(VText {
text,
is_static,
dom_id: empty_cell(),
id: empty_cell(),
}))
}
@@ -545,14 +498,14 @@ impl<'a> NodeFactory<'a> {
let key = key.map(|f| self.raw_text(f).0);
VNode::Element(self.bump.alloc(VElement {
tag_name,
tag: tag_name,
key,
namespace,
listeners,
attributes,
children,
dom_id: empty_cell(),
parent_id: empty_cell(),
id: empty_cell(),
parent: empty_cell(),
}))
}
@@ -621,9 +574,7 @@ impl<'a> NodeFactory<'a> {
}
if nodes.is_empty() {
VNode::Placeholder(self.bump.alloc(VPlaceholder {
dom_id: empty_cell(),
}))
VNode::Placeholder(self.bump.alloc(VPlaceholder { id: empty_cell() }))
} else {
VNode::Fragment(VFragment {
children: nodes.into_bump_slice(),
@@ -643,9 +594,7 @@ impl<'a> NodeFactory<'a> {
}
if nodes.is_empty() {
VNode::Placeholder(self.bump.alloc(VPlaceholder {
dom_id: empty_cell(),
}))
VNode::Placeholder(self.bump.alloc(VPlaceholder { id: empty_cell() }))
} else {
let children = nodes.into_bump_slice();
@@ -686,9 +635,9 @@ impl<'a> NodeFactory<'a> {
}
if nodes.is_empty() {
Some(VNode::Placeholder(self.bump.alloc(VPlaceholder {
dom_id: empty_cell(),
})))
Some(VNode::Placeholder(
self.bump.alloc(VPlaceholder { id: empty_cell() }),
))
} else {
let children = nodes.into_bump_slice();
@@ -766,9 +715,7 @@ impl<'a> IntoVNode<'a> for Option<LazyNodes<'a, '_>> {
fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> {
match self {
Some(lazy) => lazy.call(cx),
None => VNode::Placeholder(cx.bump.alloc(VPlaceholder {
dom_id: empty_cell(),
})),
None => VNode::Placeholder(cx.bump.alloc(VPlaceholder { id: empty_cell() })),
}
}
}
+7 -7
View File
@@ -44,11 +44,11 @@ impl ScopeArena {
// this will *never* show up in the diffing process
// todo: figure out why this is necessary. i forgot. whoops.
let el = bump.alloc(VElement {
tag_name: "root",
tag: "root",
namespace: None,
key: None,
dom_id: Cell::new(Some(ElementId(0))),
parent_id: Default::default(),
id: Cell::new(Some(ElementId(0))),
parent: Default::default(),
listeners: &[],
attributes: &[],
children: &[],
@@ -279,7 +279,7 @@ impl ScopeArena {
let node = frame
.bump
.alloc(VNode::Placeholder(frame.bump.alloc(VPlaceholder {
dom_id: Default::default(),
id: Default::default(),
})));
frame.node.set(unsafe { extend_vnode(node) });
}
@@ -310,7 +310,7 @@ impl ScopeArena {
}
}
cur_el = real_el.parent_id.get();
cur_el = real_el.parent.get();
}
}
}
@@ -869,7 +869,7 @@ impl BumpFrame {
let node = &*bump.alloc(VText {
text: "placeholdertext",
dom_id: Default::default(),
id: Default::default(),
is_static: false,
});
let node = bump.alloc(VNode::Text(unsafe { std::mem::transmute(node) }));
@@ -881,7 +881,7 @@ impl BumpFrame {
self.bump.reset();
let node = self.bump.alloc(VText {
text: "placeholdertext",
dom_id: Default::default(),
id: Default::default(),
is_static: false,
});
let node = self
+8 -9
View File
@@ -109,14 +109,14 @@ fn components_generate() {
*render_phase += 1;
cx.render(match *render_phase {
0 => rsx!("Text0"),
1 => rsx!(div {}),
2 => rsx!("Text2"),
3 => rsx!(Child {}),
4 => rsx!({ None as Option<()> }),
5 => rsx!("text 3"),
6 => rsx!({ (0..2).map(|f| rsx!("text {f}")) }),
7 => rsx!(Child {}),
1 => rsx!("Text0"),
2 => rsx!(div {}),
3 => rsx!("Text2"),
4 => rsx!(Child {}),
5 => rsx!({ None as Option<()> }),
6 => rsx!("text 3"),
7 => rsx!({ (0..2).map(|f| rsx!("text {f}")) }),
8 => rsx!(Child {}),
_ => todo!(),
})
};
@@ -220,7 +220,6 @@ fn components_generate() {
#[test]
fn component_swap() {
// simple_logger::init();
static App: Component<()> = |cx| {
let render_phase = cx.use_hook(|_| 0, |f| f);
*render_phase += 1;
+1 -1
View File
@@ -132,7 +132,7 @@ impl DesktopController {
runtime.block_on(async move {
// LocalSet::new().block_on(&runtime, async move {
let mut dom =
VirtualDom::new_with_props_and_scheduler(root, props, sender, receiver);
VirtualDom::new_with_props_and_scheduler(root, props, (sender, receiver));
let edits = dom.rebuild();
+2 -2
View File
@@ -168,7 +168,7 @@ impl<'a> TextRenderer<'a, '_> {
}
}
write!(f, "<{}", el.tag_name)?;
write!(f, "<{}", el.tag)?;
let mut inner_html = None;
let mut attr_iter = el.attributes.iter().peekable();
@@ -233,7 +233,7 @@ impl<'a> TextRenderer<'a, '_> {
}
}
write!(f, "</{}>", el.tag_name)?;
write!(f, "</{}>", el.tag)?;
if self.cfg.newline {
writeln!(f)?;
}