Skip to content

Commit

Permalink
Split update and render operations into seprate tasks
Browse files Browse the repository at this point in the history
  • Loading branch information
jstarry committed Jun 15, 2020
1 parent bb6e46d commit 1f88e1a
Show file tree
Hide file tree
Showing 2 changed files with 80 additions and 81 deletions.
151 changes: 75 additions & 76 deletions yew/src/html/scope.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,17 +144,17 @@ impl<COMP: Component> Scope<COMP> {
update,
};
scheduler().push_comp(ComponentRunnableType::Update, Box::new(update));
self.rendered(first_update);
self.render(first_update);
}

/// Schedules a task to call the rendered method on a component
pub(crate) fn rendered(&self, first_render: bool) {
/// Schedules a task to render the component and call its rendered method
pub(crate) fn render(&self, first_render: bool) {
let state = self.state.clone();
let rendered = RenderedComponent {
let rendered = RenderComponent {
state,
first_render,
};
scheduler().push_comp(ComponentRunnableType::Rendered, Box::new(rendered));
scheduler().push_comp(ComponentRunnableType::Render, Box::new(rendered));
}

/// Schedules a task to destroy a component
Expand Down Expand Up @@ -244,17 +244,14 @@ impl<COMP: Component> Scope<COMP> {
}
}

type Dirty = bool;
const DIRTY: Dirty = true;

struct ComponentState<COMP: Component> {
parent: Element,
next_sibling: NodeRef,
node_ref: NodeRef,
scope: Scope<COMP>,
component: Box<COMP>,
last_root: Option<VNode>,
render_status: Option<Dirty>,
new_root: Option<VNode>,
}

impl<COMP: Component> ComponentState<COMP> {
Expand All @@ -276,7 +273,7 @@ impl<COMP: Component> ComponentState<COMP> {
scope,
component,
last_root: ancestor,
render_status: None,
new_root: None,
}
}
}
Expand Down Expand Up @@ -348,44 +345,41 @@ where
};

if should_update {
state.render_status = state.render_status.map(|_| DIRTY);
let mut root = state.component.render();
let last_root = state.last_root.take();
let parent_scope = state.scope.clone().into();
let next_sibling = state.next_sibling.clone();
let node = root.apply(&parent_scope, &state.parent, next_sibling, last_root);
state.node_ref.link(node);
state.last_root = Some(root);
state.new_root = Some(state.component.render());
};
}
}
}

/// A `Runnable` task which calls the `rendered()` method on a `Component`.
struct RenderedComponent<COMP>
/// A `Runnable` task which renders a `Component` and calls its `rendered()` method.
struct RenderComponent<COMP>
where
COMP: Component,
{
state: Shared<Option<ComponentState<COMP>>>,
first_render: bool,
}

impl<COMP> Runnable for RenderedComponent<COMP>
impl<COMP> Runnable for RenderComponent<COMP>
where
COMP: Component,
{
fn run(self: Box<Self>) {
if let Some(mut state) = self.state.borrow_mut().as_mut() {
if self.first_render && state.render_status.is_some() {
// Skip render if we haven't seen the "first render" yet
if !self.first_render && state.last_root.is_none() {
return;
}

if !self.first_render && state.render_status != Some(DIRTY) {
return;
if let Some(mut new_root) = state.new_root.take() {
let last_root = state.last_root.take();
let parent_scope = state.scope.clone().into();
let next_sibling = state.next_sibling.clone();
let node = new_root.apply(&parent_scope, &state.parent, next_sibling, last_root);
state.node_ref.link(node);
state.last_root = Some(new_root);
state.component.rendered(self.first_render);
}

state.render_status = Some(!DIRTY);
state.component.rendered(self.first_render);
}
}
}
Expand Down Expand Up @@ -423,14 +417,16 @@ mod tests {
#[cfg(feature = "wasm_test")]
wasm_bindgen_test_configure!(run_in_browser);

#[derive(Clone, Properties)]
#[derive(Clone, Properties, Default)]
struct Props {
lifecycle: Rc<RefCell<Vec<String>>>,
create_message: Option<bool>,
view_message: RefCell<Option<bool>>,
}

struct Comp {
props: Props,
link: ComponentLink<Self>,
}

impl Component for Comp {
Expand All @@ -442,7 +438,7 @@ mod tests {
if let Some(msg) = props.create_message {
link.send_message(msg);
}
Comp { props }
Comp { props, link }
}

fn rendered(&mut self, first_render: bool) {
Expand All @@ -466,6 +462,9 @@ mod tests {
}

fn view(&self) -> Html {
if let Some(msg) = self.props.view_message.borrow_mut().take() {
self.link.send_message(msg);
}
self.props.lifecycle.borrow_mut().push("view".into());
html! {}
}
Expand All @@ -477,75 +476,75 @@ mod tests {
}
}

#[test]
fn mount() {
fn test_lifecycle(props: Props, expected: &[String]) {
let document = crate::utils::document();
let lifecycle: Rc<RefCell<Vec<String>>> = Rc::default();
let props = Props {
lifecycle: lifecycle.clone(),
create_message: None,
};

let scope = Scope::<Comp>::new(None);
let el = document.create_element("div").unwrap();
let lifecycle = props.lifecycle.clone();

lifecycle.borrow_mut().clear();
scope.mount_in_place(el, NodeRef::default(), None, NodeRef::default(), props);

assert_eq!(
lifecycle.borrow_mut().deref(),
&vec![
"create".to_string(),
"view".to_string(),
"rendered(true)".to_string()
]
);
assert_eq!(&lifecycle.borrow_mut().deref()[..], expected);
}

#[test]
fn mount_with_create_message() {
let document = crate::utils::document();
fn lifecyle_tests() {
let lifecycle: Rc<RefCell<Vec<String>>> = Rc::default();
let props = Props {
lifecycle: lifecycle.clone(),
create_message: Some(false),
};

let scope = Scope::<Comp>::new(None);
let el = document.create_element("div").unwrap();
scope.mount_in_place(el, NodeRef::default(), None, NodeRef::default(), props);
test_lifecycle(
Props {
lifecycle: lifecycle.clone(),
..Props::default()
},
&vec![
"create".to_string(),
"view".to_string(),
"rendered(true)".to_string(),
],
);

assert_eq!(
lifecycle.borrow_mut().deref(),
test_lifecycle(
Props {
lifecycle: lifecycle.clone(),
create_message: Some(false),
..Props::default()
},
&vec![
"create".to_string(),
"update(false)".to_string(),
"view".to_string(),
"rendered(true)".to_string()
]
"rendered(true)".to_string(),
],
);
}

#[test]
fn mount_with_create_render_message() {
let document = crate::utils::document();
let lifecycle: Rc<RefCell<Vec<String>>> = Rc::default();
let props = Props {
lifecycle: lifecycle.clone(),
create_message: Some(true),
};

let scope = Scope::<Comp>::new(None);
let el = document.create_element("div").unwrap();
scope.mount_in_place(el, NodeRef::default(), None, NodeRef::default(), props);

assert_eq!(
lifecycle.borrow_mut().deref(),
test_lifecycle(
Props {
lifecycle: lifecycle.clone(),
view_message: RefCell::new(Some(true)),
..Props::default()
},
&vec![
"create".to_string(),
"view".to_string(),
"update(true)".to_string(),
"view".to_string(),
"rendered(true)".to_string(),
],
);

test_lifecycle(
Props {
lifecycle: lifecycle.clone(),
view_message: RefCell::new(Some(false)),
..Props::default()
},
&vec![
"create".to_string(),
"view".to_string(),
"rendered(true)".to_string()
]
"update(false)".to_string(),
"rendered(true)".to_string(),
],
);
}
}
10 changes: 5 additions & 5 deletions yew/src/scheduler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ pub(crate) enum ComponentRunnableType {
Destroy,
Create,
Update,
Rendered,
Render,
}

#[derive(Clone)]
Expand All @@ -45,7 +45,7 @@ struct ComponentScheduler {
update: Shared<VecDeque<Box<dyn Runnable>>>,

// Stack
rendered: Shared<Vec<Box<dyn Runnable>>>,
render: Shared<Vec<Box<dyn Runnable>>>,
}

impl ComponentScheduler {
Expand All @@ -54,15 +54,15 @@ impl ComponentScheduler {
destroy: Rc::new(RefCell::new(VecDeque::new())),
create: Rc::new(RefCell::new(VecDeque::new())),
update: Rc::new(RefCell::new(VecDeque::new())),
rendered: Rc::new(RefCell::new(Vec::new())),
render: Rc::new(RefCell::new(Vec::new())),
}
}

fn next_runnable(&self) -> Option<Box<dyn Runnable>> {
None.or_else(|| self.destroy.borrow_mut().pop_front())
.or_else(|| self.create.borrow_mut().pop_front())
.or_else(|| self.update.borrow_mut().pop_front())
.or_else(|| self.rendered.borrow_mut().pop())
.or_else(|| self.render.borrow_mut().pop())
}
}

Expand All @@ -82,7 +82,7 @@ impl Scheduler {
}
ComponentRunnableType::Create => self.component.create.borrow_mut().push_back(runnable),
ComponentRunnableType::Update => self.component.update.borrow_mut().push_back(runnable),
ComponentRunnableType::Rendered => self.component.rendered.borrow_mut().push(runnable),
ComponentRunnableType::Render => self.component.render.borrow_mut().push(runnable),
};
self.start();
}
Expand Down

0 comments on commit 1f88e1a

Please sign in to comment.