Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Split render and rendered into separate task queues #1360

Merged
merged 2 commits into from
Jul 1, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 14 additions & 11 deletions yew-functional/src/use_context_hook.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,18 @@ use yew::{Children, Component, ComponentLink, Html, Properties};
type ConsumerCallback<T> = Box<dyn Fn(Rc<T>)>;

#[derive(Clone, PartialEq, Properties)]
pub struct ContextProviderProps<T: Clone> {
pub struct ContextProviderProps<T: Clone + PartialEq> {
pub context: T,
pub children: Children,
}

pub struct ContextProvider<T: Clone + 'static> {
pub struct ContextProvider<T: Clone + PartialEq + 'static> {
context: Rc<T>,
children: Children,
consumers: RefCell<Vec<Weak<ConsumerCallback<T>>>>,
}

impl<T: Clone> ContextProvider<T> {
impl<T: Clone + PartialEq> ContextProvider<T> {
/// Add the callback to the subscriber list to be called whenever the context changes.
/// The consumer is unsubscribed as soon as the callback is dropped.
fn subscribe_consumer(&self, mut callback: Weak<ConsumerCallback<T>>) {
Expand Down Expand Up @@ -53,7 +53,7 @@ impl<T: Clone> ContextProvider<T> {
}
}

impl<T: Clone + 'static> Component for ContextProvider<T> {
impl<T: Clone + PartialEq + 'static> Component for ContextProvider<T> {
type Message = ();
type Properties = ContextProviderProps<T>;

Expand All @@ -77,8 +77,11 @@ impl<T: Clone + 'static> Component for ContextProvider<T> {
false
};

self.context = Rc::new(props.context);
self.notify_consumers();
let new_context = Rc::new(props.context);
if self.context != new_context {
self.context = new_context;
self.notify_consumers();
}

should_render
}
Expand All @@ -88,7 +91,7 @@ impl<T: Clone + 'static> Component for ContextProvider<T> {
}
}

fn find_context_provider_scope<T: 'static + Clone>(
fn find_context_provider_scope<T: Clone + PartialEq + 'static>(
scope: &AnyScope,
) -> Option<Scope<ContextProvider<T>>> {
let expected_type_id = TypeId::of::<ContextProvider<T>>();
Expand All @@ -104,24 +107,24 @@ fn with_provider_component<T, F, R>(
f: F,
) -> Option<R>
where
T: Clone,
T: Clone + PartialEq,
F: FnOnce(&ContextProvider<T>) -> R,
{
provider_scope
.as_ref()
.and_then(|scope| scope.get_component().map(|comp| f(&*comp)))
}

pub fn use_context<T: 'static + Clone>() -> Option<Rc<T>> {
pub fn use_context<T: Clone + PartialEq + 'static>() -> Option<Rc<T>> {
let scope = get_current_scope()
.expect("No current Scope. `use_context` can only be called inside functional components");

struct UseContextState<T2: 'static + Clone> {
struct UseContextState<T2: Clone + PartialEq + 'static> {
provider_scope: Option<Scope<ContextProvider<T2>>>,
current_context: Option<Rc<T2>>,
callback: Option<Rc<ConsumerCallback<T2>>>,
}
impl<T: 'static + Clone> Hook for UseContextState<T> {
impl<T: Clone + PartialEq + 'static> Hook for UseContextState<T> {
fn tear_down(&mut self) {
if let Some(cb) = self.callback.take() {
drop(cb);
Expand Down
16 changes: 11 additions & 5 deletions yew-functional/tests/use_context_hook.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ fn obtain_result(id: &str) -> String {

#[wasm_bindgen_test]
fn use_context_scoping_works() {
#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq)]
struct ExampleContext(String);
struct UseContextFunctionOuter {}
struct UseContextFunctionInner {}
Expand Down Expand Up @@ -178,14 +178,15 @@ fn use_context_works_with_multiple_types() {

#[wasm_bindgen_test]
fn use_context_update_works() {
#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq)]
struct MyContext(String);

#[derive(Clone, Debug, PartialEq, Properties)]
struct RenderCounterProps {
id: String,
children: Children,
}

struct RenderCounterFunction;
impl FunctionProvider for RenderCounterFunction {
type TProps = RenderCounterProps;
Expand Down Expand Up @@ -283,7 +284,12 @@ fn use_context_update_works() {
let app: App<TestComponent> = yew::App::new();
app.mount(yew::utils::document().get_element_by_id("output").unwrap());

assert_eq!(obtain_result("test-0"), "total: 1");
assert_eq!(obtain_result("test-1"), "current: hello world!, total: 1");
assert_eq!(obtain_result("test-2"), "current: hello world!, total: 1");
// 1 initial render + 3 update steps
assert_eq!(obtain_result("test-0"), "total: 4");

// 1 initial + 2 context update
assert_eq!(obtain_result("test-1"), "current: hello world!, total: 3");

// 1 initial + 1 context update + 1 magic update + 1 context update
assert_eq!(obtain_result("test-2"), "current: hello world!, total: 4");
}
84 changes: 81 additions & 3 deletions yew/src/html/scope.rs
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,7 @@ struct ComponentState<COMP: Component> {
placeholder: Option<VNode>,
last_root: Option<VNode>,
new_root: Option<VNode>,
has_rendered: bool,
}

impl<COMP: Component> ComponentState<COMP> {
Expand All @@ -307,6 +308,7 @@ impl<COMP: Component> ComponentState<COMP> {
placeholder,
last_root: None,
new_root: None,
has_rendered: false,
}
}
}
Expand Down Expand Up @@ -381,7 +383,7 @@ where
}
}

/// A `Runnable` task which renders a `Component` and calls its `rendered()` method.
/// A `Runnable` task which renders a `Component`.
struct RenderComponent<COMP>
where
COMP: Component,
Expand All @@ -395,6 +397,7 @@ where
COMP: Component,
{
fn run(self: Box<Self>) {
let state_clone = self.state.clone();
if let Some(mut state) = self.state.borrow_mut().as_mut() {
// Skip render if we haven't seen the "first render" yet
if !self.first_render && state.last_root.is_none() {
Expand All @@ -408,12 +411,44 @@ where
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);
scheduler().push_comp(
ComponentRunnableType::Rendered,
Box::new(RenderedComponent {
state: state_clone,
first_render: self.first_render,
}),
);
}
}
}
}

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

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

state.has_rendered = true;
state.component.rendered(self.first_render);
}
}
}

/// A `Runnable` task which calls the `destroy()` method on a `Component`.
struct DestroyComponent<COMP>
where
Expand Down Expand Up @@ -447,6 +482,43 @@ mod tests {
#[cfg(feature = "wasm_test")]
wasm_bindgen_test_configure!(run_in_browser);

#[derive(Clone, Properties, Default)]
struct ChildProps {
lifecycle: Rc<RefCell<Vec<String>>>,
}

struct Child {
props: ChildProps,
}

impl Component for Child {
type Message = ();
type Properties = ChildProps;

fn create(props: Self::Properties, _link: ComponentLink<Self>) -> Self {
Child { props }
}

fn rendered(&mut self, _first_render: bool) {
self.props
.lifecycle
.borrow_mut()
.push("child rendered".into());
}

fn update(&mut self, _: Self::Message) -> ShouldRender {
false
}

fn change(&mut self, _: Self::Properties) -> ShouldRender {
false
}

fn view(&self) -> Html {
html! {}
}
}

#[derive(Clone, Properties, Default)]
struct Props {
lifecycle: Rc<RefCell<Vec<String>>>,
Expand Down Expand Up @@ -500,7 +572,7 @@ mod tests {
self.link.send_message(msg);
}
self.props.lifecycle.borrow_mut().push("view".into());
html! {}
html! { <Child lifecycle=self.props.lifecycle.clone() /> }
}
}

Expand Down Expand Up @@ -534,6 +606,7 @@ mod tests {
&vec![
"create".to_string(),
"view".to_string(),
"child rendered".to_string(),
"rendered(true)".to_string(),
],
);
Expand All @@ -548,6 +621,7 @@ mod tests {
"create".to_string(),
"update(false)".to_string(),
"view".to_string(),
"child rendered".to_string(),
"rendered(true)".to_string(),
],
);
Expand All @@ -563,6 +637,7 @@ mod tests {
"view".to_string(),
"update(true)".to_string(),
"view".to_string(),
"child rendered".to_string(),
"rendered(true)".to_string(),
],
);
Expand All @@ -577,6 +652,7 @@ mod tests {
"create".to_string(),
"view".to_string(),
"update(false)".to_string(),
"child rendered".to_string(),
"rendered(true)".to_string(),
],
);
Expand All @@ -590,6 +666,7 @@ mod tests {
&vec![
"create".to_string(),
"view".to_string(),
"child rendered".to_string(),
"rendered(true)".to_string(),
"update(false)".to_string(),
],
Expand All @@ -604,6 +681,7 @@ mod tests {
&vec![
"create".to_string(),
"view".to_string(),
"child rendered".to_string(),
"rendered(true)".to_string(),
"update(true)".to_string(),
"view".to_string(),
Expand Down
13 changes: 9 additions & 4 deletions yew/src/scheduler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ pub(crate) enum ComponentRunnableType {
Create,
Update,
Render,
Rendered,
}

#[derive(Clone)]
Expand All @@ -43,9 +44,10 @@ struct ComponentScheduler {
destroy: Shared<VecDeque<Box<dyn Runnable>>>,
create: Shared<VecDeque<Box<dyn Runnable>>>,
update: Shared<VecDeque<Box<dyn Runnable>>>,
render: Shared<VecDeque<Box<dyn Runnable>>>,

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

impl ComponentScheduler {
Expand All @@ -54,15 +56,17 @@ impl ComponentScheduler {
destroy: Rc::new(RefCell::new(VecDeque::new())),
create: Rc::new(RefCell::new(VecDeque::new())),
update: Rc::new(RefCell::new(VecDeque::new())),
render: Rc::new(RefCell::new(Vec::new())),
render: Rc::new(RefCell::new(VecDeque::new())),
rendered: 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.render.borrow_mut().pop())
.or_else(|| self.render.borrow_mut().pop_front())
.or_else(|| self.rendered.borrow_mut().pop())
}
}

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