Skip to content

Commit

Permalink
Delay Hydration second render until all assistive nodes have been rem…
Browse files Browse the repository at this point in the history
…oved (#2629)

* Separate hydration and render queue.

* Revert "Fix issue with node refs and hydration (#2597)"

This reverts commit 469cc34.

* Priority Render.

* Add some tests.

* Add more tests.

* Add test result after click.

* Fix test comment.

* Fix test timing.

* Restore test.

* Once AtomicBool, now a Cell.

* Prefer use_future.

* Revealing of Suspense always happen after the component has re-rendered itself.

* Shifting should register correct next_sibling.

* Revert to HashMap.

* cargo +nightly fmt.

* Fix comment.

* Optimise Code size?

* Add comment if assertion fails.

* Revert "Merge branch 'hydration-4' into fc-prepared-state"

This reverts commit 427b087d4db6b2e497ad618273655bd18ba9bd01, reversing
changes made to 109fcfa.

* Revert "Revert "Merge branch 'hydration-4' into fc-prepared-state""

This reverts commit f1e4089.

* Redo #2957.
  • Loading branch information
futursolo committed Apr 24, 2022
1 parent 2db4c81 commit 2576372
Show file tree
Hide file tree
Showing 16 changed files with 537 additions and 87 deletions.
2 changes: 1 addition & 1 deletion examples/js_callback/Cargo.toml
Expand Up @@ -7,7 +7,7 @@ license = "MIT OR Apache-2.0"

[dependencies]
wasm-bindgen = "0.2"
yew = { path = "../../packages/yew", features = ["csr"] }
yew = { path = "../../packages/yew", features = ["csr", "tokio"] }
wasm-bindgen-futures = "0.4"
js-sys = "0.3"
once_cell = "1"
4 changes: 3 additions & 1 deletion packages/yew/src/dom_bundle/bcomp.rs
Expand Up @@ -44,8 +44,10 @@ impl ReconcileTarget for BComp {
self.scope.destroy_boxed(parent_to_detach);
}

fn shift(&self, next_parent: &Element, next_sibling: NodeRef) {
fn shift(&self, next_parent: &Element, next_sibling: NodeRef) -> NodeRef {
self.scope.shift_node(next_parent.clone(), next_sibling);

self.node_ref.clone()
}
}

Expand Down
10 changes: 7 additions & 3 deletions packages/yew/src/dom_bundle/blist.rs
Expand Up @@ -380,10 +380,14 @@ impl ReconcileTarget for BList {
}
}

fn shift(&self, next_parent: &Element, next_sibling: NodeRef) {
for node in self.rev_children.iter().rev() {
node.shift(next_parent, next_sibling.clone());
fn shift(&self, next_parent: &Element, next_sibling: NodeRef) -> NodeRef {
let mut next_sibling = next_sibling;

for node in self.rev_children.iter() {
next_sibling = node.shift(next_parent, next_sibling.clone());
}

next_sibling
}
}

Expand Down
4 changes: 3 additions & 1 deletion packages/yew/src/dom_bundle/bnode.rs
Expand Up @@ -62,7 +62,7 @@ impl ReconcileTarget for BNode {
}
}

fn shift(&self, next_parent: &Element, next_sibling: NodeRef) {
fn shift(&self, next_parent: &Element, next_sibling: NodeRef) -> NodeRef {
match self {
Self::Tag(ref vtag) => vtag.shift(next_parent, next_sibling),
Self::Text(ref btext) => btext.shift(next_parent, next_sibling),
Expand All @@ -72,6 +72,8 @@ impl ReconcileTarget for BNode {
next_parent
.insert_before(node, next_sibling.get().as_ref())
.unwrap();

NodeRef::new(node.clone())
}
Self::Portal(ref vportal) => vportal.shift(next_parent, next_sibling),
Self::Suspense(ref vsuspense) => vsuspense.shift(next_parent, next_sibling),
Expand Down
4 changes: 3 additions & 1 deletion packages/yew/src/dom_bundle/bportal.rs
Expand Up @@ -26,8 +26,10 @@ impl ReconcileTarget for BPortal {
self.node.detach(&self.inner_root, &self.host, false);
}

fn shift(&self, _next_parent: &Element, _next_sibling: NodeRef) {
fn shift(&self, _next_parent: &Element, next_sibling: NodeRef) -> NodeRef {
// portals have nothing in it's original place of DOM, we also do nothing.

next_sibling
}
}

Expand Down
14 changes: 4 additions & 10 deletions packages/yew/src/dom_bundle/bsuspense.rs
Expand Up @@ -60,18 +60,12 @@ impl ReconcileTarget for BSuspense {
}
}

fn shift(&self, next_parent: &Element, next_sibling: NodeRef) {
fn shift(&self, next_parent: &Element, next_sibling: NodeRef) -> NodeRef {
match self.fallback.as_ref() {
Some(Fallback::Bundle(bundle)) => {
bundle.shift(next_parent, next_sibling);
}
Some(Fallback::Bundle(bundle)) => bundle.shift(next_parent, next_sibling),
#[cfg(feature = "hydration")]
Some(Fallback::Fragment(fragment)) => {
fragment.shift(next_parent, next_sibling);
}
None => {
self.children_bundle.shift(next_parent, next_sibling);
}
Some(Fallback::Fragment(fragment)) => fragment.shift(next_parent, next_sibling),
None => self.children_bundle.shift(next_parent, next_sibling),
}
}
}
Expand Down
4 changes: 3 additions & 1 deletion packages/yew/src/dom_bundle/btag/mod.rs
Expand Up @@ -94,10 +94,12 @@ impl ReconcileTarget for BTag {
}
}

fn shift(&self, next_parent: &Element, next_sibling: NodeRef) {
fn shift(&self, next_parent: &Element, next_sibling: NodeRef) -> NodeRef {
next_parent
.insert_before(&self.reference, next_sibling.get().as_ref())
.unwrap();

self.node_ref.clone()
}
}

Expand Down
4 changes: 3 additions & 1 deletion packages/yew/src/dom_bundle/btext.rs
Expand Up @@ -26,12 +26,14 @@ impl ReconcileTarget for BText {
}
}

fn shift(&self, next_parent: &Element, next_sibling: NodeRef) {
fn shift(&self, next_parent: &Element, next_sibling: NodeRef) -> NodeRef {
let node = &self.text_node;

next_parent
.insert_before(node, next_sibling.get().as_ref())
.unwrap();

NodeRef::new(self.text_node.clone().into())
}
}

Expand Down
7 changes: 6 additions & 1 deletion packages/yew/src/dom_bundle/fragment.rs
Expand Up @@ -156,11 +156,16 @@ impl Fragment {
}

/// Shift current Fragment into a different position in the dom.
pub fn shift(&self, next_parent: &Element, next_sibling: NodeRef) {
pub fn shift(&self, next_parent: &Element, next_sibling: NodeRef) -> NodeRef {
for node in self.iter() {
next_parent
.insert_before(node, next_sibling.get().as_ref())
.unwrap();
}

self.front()
.cloned()
.map(NodeRef::new)
.unwrap_or(next_sibling)
}
}
2 changes: 1 addition & 1 deletion packages/yew/src/dom_bundle/traits.rs
Expand Up @@ -16,7 +16,7 @@ pub(super) trait ReconcileTarget {
/// Move elements from one parent to another parent.
/// This is for example used by `VSuspense` to preserve component state without detaching
/// (which destroys component state).
fn shift(&self, next_parent: &Element, next_sibling: NodeRef);
fn shift(&self, next_parent: &Element, next_sibling: NodeRef) -> NodeRef;
}

/// This trait provides features to update a tree by calculating a difference against another tree.
Expand Down
119 changes: 70 additions & 49 deletions packages/yew/src/html/component/lifecycle.rs
Expand Up @@ -301,61 +301,83 @@ impl<COMP: BaseComponent> Runnable for CreateRunner<COMP> {
}
}

pub(crate) enum UpdateEvent {
/// Drain messages for a component.
Message,
/// Wraps properties, node ref, and next sibling for a component
#[cfg(feature = "csr")]
Properties(Rc<dyn Any>, NodeRef),
#[cfg(feature = "csr")]
pub(crate) struct PropsUpdateRunner {
pub props: Rc<dyn Any>,
pub state: Shared<Option<ComponentState>>,
pub next_sibling: NodeRef,
}

#[cfg(feature = "csr")]
impl Runnable for PropsUpdateRunner {
fn run(self: Box<Self>) {
let Self {
next_sibling,
props,
state: shared_state,
} = *self;

if let Some(state) = shared_state.borrow_mut().as_mut() {
let schedule_render = match state.render_state {
#[cfg(feature = "csr")]
ComponentRenderState::Render {
next_sibling: ref mut current_next_sibling,
..
} => {
// When components are updated, their siblings were likely also updated
*current_next_sibling = next_sibling;
// Only trigger changed if props were changed
state.inner.props_changed(props)
}

#[cfg(feature = "hydration")]
ComponentRenderState::Hydration {
next_sibling: ref mut current_next_sibling,
..
} => {
// When components are updated, their siblings were likely also updated
*current_next_sibling = next_sibling;
// Only trigger changed if props were changed
state.inner.props_changed(props)
}

#[cfg(feature = "ssr")]
ComponentRenderState::Ssr { .. } => {
#[cfg(debug_assertions)]
panic!("properties do not change during SSR");

#[cfg(not(debug_assertions))]
false
}
};

#[cfg(debug_assertions)]
super::log_event(
state.comp_id,
format!("props_update(schedule_render={})", schedule_render),
);

if schedule_render {
scheduler::push_component_render(
state.comp_id,
Box::new(RenderRunner {
state: shared_state.clone(),
}),
);
// Only run from the scheduler, so no need to call `scheduler::start()`
}
};
}
}

pub(crate) struct UpdateRunner {
pub state: Shared<Option<ComponentState>>,
pub event: UpdateEvent,
}

impl Runnable for UpdateRunner {
fn run(self: Box<Self>) {
if let Some(state) = self.state.borrow_mut().as_mut() {
let schedule_render = match self.event {
UpdateEvent::Message => state.inner.flush_messages(),

#[cfg(feature = "csr")]
UpdateEvent::Properties(props, next_sibling) => {
match state.render_state {
#[cfg(feature = "csr")]
ComponentRenderState::Render {
next_sibling: ref mut current_next_sibling,
..
} => {
// When components are updated, their siblings were likely also updated
*current_next_sibling = next_sibling;
// Only trigger changed if props were changed
state.inner.props_changed(props)
}

#[cfg(feature = "hydration")]
ComponentRenderState::Hydration {
next_sibling: ref mut current_next_sibling,
..
} => {
// When components are updated, their siblings were likely also updated
*current_next_sibling = next_sibling;
// Only trigger changed if props were changed
state.inner.props_changed(props)
}

#[cfg(feature = "ssr")]
ComponentRenderState::Ssr { .. } => {
#[cfg(debug_assertions)]
panic!("properties do not change during SSR");

#[cfg(not(debug_assertions))]
false
}
}
}
};
let schedule_render = state.inner.flush_messages();

#[cfg(debug_assertions)]
super::log_event(
Expand Down Expand Up @@ -453,9 +475,8 @@ impl RenderRunner {

if suspension.resumed() {
// schedule a render immediately if suspension is resumed.

scheduler::push_component_render(
state.comp_id,
comp_id,
Box::new(RenderRunner {
state: shared_state,
}),
Expand Down Expand Up @@ -542,7 +563,7 @@ impl RenderRunner {
} => {
// We schedule a "first" render to run immediately after hydration,
// to fix NodeRefs (first_node and next_sibling).
scheduler::push_component_first_render(
scheduler::push_component_priority_render(
state.comp_id,
Box::new(RenderRunner {
state: self.state.clone(),
Expand Down
28 changes: 21 additions & 7 deletions packages/yew/src/html/component/scope.rs
Expand Up @@ -9,7 +9,7 @@ use std::rc::Rc;
use std::{fmt, iter};

#[cfg(any(feature = "csr", feature = "ssr"))]
use super::lifecycle::{ComponentState, UpdateEvent, UpdateRunner};
use super::lifecycle::{ComponentState, UpdateRunner};
use super::BaseComponent;
use crate::callback::Callback;
use crate::context::{ContextHandle, ContextProvider};
Expand Down Expand Up @@ -353,10 +353,10 @@ mod feat_csr_ssr {
})
}

pub(super) fn push_update(&self, event: UpdateEvent) {
#[inline]
fn schedule_update(&self) {
scheduler::push_component_update(Box::new(UpdateRunner {
state: self.state.clone(),
event,
}));
// Not guaranteed to already have the scheduler started
scheduler::start();
Expand All @@ -369,7 +369,7 @@ mod feat_csr_ssr {
{
// We are the first message in queue, so we queue the update.
if self.pending_messages.push(msg.into()) == 1 {
self.push_update(UpdateEvent::Message);
self.schedule_update();
}
}

Expand All @@ -382,7 +382,7 @@ mod feat_csr_ssr {

// The queue was empty, so we queue the update
if self.pending_messages.append(&mut messages) == msg_len {
self.push_update(UpdateEvent::Message);
self.schedule_update();
}
}
}
Expand All @@ -400,7 +400,7 @@ mod feat_csr {
use super::*;
use crate::dom_bundle::{BSubtree, Bundle};
use crate::html::component::lifecycle::{
ComponentRenderState, CreateRunner, DestroyRunner, RenderRunner,
ComponentRenderState, CreateRunner, DestroyRunner, PropsUpdateRunner, RenderRunner,
};
use crate::html::NodeRef;
use crate::scheduler;
Expand All @@ -416,6 +416,20 @@ mod feat_csr {
}
}

fn schedule_props_update(
state: Shared<Option<ComponentState>>,
props: Rc<dyn Any>,
next_sibling: NodeRef,
) {
scheduler::push_component_props_update(Box::new(PropsUpdateRunner {
state,
next_sibling,
props,
}));
// Not guaranteed to already have the scheduler started
scheduler::start();
}

impl<COMP> Scope<COMP>
where
COMP: BaseComponent,
Expand Down Expand Up @@ -459,7 +473,7 @@ mod feat_csr {
#[cfg(debug_assertions)]
super::super::log_event(self.id, "reuse");

self.push_update(UpdateEvent::Properties(props, next_sibling));
schedule_props_update(self.state.clone(), props, next_sibling)
}
}

Expand Down

1 comment on commit 2576372

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yew master branch benchmarks (Lower is better)

Benchmark suite Current: 2576372 Previous: 2db4c81 Ratio
yew-struct-keyed 01_run1k 269.61 213.484 1.26
yew-struct-keyed 02_replace1k 289.005 233.5825 1.24
yew-struct-keyed 03_update10th1k_x16 464.3335 363.8455 1.28
yew-struct-keyed 04_select1k 93.389 74.9975 1.25
yew-struct-keyed 05_swap1k 116.125 100.34 1.16
yew-struct-keyed 06_remove-one-1k 35.1525 35.014 1.00
yew-struct-keyed 07_create10k 4135.9735 3303.7455 1.25
yew-struct-keyed 08_create1k-after1k_x2 643.3345 470.1945 1.37
yew-struct-keyed 09_clear1k_x8 263.6815 208.8745 1.26
yew-struct-keyed 21_ready-memory 1.457233428955078 1.457233428955078 1
yew-struct-keyed 22_run-memory 1.6642227172851562 1.6601448059082031 1.00
yew-struct-keyed 23_update5-memory 1.6991310119628906 1.668548583984375 1.02
yew-struct-keyed 24_run5-memory 1.709381103515625 1.9439773559570312 0.88
yew-struct-keyed 25_run-clear-memory 1.3276443481445312 1.3280982971191406 1.00
yew-struct-keyed 31_startup-ci 1738.732 1881.945 0.92
yew-struct-keyed 32_startup-bt 40.648 32.596000000000004 1.25
yew-struct-keyed 33_startup-mainthreadcost 314.94399999999996 240.072 1.31
yew-struct-keyed 34_startup-totalbytes 328.7392578125 328.7392578125 1

This comment was automatically generated by workflow using github-action-benchmark.

Please sign in to comment.