diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 936568145..795d157c5 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -55,7 +55,6 @@ use std::{cell::RefCell, cmp::Ordering, collections::VecDeque, rc::Rc}; /// subscriptions and props changes. pub struct DiffMachine<'a> { pub change_list: EditMachine<'a>, - pub diffed: FxHashSet, pub need_to_diff: FxHashSet, } @@ -125,25 +124,6 @@ impl<'a> DiffMachine<'a> { } (_, VNode::Component(new)) => { - // let VComponent { - // props, - // props_type, - // comp, - // caller, - // assigned_scope, - // .. - // } = *new; - - // make the component - // let idx = unsafe { - // // let vdom = &mut *self.vdom; - // vdom.insert_with(|f| { - // todo!() - // // - // // create_scoped(caller, props, myidx, parent) - // }) - // }; - // we have no stable reference to work from // push the lifecycle event onto the queue // self.lifecycle_events diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index 92ea4ec7e..6d3733fa2 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -89,10 +89,10 @@ pub mod builder { // types used internally that are important pub(crate) mod innerlude { // pub(crate) use crate::component::Properties; - pub(crate) use crate::component::Properties; pub(crate) use crate::context::Context; pub(crate) use crate::error::{Error, Result}; + pub use crate::events::EventTrigger; use crate::nodes; pub(crate) use crate::scope::Scope; pub(crate) use crate::virtual_dom::VirtualDom; @@ -100,7 +100,6 @@ pub(crate) mod innerlude { pub use crate::component::ScopeIdx; pub use crate::diff::DiffMachine; - pub use crate::events::EventTrigger; pub use crate::patch::{EditList, EditMachine}; // pub use crate::patchdx; // pub use crate::patchtList; diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index a767dfce8..4dddbfdc0 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -3,394 +3,325 @@ //! //! These VNodes should be *very* cheap and *very* fast to construct - building a full tree should be insanely quick. +use crate::{ + events::VirtualEvent, + innerlude::{Context, Properties, ScopeIdx, FC}, +}; + use bumpalo::Bump; -pub use vcomponent::VComponent; -pub use velement::VElement; -pub use velement::{Attribute, Listener, NodeKey}; -pub use vnode::VNode; -pub use vtext::VText; +use std::fmt::Debug; +use std::{ + any::{Any, TypeId}, + cell::RefCell, + marker::PhantomData, + rc::Rc, +}; /// A domtree represents the result of "Viewing" the context /// It's a placeholder over vnodes, to make working with lifetimes easier pub struct DomTree; +// ============================== +// VNODES +// ============================== + /// Tools for the base unit of the virtual dom - the VNode /// VNodes are intended to be quickly-allocated, lightweight enum values. /// /// Components will be generating a lot of these very quickly, so we want to /// limit the amount of heap allocations / overly large enum sizes. -mod vnode { - use super::*; +#[derive(Debug)] +pub enum VNode<'src> { + /// An element node (node type `ELEMENT_NODE`). + Element(&'src VElement<'src>), - #[derive(Debug)] - pub enum VNode<'src> { - /// An element node (node type `ELEMENT_NODE`). - Element(&'src VElement<'src>), - - /// A text node (node type `TEXT_NODE`). - /// - /// Note: This wraps a `VText` instead of a plain `String` in - /// order to enable custom methods like `create_text_node()` on the - /// wrapped type. - Text(VText<'src>), - - /// A "suspended component" - /// This is a masqeurade over an underlying future that needs to complete - /// When the future is completed, the VNode will then trigger a render - Suspended, - - /// A User-defined componen node (node type COMPONENT_NODE) - Component(VComponent<'src>), - } - - impl<'a> VNode<'a> { - /// Low-level constructor for making a new `Node` of type element with given - /// parts. - /// - /// This is primarily intended for JSX and templating proc-macros to compile - /// down into. If you are building nodes by-hand, prefer using the - /// `dodrio::builder::*` APIs. - #[inline] - pub fn element( - bump: &'a Bump, - key: NodeKey, - tag_name: &'a str, - listeners: &'a [Listener<'a>], - attributes: &'a [Attribute<'a>], - children: &'a [VNode<'a>], - namespace: Option<&'a str>, - ) -> VNode<'a> { - let element = bump.alloc_with(|| VElement { - key, - tag_name, - listeners, - attributes, - children, - namespace, - }); - VNode::Element(element) - } - - /// Construct a new text node with the given text. - #[inline] - pub fn text(text: &'a str) -> VNode<'a> { - VNode::Text(VText { text }) - } - - #[inline] - pub(crate) fn key(&self) -> NodeKey { - match &self { - VNode::Text(_) => NodeKey::NONE, - VNode::Element(e) => e.key, - VNode::Suspended => { - todo!() - } - VNode::Component(_) => { - todo!() - } - } - } - } -} - -mod velement { - - // use crate::{events::VirtualEvent, innerlude::CbIdx}; - - use crate::{events::VirtualEvent, innerlude::ScopeIdx}; - - use super::*; - use std::fmt::Debug; - - #[derive(Debug)] - pub struct VElement<'a> { - /// Elements have a tag name, zero or more attributes, and zero or more - pub key: NodeKey, - pub tag_name: &'a str, - pub listeners: &'a [Listener<'a>], - pub attributes: &'a [Attribute<'a>], - pub children: &'a [VNode<'a>], - pub namespace: Option<&'a str>, - // The HTML tag, such as "div" - // pub tag: &'a str, - - // pub tag_name: &'a str, - // pub attributes: &'a [Attribute<'a>], - // todo: hook up listeners - // pub listeners: &'a [Listener<'a>], - // / HTML attributes such as id, class, style, etc - // pub attrs: HashMap, - // TODO: @JON Get this to not heap allocate, but rather borrow - // pub attrs: HashMap<&'static str, &'static str>, - - // TODO @Jon, re-enable "events" - // - // /// Events that will get added to your real DOM element via `.addEventListener` - // pub events: Events, - // pub events: HashMap, - - // /// The children of this `VNode`. So a
structure would - // /// have a parent div and one child, em. - // pub children: Vec, - } - - impl<'a> VElement<'a> { - // The tag of a component MUST be known at compile time - pub fn new(_tag: &'a str) -> Self { - todo!() - // VElement { - // tag, - // attrs: HashMap::new(), - // events: HashMap::new(), - // // events: Events(HashMap::new()), - // children: vec![], - // } - } - } - - /// An attribute on a DOM node, such as `id="my-thing"` or - /// `href="https://example.com"`. - #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] - pub struct Attribute<'a> { - pub name: &'static str, - pub value: &'a str, - } - - impl<'a> Attribute<'a> { - /// Get this attribute's name, such as `"id"` in `
`. - #[inline] - pub fn name(&self) -> &'a str { - self.name - } - - /// The attribute value, such as `"my-thing"` in `
`. - #[inline] - pub fn value(&self) -> &'a str { - self.value - } - - /// Certain attributes are considered "volatile" and can change via user - /// input that we can't see when diffing against the old virtual DOM. For - /// these attributes, we want to always re-set the attribute on the physical - /// DOM node, even if the old and new virtual DOM nodes have the same value. - #[inline] - pub(crate) fn is_volatile(&self) -> bool { - match self.name { - "value" | "checked" | "selected" => true, - _ => false, - } - } - } - - pub struct ListenerHandle { - pub event: &'static str, - pub scope: ScopeIdx, - pub id: usize, - } - - /// An event listener. - pub struct Listener<'bump> { - /// The type of event to listen for. - pub(crate) event: &'static str, - - pub scope: ScopeIdx, - pub id: usize, - - // pub(crate) _i: &'bump str, - // #[serde(skip_serializing, skip_deserializing, default="")] - // /// The callback to invoke when the event happens. - pub(crate) callback: &'bump (dyn Fn(VirtualEvent)), - } - impl Debug for Listener<'_> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Listener") - .field("event", &self.event) - .finish() - } - } - pub(crate) type ListenerCallback<'a> = &'a (dyn Fn(VirtualEvent)); - union CallbackFatPtr<'a> { - callback: ListenerCallback<'a>, - parts: (u32, u32), - } - impl Listener<'_> { - #[inline] - pub(crate) fn get_callback_parts(&self) -> (u32, u32) { - assert_eq!( - std::mem::size_of::(), - std::mem::size_of::() - ); - - unsafe { - let fat = CallbackFatPtr { - callback: self.callback, - }; - let (a, b) = fat.parts; - debug_assert!(a != 0); - (a, b) - } - } - } - - /// The key for keyed children. + /// A text node (node type `TEXT_NODE`). /// - /// Keys must be unique among siblings. + /// Note: This wraps a `VText` instead of a plain `String` in + /// order to enable custom methods like `create_text_node()` on the + /// wrapped type. + Text(VText<'src>), + + /// A "suspended component" + /// This is a masqeurade over an underlying future that needs to complete + /// When the future is completed, the VNode will then trigger a render + Suspended, + + /// A User-defined componen node (node type COMPONENT_NODE) + Component(VComponent<'src>), +} + +impl<'a> VNode<'a> { + /// Low-level constructor for making a new `Node` of type element with given + /// parts. /// - /// If any sibling is keyed, then they all must be keyed. - #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] - pub struct NodeKey(pub(crate) u32); - - impl Default for NodeKey { - fn default() -> NodeKey { - NodeKey::NONE - } - } - impl NodeKey { - /// The default, lack of a key. - pub const NONE: NodeKey = NodeKey(u32::MAX); - - /// Is this key `NodeKey::NONE`? - #[inline] - pub fn is_none(&self) -> bool { - *self == Self::NONE - } - - /// Is this key not `NodeKey::NONE`? - #[inline] - pub fn is_some(&self) -> bool { - !self.is_none() - } - - /// Create a new `NodeKey`. - /// - /// `key` must not be `u32::MAX`. - #[inline] - pub fn new(key: u32) -> Self { - debug_assert_ne!(key, u32::MAX); - NodeKey(key) - } + /// This is primarily intended for JSX and templating proc-macros to compile + /// down into. If you are building nodes by-hand, prefer using the + /// `dodrio::builder::*` APIs. + #[inline] + pub fn element( + bump: &'a Bump, + key: NodeKey, + tag_name: &'a str, + listeners: &'a [Listener<'a>], + attributes: &'a [Attribute<'a>], + children: &'a [VNode<'a>], + namespace: Option<&'a str>, + ) -> VNode<'a> { + let element = bump.alloc_with(|| VElement { + key, + tag_name, + listeners, + attributes, + children, + namespace, + }); + VNode::Element(element) } - // todo - // use zst enum for element type. Something like ValidElements::div -} - -mod vtext { - #[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)] - pub struct VText<'bump> { - pub text: &'bump str, + /// Construct a new text node with the given text. + #[inline] + pub fn text(text: &'a str) -> VNode<'a> { + VNode::Text(VText { text }) } - impl<'a> VText<'a> { - // / Create an new `VText` instance with the specified text. - pub fn new(text: &'a str) -> Self -// pub fn new(text: S) -> Self - // where - // S: Into, - { - VText { text: text.into() } + #[inline] + pub(crate) fn key(&self) -> NodeKey { + match &self { + VNode::Text(_) => NodeKey::NONE, + VNode::Element(e) => e.key, + VNode::Suspended => { + todo!() + } + VNode::Component(_) => { + todo!() + } } } } +// ======================================================== +// VElement (div, h1, etc), attrs, keys, listener handle +// ======================================================== +#[derive(Debug)] +pub struct VElement<'a> { + /// Elements have a tag name, zero or more attributes, and zero or more + pub key: NodeKey, + pub tag_name: &'a str, + pub listeners: &'a [Listener<'a>], + pub attributes: &'a [Attribute<'a>], + pub children: &'a [VNode<'a>], + pub namespace: Option<&'a str>, + // The HTML tag, such as "div" + // pub tag: &'a str, + + // pub tag_name: &'a str, + // pub attributes: &'a [Attribute<'a>], + // todo: hook up listeners + // pub listeners: &'a [Listener<'a>], + // / HTML attributes such as id, class, style, etc + // pub attrs: HashMap, + // TODO: @JON Get this to not heap allocate, but rather borrow + // pub attrs: HashMap<&'static str, &'static str>, + + // TODO @Jon, re-enable "events" + // + // /// Events that will get added to your real DOM element via `.addEventListener` + // pub events: Events, + // pub events: HashMap, + + // /// The children of this `VNode`. So a
structure would + // /// have a parent div and one child, em. + // pub children: Vec, +} + +impl<'a> VElement<'a> { + // The tag of a component MUST be known at compile time + pub fn new(_tag: &'a str) -> Self { + todo!() + // VElement { + // tag, + // attrs: HashMap::new(), + // events: HashMap::new(), + // // events: Events(HashMap::new()), + // children: vec![], + // } + } +} + +/// An attribute on a DOM node, such as `id="my-thing"` or +/// `href="https://example.com"`. +#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] +pub struct Attribute<'a> { + pub name: &'static str, + pub value: &'a str, +} + +impl<'a> Attribute<'a> { + /// Get this attribute's name, such as `"id"` in `
`. + #[inline] + pub fn name(&self) -> &'a str { + self.name + } + + /// The attribute value, such as `"my-thing"` in `
`. + #[inline] + pub fn value(&self) -> &'a str { + self.value + } + + /// Certain attributes are considered "volatile" and can change via user + /// input that we can't see when diffing against the old virtual DOM. For + /// these attributes, we want to always re-set the attribute on the physical + /// DOM node, even if the old and new virtual DOM nodes have the same value. + #[inline] + pub(crate) fn is_volatile(&self) -> bool { + match self.name { + "value" | "checked" | "selected" => true, + _ => false, + } + } +} + +pub struct ListenerHandle { + pub event: &'static str, + pub scope: ScopeIdx, + pub id: usize, +} + +/// An event listener. +pub struct Listener<'bump> { + /// The type of event to listen for. + pub(crate) event: &'static str, + + pub scope: ScopeIdx, + pub id: usize, + + // pub(crate) _i: &'bump str, + // #[serde(skip_serializing, skip_deserializing, default="")] + // /// The callback to invoke when the event happens. + pub(crate) callback: &'bump (dyn Fn(VirtualEvent)), +} +impl Debug for Listener<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Listener") + .field("event", &self.event) + .finish() + } +} + +/// The key for keyed children. +/// +/// Keys must be unique among siblings. +/// +/// If any sibling is keyed, then they all must be keyed. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub struct NodeKey(pub(crate) u32); + +impl Default for NodeKey { + fn default() -> NodeKey { + NodeKey::NONE + } +} +impl NodeKey { + /// The default, lack of a key. + pub const NONE: NodeKey = NodeKey(u32::MAX); + + /// Is this key `NodeKey::NONE`? + #[inline] + pub fn is_none(&self) -> bool { + *self == Self::NONE + } + + /// Is this key not `NodeKey::NONE`? + #[inline] + pub fn is_some(&self) -> bool { + !self.is_none() + } + + /// Create a new `NodeKey`. + /// + /// `key` must not be `u32::MAX`. + #[inline] + pub fn new(key: u32) -> Self { + debug_assert_ne!(key, u32::MAX); + NodeKey(key) + } +} + +#[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct VText<'bump> { + pub text: &'bump str, +} + +impl<'a> VText<'a> { + // / Create an new `VText` instance with the specified text. + pub fn new(text: &'a str) -> Self +// pub fn new(text: Into) -> Self + { + VText { text: text.into() } + } +} + +// ============================== +// Custom components +// ============================== + /// Virtual Components for custom user-defined components /// Only supports the functional syntax -mod vcomponent { - use crate::innerlude::{Context, Properties, ScopeIdx, FC}; - use std::{ - any::{Any, TypeId}, - cell::RefCell, - marker::PhantomData, - rc::Rc, - }; +pub type StableScopeAddres = Rc>>; - use super::DomTree; - - pub type StableScopeAddres = Rc>>; - - #[derive(Debug)] - pub struct VComponent<'src> { - _p: PhantomData<&'src ()>, - // pub(crate) props: Box, - // pub(crate) props_type: TypeId, - // pub(crate) comp: *const (), - // pub(crate) caller: Caller, - pub(crate) comparator: Comparator, - // once a component gets mounted, its parent gets a stable address. - // this way we can carry the scope index from between renders - // genius, really! - // pub assigned_scope: StableScopeAddres, +#[derive(Debug)] +pub struct VComponent<'src> { + pub stable_addr: StableScopeAddres, + pub comparator: Comparator, + pub caller: Caller, + _p: PhantomData<&'src ()>, +} +pub struct Comparator(Box bool>); +impl std::fmt::Debug for Comparator { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + Ok(()) } - pub struct Comparator(Box bool>); - // pub struct Comparator<'src>(&'src dyn Fn(&dyn Any) -> bool); +} - pub struct Caller(Box DomTree>); - impl std::fmt::Debug for Caller { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - todo!() - } +pub struct Caller(Box DomTree>); +impl std::fmt::Debug for Caller { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + Ok(()) } +} - impl std::fmt::Debug for Comparator { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - todo!() - } - } +impl<'a> VComponent<'a> { + // use the type parameter on props creation and move it into a portable context + // this lets us keep scope generic *and* downcast its props when we need to: + // - perform comparisons when diffing (memoization) + // TODO: lift the requirement that props need to be static + // we want them to borrow references... maybe force implementing a "to_static_unsafe" trait + pub fn new(caller: FC

, props: P) -> Self { + let props = Rc::new(props); - impl<'a> VComponent<'a> { - // use the type parameter on props creation and move it into a portable context - // this lets us keep scope generic *and* downcast its props when we need to: - // - perform comparisons when diffing (memoization) - // - - pub fn new(caller: FC

, comp: P) -> Self { - todo!() - // let p = Rc::new(props); + // used for memoization + let p1 = props.clone(); + let props_comparator = move |new_props: &dyn Any| -> bool { + new_props + .downcast_ref::

() + .map(|new_p| p1.as_ref() == new_p) + .unwrap_or_else(|| { + log::debug!("downcasting failed, this receovered but SHOULD NOT FAIL"); + false + }) + }; - // let props_comparator = move |new_props: &dyn Any| -> bool { - // new_props - // .downcast_ref::

() - // .map(|new_p| p.as_ref() == new_p) - // .unwrap_or_else(|| { - // log::debug!("downcasting failed, this receovered but SHOULD NOT FAIL"); - // false - // }) - // }; + // used for actually rendering the custom component + let p2 = props.clone(); + let caller = move |ctx: Context| -> DomTree { caller(ctx, p2.as_ref()) }; - // Self { - // _p: PhantomData, - // comparator: Comparator(Box::new(props_comparator)), - // } - // let caller = move |ctx: Context| { - // let t = comp(ctx, &props); - // t - // }; - // let _caller = comp as *const (); - // let _props = Box::new(props); - - // cast to static? - // how do we save the type of props ? - // do it in the caller function? - // something like - - // the "true" entrpypoint - // - // |caller| caller.call(fn, Props {}) - // fn call

(f, new_props) { - // let old_props = old.downcast_ref::

(); - // if new_props == old_props { - // return; - // } else { - // // set the new props - // // call the fn - // } - // } - - // todo!() - // Self { - // _p: PhantomData {}, - // props, - // caller, - // } + Self { + _p: PhantomData, + comparator: Comparator(Box::new(props_comparator)), + caller: Caller(Box::new(caller)), + stable_addr: Rc::new(RefCell::new(None)), } } } diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 371a02491..cffbfdd07 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -185,8 +185,7 @@ impl VirtualDom { } enum LifeCycleEvent { - // Mount { -// props: &dyn Properties, -// // f: FC, -// }, + Mount {}, } + +// todo: add some "handle" to the vdom. Or a way of giving out closures that can mutate the vdoms internal data.