diff --git a/alchemy/src/components/text.rs b/alchemy/src/components/text.rs
index 1c20ed8..6bb292f 100644
--- a/alchemy/src/components/text.rs
+++ b/alchemy/src/components/text.rs
@@ -24,7 +24,6 @@ use alchemy_cocoa::text::{Text as PlatformTextBridge};
///
/// ```
pub struct Text {
- key: ComponentKey,
text: String,
bridge: Mutex
}
@@ -48,9 +47,8 @@ impl Text {
}
impl Component for Text {
- fn constructor(key: ComponentKey) -> Text {
+ fn constructor(_: ComponentKey) -> Text {
Text {
- key: key,
text: "".into(),
bridge: Mutex::new(PlatformTextBridge::new())
}
diff --git a/alchemy/src/window/window.rs b/alchemy/src/window/window.rs
index 4412e59..d4d383d 100644
--- a/alchemy/src/window/window.rs
+++ b/alchemy/src/window/window.rs
@@ -41,8 +41,8 @@ impl AppWindow {
}
};
- match RENDER_ENGINE.diff_and_render_root(&self.render_key, children) {
- Ok(_) => {}
+ match RENDER_ENGINE.diff_and_render_root(self.render_key, children) {
+ Ok(_) => { println!("RENDERED!!!!"); }
Err(e) => { eprintln!("Error rendering window! {}", e); }
}
}
diff --git a/cocoa/src/window.rs b/cocoa/src/window.rs
index d1c8586..fc8e43a 100644
--- a/cocoa/src/window.rs
+++ b/cocoa/src/window.rs
@@ -4,7 +4,7 @@
use std::sync::{Once, ONCE_INIT};
-use cocoa::base::{id, nil, YES, NO};
+use cocoa::base::{id, nil, /*YES,*/ NO};
use cocoa::appkit::{NSWindow, NSWindowStyleMask, NSBackingStoreType};
use cocoa::foundation::{NSRect, NSPoint, NSSize, NSString, NSAutoreleasePool};
diff --git a/lifecycle/src/reconciler/error.rs b/lifecycle/src/reconciler/error.rs
index ea72825..c3cb923 100644
--- a/lifecycle/src/reconciler/error.rs
+++ b/lifecycle/src/reconciler/error.rs
@@ -2,8 +2,6 @@
//! run. These are mostly internal to the rendering engine itself, but could potentially
//! show up elsewhere.
-use core::any::Any;
-
use crate::reconciler::key::ComponentKey;
#[derive(Debug)]
diff --git a/lifecycle/src/reconciler/instance.rs b/lifecycle/src/reconciler/instance.rs
new file mode 100644
index 0000000..07cfc03
--- /dev/null
+++ b/lifecycle/src/reconciler/instance.rs
@@ -0,0 +1,33 @@
+//! Internal struct used for tracking component instances and their
+//! associated metadata (layout, appearance, etc).
+
+use alchemy_styles::Appearance;
+use alchemy_styles::stretch::node::{Node as LayoutNode};
+
+use crate::rsx::Props;
+use crate::traits::Component;
+
+pub(crate) struct Instance {
+ pub(crate) tag: &'static str,
+ pub(crate) component: Box,
+ pub(crate) props: Props,
+ pub(crate) appearance: Appearance,
+ pub(crate) layout: Option
+}
+
+impl Instance {
+ pub(crate) fn new(
+ tag: &'static str,
+ component: Box,
+ props: Props,
+ layout: Option
+ ) -> Instance {
+ Instance {
+ tag: tag,
+ component: component,
+ props: props,
+ appearance: Appearance::default(),
+ layout: layout
+ }
+ }
+}
diff --git a/lifecycle/src/reconciler/mod.rs b/lifecycle/src/reconciler/mod.rs
index 0fd48b1..3e6ae6e 100644
--- a/lifecycle/src/reconciler/mod.rs
+++ b/lifecycle/src/reconciler/mod.rs
@@ -1,18 +1,18 @@
-//! Implements tree diffing, and attempts to cache Component instances where
-//! possible.
+//! Implements tree diffing, updating, and so on. Unlike a lot of the VDom implementations
+//! you find littered around the web, this is a bit more ECS-ish, and expects Components to retain
+//! their `ComponentKey` passed in their constructor if they want to update. Doing this
+//! enables us to avoid re-scanning or diffing an entire tree.
-use std::sync::{Arc, Mutex, RwLock};
-use std::collections::HashMap;
+use std::sync::Mutex;
use std::error::Error;
-use std::mem::{discriminant, swap};
use alchemy_styles::THEME_ENGINE;
-use alchemy_styles::styles::{Appearance,Dimension, Number, Size, Style};
+use alchemy_styles::styles::{Appearance, Number, Size, Style};
use crate::traits::Component;
use crate::rsx::{Props, RSX, VirtualNode};
-use alchemy_styles::stretch::node::{Node as StyleNode, Stretch as LayoutStore};
+use alchemy_styles::stretch::node::{Node as LayoutNode, Stretch as LayoutStore};
pub mod key;
use key::ComponentKey;
@@ -23,13 +23,17 @@ use storage::ComponentStore;
pub mod error;
use error::RenderEngineError;
-// This is never actually created, it's just to satisfy the fact that View
-// is defined in the core crate, which we can't import here without creating a
-// circular dependency.
-struct StubView;
-impl Component for StubView {
- fn constructor(key: ComponentKey) -> StubView {
- StubView {}
+mod instance;
+use instance::Instance;
+
+/// This is never actually created, and is here primarily to avoid a circular
+/// depedency issue (we can't import the View from alchemy's core crate, since the core crate
+/// depends on this crate).
+
+pub struct GenericRootView;
+impl Component for GenericRootView {
+ fn constructor(key: ComponentKey) -> GenericRootView {
+ GenericRootView {}
}
}
@@ -64,66 +68,307 @@ impl RenderEngine {
return Err(Box::new(RenderEngineError::InvalidRootComponent {}));
}
- let layout_key = {
+ let mut component_store = self.components.lock().unwrap();
+ let component_key = component_store.new_key();
+ component_store.insert(component_key, Instance::new("root", Box::new(instance), {
+ let mut props = Props::default();
+ props.styles = "root".into();
+ props
+ }, {
+ let mut layouts_store = self.layouts.lock().unwrap();
let style = Style::default();
- let mut layouts = self.layouts.lock().unwrap();
- Some(layouts.new_node(style, vec![])?)
- };
+ Some(layouts_store.new_node(style, vec![])?)
+ }))?;
- let mut components = self.components.lock().unwrap();
- let component_key = components.new_node(instance, layout_key, vec![])?;
Ok(component_key)
}
- /// Given a key, and a new root tree, will diff the tree structure (position, components,
- /// attributes and so on), and then queue the changes for application to the backing
- /// framework tree. As it goes through the tree, if a `Component` at a given position
- /// in the two trees is deemed to be the same, it will move instances from the old tree to
- /// the new tree before discarding the old tree.
- ///
- /// This calls the necessary component lifecycles per-component.
- pub fn diff_and_render_root(&self, key: &ComponentKey, child: RSX) -> Result<(), Box> {
- /*
- let mut new_root = RSX::node("root", || {
- Box::new(StubView {})
- }, {
+ /// Rendering the root node is a bit different than rendering or updating other nodes, as we
+ /// never want to unmount it, and the results come from a non-`Component` entity (e.g, a
+ /// `Window`). Thus, for this one, we do some manual mucking with what we know is the
+ /// root view (a `Window` or such root component would call this with it's registered
+ /// `ComponentKey`), and then recurse based on the children.
+ pub fn diff_and_render_root(&self, key: ComponentKey, child: RSX) -> Result<(), Box> {
+ let mut component_store = self.components.lock().unwrap();
+ let mut layout_store = self.layouts.lock().unwrap();
+
+ println!("Child: {:?}", child);
+ let new_root_node = RSX::node("root", |_| Box::new(GenericRootView {}), {
let mut props = Props::default();
props.styles = "root".into();
props
}, match child {
- RSX::VirtualNode(mut child) => {
- let mut children = vec![];
-
- if child.tag == "Fragment" {
- children.append(&mut child.children);
+ RSX::VirtualNode(node) => {
+ if node.tag == "Fragment" {
+ node.children
} else {
- children.push(RSX::VirtualNode(child));
+ println!("Def here...");
+ vec![RSX::VirtualNode(node)]
}
-
- children
},
- // If it's an RSX::None or RSX::VirtualText, we'll just do nothing, as... one
- // requires nothing, and one isn't supported unless it's inside a tag, and
- // we know the root element isn't a if we're here.
_ => vec![]
});
- let mut trees = self.trees.lock().unwrap();
- let (old_root, mut stretch) = trees.remove(key).ok_or_else(|| RenderEngineError::InvalidKey {})?;
- let patched_new_root = diff_and_patch_trees(old_root, new_root, &mut stretch, 0)?;
+ recursively_diff_tree(key, new_root_node, &mut component_store, &mut layout_store)?;
- if let RSX::VirtualNode(node) = &patched_new_root {
- if let Some(layout_node) = &node.layout_node {
- stretch.compute_layout(*layout_node, Size {
- width: Number::Defined(600.),
- height: Number::Defined(600.),
- })?;
- walk_and_apply_styles(node, &mut stretch)?;
- }
- }
+ let layout_node = {
+ let root_instance = component_store.get(key)?;
+ root_instance.layout.unwrap()
+ };
+
+ layout_store.compute_layout(layout_node, Size {
+ width: Number::Defined(600.),
+ height: Number::Defined(600.)
+ })?;
+
+ println!("Applying layout...");
+ walk_and_apply_styles(key, &mut component_store, &mut layout_store)?;
- trees.insert(*key, (patched_new_root, stretch));*/
Ok(())
}
}
+
+/// Given two trees, will diff them to see if we need to replace or update. Depending on the
+/// result, we'll either recurse down a level, or tear down and build up a new tree. The final
+/// parameter on this method, `is_root_entity_view`, should only be passed for `Window` or other
+/// such instances, as it instructs us to skip the first level since these ones act different.
+fn recursively_diff_tree(
+ key: ComponentKey,
+ new_tree: RSX,
+ component_store: &mut ComponentStore,
+ layout_store: &mut LayoutStore
+) -> Result<(), Box> {
+ // First we need to determine if this node is being replaced or updated. A replace happens if
+ // two nodes are different types - in this case, we check their tag values. This is also a case
+ // where, for instance, if the RSX tag is `::None` or `::VirtualText`, we'll treat it as
+ // replacing with nothing.
+ let is_replace = match &new_tree {
+ RSX::VirtualNode(new_tree) => {
+ let old_tree = component_store.get(key)?;
+ old_tree.tag != new_tree.tag
+ },
+
+ // The algorithm will know below not to recurse if we're trying to diff text or empty
+ // values. We return false here to avoid entering the `is_replace` phase; `Component`
+ // instances (like ) handle taking the child VirtualText instances and working with
+ // them to pass to a native widget.
+ _ => false
+ };
+
+ if is_replace {
+ println!("here, what?!");
+ unmount_component_tree(key, component_store, layout_store)?;
+ //mount_component_tree(
+ return Ok(());
+ }
+
+ // At this point, we know it's an update pass. Now we need to do a few things:
+ //
+ // - Diff our `props` and figure out what actions we can take or shortcut.
+ // - Let the `Component` instance determine what it should render.
+ // - Recurse into the child trees if necessary.
+ let mut old_children = component_store.children(key)?;
+ old_children.reverse();
+
+ if let RSX::VirtualNode(mut child) = new_tree {
+ for new_child_tree in child.children {
+ match old_children.pop() {
+ // If there's a key in the old children for this position, it's
+ // something we need to update, so let's recurse right back into it.
+ Some(old_child_key) => {
+ recursively_diff_tree(
+ old_child_key,
+ new_child_tree,
+ component_store,
+ layout_store
+ )?;
+ },
+
+ // If there's no matching old key in this position, then we've got a
+ // new component instance to mount. This part now diverts into the Mount
+ // phase.
+ None => {
+ if let RSX::VirtualNode(tr33amimustfeelohlol) = new_child_tree {
+ let new_child_key = mount_component_tree(
+ tr33amimustfeelohlol,
+ component_store,
+ layout_store
+ )?;
+
+ link_nodes(key, new_child_key, component_store, layout_store)?;
+ }
+ }
+ }
+ }
+ }
+
+ // Trim the fat. If we still have child nodes after diffing in the new child trees,
+ // then they're ones that simply need to be unmounted and dropped.
+ if old_children.len() > 0 {
+ for child in old_children {
+ unmount_component_tree(child, component_store, layout_store)?;
+ }
+ }
+
+ Ok(())
+}
+
+/// Given a new `RSX` tree, a `ComponentStore`, and a `LayoutStore`, will recursively construct the
+/// tree, emitting required lifecycle events and persisting values. This happens in an inward-out
+/// fashion, which helps avoid unnecessary reflow in environments where it can get tricky.
+///
+/// This method returns a Result, the `Ok` variant containing a tuple of Vecs. These are the child
+/// Component instances and Layout instances that need to be set in the stores.
+fn mount_component_tree(
+ tree: VirtualNode,
+ component_store: &mut ComponentStore,
+ layout_store: &mut LayoutStore
+) -> Result> {
+ println!(" Mounting Component");
+ let key = component_store.new_key();
+ let component = (tree.create_component_fn)(key);
+ let is_native_backed = component.has_native_backing_node();
+
+ // let state = get_derived_state_from_props()
+ let mut instance = Instance::new(tree.tag, component, tree.props, None);
+ if is_native_backed {
+ let mut style = Style::default();
+ THEME_ENGINE.configure_styles_for_keys(&instance.props.styles, &mut style, &mut instance.appearance);
+ instance.layout = Some(layout_store.new_node(style, vec![])?);
+ }
+
+ let rendered = instance.component.render(&instance.props);
+ // instance.get_snapshot_before_update()
+ println!("Rendered... {}", instance.tag);
+ component_store.insert(key, instance)?;
+
+ match rendered {
+ Ok(child) => if let RSX::VirtualNode(child) = child {
+ // We want to support Components being able to return arbitrary iteratable
+ // elements, but... well, it's not quite that simple. Thus we'll offer a
+ // 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 {}", child.children.len());
+ for child_tree in child.children {
+ println!(" > WHAT");
+ if let RSX::VirtualNode(child_tree) = child_tree {
+ let child_key = mount_component_tree(child_tree, component_store, layout_store)?;
+
+ if is_native_backed {
+ link_nodes(key, child_key, component_store, layout_store)?;
+ }
+ }
+ }
+ } else {
+ println!(" In regular");
+ let child_key = mount_component_tree(child, component_store, layout_store)?;
+ if is_native_backed {
+ link_nodes(key, child_key, component_store, layout_store)?;
+ }
+ }
+ } else { println!("WTF"); },
+
+ Err(e) => {
+ // return an RSX::VirtualNode(ErrorComponentView) or something?
+ /* instance.get_derived_state_from_error(e) */
+ // render error state or something I guess?
+ /* instance.component_did_catch(e, info) */
+ eprintln!("Error rendering: {}", e);
+ }
+ }
+
+ let instance_lol = component_store.get_mut(key)?;
+ instance_lol.component.component_did_mount(&instance_lol.props);
+
+ Ok(key)
+}
+
+/// Given a `ComponentKey`, a `ComponentStore`, and a `LayoutStore`, will recursively walk the tree found at
+/// said key, emitting required lifecycle events and dropping values. This happens in an inward-out
+/// fashion, so deepest nodes/components get destroyed first to ensure that the backing widget tree
+/// doesn't get some weird dangling issue.
+fn unmount_component_tree(
+ key: ComponentKey,
+ component_store: &mut ComponentStore,
+ layout_store: &mut LayoutStore
+) -> Result, Box> {
+ let mut instance = component_store.remove(key)?;
+ instance.component.component_will_unmount(&instance.props);
+
+ let mut layout_nodes = vec![];
+
+ let children = component_store.children(key)?;
+ for child in children {
+ match unmount_component_tree(child, component_store, layout_store) {
+ Ok(mut child_layout_nodes) => {
+ if let Some(parent_layout_node) = instance.layout {
+ for node in child_layout_nodes {
+ layout_store.remove_child(parent_layout_node, node)?;
+ }
+ } else {
+ layout_nodes.append(&mut child_layout_nodes);
+ }
+ },
+
+ Err(e) => { eprintln!("Error unmounting a component tree: {}", e); }
+ }
+ }
+
+ // remove node from backing tree
+
+ Ok(layout_nodes)
+}
+
+/// Given a tree, will walk the branches until it finds the next root nodes to connect.
+/// While this sounds slow, in practice it rarely has to go far in any direction. This could
+/// potentially be done away with some hoisting magic in the `mount()` recursion, but I couldn't
+/// find a pattern that didn't feel like some utter magic in Rust.
+///
+/// It might be because I'm writing this at 3AM. Feel free to improve it.
+fn link_nodes(
+ parent: ComponentKey,
+ child: ComponentKey,
+ components: &mut ComponentStore,
+ layouts: &mut LayoutStore
+) -> Result<(), Box> {
+ if let (Ok(parent_instance), Ok(child_instance)) = (components.get(parent), components.get(child)) {
+ if let (Some(parent_layout), Some(child_layout)) = (parent_instance.layout, child_instance.layout) {
+ layouts.add_child(parent_layout, child_layout)?;
+ parent_instance.component.append_child_component(&*child_instance.component);
+ println!("APPENDED NODE!");
+ return Ok(());
+ }
+ }
+
+ let children = components.children(child)?;
+ for child_key in children {
+ link_nodes(parent, child_key, components, layouts)?;
+ }
+
+ Ok(())
+}
+
+/// Walks the tree and passes necessary Layout and Appearance-based styles to Components so they can
+/// update their backing widgets accordingly. This happens after a layout computation, typically.
+fn walk_and_apply_styles(
+ key: ComponentKey,
+ components: &mut ComponentStore,
+ layouts: &mut LayoutStore
+) -> Result<(), Box> {
+ let instance = components.get_mut(key)?;
+
+ if let Some(layout_key) = instance.layout {
+ instance.component.apply_styles(
+ &instance.appearance,
+ layouts.layout(layout_key)?
+ );
+ }
+
+ for child in components.children(key)? {
+ walk_and_apply_styles(child, components, layouts)?;
+ }
+
+ Ok(())
+}
diff --git a/lifecycle/src/reconciler/storage.rs b/lifecycle/src/reconciler/storage.rs
index 3138f13..63f087b 100644
--- a/lifecycle/src/reconciler/storage.rs
+++ b/lifecycle/src/reconciler/storage.rs
@@ -5,11 +5,10 @@
use std::collections::HashMap;
pub use alchemy_styles::Appearance;
-use alchemy_styles::stretch::node::{Node as LayoutNode};
use crate::reconciler::error::{RenderEngineError as Error};
+use crate::reconciler::instance::Instance;
use crate::reconciler::key::{Allocator, Id, INSTANCE_ALLOCATOR, ComponentKey};
-use crate::traits::Component;
/// This is a clone of a structure you'll also find over in stretch. We do this separately
/// here for two reasons.
@@ -40,6 +39,13 @@ impl Storage {
}
}
+ pub fn remove(&mut self, key: ComponentKey) -> Result {
+ match self.0.remove(&key) {
+ Some(v) => Ok(v),
+ None => Err(Error::InvalidComponentKey(key))
+ }
+ }
+
pub fn insert(&mut self, key: ComponentKey, value: T) -> Option {
self.0.insert(key, value)
}
@@ -53,12 +59,6 @@ impl std::ops::Index<&ComponentKey> for Storage {
}
}
-pub struct Instance {
- component: Box,
- appearance: Appearance,
- layout: Option
-}
-
pub(crate) struct ComponentStore {
id: Id,
nodes: Allocator,
@@ -78,28 +78,31 @@ impl ComponentStore {
}
}
- fn allocate_node(&mut self) -> ComponentKey {
+ pub fn new_key(&mut self) -> ComponentKey {
let local = self.nodes.allocate();
ComponentKey { instance: self.id, local }
}
- pub fn new_node(&mut self, component: C, layout_key: Option, children: Vec) -> Result {
- let key = self.allocate_node();
-
- for child in &children {
+ pub fn insert(
+ &mut self,
+ key: ComponentKey,
+ instance: Instance
+ ) -> Result<(), Error> {
+ /*for child in &children {
self.parents.get_mut(*child)?.push(key);
- }
-
- self.components.insert(key, Instance {
- component: Box::new(component),
- appearance: Appearance::default(),
- layout: layout_key
- });
+ }*/
+ self.components.insert(key, instance);
self.parents.insert(key, Vec::with_capacity(1));
- self.children.insert(key, children);
+ self.children.insert(key, vec![]); //children);
- Ok(key)
+ Ok(())
+ }
+
+ pub fn remove(&mut self, key: ComponentKey) -> Result {
+ self.parents.remove(key)?;
+ self.children.remove(key)?;
+ self.components.remove(key)
}
pub fn add_child(&mut self, key: ComponentKey, child: ComponentKey) -> Result<(), Error> {
diff --git a/lifecycle/src/rsx/mod.rs b/lifecycle/src/rsx/mod.rs
index ad3bcf0..6a5b6f1 100644
--- a/lifecycle/src/rsx/mod.rs
+++ b/lifecycle/src/rsx/mod.rs
@@ -38,7 +38,7 @@ impl RSX {
RSX::VirtualNode(VirtualNode {
tag: tag,
create_component_fn: create_fn,
- props: Some(props),
+ props: props,
children: children
})
}
diff --git a/lifecycle/src/rsx/props.rs b/lifecycle/src/rsx/props.rs
index 8d297c9..04247e1 100644
--- a/lifecycle/src/rsx/props.rs
+++ b/lifecycle/src/rsx/props.rs
@@ -1,14 +1,12 @@
//! 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 alchemy_styles::StylesList;
use crate::rsx::RSX;
-use crate::traits::{Component};
/// A value stored inside the `attributes` field on a `Props` instance.
/// It shadows `serde_json::Value`, but also allows for some other value
diff --git a/lifecycle/src/rsx/virtual_node.rs b/lifecycle/src/rsx/virtual_node.rs
index df3ec57..ff65b0c 100644
--- a/lifecycle/src/rsx/virtual_node.rs
+++ b/lifecycle/src/rsx/virtual_node.rs
@@ -24,7 +24,7 @@ pub struct VirtualNode {
/// ownership of a VirtualNode.
///
/// This aspect of functionality may be pulled in a later release if it causes too many issues.
- pub props: Option,
+ pub props: Props,
///
pub children: Vec