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:
Kaede Hoshikawa 2023-04-02 23:51:59 +09:00 committed by GitHub
parent 86b6cb4949
commit 9d7fafa3ff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 130 additions and 26 deletions

View File

@ -4,6 +4,7 @@ use std::cmp::Ordering;
use std::collections::HashSet;
use std::hash::Hash;
use std::ops::Deref;
use std::rc::Rc;
use web_sys::Element;
@ -22,6 +23,22 @@ pub(super) struct BList {
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 {
type Target = Vec<BNode>;
@ -397,7 +414,7 @@ impl Reconcilable for VList {
}
fn reconcile(
mut self,
self,
root: &BSubtree,
parent_scope: &AnyScope,
parent: &Element,
@ -412,16 +429,16 @@ impl Reconcilable for VList {
// The left items are known since we want to insert them
// (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.
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
// and corrupts the order of rendering
// 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;
test_log!("lefts: {:?}", lefts);
test_log!("rights: {:?}", rights);
@ -435,7 +452,7 @@ impl Reconcilable for VList {
BList::apply_unkeyed(root, parent_scope, parent, slot, lefts, rights)
};
blist.fully_keyed = fully_keyed;
blist.key = self.key;
blist.key = key;
test_log!("result: {:?}", rights);
first
}
@ -454,8 +471,8 @@ mod feat_hydration {
parent: &Element,
fragment: &mut Fragment,
) -> Self::Bundle {
let fully_keyed = self.fully_keyed();
let vchildren = self.children;
let (key, fully_keyed, vchildren) = self.split_for_blist();
let mut children = Vec::with_capacity(vchildren.len());
for child in vchildren.into_iter() {
@ -469,7 +486,7 @@ mod feat_hydration {
BList {
rev_children: children,
fully_keyed,
key: self.key,
key,
}
}
}

View File

@ -1,5 +1,6 @@
//! This module contains fragments implementation.
use std::ops::{Deref, DerefMut};
use std::rc::Rc;
use super::{Key, VNode};
@ -14,7 +15,7 @@ enum FullyKeyedState {
#[derive(Clone, Debug)]
pub struct VList {
/// 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
fully_keyed: FullyKeyedState,
@ -24,7 +25,15 @@ pub struct VList {
impl PartialEq for VList {
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>;
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 {
fn deref_mut(&mut self) -> &mut Self::Target {
self.fully_keyed = FullyKeyedState::Unknown;
&mut self.children
self.children_mut()
}
}
@ -53,7 +70,7 @@ impl VList {
/// Creates a new empty [VList] instance.
pub const fn new() -> Self {
Self {
children: Vec::new(),
children: None,
key: None,
fully_keyed: FullyKeyedState::KnownFullyKeyed,
}
@ -63,30 +80,40 @@ impl VList {
pub fn with_children(children: Vec<VNode>, key: Option<Key>) -> Self {
let mut vlist = VList {
fully_keyed: FullyKeyedState::Unknown,
children,
children: Some(Rc::new(children)),
key,
};
vlist.fully_keyed = if vlist.fully_keyed() {
FullyKeyedState::KnownFullyKeyed
} else {
FullyKeyedState::KnownMissingKeys
};
vlist.recheck_fully_keyed();
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.
pub fn add_child(&mut self, child: VNode) {
if self.fully_keyed == FullyKeyedState::KnownFullyKeyed && !child.has_key() {
self.fully_keyed = FullyKeyedState::KnownMissingKeys;
}
self.children.push(child);
self.children_mut().push(child);
}
/// Add multiple [VNode] children.
pub fn add_children(&mut self, children: impl IntoIterator<Item = VNode>) {
let it = children.into_iter();
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 {
self.add_child(ch);
}
@ -108,7 +135,7 @@ impl VList {
match self.fully_keyed {
FullyKeyedState::KnownFullyKeyed => true,
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,
hydratable: bool,
) {
match &self.children[..] {
match &self[..] {
[] => {}
[child] => {
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;
}
}

View File

@ -82,6 +82,56 @@ async fn bench_router_app() -> Duration {
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 {
static TOTAL: usize = 100;
@ -214,12 +264,13 @@ async fn main() {
let args = Args::parse();
// Tests in each round.
static TESTS: usize = 4;
static TESTS: usize = 5;
let mut baseline_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 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));
@ -266,6 +317,14 @@ async fn main() {
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;
@ -280,6 +339,7 @@ async fn main() {
Statistics::from_results("Hello World", args.rounds, hello_world_results),
Statistics::from_results("Function Router", args.rounds, function_router_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()));