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::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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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()));
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user