From 7cac4d475687f6b2fffc33aec4b2bd1027ac83cc Mon Sep 17 00:00:00 2001 From: bakape Date: Mon, 24 May 2021 00:33:25 +0300 Subject: [PATCH] Optimize vtag construction (#1867) * yew-macro: optimize VTag construction in html! macro * vtag: remove inlining suggestions * vtag: clean up doc comment --- .../yew-macro/src/html_tree/html_element.rs | 261 +++++++++--------- .../tests/html_macro/element-fail.stderr | 1 + packages/yew/Cargo.toml | 4 +- packages/yew/src/virtual_dom/vtag.rs | 49 +++- 4 files changed, 168 insertions(+), 147 deletions(-) diff --git a/packages/yew-macro/src/html_tree/html_element.rs b/packages/yew-macro/src/html_tree/html_element.rs index 1bd46e093eb..e4c2057993d 100644 --- a/packages/yew-macro/src/html_tree/html_element.rs +++ b/packages/yew-macro/src/html_tree/html_element.rs @@ -132,61 +132,73 @@ impl ToTokens for HtmlElement { // attributes with special treatment - let set_node_ref = node_ref.as_ref().map(|attr| { - let value = &attr.value; - quote! { - #vtag.node_ref = #value; - } - }); - let set_key = key.as_ref().map(|attr| { - let value = &attr.value; - quote! { - #vtag.key = ::std::option::Option::Some(::std::convert::Into::<::yew::virtual_dom::Key>::into(#value)); - } - }); - let set_value = value.as_ref().map(|attr| { - let value = &attr.value; - if attr.question_mark.is_some() { - quote_spanned! {value.span()=> - if let ::std::option::Option::Some(__yew_v) = ::std::option::Option::as_ref(&(#value)) { - #vtag.set_value(__yew_v); - }; - } - } else { - quote_spanned! {value.span()=> - #vtag.set_value(&(#value)); + let node_ref = node_ref + .as_ref() + .map(|attr| { + let value = &attr.value; + quote! { #value } + }) + .unwrap_or(quote! { ::std::default::Default::default() }); + let key = key + .as_ref() + .map(|attr| { + let value = &attr.value; + quote! { + ::std::option::Option::Some + (::std::convert::Into::<::yew::virtual_dom::Key>::into(#value)) } - } - }); - let set_kind = kind.as_ref().map(|attr| { - let value = &attr.value; - if attr.question_mark.is_some() { - let sr = stringify::stringify_option_at_runtime(value); - quote_spanned! {value.span()=> - if let ::std::option::Option::Some(__yew_v) = #sr { - #vtag.set_kind(__yew_v); - }; + }) + .unwrap_or(quote! { ::std::option::Option::None }); + let value = value + .as_ref() + .map(|attr| { + let value = &attr.value; + if attr.question_mark.is_some() { + quote_spanned! {value.span()=> + ::std::option::Option::as_ref(&(#value)) + .map(::std::string::ToString::to_string) + } + } else { + quote_spanned! {value.span()=> + ::std::option::Option::Some(::std::string::ToString::to_string(&(#value))) + } } - } else { - let sr = value.stringify(); - quote_spanned! {value.span()=> - #vtag.set_kind(#sr); + }) + .unwrap_or(quote! { ::std::option::Option::None }); + let kind = kind + .as_ref() + .map(|attr| { + let value = &attr.value; + if attr.question_mark.is_some() { + let sr = stringify::stringify_option_at_runtime(value); + quote_spanned! {value.span()=> + ::std::option::Option::map( + #sr, + |v| ::std::convert::Into::<::std::borrow::Cow<'static, str>>::into(v), + ) + } + } else { + let sr = value.stringify(); + quote_spanned! {value.span()=> + ::std::option::Option::Some + (::std::convert::Into::<::std::borrow::Cow<'static, str>>::into(#sr)) + } } - } - }); - let set_checked = checked.as_ref().map(|attr| { - let value = &attr.value; - quote_spanned! {value.span()=> - #vtag.set_checked(#value); - } - }); + }) + .unwrap_or(quote! { ::std::option::Option::None }); + let checked = checked + .as_ref() + .map(|attr| { + let value = &attr.value; + quote_spanned! {value.span()=> #value} + }) + .unwrap_or(quote! { false }); // normal attributes - let set_attributes = if attributes.is_empty() { - None - } else { - let attrs = attributes.iter().map( + let mut attributes = attributes + .iter() + .map( |Prop { label, question_mark, @@ -206,33 +218,22 @@ impl ToTokens for HtmlElement { } } }, - ); - Some(quote! { - #vtag.attributes = ::yew::virtual_dom::Attributes::Vec(::std::vec![#(#attrs),*]); - }) - }; - - let push_booleans = if booleans.is_empty() { - None - } else { - let tokens = booleans - .iter() - .map(|Prop { label, value, .. }| { - let label_str = label.to_lit_str(); - let sr = label.stringify(); - quote_spanned! {value.span()=> { - if #value { - #vtag.__macro_push_attribute(#label_str, #sr); - } else { - #vtag.__macro_push_attribute_placeholder(#label_str); - }; - }} - }) - .collect::(); - Some(tokens) - }; + ) + .collect::>(); + + attributes.extend(booleans.iter().map(|Prop { label, value, .. }| { + let label_str = label.to_lit_str(); + let sr = label.stringify(); + quote_spanned! {value.span()=> { + if #value { + ::yew::virtual_dom::PositionalAttr::new(#label_str, #sr) + } else { + ::yew::virtual_dom::PositionalAttr::new_placeholder(#label_str) + } + }} + })); - let push_classes = match classes { + match classes { Some(ClassesForm::Tuple(classes)) => { let span = classes.span(); let classes: Vec<_> = classes.elems.iter().collect(); @@ -250,49 +251,47 @@ impl ToTokens for HtmlElement { }; }; - Some(quote! { + attributes.push(quote! {{ let mut __yew_classes = ::yew::html::Classes::with_capacity(#n); #(__yew_classes.push(#classes);)* #deprecation_warning if !__yew_classes.is_empty() { - #vtag.__macro_push_attribute("class", #sr); + ::yew::virtual_dom::PositionalAttr::new("class", #sr) } else { - #vtag.__macro_push_attribute_placeholder("class"); - }; - }) + ::yew::virtual_dom::PositionalAttr::new_placeholder("class") + } + }}); } Some(ClassesForm::Single(classes)) => match classes.try_into_lit() { Some(lit) => { - if lit.value().is_empty() { - None - } else { + if !lit.value().is_empty() { let sr = lit.stringify(); - Some(quote! { - #vtag.__macro_push_attribute("class", #sr); - }) + attributes.push(quote! { + ::yew::virtual_dom::PositionalAttr::new("class", #sr) + }); } } None => { let sr = stringify::stringify_at_runtime(quote! { __yew_classes }); - Some(quote! { + attributes.push(quote! {{ let __yew_classes = ::std::convert::Into::<::yew::html::Classes>::into(#classes); if !__yew_classes.is_empty() { - #vtag.__macro_push_attribute("class", #sr); + ::yew::virtual_dom::PositionalAttr::new("class", #sr) } else { - #vtag.__macro_push_attribute_placeholder("class"); - }; - }) + ::yew::virtual_dom::PositionalAttr::new_placeholder("class") + } + }}); } }, - None => None, + None => (), }; - let add_listeners = if listeners.is_empty() { - None + let listeners = if listeners.is_empty() { + quote! { ::std::vec![] } } else if listeners.iter().any(|attr| attr.question_mark.is_some()) { - let add_listeners = listeners + let listeners = listeners .iter() .map( |Prop { @@ -307,41 +306,29 @@ impl ToTokens for HtmlElement { let ident = Ident::new("__yew_listener", name.span()); let listener = to_wrapped_listener(name, &ident); quote_spanned! {value.span()=> - let #ident = ::std::option::Option::map(#value, |#ident| { - #listener - }); - if let ::std::option::Option::Some(#ident) = #ident { - #vtag.add_listener(#ident); - }; + ::std::option::Option::map(#value, |#ident| #listener) } } else { let listener = to_wrapped_listener(name, value); - quote_spanned! {value.span()=> - #vtag.add_listener(#listener); - } + quote_spanned! {value.span()=> Some(#listener)} } }, ) - .collect(); - - Some(add_listeners) + .collect::>(); + quote! {{ + use ::std::iter::{Iterator, IntoIterator}; + + ::std::vec![#(#listeners),*] + .into_iter() + .filter_map(|l| l) + .collect::<::std::vec::Vec<::std::rc::Rc>>() + }} } else { let listeners_it = listeners .iter() - .map(|Prop { label, value, .. }| to_wrapped_listener(&label.name, value)); - - Some(quote! { - #vtag.add_listeners(::std::vec![#(#listeners_it),*]); - }) - }; - - let add_children = if children.is_empty() { - None - } else { - Some(quote! { - #[allow(clippy::redundant_clone, unused_braces)] - #vtag.add_children(#children); - }) + .map(|Prop { label, value, .. }| to_wrapped_listener(&label.name, value)) + .collect::>(); + quote! { ::std::vec![#(#listeners_it),*] } }; // These are the runtime-checks exclusive to dynamic tags. @@ -380,21 +367,21 @@ impl ToTokens for HtmlElement { tokens.extend(quote_spanned! {name.span()=> { - #[allow(unused_braces)] - let mut #vtag = ::yew::virtual_dom::VTag::new(#name_sr); - - #set_node_ref - #set_key - #set_value - #set_kind - #set_checked - - #set_attributes - #push_booleans - #push_classes - - #add_listeners - #add_children + #[allow(clippy::redundant_clone, unused_braces)] + let mut #vtag = ::yew::virtual_dom::VTag::__new_complete( + #name_sr, + #node_ref, + #key, + #value, + #kind, + #checked, + ::yew::virtual_dom::Attributes::Vec(::std::vec![#(#attributes),*]), + #listeners, + ::yew::virtual_dom::VList{ + key: ::std::option::Option::None, + children: #children, + }, + ); #dyn_tag_runtime_checks #[allow(unused_braces)] @@ -408,7 +395,7 @@ fn to_wrapped_listener(name: &Ident, value: impl ToTokens) -> TokenStream { quote_spanned! {value.span()=> ::std::rc::Rc::new(::yew::html::#name::Wrapper::new( <::yew::virtual_dom::VTag as ::yew::virtual_dom::Transformer<_, _>>::transform(#value), - )) + )) as ::std::rc::Rc:: } } diff --git a/packages/yew-macro/tests/html_macro/element-fail.stderr b/packages/yew-macro/tests/html_macro/element-fail.stderr index e84c55c2a84..879a819d2e4 100644 --- a/packages/yew-macro/tests/html_macro/element-fail.stderr +++ b/packages/yew-macro/tests/html_macro/element-fail.stderr @@ -200,6 +200,7 @@ error[E0277]: `()` doesn't implement `std::fmt::Display` = help: the trait `std::fmt::Display` is not implemented for `()` = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead = note: required because of the requirements on the impl of `ToString` for `()` + = note: required by `to_string` error[E0277]: `()` doesn't implement `std::fmt::Display` --> $DIR/element-fail.rs:30:21 diff --git a/packages/yew/Cargo.toml b/packages/yew/Cargo.toml index 560d12a0f18..cf618884209 100644 --- a/packages/yew/Cargo.toml +++ b/packages/yew/Cargo.toml @@ -28,7 +28,7 @@ serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" slab = "0.4" thiserror = "1" -wasm-bindgen = "0.2.60" +wasm-bindgen = "0.2.74" wasm-bindgen-futures = "0.4" yew-macro = { version = "0.17.0", path = "../yew-macro" } @@ -107,7 +107,7 @@ rustversion = "1.0" serde_derive = "1" ssri = "6.0.0" trybuild = "1.0" -wasm-bindgen-test = "0.3.4" +wasm-bindgen-test = "0.3.24" [features] default = ["agent"] diff --git a/packages/yew/src/virtual_dom/vtag.rs b/packages/yew/src/virtual_dom/vtag.rs index 42ca67c2466..df18c852853 100644 --- a/packages/yew/src/virtual_dom/vtag.rs +++ b/packages/yew/src/virtual_dom/vtag.rs @@ -103,23 +103,56 @@ impl Clone for VTag { impl VTag { /// Creates a new `VTag` instance with `tag` name (cannot be changed later in DOM). pub fn new(tag: impl Into>) -> Self { + Self::__new_complete( + tag, + Default::default(), + Default::default(), + None, + None, + Default::default(), + Default::default(), + Default::default(), + Default::default(), + ) + } + + /// Creates a new `VTag` instance with `tag` name (cannot be changed later in DOM). + /// + /// Unlike `new()`, this sets all the public fields of `VTag` in one call. This allows the + /// compiler to inline property and child list construction in the html! macro. This enables + /// higher instruction parallelism by reducing data dependency and avoids `memcpy` of Vtag + /// fields amd child `VTag`s. + #[doc(hidden)] + #[allow(clippy::too_many_arguments)] + pub fn __new_complete( + tag: impl Into>, + node_ref: NodeRef, + key: Option, + value: Option, + kind: Option>, + checked: bool, + // at bottom for more readable macro-expanded coded + attributes: Attributes, + listeners: Listeners, + children: VList, + ) -> Self { let tag: Cow<'static, str> = tag.into(); let element_type = ElementType::from_tag(&tag); VTag { tag, element_type, reference: None, - attributes: Attributes::new(), - listeners: Vec::new(), + attributes, + listeners, captured: Vec::new(), - children: VList::new(), - node_ref: NodeRef::default(), - key: None, - value: None, - kind: None, + children, + node_ref, + key, + value, + kind, // In HTML node `checked` attribute sets `defaultChecked` parameter, // but we use own field to control real `checked` parameter - checked: false, + checked, } }