wip: rewrite core to be template focused

This commit is contained in:
Jonathan Kelley committed 2022-10-27 21:58:47 -07:00
1 parent 67012c38df
commit 23603aaaf5
34 files changed
+3177 -2456

No files matched your search

File diff suppressed because it is too large. Load diff
File renamed without changes.
File renamed without changes.
+131
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")
)
};
}
+98
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)
*/
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+88
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 renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File diff suppressed because it is too large. Load diff
File renamed without changes.
File renamed without changes.
+71
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) })
}
}
+30
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)
}
}
+24
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());
}
}
+7
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
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)),
}
}
}
+14 -1242
View File
File diff suppressed because it is too large. Load diff
+3
View File
@@ -0,0 +1,3 @@
use crate::nodes::{Template, VTemplate};
pub type Element<'a> = Option<VTemplate<'a>>;
+66 -122
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")
)
};
}
+39 -92
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
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>());
}
+62
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