wip: rewrite core to be template focused

This commit is contained in:
Jonathan Kelley 2022-10-27 21:58:47 -07:00
parent 67012c38df
commit 23603aaaf5
34 changed files with 3177 additions and 2456 deletions

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,131 @@
#![allow(non_snake_case)]
#![doc = include_str!("../README.md")]
#![warn(missing_docs)]
pub(crate) mod diff;
pub(crate) mod events;
pub(crate) mod lazynodes;
pub(crate) mod mutations;
pub(crate) mod nodes;
pub(crate) mod properties;
pub(crate) mod scopes;
pub(crate) mod virtual_dom;
pub(crate) mod innerlude {
pub use crate::events::*;
pub use crate::lazynodes::*;
pub use crate::mutations::*;
pub use crate::nodes::*;
pub use crate::properties::*;
pub use crate::scopes::*;
pub use crate::virtual_dom::*;
/// An [`Element`] is a possibly-none [`VNode`] created by calling `render` on [`Scope`] or [`ScopeState`].
///
/// Any [`None`] [`Element`] will automatically be coerced into a placeholder [`VNode`] with the [`VNode::Placeholder`] variant.
pub type Element<'a> = Option<VNode<'a>>;
/// A [`Component`] is a function that takes a [`Scope`] and returns an [`Element`].
///
/// Components can be used in other components with two syntax options:
/// - lowercase as a function call with named arguments (rust style)
/// - uppercase as an element (react style)
///
/// ## Rust-Style
///
/// ```rust, ignore
/// fn example(cx: Scope<Props>) -> Element {
/// // ...
/// }
///
/// rsx!(
/// example()
/// )
/// ```
/// ## React-Style
/// ```rust, ignore
/// fn Example(cx: Scope<Props>) -> Element {
/// // ...
/// }
///
/// rsx!(
/// Example {}
/// )
/// ```
pub type Component<P = ()> = fn(Scope<P>) -> Element;
/// A list of attributes
pub type Attributes<'a> = Option<&'a [Attribute<'a>]>;
}
pub use crate::innerlude::{
AnyAttributeValue, AnyEvent, Attribute, AttributeValue, Component, Element, ElementId,
EventHandler, EventPriority, IntoVNode, LazyNodes, Listener, NodeFactory, Properties, Renderer,
SchedulerMsg, Scope, ScopeId, ScopeState, TaskId, Template, TemplateAttribute, TemplateNode,
UiEvent, UserEvent, VComponent, VElement, VNode, VTemplate, VText, VirtualDom,
};
/// The purpose of this module is to alleviate imports of many common types
///
/// This includes types like [`Scope`], [`Element`], and [`Component`].
pub mod prelude {
pub use crate::innerlude::{
fc_to_builder, Attributes, Component, Element, EventHandler, LazyNodes, NodeFactory,
Properties, Scope, ScopeId, ScopeState, Template, VNode, VirtualDom,
};
}
pub mod exports {
//! Important dependencies that are used by the rest of the library
//! Feel free to just add the dependencies in your own Crates.toml
pub use bumpalo;
pub use futures_channel;
}
/// Functions that wrap unsafe functionality to prevent us from misusing it at the callsite
pub(crate) mod unsafe_utils {
use crate::VNode;
pub(crate) unsafe fn extend_vnode<'a, 'b>(node: &'a VNode<'a>) -> &'b VNode<'b> {
std::mem::transmute(node)
}
}
#[macro_export]
/// A helper macro for using hooks in async environements.
///
/// # Usage
///
///
/// ```ignore
/// let (data) = use_ref(&cx, || {});
///
/// let handle_thing = move |_| {
/// to_owned![data]
/// cx.spawn(async move {
/// // do stuff
/// });
/// }
/// ```
macro_rules! to_owned {
($($es:ident),+) => {$(
#[allow(unused_mut)]
let mut $es = $es.to_owned();
)*}
}
/// get the code location of the code that called this function
#[macro_export]
macro_rules! get_line_num {
() => {
concat!(
file!(),
":",
line!(),
":",
column!(),
":",
env!("CARGO_MANIFEST_DIR")
)
};
}

View file

@ -0,0 +1,98 @@
//! Instructions returned by the VirtualDOM on how to modify the Real DOM.
//!
//! This module contains an internal API to generate these instructions.
//!
//! Beware that changing code in this module will break compatibility with
//! interpreters for these types of DomEdits.
use crate::innerlude::*;
/// A renderer for Dioxus to modify during diffing
///
/// The renderer should implement a Stack Machine. IE each call to the below methods are modifications to the renderer's
/// internal stack for creating and modifying nodes.
///
/// Dioxus guarantees that the stack is always in a valid state.
pub trait Renderer<'a> {
/// Load this element onto the stack
fn push_root(&mut self, root: ElementId);
/// Pop the topmost element from the stack
fn pop_root(&mut self);
/// Replace the given element with the next m elements on the stack
fn replace_with(&mut self, root: ElementId, m: u32);
/// Insert the next m elements on the stack after the given element
fn insert_after(&mut self, root: ElementId, n: u32);
/// Insert the next m elements on the stack before the given element
fn insert_before(&mut self, root: ElementId, n: u32);
/// Append the next n elements on the stack to the n+1 element on the stack
fn append_children(&mut self, n: u32);
/// Create a new element with the given text and ElementId
fn create_text_node(&mut self, text: &'a str, root: ElementId);
/// Create an element with the given tag name, optional namespace, and ElementId
/// Note that namespaces do not cascade down the tree, so the renderer must handle this if it implements namespaces
fn create_element(&mut self, tag: &'static str, ns: Option<&'static str>, id: ElementId);
/// Create a hidden element to be used later for replacement.
/// Used in suspense, lists, and other places where we need to hide a node before it is ready to be shown.
/// This is up to the renderer to implement, but it should not be visible to the user.
fn create_placeholder(&mut self, id: ElementId);
/// Remove the targeted node from the DOM
fn remove(&mut self, root: ElementId);
/// Remove an attribute from an existing element
fn remove_attribute(&mut self, attribute: &Attribute, root: ElementId);
/// Remove all the children of the given element
fn remove_children(&mut self, root: ElementId);
/// Attach a new listener to the dom
fn new_event_listener(&mut self, listener: &Listener, scope: ScopeId);
/// Remove an existing listener from the dom
fn remove_event_listener(&mut self, event: &'static str, root: ElementId);
/// Set the text content of a node
fn set_text(&mut self, text: &'a str, root: ElementId);
/// Set an attribute on an element
fn set_attribute(
&mut self,
name: &'static str,
value: AttributeValue<'a>,
namespace: Option<&'a str>,
root: ElementId,
);
/// General statistics for doing things that extend outside of the renderer
fn mark_dirty_scope(&mut self, scope: ScopeId);
/// Save the current n nodes to the ID to be loaded later
fn save(&mut self, id: &'static str, num: u32);
/// Loads a set of saved nodes from the ID into a scratch space
fn load(&mut self, id: &'static str, index: u32);
/// Assign the element on the stack's descendent the given ID
fn assign_id(&mut self, descendent: &'static [u8], id: ElementId);
/// Replace the given element of the topmost element with the next m elements on the stack
/// Is essentially a combination of assign_id and replace_with
fn replace_descendant(&mut self, descendent: &'static [u8], m: u32);
}
/*
div {
div {
div {
div {}
}
}
}
push_child(0)
push_child(1)
push_child(3)
push_child(4)
pop
pop
clone_node(0)
set_node(el, [1,2,3,4])
set_attribute("class", "foo")
append_child(1)
*/

View file

@ -0,0 +1,88 @@
use std::{cell::Cell, num::NonZeroUsize};
/// A reference to a template along with any context needed to hydrate it
pub struct VTemplate<'a> {
// The ID assigned for the root of this template
pub node_id: Cell<ElementId>,
pub template: &'static Template,
/// All the dynamic nodes for a template
pub dynamic_nodes: &'a [DynamicNode<'a>],
pub dynamic_attrs: &'a [AttributeLocation<'a>],
}
#[derive(Debug, Clone, Copy)]
pub struct Template {
pub id: &'static str,
pub root: TemplateNode<'static>,
// todo: locations of dynamic nodes
pub node_pathways: &'static [&'static [u8]],
// todo: locations of dynamic nodes
pub attr_pathways: &'static [&'static [u8]],
}
/// A weird-ish variant of VNodes with way more limited types
#[derive(Debug, Clone, Copy)]
pub enum TemplateNode<'a> {
/// A simple element
Element {
tag: &'a str,
namespace: Option<&'a str>,
attrs: &'a [TemplateAttribute<'a>],
children: &'a [TemplateNode<'a>],
},
Text(&'a str),
Dynamic(usize),
DynamicText(usize),
}
pub enum DynamicNode<'a> {
// Anything declared in component form
// IE in caps or with underscores
Component {
name: &'static str,
},
// Comes in with string interpolation or from format_args, include_str, etc
Text {
id: Cell<ElementId>,
value: &'static str,
},
// Anything that's coming in as an iterator
Fragment {
children: &'a [VTemplate<'a>],
},
}
#[derive(Debug)]
pub struct TemplateAttribute<'a> {
pub name: &'static str,
pub value: &'a str,
pub namespace: Option<&'static str>,
pub volatile: bool,
}
pub struct AttributeLocation<'a> {
pub mounted_element: Cell<ElementId>,
pub attrs: &'a [Attribute<'a>],
}
#[derive(Debug)]
pub struct Attribute<'a> {
pub name: &'static str,
pub value: &'a str,
pub namespace: Option<&'static str>,
}
#[test]
fn what_are_the_sizes() {
dbg!(std::mem::size_of::<VTemplate>());
dbg!(std::mem::size_of::<Template>());
dbg!(std::mem::size_of::<TemplateNode>());
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,71 @@
use std::cell::Cell;
use crate::{
component::Component,
element::Element,
scopes::{Scope, ScopeState},
};
pub trait AnyProps {
fn as_ptr(&self) -> *const ();
fn render<'a>(&'a self, bump: &'a ScopeState) -> Element<'a>;
unsafe fn memoize(&self, other: &dyn AnyProps) -> bool;
}
pub(crate) struct VComponentProps<P> {
pub render_fn: Component<P>,
pub memo: unsafe fn(&P, &P) -> bool,
pub props: Scope<P>,
}
impl VComponentProps<()> {
pub fn new_empty(render_fn: Component<()>) -> Self {
Self {
render_fn,
memo: <() as PartialEq>::eq,
props: Scope {
props: (),
state: Cell::new(std::ptr::null_mut()),
},
}
}
}
impl<P> VComponentProps<P> {
pub(crate) fn new(
render_fn: Component<P>,
memo: unsafe fn(&P, &P) -> bool,
props: Scope<P>,
) -> Self {
Self {
render_fn,
memo,
props,
}
}
}
impl<P> AnyProps for VComponentProps<P> {
fn as_ptr(&self) -> *const () {
&self.props as *const _ as *const ()
}
// Safety:
// this will downcast the other ptr as our swallowed type!
// you *must* make this check *before* calling this method
// if your functions are not the same, then you will downcast a pointer into a different type (UB)
unsafe fn memoize(&self, other: &dyn AnyProps) -> bool {
let real_other: &P = &*(other.as_ptr() as *const _ as *const P);
let real_us: &P = &*(self.as_ptr() as *const _ as *const P);
(self.memo)(real_us, real_other)
}
fn render<'a>(&'a self, scope: &'a ScopeState) -> Element<'a> {
// Make sure the scope ptr is not null
self.props.state.set(scope);
// Call the render function directly
// todo: implement async
(self.render_fn)(unsafe { std::mem::transmute(&self.props) })
}
}

View file

@ -0,0 +1,30 @@
use std::num::NonZeroUsize;
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct ElementId(pub usize);
// pub struct ElementId(pub NonZeroUsize);
// impl Default for ElementId {
// fn default() -> Self {
// Self(NonZeroUsize::new(1).unwrap())
// }
// }
pub struct ElementArena {
counter: usize,
}
impl Default for ElementArena {
fn default() -> Self {
Self { counter: 1 }
}
}
impl ElementArena {
pub fn next(&mut self) -> ElementId {
let id = self.counter;
self.counter += 1;
ElementId(id)
}
}

View file

@ -0,0 +1,24 @@
use std::cell::Cell;
use bumpalo::Bump;
use crate::nodes::VTemplate;
pub struct BumpFrame {
pub bump: Bump,
pub node: Cell<*const VTemplate<'static>>,
}
impl BumpFrame {
pub fn new(capacity: usize) -> Self {
let bump = Bump::with_capacity(capacity);
Self {
bump,
node: Cell::new(std::ptr::null()),
}
}
pub fn reset(&mut self) {
self.bump.reset();
self.node.set(std::ptr::null());
}
}

View file

@ -0,0 +1,7 @@
// pub trait IntoComponentType<T> {
// fn into_component_type(self) -> ComponentType;
// }
use crate::{element::Element, scopes::Scope};
pub type Component<T = ()> = fn(&Scope<T>) -> Element;

114
packages/core/src/create.rs Normal file
View file

@ -0,0 +1,114 @@
use crate::VirtualDom;
use crate::any_props::VComponentProps;
use crate::arena::ElementArena;
use crate::component::Component;
use crate::mutations::Mutation;
use crate::nodes::{
AttributeLocation, DynamicNode, DynamicNodeKind, Template, TemplateId, TemplateNode,
};
use crate::scopes::Scope;
use crate::{
any_props::AnyProps,
arena::ElementId,
bump_frame::BumpFrame,
nodes::VTemplate,
scopes::{ComponentPtr, ScopeId, ScopeState},
};
use slab::Slab;
impl VirtualDom {
/// Create this template and write its mutations
pub fn create<'a>(
&mut self,
mutations: &mut Vec<Mutation<'a>>,
template: &'a VTemplate<'a>,
) -> usize {
// The best renderers will have tempaltes prehydrated
// Just in case, let's create the template using instructions anyways
if !self.templates.contains_key(&template.template.id) {
self.create_static_template(mutations, template.template);
}
// Walk the roots backwards, creating nodes and assigning IDs
// todo: adjust dynamic nodes to be in the order of roots and then leaves (ie BFS)
let mut dynamic_attrs = template.dynamic_attrs.iter().peekable();
let mut dynamic_nodes = template.dynamic_nodes.iter().peekable();
let mut on_stack = 0;
for (root_idx, root) in template.template.roots.iter().enumerate() {
on_stack += match root {
TemplateNode::Dynamic(id) => {
self.create_dynamic_node(mutations, template, &template.dynamic_nodes[*id])
}
TemplateNode::DynamicText { .. }
| TemplateNode::Element { .. }
| TemplateNode::Text(_) => 1,
};
// we're on top of a node that has a dynamic attribute for a descendant
// Set that attribute now before the stack gets in a weird state
while let Some(loc) = dynamic_attrs.next_if(|a| a.path[0] == root_idx as u8) {
// Attach all the elementIDs to the nodes with dynamic content
let id = self.elements.next();
mutations.push(Mutation::AssignId {
path: &loc.path[1..],
id,
});
loc.mounted_element.set(id);
for attr in loc.attrs {
mutations.push(Mutation::SetAttribute {
name: attr.name,
value: attr.value,
id,
});
}
}
// We're on top of a node that has a dynamic child for a descndent
// Might as well set it now while we can
while let Some(node) = dynamic_nodes.next_if(|f| f.path[0] == root_idx as u8) {
self.create_dynamic_node(mutations, template, node);
}
}
on_stack
}
fn create_static_template(&mut self, mutations: &mut Vec<Mutation>, template: &Template) {
todo!("load template")
}
fn create_dynamic_node<'a>(
&mut self,
mutations: &mut Vec<Mutation<'a>>,
template: &VTemplate<'a>,
node: &'a DynamicNode<'a>,
) -> usize {
match &node.kind {
DynamicNodeKind::Text { id, value } => {
let new_id = self.elements.next();
id.set(new_id);
mutations.push(Mutation::HydrateText {
id: new_id,
path: &node.path[1..],
value,
});
1
}
DynamicNodeKind::Component { props, fn_ptr, .. } => {
let id = self.new_scope(*fn_ptr, None, ElementId(0), *props);
let template = self.run_scope(id);
todo!("create component has bad data");
}
DynamicNodeKind::Fragment { children } => children
.iter()
.fold(0, |acc, child| acc + self.create(mutations, child)),
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,3 @@
use crate::nodes::{Template, VTemplate};
pub type Element<'a> = Option<VTemplate<'a>>;

View file

@ -1,131 +1,75 @@
#![allow(non_snake_case)]
#![doc = include_str!("../README.md")]
#![warn(missing_docs)]
use std::collections::HashMap;
pub(crate) mod diff;
pub(crate) mod events;
pub(crate) mod lazynodes;
pub(crate) mod mutations;
pub(crate) mod nodes;
pub(crate) mod properties;
pub(crate) mod scopes;
pub(crate) mod virtual_dom;
pub(crate) mod innerlude {
pub use crate::events::*;
pub use crate::lazynodes::*;
pub use crate::mutations::*;
pub use crate::nodes::*;
pub use crate::properties::*;
pub use crate::scopes::*;
pub use crate::virtual_dom::*;
/// An [`Element`] is a possibly-none [`VNode`] created by calling `render` on [`Scope`] or [`ScopeState`].
///
/// Any [`None`] [`Element`] will automatically be coerced into a placeholder [`VNode`] with the [`VNode::Placeholder`] variant.
pub type Element<'a> = Option<VNode<'a>>;
/// A [`Component`] is a function that takes a [`Scope`] and returns an [`Element`].
///
/// Components can be used in other components with two syntax options:
/// - lowercase as a function call with named arguments (rust style)
/// - uppercase as an element (react style)
///
/// ## Rust-Style
///
/// ```rust, ignore
/// fn example(cx: Scope<Props>) -> Element {
/// // ...
/// }
///
/// rsx!(
/// example()
/// )
/// ```
/// ## React-Style
/// ```rust, ignore
/// fn Example(cx: Scope<Props>) -> Element {
/// // ...
/// }
///
/// rsx!(
/// Example {}
/// )
/// ```
pub type Component<P = ()> = fn(Scope<P>) -> Element;
/// A list of attributes
pub type Attributes<'a> = Option<&'a [Attribute<'a>]>;
}
pub use crate::innerlude::{
AnyAttributeValue, AnyEvent, Attribute, AttributeValue, Component, Element, ElementId,
EventHandler, EventPriority, IntoVNode, LazyNodes, Listener, NodeFactory, Properties, Renderer,
SchedulerMsg, Scope, ScopeId, ScopeState, TaskId, Template, TemplateAttribute, TemplateNode,
UiEvent, UserEvent, VComponent, VElement, VNode, VTemplate, VText, VirtualDom,
use crate::{
any_props::AnyProps,
arena::ElementId,
bump_frame::BumpFrame,
nodes::VTemplate,
scopes::{ComponentPtr, ScopeId, ScopeState},
};
use any_props::VComponentProps;
use arena::ElementArena;
use component::Component;
use mutations::Mutation;
use nodes::{DynamicNode, Template, TemplateId};
use scopes::Scope;
use slab::Slab;
/// The purpose of this module is to alleviate imports of many common types
///
/// This includes types like [`Scope`], [`Element`], and [`Component`].
pub mod prelude {
pub use crate::innerlude::{
fc_to_builder, Attributes, Component, Element, EventHandler, LazyNodes, NodeFactory,
Properties, Scope, ScopeId, ScopeState, Template, VNode, VirtualDom,
};
mod any_props;
mod arena;
mod bump_frame;
mod component;
mod create;
mod diff;
mod element;
mod mutations;
mod nodes;
mod scope_arena;
mod scopes;
pub struct VirtualDom {
templates: HashMap<TemplateId, Template>,
elements: ElementArena,
scopes: Slab<ScopeState>,
scope_stack: Vec<ScopeId>,
}
pub mod exports {
//! Important dependencies that are used by the rest of the library
//! Feel free to just add the dependencies in your own Crates.toml
pub use bumpalo;
pub use futures_channel;
}
impl VirtualDom {
pub fn new(app: Component) -> Self {
let mut res = Self {
templates: Default::default(),
scopes: Slab::default(),
elements: ElementArena::default(),
scope_stack: Vec::new(),
};
/// Functions that wrap unsafe functionality to prevent us from misusing it at the callsite
pub(crate) mod unsafe_utils {
use crate::VNode;
res.new_scope(
app as _,
None,
ElementId(0),
Box::new(VComponentProps::new_empty(app)),
);
pub(crate) unsafe fn extend_vnode<'a, 'b>(node: &'a VNode<'a>) -> &'b VNode<'b> {
std::mem::transmute(node)
res
}
fn root_scope(&self) -> &ScopeState {
todo!()
}
/// Render the virtualdom, waiting for all suspended nodes to complete before moving on
///
/// Forces a full render of the virtualdom from scratch.
///
/// Use other methods to update the virtualdom incrementally.
pub fn render_all<'a>(&'a mut self, mutations: &mut Vec<Mutation<'a>>) {
let root = self.root_scope();
let root_template = root.current_arena();
let root_node: &'a VTemplate = unsafe { &*root_template.node.get() };
let root_node: &'a VTemplate<'a> = unsafe { std::mem::transmute(root_node) };
self.create(mutations, root_node);
}
}
#[macro_export]
/// A helper macro for using hooks in async environements.
///
/// # Usage
///
///
/// ```ignore
/// let (data) = use_ref(&cx, || {});
///
/// let handle_thing = move |_| {
/// to_owned![data]
/// cx.spawn(async move {
/// // do stuff
/// });
/// }
/// ```
macro_rules! to_owned {
($($es:ident),+) => {$(
#[allow(unused_mut)]
let mut $es = $es.to_owned();
)*}
}
/// get the code location of the code that called this function
#[macro_export]
macro_rules! get_line_num {
() => {
concat!(
file!(),
":",
line!(),
":",
column!(),
":",
env!("CARGO_MANIFEST_DIR")
)
};
}

View file

@ -1,98 +1,45 @@
//! Instructions returned by the VirtualDOM on how to modify the Real DOM.
//!
//! This module contains an internal API to generate these instructions.
//!
//! Beware that changing code in this module will break compatibility with
//! interpreters for these types of DomEdits.
use crate::arena::ElementId;
use crate::innerlude::*;
pub struct Renderer<'a> {
mutations: Vec<Mutation<'a>>,
}
/// A renderer for Dioxus to modify during diffing
///
/// The renderer should implement a Stack Machine. IE each call to the below methods are modifications to the renderer's
/// internal stack for creating and modifying nodes.
///
/// Dioxus guarantees that the stack is always in a valid state.
pub trait Renderer<'a> {
/// Load this element onto the stack
fn push_root(&mut self, root: ElementId);
/// Pop the topmost element from the stack
fn pop_root(&mut self);
/// Replace the given element with the next m elements on the stack
fn replace_with(&mut self, root: ElementId, m: u32);
/// Insert the next m elements on the stack after the given element
fn insert_after(&mut self, root: ElementId, n: u32);
/// Insert the next m elements on the stack before the given element
fn insert_before(&mut self, root: ElementId, n: u32);
/// Append the next n elements on the stack to the n+1 element on the stack
fn append_children(&mut self, n: u32);
/// Create a new element with the given text and ElementId
fn create_text_node(&mut self, text: &'a str, root: ElementId);
/// Create an element with the given tag name, optional namespace, and ElementId
/// Note that namespaces do not cascade down the tree, so the renderer must handle this if it implements namespaces
fn create_element(&mut self, tag: &'static str, ns: Option<&'static str>, id: ElementId);
/// Create a hidden element to be used later for replacement.
/// Used in suspense, lists, and other places where we need to hide a node before it is ready to be shown.
/// This is up to the renderer to implement, but it should not be visible to the user.
fn create_placeholder(&mut self, id: ElementId);
/// Remove the targeted node from the DOM
fn remove(&mut self, root: ElementId);
/// Remove an attribute from an existing element
fn remove_attribute(&mut self, attribute: &Attribute, root: ElementId);
/// Remove all the children of the given element
fn remove_children(&mut self, root: ElementId);
/// Attach a new listener to the dom
fn new_event_listener(&mut self, listener: &Listener, scope: ScopeId);
/// Remove an existing listener from the dom
fn remove_event_listener(&mut self, event: &'static str, root: ElementId);
/// Set the text content of a node
fn set_text(&mut self, text: &'a str, root: ElementId);
/// Set an attribute on an element
fn set_attribute(
&mut self,
#[derive(Debug)]
pub enum Mutation<'a> {
SetAttribute {
name: &'static str,
value: AttributeValue<'a>,
namespace: Option<&'a str>,
root: ElementId,
);
value: &'a str,
id: ElementId,
},
/// General statistics for doing things that extend outside of the renderer
fn mark_dirty_scope(&mut self, scope: ScopeId);
LoadTemplate {
name: &'static str,
index: usize,
},
/// Save the current n nodes to the ID to be loaded later
fn save(&mut self, id: &'static str, num: u32);
/// Loads a set of saved nodes from the ID into a scratch space
fn load(&mut self, id: &'static str, index: u32);
/// Assign the element on the stack's descendent the given ID
fn assign_id(&mut self, descendent: &'static [u8], id: ElementId);
/// Replace the given element of the topmost element with the next m elements on the stack
/// Is essentially a combination of assign_id and replace_with
fn replace_descendant(&mut self, descendent: &'static [u8], m: u32);
HydrateText {
path: &'static [u8],
value: &'a str,
id: ElementId,
},
SetText {
value: &'a str,
id: ElementId,
},
ReplacePlaceholder {
path: &'static [u8],
id: ElementId,
},
AssignId {
path: &'static [u8],
id: ElementId,
},
// Take the current element and replace it with the element with the given id.
Replace {
id: ElementId,
},
}
/*
div {
div {
div {
div {}
}
}
}
push_child(0)
push_child(1)
push_child(3)
push_child(4)
pop
pop
clone_node(0)
set_node(el, [1,2,3,4])
set_attribute("class", "foo")
append_child(1)
*/

129
packages/core/src/nodes.rs Normal file
View file

@ -0,0 +1,129 @@
use crate::{any_props::AnyProps, arena::ElementId, scopes::ComponentPtr};
use std::{
any::{Any, TypeId},
cell::Cell,
num::NonZeroUsize,
};
pub type TemplateId = &'static str;
/// A reference to a template along with any context needed to hydrate it
pub struct VTemplate<'a> {
// The ID assigned for the root of this template
pub node_id: Cell<ElementId>,
pub template: &'static Template,
pub root_ids: &'a [Cell<ElementId>],
/// All the dynamic nodes for a template
pub dynamic_nodes: &'a mut [DynamicNode<'a>],
pub dynamic_attrs: &'a mut [AttributeLocation<'a>],
}
#[derive(Debug, Clone, Copy)]
pub struct Template {
pub id: &'static str,
pub roots: &'static [TemplateNode<'static>],
}
/// A weird-ish variant of VNodes with way more limited types
#[derive(Debug, Clone, Copy)]
pub enum TemplateNode<'a> {
/// A simple element
Element {
tag: &'a str,
namespace: Option<&'a str>,
attrs: &'a [TemplateAttribute<'a>],
children: &'a [TemplateNode<'a>],
},
Text(&'a str),
Dynamic(usize),
DynamicText(usize),
}
pub struct DynamicNode<'a> {
pub path: &'static [u8],
pub kind: DynamicNodeKind<'a>,
}
pub enum DynamicNodeKind<'a> {
// Anything declared in component form
// IE in caps or with underscores
Component {
name: &'static str,
fn_ptr: ComponentPtr,
props: Box<dyn AnyProps>,
},
// Comes in with string interpolation or from format_args, include_str, etc
Text {
id: Cell<ElementId>,
value: &'static str,
},
// Anything that's coming in as an iterator
Fragment {
children: &'a [VTemplate<'a>],
},
}
#[derive(Debug)]
pub struct TemplateAttribute<'a> {
pub name: &'static str,
pub value: &'a str,
pub namespace: Option<&'static str>,
pub volatile: bool,
}
pub struct AttributeLocation<'a> {
pub mounted_element: Cell<ElementId>,
pub attrs: &'a [Attribute<'a>],
pub listeners: &'a [Listener<'a>],
pub path: &'static [u8],
}
#[derive(Debug)]
pub struct Attribute<'a> {
pub name: &'static str,
pub value: &'a str,
pub namespace: Option<&'static str>,
}
pub enum AttributeValue<'a> {
Text(&'a str),
Float(f32),
Int(i32),
Bool(bool),
Any(&'a dyn AnyValue),
}
pub trait AnyValue {
fn any_cmp(&self, other: &dyn Any) -> bool;
}
impl<T> AnyValue for T
where
T: PartialEq + Any,
{
fn any_cmp(&self, other: &dyn Any) -> bool {
if self.type_id() != other.type_id() {
return false;
}
self == unsafe { &*(other as *const _ as *const T) }
}
}
pub struct Listener<'a> {
pub name: &'static str,
pub callback: &'a dyn Fn(),
}
#[test]
fn what_are_the_sizes() {
dbg!(std::mem::size_of::<VTemplate>());
dbg!(std::mem::size_of::<Template>());
dbg!(std::mem::size_of::<TemplateNode>());
}

View file

@ -0,0 +1,62 @@
use slab::Slab;
use crate::{
any_props::AnyProps,
arena::ElementId,
bump_frame::BumpFrame,
nodes::VTemplate,
scopes::{ComponentPtr, ScopeId, ScopeState},
VirtualDom,
};
impl VirtualDom {
pub fn new_scope(
&mut self,
fn_ptr: ComponentPtr,
parent: Option<*mut ScopeState>,
container: ElementId,
props: Box<dyn AnyProps>,
) -> ScopeId {
let entry = self.scopes.vacant_entry();
let our_arena_idx = entry.key();
let height = unsafe { parent.map(|f| (*f).height).unwrap_or(0) + 1 };
entry.insert(ScopeState {
parent,
container,
our_arena_idx,
height,
fn_ptr,
props,
node_arena_1: BumpFrame::new(50),
node_arena_2: BumpFrame::new(50),
render_cnt: Default::default(),
hook_arena: Default::default(),
hook_vals: Default::default(),
hook_idx: Default::default(),
});
our_arena_idx
}
pub fn run_scope<'a>(&'a mut self, id: ScopeId) -> &'a VTemplate<'a> {
let scope = &mut self.scopes[id];
scope.hook_idx.set(0);
let res = scope.props.render(scope).unwrap();
let res: VTemplate<'static> = unsafe { std::mem::transmute(res) };
let frame = match scope.render_cnt % 2 {
0 => &mut scope.node_arena_1,
1 => &mut scope.node_arena_2,
_ => unreachable!(),
};
// set the head of the bump frame
let alloced = frame.bump.alloc(res);
frame.node.set(alloced);
// rebind the lifetime now that its stored internally
unsafe { std::mem::transmute(alloced) }
}
}

File diff suppressed because it is too large Load diff