Skip to content

Commit

Permalink
Simplify component state
Browse files Browse the repository at this point in the history
  • Loading branch information
jstarry committed May 4, 2020
1 parent e4a609f commit 6e01b54
Show file tree
Hide file tree
Showing 2 changed files with 80 additions and 192 deletions.
267 changes: 80 additions & 187 deletions yew/src/html/scope.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ cfg_if! {

/// Updates for a `Component` instance. Used by scope sender.
pub(crate) enum ComponentUpdate<COMP: Component> {
/// Force update
Force,
/// Wraps messages for a component.
Message(COMP::Message),
/// Wraps batch of messages for a component.
Expand All @@ -33,16 +35,6 @@ pub struct AnyScope {
state: Rc<dyn Any>,
}

impl Default for AnyScope {
fn default() -> Self {
Self {
type_id: TypeId::of::<()>(),
parent: None,
state: Rc::new(()),
}
}
}

impl<COMP: Component> From<Scope<COMP>> for AnyScope {
fn from(scope: Scope<COMP>) -> Self {
AnyScope {
Expand Down Expand Up @@ -70,7 +62,7 @@ impl AnyScope {
parent: self.parent,
state: self
.state
.downcast_ref::<Shared<ComponentState<COMP>>>()
.downcast_ref::<Shared<Option<ComponentState<COMP>>>>()
.expect("unexpected component type")
.clone(),
}
Expand All @@ -80,7 +72,7 @@ impl AnyScope {
/// A context which allows sending messages to a component.
pub struct Scope<COMP: Component> {
parent: Option<Rc<AnyScope>>,
state: Shared<ComponentState<COMP>>,
state: Shared<Option<ComponentState<COMP>>>,
}

impl<COMP: Component> fmt::Debug for Scope<COMP> {
Expand All @@ -107,14 +99,16 @@ impl<COMP: Component> Scope<COMP> {
/// Returns the linked component if available
pub fn get_component(&self) -> Option<impl Deref<Target = COMP> + '_> {
self.state.try_borrow().ok().and_then(|state_ref| {
state_ref.component()?;
Some(Ref::map(state_ref, |this| this.component().unwrap()))
state_ref.as_ref()?;
Some(Ref::map(state_ref, |state| {
state.as_ref().unwrap().component.as_ref()
}))
})
}

pub(crate) fn new(parent: Option<AnyScope>) -> Self {
let parent = parent.map(Rc::new);
let state = Rc::new(RefCell::new(ComponentState::Empty));
let state = Rc::new(RefCell::new(None));
Scope { parent, state }
}

Expand All @@ -126,25 +120,15 @@ impl<COMP: Component> Scope<COMP> {
node_ref: NodeRef,
props: COMP::Properties,
) -> Scope<COMP> {
let mut scope = self;
let ready_state = ReadyState {
*self.state.borrow_mut() = Some(ComponentState::new(
element,
ancestor,
node_ref,
scope: scope.clone(),
self.clone(),
props,
ancestor,
};
*scope.state.borrow_mut() = ComponentState::Ready(ready_state);
scope.create();
scope
}

/// Schedules a task to create and render a component and then mount it to the DOM
pub(crate) fn create(&mut self) {
let state = self.state.clone();
let create = CreateComponent { state };
scheduler().push_comp(ComponentRunnableType::Create, Box::new(create));
self.rendered(true);
));
self.update(ComponentUpdate::Force);
self
}

/// Schedules a task to send a message or new props to a component
Expand Down Expand Up @@ -234,100 +218,40 @@ impl<COMP: Component> Scope<COMP> {
}
}

enum ComponentState<COMP: Component> {
Empty,
Ready(ReadyState<COMP>),
Created(CreatedState<COMP>),
Processing,
Destroyed,
}

impl<COMP: Component> ComponentState<COMP> {
fn component(&self) -> Option<&COMP> {
match self {
ComponentState::Created(state) => Some(&state.component),
_ => None,
}
}
}

impl<COMP: Component> fmt::Display for ComponentState<COMP> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let name = match self {
ComponentState::Empty => "empty",
ComponentState::Ready(_) => "ready",
ComponentState::Created(_) => "created",
ComponentState::Processing => "processing",
ComponentState::Destroyed => "destroyed",
};
write!(f, "{}", name)
}
}

struct ReadyState<COMP: Component> {
struct ComponentState<COMP: Component> {
element: Element,
node_ref: NodeRef,
props: COMP::Properties,
scope: Scope<COMP>,
ancestor: Option<VNode>,
}

impl<COMP: Component> ReadyState<COMP> {
fn create(self) -> CreatedState<COMP> {
CreatedState {
rendered: false,
component: COMP::create(self.props, self.scope.clone()),
element: self.element,
last_frame: self.ancestor,
node_ref: self.node_ref,
scope: self.scope,
}
}
}

struct CreatedState<COMP: Component> {
component: Box<COMP>,
last_root: Option<VNode>,
rendered: bool,
element: Element,
component: COMP,
last_frame: Option<VNode>,
node_ref: NodeRef,
scope: Scope<COMP>,
}

impl<COMP: Component> CreatedState<COMP> {
/// Called after a component and all of its children have been rendered.
fn rendered(mut self, first_render: bool) -> Self {
self.rendered = true;
self.component.rendered(first_render);
self
}

fn update(mut self) -> Self {
let mut root = self.component.render();
if let Some(node) = root.apply(
&self.scope.clone().into(),
&self.element,
None,
self.last_frame,
) {
self.node_ref.set(Some(node));
} else if let VNode::VComp(child) = &root {
// If the root VNode is a VComp, we won't have access to the rendered DOM node
// because components render asynchronously. In order to bubble up the DOM node
// from the VComp, we need to link the currently rendering component with its
// root child component.
self.node_ref.link(child.node_ref.clone());
impl<COMP: Component> ComponentState<COMP> {
fn new(
element: Element,
ancestor: Option<VNode>,
node_ref: NodeRef,
scope: Scope<COMP>,
props: COMP::Properties,
) -> Self {
let component = Box::new(COMP::create(props, scope.clone()));
Self {
element,
node_ref,
scope,
component,
last_root: ancestor,
rendered: false,
}
self.last_frame = Some(root);
self
}
}

struct RenderedComponent<COMP>
where
COMP: Component,
{
state: Shared<ComponentState<COMP>>,
state: Shared<Option<ComponentState<COMP>>>,
first_render: bool,
}

Expand All @@ -336,77 +260,41 @@ where
COMP: Component,
{
fn run(self: Box<Self>) {
let current_state = self.state.replace(ComponentState::Processing);
self.state.replace(match current_state {
ComponentState::Created(s) if !s.rendered => {
ComponentState::Created(s.rendered(self.first_render))
}
ComponentState::Destroyed | ComponentState::Created(_) => current_state,
ComponentState::Empty | ComponentState::Processing | ComponentState::Ready(_) => {
panic!("unexpected component state: {}", current_state);
}
});
}
}

struct CreateComponent<COMP>
where
COMP: Component,
{
state: Shared<ComponentState<COMP>>,
}

impl<COMP> Runnable for CreateComponent<COMP>
where
COMP: Component,
{
fn run(self: Box<Self>) {
let current_state = self.state.replace(ComponentState::Processing);
self.state.replace(match current_state {
ComponentState::Ready(s) => ComponentState::Created(s.create().update()),
ComponentState::Created(_) | ComponentState::Destroyed => current_state,
ComponentState::Empty | ComponentState::Processing => {
panic!("unexpected component state: {}", current_state);
if let Some(mut state) = self.state.borrow_mut().as_mut() {
if !state.rendered {
state.rendered = true;
state.component.rendered(self.first_render);
}
});
}
}
}

struct DestroyComponent<COMP>
where
COMP: Component,
{
state: Shared<ComponentState<COMP>>,
state: Shared<Option<ComponentState<COMP>>>,
}

impl<COMP> Runnable for DestroyComponent<COMP>
where
COMP: Component,
{
fn run(self: Box<Self>) {
match self.state.replace(ComponentState::Destroyed) {
ComponentState::Created(mut this) => {
this.component.destroy();
if let Some(last_frame) = &mut this.last_frame {
last_frame.detach(&this.element);
}
if let Some(mut state) = self.state.borrow_mut().take() {
state.component.destroy();
if let Some(last_frame) = &mut state.last_root {
last_frame.detach(&state.element);
}
ComponentState::Ready(mut this) => {
if let Some(ancestor) = &mut this.ancestor {
ancestor.detach(&this.element);
}
}
ComponentState::Empty | ComponentState::Destroyed => {}
s @ ComponentState::Processing => panic!("unexpected component state: {}", s),
};
}
}
}

struct UpdateComponent<COMP>
where
COMP: Component,
{
state: Shared<ComponentState<COMP>>,
state: Shared<Option<ComponentState<COMP>>>,
update: ComponentUpdate<COMP>,
}

Expand All @@ -415,33 +303,38 @@ where
COMP: Component,
{
fn run(self: Box<Self>) {
let current_state = self.state.replace(ComponentState::Processing);
self.state.replace(match current_state {
ComponentState::Created(mut this) => {
let should_update = match self.update {
ComponentUpdate::Message(message) => this.component.update(message),
ComponentUpdate::MessageBatch(messages) => messages
.into_iter()
.fold(false, |acc, msg| this.component.update(msg) || acc),
ComponentUpdate::Properties(props, node_ref) => {
// When components are updated, they receive a new node ref that
// must be linked to previous one.
node_ref.link(this.node_ref.clone());
this.component.change(props)
}
};
let next_state = if should_update {
this.rendered = false;
this.update()
} else {
this
};
ComponentState::Created(next_state)
}
ComponentState::Destroyed => current_state,
ComponentState::Processing | ComponentState::Ready(_) | ComponentState::Empty => {
panic!("unexpected component state: {}", current_state);
}
});
if let Some(mut state) = self.state.borrow_mut().as_mut() {
let should_update = match self.update {
ComponentUpdate::Force => true,
ComponentUpdate::Message(message) => state.component.update(message),
ComponentUpdate::MessageBatch(messages) => messages
.into_iter()
.fold(false, |acc, msg| state.component.update(msg) || acc),
ComponentUpdate::Properties(props, node_ref) => {
// When components are updated, they receive a new node ref that
// must be linked to previous one.
node_ref.link(state.node_ref.clone());
state.component.change(props)
}
};

if should_update {
state.rendered = false;
let mut root = state.component.render();
let last_root = state.last_root.take();
if let Some(node) =
root.apply(&state.scope.clone().into(), &state.element, None, last_root)
{
state.node_ref.set(Some(node));
} else if let VNode::VComp(child) = &root {
// If the root VNode is a VComp, we won't have access to the rendered DOM node
// because components render asynchronously. In order to bubble up the DOM node
// from the VComp, we need to link the currently rendering component with its
// root child component.
state.node_ref.link(child.node_ref.clone());
}
state.last_root = Some(root);
};
}
}
}

0 comments on commit 6e01b54

Please sign in to comment.