From c7ef19a943bcc1aaf505544bac388db508bccdb5 Mon Sep 17 00:00:00 2001 From: Ryan McGrath Date: Fri, 24 May 2019 21:44:35 -0700 Subject: [PATCH] Slight tweak to how gets created on demand. We use an now, for mutability/immutability needs in the reconciliation process. Not sure offhand how much the double lock of will affect things, but that's a problem for later. --- alchemy/src/components/view.rs | 6 +-- alchemy/src/reconciler.rs | 64 +++++++++++++++++-------------- alchemy/src/window/window.rs | 8 ++-- lifecycle/src/rsx/mod.rs | 4 +- lifecycle/src/rsx/props.rs | 2 + lifecycle/src/rsx/virtual_node.rs | 6 +-- lifecycle/src/traits.rs | 2 +- macros/src/rsx.rs | 4 +- 8 files changed, 53 insertions(+), 43 deletions(-) diff --git a/alchemy/src/components/view.rs b/alchemy/src/components/view.rs index 71f1411..e969467 100644 --- a/alchemy/src/components/view.rs +++ b/alchemy/src/components/view.rs @@ -3,7 +3,7 @@ //! hence why they're all (somewhat annoyingly, but lovingly) re-implemented //! as bridges. -use std::sync::{Arc, Mutex}; +use std::sync::{Arc, Mutex, RwLock}; use alchemy_styles::styles::{Layout, Style}; @@ -40,7 +40,7 @@ impl Component for View { Some(bridge.borrow_native_backing_node()) } - fn append_child_component(&self, component: &Arc) { + fn append_child_component(&self, component: &Component) { if let Some(child) = component.borrow_native_backing_node() { let mut bridge = self.0.lock().unwrap(); bridge.append_child(child); @@ -53,7 +53,7 @@ impl Component for View { } fn render(&self, props: &Props) -> Result { - Ok(RSX::node("Fragment", || Box::new(Fragment::default()), Props { + Ok(RSX::node("Fragment", || Arc::new(RwLock::new(Fragment::default())), Props { attributes: std::collections::HashMap::new(), key: "".into(), styles: StylesList::new(), diff --git a/alchemy/src/reconciler.rs b/alchemy/src/reconciler.rs index 1e75309..693577b 100644 --- a/alchemy/src/reconciler.rs +++ b/alchemy/src/reconciler.rs @@ -76,7 +76,10 @@ pub fn diff_and_patch_tree(old: RSX, new: RSX, stretch: &mut Stretch, depth: usi // This None path should never be hit, we just need to use a rather verbose pattern // here. It's unsightly, I know. let is_native_backed = match &new_element.instance { - Some(instance) => instance.has_native_backing_node(), + Some(instance) => { + let lock = instance.read().unwrap(); + lock.has_native_backing_node() + }, None => false }; @@ -174,7 +177,8 @@ fn configure_styles(style_keys: &StylesList, style: &mut Style) { /// Walks the tree and applies styles. This happens after a layout computation, typically. pub(crate) fn walk_and_apply_styles(node: &VirtualNode, layout_manager: &mut Stretch) -> Result<(), Box> { if let (Some(layout_node), Some(instance)) = (node.layout_node, &node.instance) { - instance.apply_styles( + let component = instance.write().unwrap(); + component.apply_styles( layout_manager.layout(layout_node)?, layout_manager.style(layout_node)? ); @@ -195,7 +199,9 @@ fn find_and_link_layout_nodes(parent_node: &mut VirtualNode, child_tree: &mut Vi if let (Some(parent_instance), Some(child_instance)) = (&mut parent_node.instance, &mut child_tree.instance) { if let (Some(parent_layout_node), Some(child_layout_node)) = (&parent_node.layout_node, &child_tree.layout_node) { stretch.add_child(*parent_layout_node, *child_layout_node)?; - parent_instance.append_child_component(child_instance); + if let (parent_component, child_component) = (parent_instance.write().unwrap(), child_instance.read().unwrap()) { + parent_component.append_child_component(&*child_component); + } return Ok(()); } } @@ -213,25 +219,30 @@ fn find_and_link_layout_nodes(parent_node: &mut VirtualNode, child_tree: &mut Vi /// view tree, firing various lifecycle methods, and ensuring that nodes for layout /// passes are configured. fn mount_component_tree(mut new_element: VirtualNode, stretch: &mut Stretch) -> Result> { - let mut instance = (new_element.create_component_fn)(); - // "compute" props, set on instance - // instance.get_derived_state_from_props(props) + let instance = (new_element.create_component_fn)(); - let is_native_backed = instance.has_native_backing_node(); - - if is_native_backed { - let mut style = Style::default(); - configure_styles(&new_element.props.styles, &mut style); - - let layout_node = stretch.new_node(style, vec![])?; - new_element.layout_node = Some(layout_node); - } + let mut is_native_backed = false; - let x: std::sync::Arc = instance.into(); - let renderer = x.clone(); - new_element.instance = Some(x); + let rendered = { + let component = instance.read().unwrap(); + // instance.get_derived_state_from_props(props) - let mut children = match renderer.render(&new_element.props) { + is_native_backed = component.has_native_backing_node(); + + if is_native_backed { + let mut style = Style::default(); + configure_styles(&new_element.props.styles, &mut style); + + let layout_node = stretch.new_node(style, vec![])?; + new_element.layout_node = Some(layout_node); + } + + component.render(&new_element.props) + }; + + new_element.instance = Some(instance); + + let mut children = match rendered { Ok(opt) => match opt { RSX::VirtualNode(child) => { let mut children = vec![]; @@ -241,27 +252,21 @@ fn mount_component_tree(mut new_element: VirtualNode, stretch: &mut Stretch) -> // tag similar to what React does, which just hoists the children out of it and // discards the rest. if child.tag == "Fragment" { - println!(" > In Fragment"); for child_node in child.props.children { if let RSX::VirtualNode(node) = child_node { let mut mounted = mount_component_tree(node, stretch)?; - println!(" > Mounted Fragment..."); if is_native_backed { - println!(" > Linking Fragment: {} {}", new_element.tag, mounted.tag); find_and_link_layout_nodes(&mut new_element, &mut mounted, stretch)?; } children.push(RSX::VirtualNode(mounted)); - } else { - println!(" > Mounting other type of node..."); } } } else { let mut mounted = mount_component_tree(child, stretch)?; if is_native_backed { - println!("Linking Child"); find_and_link_layout_nodes(&mut new_element, &mut mounted, stretch)?; } @@ -288,13 +293,14 @@ fn mount_component_tree(mut new_element: VirtualNode, stretch: &mut Stretch) -> new_element.children.append(&mut children); + if let Some(instance) = &mut new_element.instance { + let mut component = instance.write().unwrap(); + component.component_did_mount(&new_element.props); + } + // instance.get_snapshot_before_update() //renderer.component_did_mount(&new_element.props); - //let x: std::sync::Arc = instance.into(); - - // new_element.instance = Some(instance); - //new_element.instance = Some(x); Ok(new_element) } diff --git a/alchemy/src/window/window.rs b/alchemy/src/window/window.rs index a60fae0..5b2fa65 100644 --- a/alchemy/src/window/window.rs +++ b/alchemy/src/window/window.rs @@ -1,7 +1,7 @@ //! Implements the Window API. It attempts to provide a nice, common interface across //! per-platform Window APIs. -use std::sync::{Arc, Mutex}; +use std::sync::{Arc, Mutex, RwLock}; use alchemy_lifecycle::traits::{Component, WindowDelegate}; use alchemy_lifecycle::rsx::{Props, RSX}; @@ -19,11 +19,11 @@ use crate::reconciler::{diff_and_patch_tree, walk_and_apply_styles}; use alchemy_cocoa::window::{Window as PlatformWindowBridge}; /// Utility function for creating a root_node. -fn create_root_node(instance: Option>, layout_manager: &mut Stretch) -> RSX { +fn create_root_node(instance: Option>>, layout_manager: &mut Stretch) -> RSX { let mut props = Props::default(); props.styles = "root".into(); - let mut root_node = RSX::node("root", || Box::new(View::default()), props); + let mut root_node = RSX::node("root", || Arc::new(RwLock::new(View::default())), props); if let RSX::VirtualNode(root) = &mut root_node { root.layout_node = match instance.is_some() { @@ -159,7 +159,7 @@ impl Window { title: title.into(), bridge: bridge, delegate: Box::new(delegate), - root_node: create_root_node(Some(Arc::new(view)), &mut layout), + root_node: create_root_node(Some(Arc::new(RwLock::new(view))), &mut layout), layout: layout }))) } diff --git a/lifecycle/src/rsx/mod.rs b/lifecycle/src/rsx/mod.rs index e0c5e04..8ae096d 100644 --- a/lifecycle/src/rsx/mod.rs +++ b/lifecycle/src/rsx/mod.rs @@ -3,7 +3,7 @@ //! uses these to build and alter UI; they're typically returned from `render()` //! methods. -use std::sync::Arc; +use std::sync::{Arc, RwLock}; use std::fmt::{Debug, Display}; mod virtual_node; @@ -40,7 +40,7 @@ pub enum RSX { impl RSX { /// Shorthand method for creating a new `RSX::VirtualNode` instance. Rarely should you call /// this yourself; the `rsx! {}` macro handles this for you. - pub fn node Box + Send + Sync + 'static>(tag: &'static str, create_fn: F, props: Props) -> RSX { + pub fn node Arc> + Send + Sync + 'static>(tag: &'static str, create_fn: F, props: Props) -> RSX { RSX::VirtualNode(VirtualNode { tag: tag, create_component_fn: Arc::new(create_fn), diff --git a/lifecycle/src/rsx/props.rs b/lifecycle/src/rsx/props.rs index 4390cdf..b7c3017 100644 --- a/lifecycle/src/rsx/props.rs +++ b/lifecycle/src/rsx/props.rs @@ -1,9 +1,11 @@ //! Implements a Props struct that mostly acts as expected. For arbitrary primitive values, //! it shadows a `serde_json::Value`. +use std::sync::{Arc, RwLock}; use serde_json::Value; use std::collections::HashMap; +use crate::traits::{Component}; use crate::rsx::{RSX, StylesList}; /// A value stored inside the `attributes` field on a `Props` instance. diff --git a/lifecycle/src/rsx/virtual_node.rs b/lifecycle/src/rsx/virtual_node.rs index 926dd14..c2ec13b 100644 --- a/lifecycle/src/rsx/virtual_node.rs +++ b/lifecycle/src/rsx/virtual_node.rs @@ -1,7 +1,7 @@ //! Implements the `RSX::VirtualNode` struct, which is a bit of a recursive //! structure. -use std::sync::Arc; +use std::sync::{Arc, RwLock}; use std::fmt::{Display, Debug}; use alchemy_styles::node::Node; @@ -19,11 +19,11 @@ pub struct VirtualNode { /// `Component` instances are created on-demand, if the reconciler deems it be so. This /// is a closure that should return an instance of the correct type. - pub create_component_fn: Arc Box + Send + Sync + 'static>, + pub create_component_fn: Arc Arc> + Send + Sync + 'static>, /// A cached component instance, which is transferred between trees. Since `Component` /// instances are lazily created, this is an `Option`, and defaults to `None`. - pub instance: Option>, + pub instance: Option>>, /// A cached `Node` for computing `Layout` with `Stretch`. Some components may not have /// a need for layout (e.g, if they don't have a backing node), and thus this is optional. diff --git a/lifecycle/src/traits.rs b/lifecycle/src/traits.rs index 7170c92..c0384b8 100644 --- a/lifecycle/src/traits.rs +++ b/lifecycle/src/traits.rs @@ -85,7 +85,7 @@ pub trait Component: Send + Sync { /// If you implement a Native-backed component, you'll need to implement this. Given a /// `component`, you need to instruct the system how to append it to the tree at your point. - fn append_child_component(&self, _component: &Arc) {} + fn append_child_component(&self, _component: &Component) {} /// If you implement a Native-backed component, you'll need to implement this. Given a /// `component`, you need to instruct the system how to replace it in the tree at your point. diff --git a/macros/src/rsx.rs b/macros/src/rsx.rs index 0f67340..1ea4818 100644 --- a/macros/src/rsx.rs +++ b/macros/src/rsx.rs @@ -220,7 +220,9 @@ impl Element { let component_name = Literal::string(&typename.to_string()); Ok(quote!( - alchemy::RSX::node(#component_name, || Box::new(#typename::default()), alchemy::Props { + alchemy::RSX::node(#component_name, || { + std::sync::Arc::new(std::sync::RwLock::new(#typename::default())) + }, alchemy::Props { attributes: { let mut attributes = std::collections::HashMap::new(); #attributes