mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-23 12:43:08 +00:00
wip: rewrite core to be template focused
This commit is contained in:
parent
67012c38df
commit
23603aaaf5
34 changed files with 3177 additions and 2456 deletions
1246
packages/core/src.old/diff.rs
Normal file
1246
packages/core/src.old/diff.rs
Normal file
File diff suppressed because it is too large
Load diff
131
packages/core/src.old/lib.rs
Normal file
131
packages/core/src.old/lib.rs
Normal 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
packages/core/src.old/mutations.rs
Normal file
98
packages/core/src.old/mutations.rs
Normal 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)
|
||||
*/
|
88
packages/core/src.old/nodes/mod.rs
Normal file
88
packages/core/src.old/nodes/mod.rs
Normal 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>());
|
||||
}
|
1012
packages/core/src.old/scopes.rs
Normal file
1012
packages/core/src.old/scopes.rs
Normal file
File diff suppressed because it is too large
Load diff
71
packages/core/src/any_props.rs
Normal file
71
packages/core/src/any_props.rs
Normal 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
packages/core/src/arena.rs
Normal file
30
packages/core/src/arena.rs
Normal 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
packages/core/src/bump_frame.rs
Normal file
24
packages/core/src/bump_frame.rs
Normal 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
packages/core/src/component.rs
Normal file
7
packages/core/src/component.rs
Normal 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
114
packages/core/src/create.rs
Normal 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
3
packages/core/src/element.rs
Normal file
3
packages/core/src/element.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
use crate::nodes::{Template, VTemplate};
|
||||
|
||||
pub type Element<'a> = Option<VTemplate<'a>>;
|
|
@ -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")
|
||||
)
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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
129
packages/core/src/nodes.rs
Normal 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
packages/core/src/scope_arena.rs
Normal file
62
packages/core/src/scope_arena.rs
Normal 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
Loading…
Reference in a new issue