|
@@ -4,225 +4,66 @@
|
|
|
//! These VNodes should be *very* cheap and *very* fast to construct - building a full tree should be insanely quick.
|
|
|
|
|
|
use crate::{
|
|
|
- arena::SharedArena,
|
|
|
events::VirtualEvent,
|
|
|
innerlude::{Context, Properties, RealDom, RealDomNode, Scope, ScopeIdx, FC},
|
|
|
- nodebuilder::NodeFactory,
|
|
|
};
|
|
|
-use appendlist::AppendList;
|
|
|
-use bumpalo::Bump;
|
|
|
use std::{
|
|
|
cell::{Cell, RefCell},
|
|
|
fmt::{Arguments, Debug, Formatter},
|
|
|
+ marker::PhantomData,
|
|
|
rc::Rc,
|
|
|
};
|
|
|
|
|
|
+pub struct VNode<'src> {
|
|
|
+ pub kind: VNodeKind<'src>,
|
|
|
+ pub dom_id: Cell<RealDomNode>,
|
|
|
+ pub key: Option<&'src str>,
|
|
|
+}
|
|
|
+
|
|
|
/// 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.
|
|
|
-pub enum VNode<'src> {
|
|
|
- /// An element node (node type `ELEMENT_NODE`).
|
|
|
- Element(&'src VElement<'src>),
|
|
|
-
|
|
|
- /// A text node (node type `TEXT_NODE`).
|
|
|
+pub enum VNodeKind<'src> {
|
|
|
Text(VText<'src>),
|
|
|
-
|
|
|
- /// A fragment is a list of elements that might have a dynamic order.
|
|
|
- /// Normally, children will have a fixed order. However, Fragments allow a dynamic order and must be diffed differently.
|
|
|
- ///
|
|
|
- /// Fragments don't have a single mount into the dom, so their position is characterized by the head and tail nodes.
|
|
|
- ///
|
|
|
- /// Fragments may have children and keys
|
|
|
- Fragment(&'src VFragment<'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 and the `real` field gets populated
|
|
|
- Suspended { real: Cell<RealDomNode> },
|
|
|
-
|
|
|
- /// A User-defined componen node (node type COMPONENT_NODE)
|
|
|
+ Element(&'src VElement<'src>),
|
|
|
+ Fragment(VFragment<'src>),
|
|
|
Component(&'src VComponent<'src>),
|
|
|
+ Suspended,
|
|
|
}
|
|
|
|
|
|
-// it's okay to clone because vnodes are just references to places into the bump
|
|
|
-impl<'a> Clone for VNode<'a> {
|
|
|
- fn clone(&self) -> Self {
|
|
|
- match self {
|
|
|
- VNode::Element(element) => VNode::Element(element),
|
|
|
- VNode::Text(old) => VNode::Text(old.clone()),
|
|
|
- VNode::Fragment(fragment) => VNode::Fragment(fragment),
|
|
|
- VNode::Component(component) => VNode::Component(component),
|
|
|
- VNode::Suspended { real } => VNode::Suspended { real: real.clone() },
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-impl<'old, 'new> VNode<'old> {
|
|
|
- // performs a somewhat costly clone of this vnode into another bump
|
|
|
- // this is used when you want to drag nodes from an old frame into a new frame
|
|
|
- // There is no way to safely drag listeners over (no way to clone a closure)
|
|
|
- //
|
|
|
- // This method will only be called if a component was once a real node and then becomes suspended
|
|
|
- fn deep_clone_to_new_bump(&self, new: &'new Bump) -> VNode<'new> {
|
|
|
- match self {
|
|
|
- VNode::Element(el) => {
|
|
|
- let new_el: VElement<'new> = VElement {
|
|
|
- key: NodeKey::NONE,
|
|
|
- // key: el.key.clone(),
|
|
|
- tag_name: el.tag_name,
|
|
|
- // wipe listeners on deep clone, there's no way to know what other bump material they might be referencing (nodes, etc)
|
|
|
- listeners: &[],
|
|
|
- attributes: {
|
|
|
- let attr_vec = bumpalo::collections::Vec::new_in(new);
|
|
|
- attr_vec.into_bump_slice()
|
|
|
- },
|
|
|
- children: {
|
|
|
- let attr_vec = bumpalo::collections::Vec::new_in(new);
|
|
|
- attr_vec.into_bump_slice()
|
|
|
- },
|
|
|
- namespace: el.namespace.clone(),
|
|
|
- dom_id: el.dom_id.clone(),
|
|
|
- is_static: el.is_static.clone(),
|
|
|
- };
|
|
|
-
|
|
|
- VNode::Element(new.alloc_with(move || new_el))
|
|
|
- }
|
|
|
- VNode::Text(_) => todo!(),
|
|
|
- VNode::Fragment(_) => todo!(),
|
|
|
- VNode::Suspended { real } => todo!(),
|
|
|
- VNode::Component(_) => todo!(),
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-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<'a>,
|
|
|
- tag_name: &'static str,
|
|
|
- listeners: &'a [Listener<'a>],
|
|
|
- attributes: &'a [Attribute<'a>],
|
|
|
- children: &'a [VNode<'a>],
|
|
|
- namespace: Option<&'static str>,
|
|
|
- ) -> VNode<'a> {
|
|
|
- let element = bump.alloc_with(|| VElement {
|
|
|
- key,
|
|
|
- tag_name,
|
|
|
- listeners,
|
|
|
- attributes,
|
|
|
- children,
|
|
|
- namespace,
|
|
|
- dom_id: Cell::new(RealDomNode::empty()),
|
|
|
- is_static: Cell::new(false),
|
|
|
- });
|
|
|
- VNode::Element(element)
|
|
|
- }
|
|
|
-
|
|
|
- pub fn static_text(text: &'static str) -> VNode {
|
|
|
- VNode::Text(VText {
|
|
|
- text,
|
|
|
- is_static: true,
|
|
|
- dom_id: Cell::new(RealDomNode::empty()),
|
|
|
- })
|
|
|
- }
|
|
|
- /// Construct a new text node with the given text.
|
|
|
- pub fn text(bump: &'a Bump, args: Arguments) -> VNode<'a> {
|
|
|
- match args.as_str() {
|
|
|
- Some(text) => VNode::static_text(text),
|
|
|
- None => {
|
|
|
- use bumpalo::core_alloc::fmt::Write;
|
|
|
- let mut s = bumpalo::collections::String::new_in(bump);
|
|
|
- s.write_fmt(args).unwrap();
|
|
|
- VNode::Text(VText {
|
|
|
- text: s.into_bump_str(),
|
|
|
- is_static: false,
|
|
|
- dom_id: Cell::new(RealDomNode::empty()),
|
|
|
- })
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- #[inline]
|
|
|
- pub(crate) fn key(&self) -> NodeKey {
|
|
|
- match &self {
|
|
|
- VNode::Text { .. } => NodeKey::NONE,
|
|
|
- VNode::Element(e) => e.key,
|
|
|
- VNode::Fragment(frag) => frag.key,
|
|
|
- VNode::Component(c) => c.key,
|
|
|
-
|
|
|
- // todo suspend should be allowed to have keys
|
|
|
- VNode::Suspended { .. } => NodeKey::NONE,
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- fn get_child(&self, id: u32) -> Option<&'a VNode<'a>> {
|
|
|
- todo!()
|
|
|
- }
|
|
|
-
|
|
|
- pub fn is_real(&self) -> bool {
|
|
|
- match self {
|
|
|
- VNode::Element(_) => true,
|
|
|
- VNode::Text(_) => true,
|
|
|
- VNode::Fragment(_) => false,
|
|
|
- VNode::Suspended { .. } => false,
|
|
|
- VNode::Component(_) => false,
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- pub fn get_mounted_id(&self, components: &SharedArena) -> Option<RealDomNode> {
|
|
|
- match self {
|
|
|
- VNode::Element(el) => Some(el.dom_id.get()),
|
|
|
- VNode::Text(te) => Some(te.dom_id.get()),
|
|
|
- VNode::Fragment(frag) => frag.void_root.get(),
|
|
|
- VNode::Suspended { .. } => todo!(),
|
|
|
- VNode::Component(el) => todo!(),
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-impl Debug for VNode<'_> {
|
|
|
- fn fmt(&self, s: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
|
|
- match self {
|
|
|
- VNode::Element(el) => write!(s, "element, {}", el.tag_name),
|
|
|
- VNode::Text(t) => write!(s, "text, {}", t.text),
|
|
|
- VNode::Fragment(_) => write!(s, "fragment"),
|
|
|
- VNode::Suspended { .. } => write!(s, "suspended"),
|
|
|
- VNode::Component(_) => write!(s, "component"),
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-#[derive(Clone)]
|
|
|
pub struct VText<'src> {
|
|
|
pub text: &'src str,
|
|
|
pub is_static: bool,
|
|
|
- pub dom_id: Cell<RealDomNode>,
|
|
|
}
|
|
|
|
|
|
-// ========================================================
|
|
|
-// VElement (div, h1, etc), attrs, keys, listener handle
|
|
|
-// ========================================================
|
|
|
+pub struct VFragment<'src> {
|
|
|
+ pub children: &'src [VNode<'src>],
|
|
|
+}
|
|
|
|
|
|
-#[derive(Clone)]
|
|
|
+pub trait DioxusElement {
|
|
|
+ const TAG_NAME: &'static str;
|
|
|
+ const NAME_SPACE: Option<&'static str>;
|
|
|
+ fn tag_name(&self) -> &'static str {
|
|
|
+ Self::TAG_NAME
|
|
|
+ }
|
|
|
+}
|
|
|
pub struct VElement<'a> {
|
|
|
- /// Elements have a tag name, zero or more attributes, and zero or more
|
|
|
- pub key: NodeKey<'a>,
|
|
|
+ // tag is always static
|
|
|
pub tag_name: &'static str,
|
|
|
+
|
|
|
+ pub static_listeners: bool,
|
|
|
pub listeners: &'a [Listener<'a>],
|
|
|
+
|
|
|
+ pub static_attrs: bool,
|
|
|
pub attributes: &'a [Attribute<'a>],
|
|
|
+
|
|
|
+ pub static_children: bool,
|
|
|
pub children: &'a [VNode<'a>],
|
|
|
+
|
|
|
+ // namespace is always static
|
|
|
pub namespace: Option<&'static str>,
|
|
|
- pub dom_id: Cell<RealDomNode>,
|
|
|
- pub is_static: Cell<bool>,
|
|
|
}
|
|
|
|
|
|
/// An attribute on a DOM node, such as `id="my-thing"` or
|
|
@@ -232,175 +73,141 @@ pub struct Attribute<'a> {
|
|
|
pub name: &'static str,
|
|
|
pub value: &'a str,
|
|
|
pub is_static: bool,
|
|
|
-
|
|
|
- /// If an attribute is "namespaced", then it belongs to a group
|
|
|
- /// The most common namespace is the "style" namespace
|
|
|
- // pub is_dynamic: bool,
|
|
|
+ pub is_volatile: bool,
|
|
|
+ // Doesn't exist in the html spec, mostly used to denote "style" tags - could be for any type of group
|
|
|
pub namespace: Option<&'static str>,
|
|
|
}
|
|
|
|
|
|
-impl<'a> Attribute<'a> {
|
|
|
- /// Get this attribute's name, such as `"id"` in `<div id="my-thing" />`.
|
|
|
- #[inline]
|
|
|
- pub fn name(&self) -> &'a str {
|
|
|
- self.name
|
|
|
- }
|
|
|
-
|
|
|
- /// The attribute value, such as `"my-thing"` in `<div id="my-thing" />`.
|
|
|
- #[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.
|
|
|
+/// IE onclick, onkeydown, etc
|
|
|
pub struct Listener<'bump> {
|
|
|
/// The type of event to listen for.
|
|
|
pub(crate) event: &'static str,
|
|
|
-
|
|
|
- /// Which scope?
|
|
|
- /// This might not actually be relevant
|
|
|
pub scope: ScopeIdx,
|
|
|
-
|
|
|
pub mounted_node: &'bump Cell<RealDomNode>,
|
|
|
-
|
|
|
- /// The callback to invoke when the event happens.
|
|
|
pub(crate) callback: &'bump dyn FnMut(VirtualEvent),
|
|
|
}
|
|
|
|
|
|
-/// 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<'a>(pub(crate) Option<&'a str>);
|
|
|
+/// Virtual Components for custom user-defined components
|
|
|
+/// Only supports the functional syntax
|
|
|
+pub struct VComponent<'src> {
|
|
|
+ pub ass_scope: Cell<Option<ScopeIdx>>,
|
|
|
+ pub caller: Rc<dyn Fn(&Scope) -> VNode>,
|
|
|
+ pub children: &'src [VNode<'src>],
|
|
|
+ pub comparator: Option<&'src dyn Fn(&VComponent) -> bool>,
|
|
|
+ // a pointer into the bump arena (given by the 'src lifetime)
|
|
|
+ raw_props: *const (),
|
|
|
+ // a pointer to the raw fn typ
|
|
|
+ pub user_fc: *const (),
|
|
|
+ pub is_static: bool,
|
|
|
+}
|
|
|
|
|
|
-impl<'a> Default for NodeKey<'a> {
|
|
|
- fn default() -> NodeKey<'a> {
|
|
|
- NodeKey::NONE
|
|
|
- }
|
|
|
+/// This struct provides an ergonomic API to quickly build VNodes.
|
|
|
+///
|
|
|
+/// NodeFactory is used to build VNodes in the component's memory space.
|
|
|
+/// This struct adds metadata to the final VNode about listeners, attributes, and children
|
|
|
+#[derive(Copy, Clone)]
|
|
|
+pub struct NodeFactory<'a> {
|
|
|
+ pub scope_ref: &'a Scope,
|
|
|
+ pub listener_id: &'a Cell<usize>,
|
|
|
}
|
|
|
-impl<'a> NodeKey<'a> {
|
|
|
- /// The default, lack of a key.
|
|
|
- pub const NONE: NodeKey<'a> = NodeKey(None);
|
|
|
|
|
|
- /// Is this key `NodeKey::NONE`?
|
|
|
+impl<'a> NodeFactory<'a> {
|
|
|
#[inline]
|
|
|
- pub fn is_none(&self) -> bool {
|
|
|
- *self == Self::NONE
|
|
|
+ pub fn bump(&self) -> &'a bumpalo::Bump {
|
|
|
+ &self.scope_ref.cur_frame().bump
|
|
|
}
|
|
|
|
|
|
- /// Is this key not `NodeKey::NONE`?
|
|
|
- #[inline]
|
|
|
- pub fn is_some(&self) -> bool {
|
|
|
- !self.is_none()
|
|
|
+ pub fn static_text(text: &'static str) -> VNode {
|
|
|
+ VNode {
|
|
|
+ dom_id: RealDomNode::empty_cell(),
|
|
|
+ key: None,
|
|
|
+ kind: VNodeKind::Text(VText {
|
|
|
+ text,
|
|
|
+ is_static: true,
|
|
|
+ }),
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- /// Create a new `NodeKey`.
|
|
|
- ///
|
|
|
- /// `key` must not be `u32::MAX`.
|
|
|
- #[inline]
|
|
|
- pub fn new(key: &'a str) -> Self {
|
|
|
- NodeKey(Some(key))
|
|
|
+ pub fn raw_text(&self, args: Arguments) -> (&'a str, bool) {
|
|
|
+ match args.as_str() {
|
|
|
+ Some(static_str) => (static_str, true),
|
|
|
+ None => {
|
|
|
+ use bumpalo::core_alloc::fmt::Write;
|
|
|
+ let mut s = bumpalo::collections::String::new_in(self.bump());
|
|
|
+ s.write_fmt(args).unwrap();
|
|
|
+ (s.into_bump_str(), false)
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- #[inline]
|
|
|
- pub fn new_opt(key: Option<&'a str>) -> Self {
|
|
|
- NodeKey(key)
|
|
|
+ /// Create some text that's allocated along with the other vnodes
|
|
|
+ pub fn text(&self, args: Arguments) -> VNode<'a> {
|
|
|
+ let (text, is_static) = self.raw_text(args);
|
|
|
+ VNode {
|
|
|
+ dom_id: RealDomNode::empty_cell(),
|
|
|
+ key: None,
|
|
|
+ kind: VNodeKind::Text(VText { text, is_static }),
|
|
|
+ }
|
|
|
}
|
|
|
-}
|
|
|
-
|
|
|
-// ==============================
|
|
|
-// Custom components
|
|
|
-// ==============================
|
|
|
-
|
|
|
-/// Virtual Components for custom user-defined components
|
|
|
-/// Only supports the functional syntax
|
|
|
-pub type StableScopeAddres = Option<u32>;
|
|
|
-pub type VCompAssociatedScope = Option<ScopeIdx>;
|
|
|
|
|
|
-pub struct VComponent<'src> {
|
|
|
- pub key: NodeKey<'src>,
|
|
|
-
|
|
|
- // pub void_root: Cell<Option<RealDomNode>>,
|
|
|
- pub ass_scope: Cell<VCompAssociatedScope>,
|
|
|
-
|
|
|
- // todo: swap the RC out with
|
|
|
- pub caller: Rc<dyn Fn(&Scope) -> VNode>,
|
|
|
-
|
|
|
- pub children: &'src [VNode<'src>],
|
|
|
+ pub const fn const_el(&self) {}
|
|
|
|
|
|
- pub comparator: Option<&'src dyn Fn(&VComponent) -> bool>,
|
|
|
+ pub fn raw_element(
|
|
|
+ &self,
|
|
|
+ tag: &'static str,
|
|
|
+ listeners: &[Listener],
|
|
|
+ attributes: &[Attribute],
|
|
|
+ children: &'a [VNode<'a>],
|
|
|
+ ) {
|
|
|
+ }
|
|
|
|
|
|
- pub is_static: bool,
|
|
|
+ pub fn element() {}
|
|
|
|
|
|
- // a pointer into the bump arena (given by the 'src lifetime)
|
|
|
- // raw_props: Box<dyn Any>,
|
|
|
- raw_props: *const (),
|
|
|
+ pub fn suspended() -> VNode<'static> {
|
|
|
+ VNode {
|
|
|
+ dom_id: RealDomNode::empty_cell(),
|
|
|
+ key: None,
|
|
|
+ kind: VNodeKind::Suspended,
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- // a pointer to the raw fn typ
|
|
|
- pub user_fc: *const (),
|
|
|
-}
|
|
|
+ pub fn attr(
|
|
|
+ &self,
|
|
|
+ name: &'static str,
|
|
|
+ val: Arguments,
|
|
|
+ namespace: Option<&'static str>,
|
|
|
+ is_volatile: bool,
|
|
|
+ ) -> Attribute<'a> {
|
|
|
+ let (value, is_static) = self.raw_text(val);
|
|
|
+ Attribute {
|
|
|
+ name,
|
|
|
+ value,
|
|
|
+ is_static,
|
|
|
+ namespace,
|
|
|
+ is_volatile,
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
-impl<'a> VComponent<'a> {
|
|
|
- /// When the rsx! macro is called, it will check if the CanMemo flag is set to true (from the Props impl)
|
|
|
- /// If it is set to true, then this method will be called which implements automatic memoization.
|
|
|
- ///
|
|
|
- /// If the CanMemo is `false`, then the macro will call the backup method which always defaults to "false"
|
|
|
- pub fn new<P: Properties + 'a>(
|
|
|
- cx: &NodeFactory<'a>,
|
|
|
+ pub fn virtual_child<P, C>(
|
|
|
+ &self,
|
|
|
component: FC<P>,
|
|
|
props: P,
|
|
|
- key: Option<&'a str>,
|
|
|
+ key: Option<&'a str>, // key: NodeKey<'a>,
|
|
|
children: &'a [VNode<'a>],
|
|
|
- ) -> Self {
|
|
|
- let bump = cx.bump();
|
|
|
- let user_fc = component as *const ();
|
|
|
-
|
|
|
- let props = bump.alloc(props);
|
|
|
+ ) -> VNode<'a>
|
|
|
+ where
|
|
|
+ P: Properties + 'a,
|
|
|
+ {
|
|
|
+ // We don't want the fat part of the fat pointer
|
|
|
+ // This function does static dispatch so we don't need any VTable stuff
|
|
|
+ let props = self.bump().alloc(props);
|
|
|
let raw_props = props as *const P as *const ();
|
|
|
|
|
|
- let comparator: Option<&dyn Fn(&VComponent) -> bool> = Some(bump.alloc_with(|| {
|
|
|
+ let user_fc = component as *const ();
|
|
|
+
|
|
|
+ let comparator: Option<&dyn Fn(&VComponent) -> bool> = Some(self.bump().alloc_with(|| {
|
|
|
move |other: &VComponent| {
|
|
|
- // Safety:
|
|
|
- // ------
|
|
|
- //
|
|
|
- // Invariants:
|
|
|
- // - Component function pointers are the same
|
|
|
- // - Generic properties on the same function pointer are the same
|
|
|
- // - Lifetime of P borrows from its parent
|
|
|
- // - The parent scope still exists when method is called
|
|
|
- // - Casting from T to *const () is portable
|
|
|
- // - Casting raw props to P can only happen when P is static
|
|
|
- //
|
|
|
- // Explanation:
|
|
|
- // We are guaranteed that the props will be of the same type because
|
|
|
- // there is no way to create a VComponent other than this `new` method.
|
|
|
- //
|
|
|
- // Therefore, if the render functions are identical (by address), then so will be
|
|
|
- // props type paramter (because it is the same render function). Therefore, we can be
|
|
|
- // sure that it is safe to interperet the previous props raw pointer as the same props
|
|
|
- // type. From there, we can call the props' "memoize" method to see if we can
|
|
|
- // avoid re-rendering the component.
|
|
|
if user_fc == other.user_fc {
|
|
|
let real_other = unsafe { &*(other.raw_props as *const _ as *const P) };
|
|
|
let props_memoized = unsafe { props.memoize(&real_other) };
|
|
@@ -414,81 +221,200 @@ impl<'a> VComponent<'a> {
|
|
|
}
|
|
|
}));
|
|
|
|
|
|
- let key = match key {
|
|
|
- Some(key) => NodeKey::new(key),
|
|
|
- None => NodeKey(None),
|
|
|
- };
|
|
|
+ VNode {
|
|
|
+ key,
|
|
|
+ dom_id: Cell::new(RealDomNode::empty()),
|
|
|
+ kind: VNodeKind::Component(self.bump().alloc_with(|| VComponent {
|
|
|
+ user_fc,
|
|
|
+ comparator,
|
|
|
+ raw_props,
|
|
|
+ children,
|
|
|
+ caller: NodeFactory::create_component_caller(component, raw_props),
|
|
|
+ is_static: children.len() == 0 && P::IS_STATIC && key.is_none(),
|
|
|
+ ass_scope: Cell::new(None),
|
|
|
+ })),
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- let caller = create_component_caller(component, raw_props);
|
|
|
+ pub fn create_component_caller<'g, P: 'g>(
|
|
|
+ component: FC<P>,
|
|
|
+ raw_props: *const (),
|
|
|
+ ) -> Rc<dyn for<'r> Fn(&'r Scope) -> VNode<'r>> {
|
|
|
+ type Captured<'a> = Rc<dyn for<'r> Fn(&'r Scope) -> VNode<'r> + 'a>;
|
|
|
+ let caller: Captured = Rc::new(move |scp: &Scope| -> VNode {
|
|
|
+ // cast back into the right lifetime
|
|
|
+ let safe_props: &'_ P = unsafe { &*(raw_props as *const P) };
|
|
|
+ let tasks = RefCell::new(Vec::new());
|
|
|
+ let cx: Context<P> = Context {
|
|
|
+ props: safe_props,
|
|
|
+ scope: scp,
|
|
|
+ tasks: &tasks,
|
|
|
+ };
|
|
|
+
|
|
|
+ let res = component(cx);
|
|
|
+
|
|
|
+ // submit any async tasks to the scope
|
|
|
+ for task in tasks.borrow_mut().drain(..) {
|
|
|
+ scp.submit_task(task);
|
|
|
+ }
|
|
|
|
|
|
- // If the component does not have children, has no props (we can't memoize props), and has no no key, then we don't
|
|
|
- // need to bother diffing it in the future
|
|
|
- //
|
|
|
- // This is more of an optimization to prevent unnecessary descending through the tree during diffing, rather than
|
|
|
- // actually speeding up the diff process itself
|
|
|
- let is_static = children.len() == 0 && P::IS_STATIC && key.is_none();
|
|
|
+ let g2 = unsafe { std::mem::transmute(res) };
|
|
|
|
|
|
- Self {
|
|
|
- user_fc,
|
|
|
- comparator,
|
|
|
- raw_props,
|
|
|
- children,
|
|
|
- ass_scope: Cell::new(None),
|
|
|
- key,
|
|
|
- caller,
|
|
|
- is_static,
|
|
|
- // void_root: Cell::new(None),
|
|
|
+ g2
|
|
|
+ });
|
|
|
+ unsafe { std::mem::transmute::<_, Captured<'static>>(caller) }
|
|
|
+ }
|
|
|
+
|
|
|
+ pub fn fragment_from_iter(
|
|
|
+ self,
|
|
|
+ node_iter: impl IntoIterator<Item = impl IntoVNode<'a>>,
|
|
|
+ ) -> VNode<'a> {
|
|
|
+ let mut nodes = bumpalo::collections::Vec::new_in(self.bump());
|
|
|
+ // TODO throw an error if there are nodes without keys
|
|
|
+ for node in node_iter.into_iter() {
|
|
|
+ nodes.push(node.into_vnode(self));
|
|
|
+ }
|
|
|
+ VNode {
|
|
|
+ dom_id: RealDomNode::empty_cell(),
|
|
|
+ key: None,
|
|
|
+ kind: VNodeKind::Fragment(VFragment {
|
|
|
+ children: nodes.into_bump_slice(),
|
|
|
+ }),
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-type Captured<'a> = Rc<dyn for<'r> Fn(&'r Scope) -> VNode<'r> + 'a>;
|
|
|
+impl<'a> IntoIterator for VNode<'a> {
|
|
|
+ type Item = VNode<'a>;
|
|
|
+ type IntoIter = std::iter::Once<Self::Item>;
|
|
|
+ fn into_iter(self) -> Self::IntoIter {
|
|
|
+ std::iter::once(self)
|
|
|
+ }
|
|
|
+}
|
|
|
+impl<'a> IntoVNode<'a> for VNode<'a> {
|
|
|
+ fn into_vnode(self, _: NodeFactory<'a>) -> VNode<'a> {
|
|
|
+ self
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl<'a> IntoVNode<'a> for &VNode<'a> {
|
|
|
+ fn into_vnode(self, _: NodeFactory<'a>) -> VNode<'a> {
|
|
|
+ self.clone()
|
|
|
+ }
|
|
|
+}
|
|
|
|
|
|
-pub fn create_component_caller<'a, P: 'a>(
|
|
|
- user_component: FC<P>,
|
|
|
- raw_props: *const (),
|
|
|
-) -> Rc<dyn for<'r> Fn(&'r Scope) -> VNode<'r>> {
|
|
|
- let g: Captured = Rc::new(move |scp: &Scope| -> VNode {
|
|
|
- // cast back into the right lifetime
|
|
|
- let safe_props: &'_ P = unsafe { &*(raw_props as *const P) };
|
|
|
- let tasks = RefCell::new(Vec::new());
|
|
|
- let cx: Context<P> = Context {
|
|
|
- props: safe_props,
|
|
|
- scope: scp,
|
|
|
- tasks: &tasks,
|
|
|
- };
|
|
|
+pub trait IntoVNode<'a> {
|
|
|
+ fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a>;
|
|
|
+}
|
|
|
|
|
|
- let g = user_component(cx);
|
|
|
+// Wrap the the node-builder closure in a concrete type.
|
|
|
+// ---
|
|
|
+// This is a bit of a hack to implement the IntoVNode trait for closure types.
|
|
|
+pub struct LazyNodes<'a, G>
|
|
|
+where
|
|
|
+ G: FnOnce(NodeFactory<'a>) -> VNode<'a>,
|
|
|
+{
|
|
|
+ inner: G,
|
|
|
+ _p: PhantomData<&'a ()>,
|
|
|
+}
|
|
|
|
|
|
- for task in tasks.borrow_mut().drain(..) {
|
|
|
- scp.submit_task(task);
|
|
|
+impl<'a, G> LazyNodes<'a, G>
|
|
|
+where
|
|
|
+ G: FnOnce(NodeFactory<'a>) -> VNode<'a>,
|
|
|
+{
|
|
|
+ pub fn new(f: G) -> Self {
|
|
|
+ Self {
|
|
|
+ inner: f,
|
|
|
+ _p: PhantomData {},
|
|
|
}
|
|
|
+ }
|
|
|
+}
|
|
|
|
|
|
- let g2 = unsafe { std::mem::transmute(g) };
|
|
|
+// Cover the cases where nodes are used by macro.
|
|
|
+// Likely used directly.
|
|
|
+// ---
|
|
|
+// let nodes = rsx!{ ... };
|
|
|
+// rsx! { {nodes } }
|
|
|
+impl<'a, G> IntoVNode<'a> for LazyNodes<'a, G>
|
|
|
+where
|
|
|
+ G: FnOnce(NodeFactory<'a>) -> VNode<'a>,
|
|
|
+{
|
|
|
+ fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> {
|
|
|
+ (self.inner)(cx)
|
|
|
+ }
|
|
|
+}
|
|
|
|
|
|
- g2
|
|
|
- });
|
|
|
- let r: Captured<'static> = unsafe { std::mem::transmute(g) };
|
|
|
- r
|
|
|
+// Required because anything that enters brackets in the rsx! macro needs to implement IntoIterator
|
|
|
+impl<'a, G> IntoIterator for LazyNodes<'a, G>
|
|
|
+where
|
|
|
+ G: FnOnce(NodeFactory<'a>) -> VNode<'a>,
|
|
|
+{
|
|
|
+ type Item = Self;
|
|
|
+ type IntoIter = std::iter::Once<Self::Item>;
|
|
|
+ fn into_iter(self) -> Self::IntoIter {
|
|
|
+ std::iter::once(self)
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
-pub struct VFragment<'src> {
|
|
|
- pub key: NodeKey<'src>,
|
|
|
- pub children: &'src [VNode<'src>],
|
|
|
- pub void_root: Cell<Option<RealDomNode>>,
|
|
|
+impl IntoVNode<'_> for () {
|
|
|
+ fn into_vnode<'a>(self, cx: NodeFactory<'a>) -> VNode<'a> {
|
|
|
+ cx.fragment_from_iter(None as Option<VNode>)
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
-impl<'a> VFragment<'a> {
|
|
|
- pub fn new(key: Option<&'a str>, children: &'a [VNode<'a>]) -> Self {
|
|
|
- let key = match key {
|
|
|
- Some(key) => NodeKey::new(key),
|
|
|
- None => NodeKey(None),
|
|
|
+impl IntoVNode<'_> for Option<()> {
|
|
|
+ fn into_vnode<'a>(self, cx: NodeFactory<'a>) -> VNode<'a> {
|
|
|
+ cx.fragment_from_iter(None as Option<VNode>)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl Debug for NodeFactory<'_> {
|
|
|
+ fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
+ Ok(())
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// it's okay to clone because vnodes are just references to places into the bump
|
|
|
+impl<'a> Clone for VNode<'a> {
|
|
|
+ fn clone(&self) -> Self {
|
|
|
+ let kind = match &self.kind {
|
|
|
+ VNodeKind::Element(element) => VNodeKind::Element(element),
|
|
|
+ VNodeKind::Text(old) => VNodeKind::Text(VText {
|
|
|
+ text: old.text,
|
|
|
+ is_static: old.is_static,
|
|
|
+ }),
|
|
|
+ VNodeKind::Fragment(fragment) => VNodeKind::Fragment(VFragment {
|
|
|
+ children: fragment.children,
|
|
|
+ }),
|
|
|
+ VNodeKind::Component(component) => VNodeKind::Component(component),
|
|
|
+ VNodeKind::Suspended => VNodeKind::Suspended,
|
|
|
};
|
|
|
|
|
|
- Self {
|
|
|
- key,
|
|
|
- children,
|
|
|
- void_root: Cell::new(None),
|
|
|
+ todo!()
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl Debug for VNode<'_> {
|
|
|
+ fn fmt(&self, s: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
|
|
+ match &self.kind {
|
|
|
+ VNodeKind::Element(el) => write!(s, "element, {}", el.tag_name),
|
|
|
+ VNodeKind::Text(t) => write!(s, "text, {}", t.text),
|
|
|
+ VNodeKind::Fragment(_) => write!(s, "fragment"),
|
|
|
+ VNodeKind::Suspended { .. } => write!(s, "suspended"),
|
|
|
+ VNodeKind::Component(_) => write!(s, "component"),
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+mod tests {
|
|
|
+ use super::*;
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn test() {}
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn sizing() {
|
|
|
+ dbg!(std::mem::size_of::<VElement>());
|
|
|
+ dbg!(std::mem::align_of::<VElement>());
|
|
|
+ }
|
|
|
+}
|