Skip to content

Commit

Permalink
Separate hydration and render queue.
Browse files Browse the repository at this point in the history
  • Loading branch information
futursolo committed Apr 20, 2022
1 parent cd5b8a5 commit 69da10a
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 42 deletions.
60 changes: 37 additions & 23 deletions packages/yew/src/html/component/lifecycle.rs
Expand Up @@ -11,6 +11,8 @@ use crate::suspense::{BaseSuspense, Suspension};
use crate::{Callback, Context, HtmlResult};
use std::any::Any;
use std::rc::Rc;
#[cfg(feature = "csr")]
use std::sync::atomic::{AtomicBool, Ordering};

#[cfg(feature = "hydration")]
use crate::dom_bundle::Fragment;
Expand Down Expand Up @@ -219,7 +221,7 @@ pub(crate) struct ComponentState {
pub(super) render_state: ComponentRenderState,

#[cfg(feature = "csr")]
has_rendered: bool,
has_rendered: Rc<AtomicBool>,

suspension: Option<Suspension>,

Expand Down Expand Up @@ -261,7 +263,7 @@ impl ComponentState {
suspension: None,

#[cfg(feature = "csr")]
has_rendered: false,
has_rendered: Rc::default(),

comp_id,
}
Expand Down Expand Up @@ -450,15 +452,38 @@ impl RenderRunner {

let comp_id = state.comp_id;

if suspension.resumed() {
// schedule a render immediately if suspension is resumed.
#[cfg(feature = "csr")]
let schedule_render = {
let has_rendered = state.has_rendered.clone();
move || {
if has_rendered.load(Ordering::Relaxed) {
scheduler::push_component_render(
comp_id,
Box::new(RenderRunner {
state: shared_state.clone(),
}),
);
} else {
scheduler::push_component_first_render(Box::new(RenderRunner {
state: shared_state.clone(),
}));
}
}
};

#[cfg(not(feature = "csr"))]
let schedule_render = move || {
scheduler::push_component_render(
state.comp_id,
comp_id,
Box::new(RenderRunner {
state: shared_state,
state: shared_state.clone(),
}),
);
};

if suspension.resumed() {
// schedule a render immediately if suspension is resumed.
schedule_render();
} else {
// We schedule a render after current suspension is resumed.
let comp_scope = state.inner.any_scope();
Expand All @@ -468,15 +493,7 @@ impl RenderRunner {
.expect("To suspend rendering, a <Suspense /> component is required.");
let suspense = suspense_scope.get_component().unwrap();

suspension.listen(Callback::from(move |_| {
scheduler::push_component_render(
comp_id,
Box::new(RenderRunner {
state: shared_state.clone(),
}),
);
scheduler::start();
}));
suspension.listen(Callback::from(move |_| schedule_render()));

if let Some(ref last_suspension) = state.suspension {
if &suspension != last_suspension {
Expand Down Expand Up @@ -518,8 +535,8 @@ impl RenderRunner {
bundle.reconcile(root, &scope, parent, next_sibling.clone(), new_root);
node_ref.link(new_node_ref);

let first_render = !state.has_rendered;
state.has_rendered = true;
let first_render = !state.has_rendered.load(Ordering::Relaxed);
state.has_rendered.store(true, Ordering::Relaxed);

scheduler::push_component_rendered(
state.comp_id,
Expand All @@ -541,12 +558,9 @@ 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(
state.comp_id,
Box::new(RenderRunner {
state: self.state.clone(),
}),
);
scheduler::push_component_first_render(Box::new(RenderRunner {
state: self.state.clone(),
}));

let scope = state.inner.any_scope();

Expand Down
5 changes: 1 addition & 4 deletions packages/yew/src/html/component/scope.rs
Expand Up @@ -204,7 +204,6 @@ mod feat_ssr {
let state = ComponentRenderState::Ssr { sender: Some(tx) };

scheduler::push_component_create(
self.id,
Box::new(CreateRunner {
initial_render_state: state,
props,
Expand Down Expand Up @@ -440,7 +439,6 @@ mod feat_csr {
};

scheduler::push_component_create(
self.id,
Box::new(CreateRunner {
initial_render_state: state,
props,
Expand Down Expand Up @@ -572,8 +570,7 @@ mod feat_hydration {
fragment,
};

scheduler::push_component_create(
self.id,
scheduler::push_component_hydrate(
Box::new(CreateRunner {
initial_render_state: state,
props,
Expand Down
45 changes: 30 additions & 15 deletions packages/yew/src/scheduler.rs
@@ -1,7 +1,7 @@
//! This module contains a scheduler.

use std::cell::RefCell;
use std::collections::BTreeMap;
use std::collections::{BTreeMap, VecDeque};
use std::rc::Rc;

/// Alias for Rc<RefCell<T>>
Expand All @@ -25,12 +25,17 @@ struct Scheduler {
create: Vec<Box<dyn Runnable>>,
update: Vec<Box<dyn Runnable>>,

/// These are safe as it's not possible to schedule a child component to be rendered before a
/// parent becomes rendered for the first time.
#[cfg(feature = "hydration")]
hydrate_first: VecDeque<Box<dyn Runnable>>,
render_first: VecDeque<Box<dyn Runnable>>,

/// The Binary Tree Map guarantees components with lower id (parent) is rendered first and
/// no more than 1 render can be scheduled before a component is rendered.
///
/// Parent can destroy child components but not otherwise, we can save unnecessary render by
/// rendering parent first.
render_first: BTreeMap<usize, Box<dyn Runnable>>,
render: BTreeMap<usize, Box<dyn Runnable>>,

/// Binary Tree Map to guarantee children rendered are always called before parent calls
Expand Down Expand Up @@ -65,13 +70,12 @@ mod feat_csr_ssr {
use super::*;
/// Push a component creation, first render and first rendered [Runnable]s to be executed
pub(crate) fn push_component_create(
component_id: usize,
create: Box<dyn Runnable>,
first_render: Box<dyn Runnable>,
) {
with(|s| {
s.create.push(create);
s.render_first.insert(component_id, first_render);
s.render_first.push_back(first_render);
});
}

Expand Down Expand Up @@ -113,15 +117,25 @@ mod feat_csr {
}
});
}

pub(crate) fn push_component_first_render(render: Box<dyn Runnable>) {
with(|s| {
s.render_first.push_back(render);
});
}
}

#[cfg(feature = "hydration")]
mod feat_hydration {
use super::*;

pub(crate) fn push_component_first_render(component_id: usize, render: Box<dyn Runnable>) {
pub(crate) fn push_component_hydrate(
create: Box<dyn Runnable>,
first_hydrate: Box<dyn Runnable>,
) {
with(|s| {
s.render_first.insert(component_id, render);
s.create.push(create);
s.hydrate_first.push_back(first_hydrate);
});
}
}
Expand Down Expand Up @@ -210,20 +224,21 @@ impl Scheduler {
return;
}

// First hydrate must never be skipped and takes priority over first render.
//
// Should be processed one at time, because they can spawn more create and rendered events
// for their children.
#[cfg(feature = "hydration")]
if let Some(r) = self.hydrate_first.pop_front() {
to_run.push(r);
}

// First render must never be skipped and takes priority over main, because it may need
// to init `NodeRef`s
//
// Should be processed one at time, because they can spawn more create and rendered events
// for their children.
//
// To be replaced with BTreeMap::pop_first once it is stable.
if let Some(r) = self
.render_first
.keys()
.next()
.cloned()
.and_then(|m| self.render_first.remove(&m))
{
if let Some(r) = self.render_first.pop_front() {
to_run.push(r);
}

Expand Down

0 comments on commit 69da10a

Please sign in to comment.