wip: use the new structure

This commit is contained in:
Jonathan Kelley committed 2021-07-12 18:19:27 -04:00
1 parent a85b8c4b6b
commit a05047d01e
12 files changed
+884 -1241

No files matched your search

+318 -392
View File
@@ -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>);
impl<'a> Default for NodeKey<'a> {
fn default() -> NodeKey<'a> {
NodeKey::NONE
}
}
impl<'a> NodeKey<'a> {
/// The default, lack of a key.
pub const NONE: NodeKey<'a> = NodeKey(None);
/// 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: &'a str) -> Self {
NodeKey(Some(key))
}
#[inline]
pub fn new_opt(key: Option<&'a str>) -> Self {
NodeKey(key)
}
}
// ==============================
// 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 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>,
pub is_static: bool,
// a pointer into the bump arena (given by the 'src lifetime)
// raw_props: Box<dyn Any>,
raw_props: *const (),
// a pointer to the raw fn typ
pub user_fc: *const (),
pub is_static: bool,
}
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>,
/// 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> NodeFactory<'a> {
#[inline]
pub fn bump(&self) -> &'a bumpalo::Bump {
&self.scope_ref.cur_frame().bump
}
pub fn static_text(text: &'static str) -> VNode {
VNode {
dom_id: RealDomNode::empty_cell(),
key: None,
kind: VNodeKind::Text(VText {
text,
is_static: true,
}),
}
}
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)
}
}
}
/// 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 }),
}
}
pub const fn const_el(&self) {}
pub fn raw_element(
&self,
tag: &'static str,
listeners: &[Listener],
attributes: &[Attribute],
children: &'a [VNode<'a>],
) {
}
pub fn element() {}
pub fn suspended() -> VNode<'static> {
VNode {
dom_id: RealDomNode::empty_cell(),
key: None,
kind: VNodeKind::Suspended,
}
}
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,
}
}
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),
};
let caller = create_component_caller(component, raw_props);
// 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();
Self {
user_fc,
comparator,
raw_props,
children,
ass_scope: Cell::new(None),
VNode {
key,
caller,
is_static,
// void_root: Cell::new(None),
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),
})),
}
}
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);
}
let g2 = unsafe { std::mem::transmute(res) };
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>;
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,
};
let g = user_component(cx);
for task in tasks.borrow_mut().drain(..) {
scp.submit_task(task);
}
let g2 = unsafe { std::mem::transmute(g) };
g2
});
let r: Captured<'static> = unsafe { std::mem::transmute(g) };
r
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
}
}
pub struct VFragment<'src> {
pub key: NodeKey<'src>,
pub children: &'src [VNode<'src>],
pub void_root: Cell<Option<RealDomNode>>,
impl<'a> IntoVNode<'a> for &VNode<'a> {
fn into_vnode(self, _: NodeFactory<'a>) -> VNode<'a> {
self.clone()
}
}
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),
};
pub trait IntoVNode<'a> {
fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a>;
}
// 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 ()>,
}
impl<'a, G> LazyNodes<'a, G>
where
G: FnOnce(NodeFactory<'a>) -> VNode<'a>,
{
pub fn new(f: G) -> Self {
Self {
key,
children,
void_root: Cell::new(None),
inner: f,
_p: PhantomData {},
}
}
}
// 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)
}
}
// 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)
}
}
impl IntoVNode<'_> for () {
fn into_vnode<'a>(self, cx: NodeFactory<'a>) -> VNode<'a> {
cx.fragment_from_iter(None as Option<VNode>)
}
}
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,
};
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>());
}
}