mirror of
https://github.com/yewstack/yew.git
synced 2025-12-08 21:26:25 +00:00
Make VList's children Rc'ed (#3050)
* Make VList's children Rc'ed. * Slightly optimise for bundle size. * Revert manual cloning. * Add a benchmark. * Revert "Revert manual cloning." This reverts commit 1f737f26f85a13e49186df71eb41425387f3aa02. * Revert "Slightly optimise for bundle size." This reverts commit 71cf2a1d36f1a99463c68aae3d128aa3be93dc03. * Add size marker. * Revert "Add size marker." This reverts commit 3ca55be4ea309f516d91ecd8e2aa8256dd810d11. * Update benchmark. * Fix children_mut visibility. * Try to exclude add_child behaviour. * Fix typo. * Adjust visibility and docs. * Fix hydration with empty children.
This commit is contained in:
parent
86b6cb4949
commit
9d7fafa3ff
@ -4,6 +4,7 @@ use std::cmp::Ordering;
|
|||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
use web_sys::Element;
|
use web_sys::Element;
|
||||||
|
|
||||||
@ -22,6 +23,22 @@ pub(super) struct BList {
|
|||||||
key: Option<Key>,
|
key: Option<Key>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl VList {
|
||||||
|
// Splits a VList for creating / reconciling to a BList.
|
||||||
|
fn split_for_blist(self) -> (Option<Key>, bool, Vec<VNode>) {
|
||||||
|
let fully_keyed = self.fully_keyed();
|
||||||
|
|
||||||
|
let children = self
|
||||||
|
.children
|
||||||
|
.map(Rc::try_unwrap)
|
||||||
|
.unwrap_or_else(|| Ok(Vec::new()))
|
||||||
|
// Rc::unwrap_or_clone is not stable yet.
|
||||||
|
.unwrap_or_else(|m| m.to_vec());
|
||||||
|
|
||||||
|
(self.key, fully_keyed, children)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Deref for BList {
|
impl Deref for BList {
|
||||||
type Target = Vec<BNode>;
|
type Target = Vec<BNode>;
|
||||||
|
|
||||||
@ -397,7 +414,7 @@ impl Reconcilable for VList {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn reconcile(
|
fn reconcile(
|
||||||
mut self,
|
self,
|
||||||
root: &BSubtree,
|
root: &BSubtree,
|
||||||
parent_scope: &AnyScope,
|
parent_scope: &AnyScope,
|
||||||
parent: &Element,
|
parent: &Element,
|
||||||
@ -412,16 +429,16 @@ impl Reconcilable for VList {
|
|||||||
// The left items are known since we want to insert them
|
// The left items are known since we want to insert them
|
||||||
// (self.children). For the right ones, we will look at the bundle,
|
// (self.children). For the right ones, we will look at the bundle,
|
||||||
// i.e. the current DOM list element that we want to replace with self.
|
// i.e. the current DOM list element that we want to replace with self.
|
||||||
|
let (key, mut fully_keyed, mut lefts) = self.split_for_blist();
|
||||||
|
|
||||||
if self.children.is_empty() {
|
if lefts.is_empty() {
|
||||||
// Without a placeholder the next element becomes first
|
// Without a placeholder the next element becomes first
|
||||||
// and corrupts the order of rendering
|
// and corrupts the order of rendering
|
||||||
// We use empty text element to stake out a place
|
// We use empty text element to stake out a place
|
||||||
self.add_child(VText::new("").into());
|
lefts.push(VText::new("").into());
|
||||||
|
fully_keyed = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let fully_keyed = self.fully_keyed();
|
|
||||||
let lefts = self.children;
|
|
||||||
let rights = &mut blist.rev_children;
|
let rights = &mut blist.rev_children;
|
||||||
test_log!("lefts: {:?}", lefts);
|
test_log!("lefts: {:?}", lefts);
|
||||||
test_log!("rights: {:?}", rights);
|
test_log!("rights: {:?}", rights);
|
||||||
@ -435,7 +452,7 @@ impl Reconcilable for VList {
|
|||||||
BList::apply_unkeyed(root, parent_scope, parent, slot, lefts, rights)
|
BList::apply_unkeyed(root, parent_scope, parent, slot, lefts, rights)
|
||||||
};
|
};
|
||||||
blist.fully_keyed = fully_keyed;
|
blist.fully_keyed = fully_keyed;
|
||||||
blist.key = self.key;
|
blist.key = key;
|
||||||
test_log!("result: {:?}", rights);
|
test_log!("result: {:?}", rights);
|
||||||
first
|
first
|
||||||
}
|
}
|
||||||
@ -454,8 +471,8 @@ mod feat_hydration {
|
|||||||
parent: &Element,
|
parent: &Element,
|
||||||
fragment: &mut Fragment,
|
fragment: &mut Fragment,
|
||||||
) -> Self::Bundle {
|
) -> Self::Bundle {
|
||||||
let fully_keyed = self.fully_keyed();
|
let (key, fully_keyed, vchildren) = self.split_for_blist();
|
||||||
let vchildren = self.children;
|
|
||||||
let mut children = Vec::with_capacity(vchildren.len());
|
let mut children = Vec::with_capacity(vchildren.len());
|
||||||
|
|
||||||
for child in vchildren.into_iter() {
|
for child in vchildren.into_iter() {
|
||||||
@ -469,7 +486,7 @@ mod feat_hydration {
|
|||||||
BList {
|
BList {
|
||||||
rev_children: children,
|
rev_children: children,
|
||||||
fully_keyed,
|
fully_keyed,
|
||||||
key: self.key,
|
key,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
//! This module contains fragments implementation.
|
//! This module contains fragments implementation.
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
use super::{Key, VNode};
|
use super::{Key, VNode};
|
||||||
|
|
||||||
@ -14,7 +15,7 @@ enum FullyKeyedState {
|
|||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct VList {
|
pub struct VList {
|
||||||
/// The list of child [VNode]s
|
/// The list of child [VNode]s
|
||||||
pub(crate) children: Vec<VNode>,
|
pub(crate) children: Option<Rc<Vec<VNode>>>,
|
||||||
|
|
||||||
/// All [VNode]s in the VList have keys
|
/// All [VNode]s in the VList have keys
|
||||||
fully_keyed: FullyKeyedState,
|
fully_keyed: FullyKeyedState,
|
||||||
@ -24,7 +25,15 @@ pub struct VList {
|
|||||||
|
|
||||||
impl PartialEq for VList {
|
impl PartialEq for VList {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
self.children == other.children && self.key == other.key
|
self.key == other.key
|
||||||
|
&& match (self.children.as_ref(), other.children.as_ref()) {
|
||||||
|
// We try to use ptr_eq if both are behind Rc,
|
||||||
|
// Somehow VNode is not Eq?
|
||||||
|
(Some(l), Some(r)) if Rc::ptr_eq(l, r) => true,
|
||||||
|
// We fallback to PartialEq if left and right didn't point to the same memory
|
||||||
|
// address.
|
||||||
|
(l, r) => l == r,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,14 +47,22 @@ impl Deref for VList {
|
|||||||
type Target = Vec<VNode>;
|
type Target = Vec<VNode>;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
&self.children
|
match self.children {
|
||||||
|
Some(ref m) => m,
|
||||||
|
None => {
|
||||||
|
// This is mutable because the Vec<VNode> is not Sync
|
||||||
|
static mut EMPTY: Vec<VNode> = Vec::new();
|
||||||
|
// SAFETY: The EMPTY value is always read-only
|
||||||
|
unsafe { &EMPTY }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DerefMut for VList {
|
impl DerefMut for VList {
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
self.fully_keyed = FullyKeyedState::Unknown;
|
self.fully_keyed = FullyKeyedState::Unknown;
|
||||||
&mut self.children
|
self.children_mut()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,7 +70,7 @@ impl VList {
|
|||||||
/// Creates a new empty [VList] instance.
|
/// Creates a new empty [VList] instance.
|
||||||
pub const fn new() -> Self {
|
pub const fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
children: Vec::new(),
|
children: None,
|
||||||
key: None,
|
key: None,
|
||||||
fully_keyed: FullyKeyedState::KnownFullyKeyed,
|
fully_keyed: FullyKeyedState::KnownFullyKeyed,
|
||||||
}
|
}
|
||||||
@ -63,30 +80,40 @@ impl VList {
|
|||||||
pub fn with_children(children: Vec<VNode>, key: Option<Key>) -> Self {
|
pub fn with_children(children: Vec<VNode>, key: Option<Key>) -> Self {
|
||||||
let mut vlist = VList {
|
let mut vlist = VList {
|
||||||
fully_keyed: FullyKeyedState::Unknown,
|
fully_keyed: FullyKeyedState::Unknown,
|
||||||
children,
|
children: Some(Rc::new(children)),
|
||||||
key,
|
key,
|
||||||
};
|
};
|
||||||
vlist.fully_keyed = if vlist.fully_keyed() {
|
vlist.recheck_fully_keyed();
|
||||||
FullyKeyedState::KnownFullyKeyed
|
|
||||||
} else {
|
|
||||||
FullyKeyedState::KnownMissingKeys
|
|
||||||
};
|
|
||||||
vlist
|
vlist
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns a mutable reference to children, allocates the children if it hasn't been done.
|
||||||
|
//
|
||||||
|
// This method does not reassign key state. So it should only be used internally.
|
||||||
|
fn children_mut(&mut self) -> &mut Vec<VNode> {
|
||||||
|
loop {
|
||||||
|
match self.children {
|
||||||
|
Some(ref mut m) => return Rc::make_mut(m),
|
||||||
|
None => {
|
||||||
|
self.children = Some(Rc::new(Vec::new()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Add [VNode] child.
|
/// Add [VNode] child.
|
||||||
pub fn add_child(&mut self, child: VNode) {
|
pub fn add_child(&mut self, child: VNode) {
|
||||||
if self.fully_keyed == FullyKeyedState::KnownFullyKeyed && !child.has_key() {
|
if self.fully_keyed == FullyKeyedState::KnownFullyKeyed && !child.has_key() {
|
||||||
self.fully_keyed = FullyKeyedState::KnownMissingKeys;
|
self.fully_keyed = FullyKeyedState::KnownMissingKeys;
|
||||||
}
|
}
|
||||||
self.children.push(child);
|
self.children_mut().push(child);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add multiple [VNode] children.
|
/// Add multiple [VNode] children.
|
||||||
pub fn add_children(&mut self, children: impl IntoIterator<Item = VNode>) {
|
pub fn add_children(&mut self, children: impl IntoIterator<Item = VNode>) {
|
||||||
let it = children.into_iter();
|
let it = children.into_iter();
|
||||||
let bound = it.size_hint();
|
let bound = it.size_hint();
|
||||||
self.children.reserve(bound.1.unwrap_or(bound.0));
|
self.children_mut().reserve(bound.1.unwrap_or(bound.0));
|
||||||
for ch in it {
|
for ch in it {
|
||||||
self.add_child(ch);
|
self.add_child(ch);
|
||||||
}
|
}
|
||||||
@ -108,7 +135,7 @@ impl VList {
|
|||||||
match self.fully_keyed {
|
match self.fully_keyed {
|
||||||
FullyKeyedState::KnownFullyKeyed => true,
|
FullyKeyedState::KnownFullyKeyed => true,
|
||||||
FullyKeyedState::KnownMissingKeys => false,
|
FullyKeyedState::KnownMissingKeys => false,
|
||||||
FullyKeyedState::Unknown => self.children.iter().all(|c| c.has_key()),
|
FullyKeyedState::Unknown => self.iter().all(|c| c.has_key()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -173,7 +200,7 @@ mod feat_ssr {
|
|||||||
parent_scope: &AnyScope,
|
parent_scope: &AnyScope,
|
||||||
hydratable: bool,
|
hydratable: bool,
|
||||||
) {
|
) {
|
||||||
match &self.children[..] {
|
match &self[..] {
|
||||||
[] => {}
|
[] => {}
|
||||||
[child] => {
|
[child] => {
|
||||||
child.render_into_stream(w, parent_scope, hydratable).await;
|
child.render_into_stream(w, parent_scope, hydratable).await;
|
||||||
@ -237,7 +264,7 @@ mod feat_ssr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let children = self.children.iter();
|
let children = self.iter();
|
||||||
render_child_iter(children, w, parent_scope, hydratable).await;
|
render_child_iter(children, w, parent_scope, hydratable).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -82,6 +82,56 @@ async fn bench_router_app() -> Duration {
|
|||||||
start_time.elapsed()
|
start_time.elapsed()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn bench_many_providers() -> Duration {
|
||||||
|
static TOTAL: usize = 250_000;
|
||||||
|
|
||||||
|
#[derive(Properties, PartialEq, Clone)]
|
||||||
|
struct ProviderProps {
|
||||||
|
children: Children,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[function_component]
|
||||||
|
fn Provider(props: &ProviderProps) -> Html {
|
||||||
|
let ProviderProps { children } = props.clone();
|
||||||
|
|
||||||
|
html! {<>{children}</>}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[function_component]
|
||||||
|
fn App() -> Html {
|
||||||
|
// Let's make 10 providers.
|
||||||
|
html! {
|
||||||
|
<Provider>
|
||||||
|
<Provider>
|
||||||
|
<Provider>
|
||||||
|
<Provider>
|
||||||
|
<Provider>
|
||||||
|
<Provider>
|
||||||
|
<Provider>
|
||||||
|
<Provider>
|
||||||
|
<Provider>
|
||||||
|
<Provider>{"Hello, World!"}</Provider>
|
||||||
|
</Provider>
|
||||||
|
</Provider>
|
||||||
|
</Provider>
|
||||||
|
</Provider>
|
||||||
|
</Provider>
|
||||||
|
</Provider>
|
||||||
|
</Provider>
|
||||||
|
</Provider>
|
||||||
|
</Provider>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let start_time = Instant::now();
|
||||||
|
|
||||||
|
for _ in 0..TOTAL {
|
||||||
|
yew::LocalServerRenderer::<App>::new().render().await;
|
||||||
|
}
|
||||||
|
|
||||||
|
start_time.elapsed()
|
||||||
|
}
|
||||||
|
|
||||||
async fn bench_concurrent_task() -> Duration {
|
async fn bench_concurrent_task() -> Duration {
|
||||||
static TOTAL: usize = 100;
|
static TOTAL: usize = 100;
|
||||||
|
|
||||||
@ -214,12 +264,13 @@ async fn main() {
|
|||||||
let args = Args::parse();
|
let args = Args::parse();
|
||||||
|
|
||||||
// Tests in each round.
|
// Tests in each round.
|
||||||
static TESTS: usize = 4;
|
static TESTS: usize = 5;
|
||||||
|
|
||||||
let mut baseline_results = Vec::with_capacity(args.rounds);
|
let mut baseline_results = Vec::with_capacity(args.rounds);
|
||||||
let mut hello_world_results = Vec::with_capacity(args.rounds);
|
let mut hello_world_results = Vec::with_capacity(args.rounds);
|
||||||
let mut function_router_results = Vec::with_capacity(args.rounds);
|
let mut function_router_results = Vec::with_capacity(args.rounds);
|
||||||
let mut concurrent_tasks_results = Vec::with_capacity(args.rounds);
|
let mut concurrent_tasks_results = Vec::with_capacity(args.rounds);
|
||||||
|
let mut many_provider_results = Vec::with_capacity(args.rounds);
|
||||||
|
|
||||||
let bar = (!args.no_term).then(|| create_progress(TESTS, args.rounds));
|
let bar = (!args.no_term).then(|| create_progress(TESTS, args.rounds));
|
||||||
|
|
||||||
@ -266,6 +317,14 @@ async fn main() {
|
|||||||
bar.inc(1);
|
bar.inc(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let dur = bench_many_providers().await;
|
||||||
|
if i > 0 {
|
||||||
|
many_provider_results.push(dur);
|
||||||
|
if let Some(ref bar) = bar {
|
||||||
|
bar.inc(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
@ -280,6 +339,7 @@ async fn main() {
|
|||||||
Statistics::from_results("Hello World", args.rounds, hello_world_results),
|
Statistics::from_results("Hello World", args.rounds, hello_world_results),
|
||||||
Statistics::from_results("Function Router", args.rounds, function_router_results),
|
Statistics::from_results("Function Router", args.rounds, function_router_results),
|
||||||
Statistics::from_results("Concurrent Task", args.rounds, concurrent_tasks_results),
|
Statistics::from_results("Concurrent Task", args.rounds, concurrent_tasks_results),
|
||||||
|
Statistics::from_results("Many Providers", args.rounds, many_provider_results),
|
||||||
];
|
];
|
||||||
|
|
||||||
println!("{}", output.as_ref().table().with(Style::rounded()));
|
println!("{}", output.as_ref().table().with(Style::rounded()));
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user