feat: simple tests passing
This commit is contained in:
1 parent
23603aaaf5
commit
4a31b29703
27 files changed
+1560
-367
No files matched your search
+1
-1
@@ -19,7 +19,7 @@ members = [
|
|||||||
"packages/rsx",
|
"packages/rsx",
|
||||||
"packages/native-core",
|
"packages/native-core",
|
||||||
"packages/native-core-macro",
|
"packages/native-core-macro",
|
||||||
"packages/edit-stream",
|
# "packages/edit-stream",
|
||||||
"docs/guide",
|
"docs/guide",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -11,8 +11,7 @@ use dioxus_rsx as rsx;
|
|||||||
#[proc_macro]
|
#[proc_macro]
|
||||||
pub fn format_args_f(input: TokenStream) -> TokenStream {
|
pub fn format_args_f(input: TokenStream) -> TokenStream {
|
||||||
use rsx::*;
|
use rsx::*;
|
||||||
let item = parse_macro_input!(input as IfmtInput);
|
format_args_f_impl(parse_macro_input!(input as IfmtInput))
|
||||||
format_args_f_impl(item)
|
|
||||||
.unwrap_or_else(|err| err.to_compile_error())
|
.unwrap_or_else(|err| err.to_compile_error())
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -424,7 +424,6 @@ impl<P> Clone for Scope<'_, P> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, P> std::ops::Deref for Scope<'a, P> {
|
impl<'a, P> std::ops::Deref for Scope<'a, P> {
|
||||||
// rust will auto deref again to the original 'a lifetime at the call site
|
|
||||||
type Target = &'a ScopeState;
|
type Target = &'a ScopeState;
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
&self.scope
|
&self.scope
|
||||||
@@ -621,9 +620,6 @@ impl ScopeState {
|
|||||||
Arc::new(move |id| drop(chan.unbounded_send(SchedulerMsg::Immediate(id))))
|
Arc::new(move |id| drop(chan.unbounded_send(SchedulerMsg::Immediate(id))))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the [`ScopeId`] of a mounted component.
|
|
||||||
///
|
|
||||||
/// `ScopeId` is not unique for the lifetime of the [`VirtualDom`] - a [`ScopeId`] will be reused if a component is unmounted.
|
|
||||||
pub fn needs_update(&self) {
|
pub fn needs_update(&self) {
|
||||||
self.needs_update_any(self.scope_id());
|
self.needs_update_any(self.scope_id());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
use std::cell::Cell;
|
use std::cell::Cell;
|
||||||
|
|
||||||
|
use futures_util::Future;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
component::Component,
|
component::{Component, ComponentFn, Dummy, IntoComponent},
|
||||||
element::Element,
|
element::Element,
|
||||||
scopes::{Scope, ScopeState},
|
scopes::{Scope, ScopeState},
|
||||||
};
|
};
|
||||||
@@ -12,21 +14,18 @@ pub trait AnyProps {
|
|||||||
unsafe fn memoize(&self, other: &dyn AnyProps) -> bool;
|
unsafe fn memoize(&self, other: &dyn AnyProps) -> bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct VComponentProps<P> {
|
pub(crate) struct VComponentProps<P, F: Future<Output = ()> = Dummy> {
|
||||||
pub render_fn: Component<P>,
|
pub render_fn: ComponentFn<P, F>,
|
||||||
pub memo: unsafe fn(&P, &P) -> bool,
|
pub memo: unsafe fn(&P, &P) -> bool,
|
||||||
pub props: Scope<P>,
|
pub props: *const P,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VComponentProps<()> {
|
impl VComponentProps<()> {
|
||||||
pub fn new_empty(render_fn: Component<()>) -> Self {
|
pub fn new_empty(render_fn: Component<()>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
render_fn,
|
render_fn: render_fn.into_component(),
|
||||||
memo: <() as PartialEq>::eq,
|
memo: <() as PartialEq>::eq,
|
||||||
props: Scope {
|
props: std::ptr::null_mut(),
|
||||||
props: (),
|
|
||||||
state: Cell::new(std::ptr::null_mut()),
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -35,10 +34,10 @@ impl<P> VComponentProps<P> {
|
|||||||
pub(crate) fn new(
|
pub(crate) fn new(
|
||||||
render_fn: Component<P>,
|
render_fn: Component<P>,
|
||||||
memo: unsafe fn(&P, &P) -> bool,
|
memo: unsafe fn(&P, &P) -> bool,
|
||||||
props: Scope<P>,
|
props: *const P,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
render_fn,
|
render_fn: render_fn.into_component(),
|
||||||
memo,
|
memo,
|
||||||
props,
|
props,
|
||||||
}
|
}
|
||||||
@@ -62,10 +61,18 @@ impl<P> AnyProps for VComponentProps<P> {
|
|||||||
|
|
||||||
fn render<'a>(&'a self, scope: &'a ScopeState) -> Element<'a> {
|
fn render<'a>(&'a self, scope: &'a ScopeState) -> Element<'a> {
|
||||||
// Make sure the scope ptr is not null
|
// Make sure the scope ptr is not null
|
||||||
self.props.state.set(scope);
|
// self.props.state.set(scope);
|
||||||
|
|
||||||
|
let scope = Scope {
|
||||||
|
props: unsafe { &*self.props },
|
||||||
|
scope,
|
||||||
|
};
|
||||||
|
|
||||||
// Call the render function directly
|
// Call the render function directly
|
||||||
// todo: implement async
|
// todo: implement async
|
||||||
(self.render_fn)(unsafe { std::mem::transmute(&self.props) })
|
match self.render_fn {
|
||||||
|
ComponentFn::Sync(f) => f(scope),
|
||||||
|
ComponentFn::Async(_) => todo!(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+21
-19
@@ -1,30 +1,32 @@
|
|||||||
use std::num::NonZeroUsize;
|
use std::num::NonZeroUsize;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
nodes::{Template, VNode},
|
||||||
|
virtualdom::VirtualDom,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
|
||||||
pub struct ElementId(pub usize);
|
pub struct ElementId(pub usize);
|
||||||
|
|
||||||
// pub struct ElementId(pub NonZeroUsize);
|
pub struct ElementPath {
|
||||||
|
pub template: *mut VNode<'static>,
|
||||||
// impl Default for ElementId {
|
pub element: usize,
|
||||||
// fn default() -> Self {
|
|
||||||
// Self(NonZeroUsize::new(1).unwrap())
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
pub struct ElementArena {
|
|
||||||
counter: usize,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ElementArena {
|
impl VirtualDom {
|
||||||
fn default() -> Self {
|
pub fn next_element(&mut self, template: &VNode) -> ElementId {
|
||||||
Self { counter: 1 }
|
let entry = self.elements.vacant_entry();
|
||||||
}
|
let id = entry.key();
|
||||||
}
|
|
||||||
|
entry.insert(ElementPath {
|
||||||
|
template: template as *const _ as *mut _,
|
||||||
|
element: id,
|
||||||
|
});
|
||||||
|
|
||||||
impl ElementArena {
|
|
||||||
pub fn next(&mut self) -> ElementId {
|
|
||||||
let id = self.counter;
|
|
||||||
self.counter += 1;
|
|
||||||
ElementId(id)
|
ElementId(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn cleanup_element(&mut self, id: ElementId) {
|
||||||
|
self.elements.remove(id.0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -2,11 +2,11 @@ use std::cell::Cell;
|
|||||||
|
|
||||||
use bumpalo::Bump;
|
use bumpalo::Bump;
|
||||||
|
|
||||||
use crate::nodes::VTemplate;
|
use crate::nodes::VNode;
|
||||||
|
|
||||||
pub struct BumpFrame {
|
pub struct BumpFrame {
|
||||||
pub bump: Bump,
|
pub bump: Bump,
|
||||||
pub node: Cell<*const VTemplate<'static>>,
|
pub node: Cell<*const VNode<'static>>,
|
||||||
}
|
}
|
||||||
impl BumpFrame {
|
impl BumpFrame {
|
||||||
pub fn new(capacity: usize) -> Self {
|
pub fn new(capacity: usize) -> Self {
|
||||||
|
|||||||
@@ -2,6 +2,44 @@
|
|||||||
// fn into_component_type(self) -> ComponentType;
|
// fn into_component_type(self) -> ComponentType;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
use futures_util::Future;
|
||||||
|
|
||||||
use crate::{element::Element, scopes::Scope};
|
use crate::{element::Element, scopes::Scope};
|
||||||
|
|
||||||
pub type Component<T = ()> = fn(&Scope<T>) -> Element;
|
pub type Component<T = ()> = fn(Scope<T>) -> Element;
|
||||||
|
|
||||||
|
pub enum ComponentFn<T, F: Future<Output = ()> = Dummy> {
|
||||||
|
Sync(fn(Scope<T>) -> Element),
|
||||||
|
Async(fn(Scope<T>) -> F),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait IntoComponent<T, A = ()> {
|
||||||
|
fn into_component(self) -> ComponentFn<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> IntoComponent<T> for fn(Scope<T>) -> Element {
|
||||||
|
fn into_component(self) -> ComponentFn<T> {
|
||||||
|
ComponentFn::Sync(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct AsyncMarker;
|
||||||
|
impl<'a, T, F: Future<Output = Element<'a>>> IntoComponent<T, AsyncMarker>
|
||||||
|
for fn(&'a Scope<T>) -> F
|
||||||
|
{
|
||||||
|
fn into_component(self) -> ComponentFn<T> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Dummy;
|
||||||
|
impl Future for Dummy {
|
||||||
|
type Output = ();
|
||||||
|
|
||||||
|
fn poll(
|
||||||
|
self: std::pin::Pin<&mut Self>,
|
||||||
|
cx: &mut std::task::Context<'_>,
|
||||||
|
) -> std::task::Poll<Self::Output> {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
}
|
||||||
+98
-40
@@ -1,33 +1,30 @@
|
|||||||
use crate::VirtualDom;
|
|
||||||
|
|
||||||
use crate::any_props::VComponentProps;
|
|
||||||
use crate::arena::ElementArena;
|
|
||||||
use crate::component::Component;
|
|
||||||
use crate::mutations::Mutation;
|
use crate::mutations::Mutation;
|
||||||
use crate::nodes::{
|
use crate::mutations::Mutation::*;
|
||||||
AttributeLocation, DynamicNode, DynamicNodeKind, Template, TemplateId, TemplateNode,
|
use crate::nodes::VNode;
|
||||||
};
|
use crate::nodes::{DynamicNode, DynamicNodeKind, TemplateNode};
|
||||||
use crate::scopes::Scope;
|
use crate::virtualdom::VirtualDom;
|
||||||
use crate::{
|
|
||||||
any_props::AnyProps,
|
|
||||||
arena::ElementId,
|
|
||||||
bump_frame::BumpFrame,
|
|
||||||
nodes::VTemplate,
|
|
||||||
scopes::{ComponentPtr, ScopeId, ScopeState},
|
|
||||||
};
|
|
||||||
use slab::Slab;
|
|
||||||
|
|
||||||
impl VirtualDom {
|
impl VirtualDom {
|
||||||
/// Create this template and write its mutations
|
/// Create this template and write its mutations
|
||||||
pub fn create<'a>(
|
pub fn create<'a>(
|
||||||
&mut self,
|
&mut self,
|
||||||
mutations: &mut Vec<Mutation<'a>>,
|
mutations: &mut Vec<Mutation<'a>>,
|
||||||
template: &'a VTemplate<'a>,
|
template: &'a VNode<'a>,
|
||||||
) -> usize {
|
) -> usize {
|
||||||
// The best renderers will have tempaltes prehydrated
|
// The best renderers will have templates prehydrated
|
||||||
// Just in case, let's create the template using instructions anyways
|
// Just in case, let's create the template using instructions anyways
|
||||||
if !self.templates.contains_key(&template.template.id) {
|
if !self.templates.contains_key(&template.template.id) {
|
||||||
self.create_static_template(mutations, template.template);
|
for node in template.template.roots {
|
||||||
|
self.create_static_node(mutations, template, node);
|
||||||
|
}
|
||||||
|
|
||||||
|
mutations.push(SaveTemplate {
|
||||||
|
name: template.template.id,
|
||||||
|
m: template.template.roots.len(),
|
||||||
|
});
|
||||||
|
|
||||||
|
self.templates
|
||||||
|
.insert(template.template.id, template.template.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Walk the roots backwards, creating nodes and assigning IDs
|
// Walk the roots backwards, creating nodes and assigning IDs
|
||||||
@@ -38,28 +35,37 @@ impl VirtualDom {
|
|||||||
let mut on_stack = 0;
|
let mut on_stack = 0;
|
||||||
for (root_idx, root) in template.template.roots.iter().enumerate() {
|
for (root_idx, root) in template.template.roots.iter().enumerate() {
|
||||||
on_stack += match root {
|
on_stack += match root {
|
||||||
|
TemplateNode::Element { .. } | TemplateNode::Text(_) => {
|
||||||
|
mutations.push(LoadTemplate {
|
||||||
|
name: template.template.id,
|
||||||
|
index: root_idx,
|
||||||
|
});
|
||||||
|
|
||||||
|
1
|
||||||
|
}
|
||||||
|
|
||||||
TemplateNode::Dynamic(id) => {
|
TemplateNode::Dynamic(id) => {
|
||||||
self.create_dynamic_node(mutations, template, &template.dynamic_nodes[*id])
|
self.create_dynamic_node(mutations, template, &template.dynamic_nodes[*id])
|
||||||
}
|
}
|
||||||
TemplateNode::DynamicText { .. }
|
|
||||||
| TemplateNode::Element { .. }
|
TemplateNode::DynamicText { .. } => 1,
|
||||||
| TemplateNode::Text(_) => 1,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// we're on top of a node that has a dynamic attribute for a descendant
|
// 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
|
// 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) {
|
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
|
// Attach all the elementIDs to the nodes with dynamic content
|
||||||
let id = self.elements.next();
|
let id = self.next_element(template);
|
||||||
mutations.push(Mutation::AssignId {
|
|
||||||
|
mutations.push(AssignId {
|
||||||
path: &loc.path[1..],
|
path: &loc.path[1..],
|
||||||
id,
|
id,
|
||||||
});
|
});
|
||||||
|
|
||||||
loc.mounted_element.set(id);
|
loc.mounted_element.set(id);
|
||||||
|
|
||||||
for attr in loc.attrs {
|
for attr in loc.attrs.iter() {
|
||||||
mutations.push(Mutation::SetAttribute {
|
mutations.push(SetAttribute {
|
||||||
name: attr.name,
|
name: attr.name,
|
||||||
value: attr.value,
|
value: attr.value,
|
||||||
id,
|
id,
|
||||||
@@ -67,31 +73,71 @@ impl VirtualDom {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// We're on top of a node that has a dynamic child for a descndent
|
// We're on top of a node that has a dynamic child for a descendant
|
||||||
// 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) {
|
while let Some(node) = dynamic_nodes.next_if(|f| f.path[0] == root_idx as u8) {
|
||||||
self.create_dynamic_node(mutations, template, node);
|
let m = self.create_dynamic_node(mutations, template, node);
|
||||||
|
mutations.push(ReplacePlaceholder {
|
||||||
|
m,
|
||||||
|
path: &node.path[1..],
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
on_stack
|
on_stack
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_static_template(&mut self, mutations: &mut Vec<Mutation>, template: &Template) {
|
fn create_static_node<'a>(
|
||||||
todo!("load template")
|
&mut self,
|
||||||
|
mutations: &mut Vec<Mutation<'a>>,
|
||||||
|
template: &'a VNode<'a>,
|
||||||
|
node: &'a TemplateNode<'static>,
|
||||||
|
) {
|
||||||
|
match *node {
|
||||||
|
TemplateNode::Dynamic(_) => mutations.push(CreatePlaceholder),
|
||||||
|
TemplateNode::Text(value) => mutations.push(CreateText { value }),
|
||||||
|
TemplateNode::DynamicText { .. } => mutations.push(CreateText {
|
||||||
|
value: "placeholder",
|
||||||
|
}),
|
||||||
|
TemplateNode::Element {
|
||||||
|
attrs,
|
||||||
|
children,
|
||||||
|
namespace,
|
||||||
|
tag,
|
||||||
|
} => {
|
||||||
|
let id = self.next_element(template);
|
||||||
|
|
||||||
|
mutations.push(CreateElement {
|
||||||
|
name: tag,
|
||||||
|
namespace,
|
||||||
|
id,
|
||||||
|
});
|
||||||
|
|
||||||
|
mutations.extend(attrs.into_iter().map(|attr| SetAttribute {
|
||||||
|
name: attr.name,
|
||||||
|
value: attr.value,
|
||||||
|
id,
|
||||||
|
}));
|
||||||
|
|
||||||
|
children
|
||||||
|
.into_iter()
|
||||||
|
.for_each(|child| self.create_static_node(mutations, template, child));
|
||||||
|
|
||||||
|
mutations.push(AppendChildren { m: children.len() })
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_dynamic_node<'a>(
|
fn create_dynamic_node<'a>(
|
||||||
&mut self,
|
&mut self,
|
||||||
mutations: &mut Vec<Mutation<'a>>,
|
mutations: &mut Vec<Mutation<'a>>,
|
||||||
template: &VTemplate<'a>,
|
template: &'a VNode<'a>,
|
||||||
node: &'a DynamicNode<'a>,
|
node: &'a DynamicNode<'a>,
|
||||||
) -> usize {
|
) -> usize {
|
||||||
match &node.kind {
|
match &node.kind {
|
||||||
DynamicNodeKind::Text { id, value } => {
|
DynamicNodeKind::Text { id, value } => {
|
||||||
let new_id = self.elements.next();
|
let new_id = self.next_element(template);
|
||||||
id.set(new_id);
|
id.set(new_id);
|
||||||
mutations.push(Mutation::HydrateText {
|
mutations.push(HydrateText {
|
||||||
id: new_id,
|
id: new_id,
|
||||||
path: &node.path[1..],
|
path: &node.path[1..],
|
||||||
value,
|
value,
|
||||||
@@ -99,16 +145,28 @@ impl VirtualDom {
|
|||||||
|
|
||||||
1
|
1
|
||||||
}
|
}
|
||||||
DynamicNodeKind::Component { props, fn_ptr, .. } => {
|
|
||||||
let id = self.new_scope(*fn_ptr, None, ElementId(0), *props);
|
DynamicNodeKind::Component { props, .. } => {
|
||||||
|
let id = self.new_scope(*props);
|
||||||
|
|
||||||
let template = self.run_scope(id);
|
let template = self.run_scope(id);
|
||||||
|
|
||||||
todo!("create component has bad data");
|
// shut up about lifetimes please, I know what I'm doing
|
||||||
|
let template: &VNode = unsafe { std::mem::transmute(template) };
|
||||||
|
|
||||||
|
self.scope_stack.push(id);
|
||||||
|
let created = self.create(mutations, template);
|
||||||
|
self.scope_stack.pop();
|
||||||
|
|
||||||
|
created
|
||||||
}
|
}
|
||||||
DynamicNodeKind::Fragment { children } => children
|
|
||||||
|
DynamicNodeKind::Fragment { children } => {
|
||||||
|
//
|
||||||
|
children
|
||||||
.iter()
|
.iter()
|
||||||
.fold(0, |acc, child| acc + self.create(mutations, child)),
|
.fold(0, |acc, child| acc + self.create(mutations, child))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
use crate::VirtualDom;
|
use crate::virtualdom::VirtualDom;
|
||||||
|
|
||||||
use crate::any_props::VComponentProps;
|
use crate::any_props::VComponentProps;
|
||||||
use crate::arena::ElementArena;
|
|
||||||
use crate::component::Component;
|
use crate::component::Component;
|
||||||
use crate::mutations::Mutation;
|
use crate::mutations::Mutation;
|
||||||
use crate::nodes::{DynamicNode, Template, TemplateId};
|
use crate::nodes::{DynamicNode, Template, TemplateId};
|
||||||
@@ -10,9 +10,18 @@ use crate::{
|
|||||||
any_props::AnyProps,
|
any_props::AnyProps,
|
||||||
arena::ElementId,
|
arena::ElementId,
|
||||||
bump_frame::BumpFrame,
|
bump_frame::BumpFrame,
|
||||||
nodes::VTemplate,
|
nodes::VNode,
|
||||||
scopes::{ComponentPtr, ScopeId, ScopeState},
|
scopes::{ScopeId, ScopeState},
|
||||||
};
|
};
|
||||||
use slab::Slab;
|
use slab::Slab;
|
||||||
|
|
||||||
impl VirtualDom {}
|
pub struct DirtyScope {
|
||||||
|
height: usize,
|
||||||
|
id: ScopeId,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VirtualDom {
|
||||||
|
fn diff_scope<'a>(&'a mut self, mutations: &mut Vec<Mutation<'a>>, scope: ScopeId) {
|
||||||
|
let scope_state = &mut self.scopes[scope.0];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
use crate::nodes::{Template, VTemplate};
|
use crate::nodes::{Template, VNode};
|
||||||
|
|
||||||
pub type Element<'a> = Option<VTemplate<'a>>;
|
pub type Element<'a> = Option<VNode<'a>>;
|
||||||
@@ -0,0 +1,159 @@
|
|||||||
|
use std::{any::Any, cell::Cell};
|
||||||
|
|
||||||
|
use crate::{arena::ElementId, nodes::Listener, scopes::ScopeId, virtualdom::VirtualDom};
|
||||||
|
|
||||||
|
/// User Events are events that are shuttled from the renderer into the [`VirtualDom`] through the scheduler channel.
|
||||||
|
///
|
||||||
|
/// These events will be passed to the appropriate Element given by `mounted_dom_id` and then bubbled up through the tree
|
||||||
|
/// where each listener is checked and fired if the event name matches.
|
||||||
|
///
|
||||||
|
/// It is the expectation that the event name matches the corresponding event listener, otherwise Dioxus will panic in
|
||||||
|
/// attempting to downcast the event data.
|
||||||
|
///
|
||||||
|
/// Because Event Data is sent across threads, it must be `Send + Sync`. We are hoping to lift the `Sync` restriction but
|
||||||
|
/// `Send` will not be lifted. The entire `UserEvent` must also be `Send + Sync` due to its use in the scheduler channel.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```rust, ignore
|
||||||
|
/// fn App(cx: Scope) -> Element {
|
||||||
|
/// render!(div {
|
||||||
|
/// onclick: move |_| println!("Clicked!")
|
||||||
|
/// })
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// let mut dom = VirtualDom::new(App);
|
||||||
|
/// let mut scheduler = dom.get_scheduler_channel();
|
||||||
|
/// scheduler.unbounded_send(SchedulerMsg::UiEvent(
|
||||||
|
/// UserEvent {
|
||||||
|
/// scope_id: None,
|
||||||
|
/// priority: EventPriority::Medium,
|
||||||
|
/// name: "click",
|
||||||
|
/// element: Some(ElementId(0)),
|
||||||
|
/// data: Arc::new(ClickEvent { .. })
|
||||||
|
/// }
|
||||||
|
/// )).unwrap();
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct UiEvent<T> {
|
||||||
|
/// The priority of the event to be scheduled around ongoing work
|
||||||
|
pub priority: EventPriority,
|
||||||
|
|
||||||
|
/// The optional real node associated with the trigger
|
||||||
|
pub element: ElementId,
|
||||||
|
|
||||||
|
/// The event type IE "onclick" or "onmouseover"
|
||||||
|
pub name: &'static str,
|
||||||
|
pub bubble: Cell<bool>,
|
||||||
|
pub event: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Priority of Event Triggers.
|
||||||
|
///
|
||||||
|
/// Internally, Dioxus will abort work that's taking too long if new, more important work arrives. Unlike React, Dioxus
|
||||||
|
/// won't be afraid to pause work or flush changes to the Real Dom. This is called "cooperative scheduling". Some Renderers
|
||||||
|
/// implement this form of scheduling internally, however Dioxus will perform its own scheduling as well.
|
||||||
|
///
|
||||||
|
/// The ultimate goal of the scheduler is to manage latency of changes, prioritizing "flashier" changes over "subtler" changes.
|
||||||
|
///
|
||||||
|
/// React has a 5-tier priority system. However, they break things into "Continuous" and "Discrete" priority. For now,
|
||||||
|
/// we keep it simple, and just use a 3-tier priority system.
|
||||||
|
///
|
||||||
|
/// - `NoPriority` = 0
|
||||||
|
/// - `LowPriority` = 1
|
||||||
|
/// - `NormalPriority` = 2
|
||||||
|
/// - `UserBlocking` = 3
|
||||||
|
/// - `HighPriority` = 4
|
||||||
|
/// - `ImmediatePriority` = 5
|
||||||
|
///
|
||||||
|
/// We still have a concept of discrete vs continuous though - discrete events won't be batched, but continuous events will.
|
||||||
|
/// This means that multiple "scroll" events will be processed in a single frame, but multiple "click" events will be
|
||||||
|
/// flushed before proceeding. Multiple discrete events is highly unlikely, though.
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, PartialOrd, Ord)]
|
||||||
|
pub enum EventPriority {
|
||||||
|
/// Work that must be completed during the EventHandler phase.
|
||||||
|
///
|
||||||
|
/// Currently this is reserved for controlled inputs.
|
||||||
|
Immediate = 3,
|
||||||
|
|
||||||
|
/// "High Priority" work will not interrupt other high priority work, but will interrupt medium and low priority work.
|
||||||
|
///
|
||||||
|
/// This is typically reserved for things like user interaction.
|
||||||
|
///
|
||||||
|
/// React calls these "discrete" events, but with an extra category of "user-blocking" (Immediate).
|
||||||
|
High = 2,
|
||||||
|
|
||||||
|
/// "Medium priority" work is generated by page events not triggered by the user. These types of events are less important
|
||||||
|
/// than "High Priority" events and will take precedence over low priority events.
|
||||||
|
///
|
||||||
|
/// This is typically reserved for VirtualEvents that are not related to keyboard or mouse input.
|
||||||
|
///
|
||||||
|
/// React calls these "continuous" events (e.g. mouse move, mouse wheel, touch move, etc).
|
||||||
|
Medium = 1,
|
||||||
|
|
||||||
|
/// "Low Priority" work will always be preempted unless the work is significantly delayed, in which case it will be
|
||||||
|
/// advanced to the front of the work queue until completed.
|
||||||
|
///
|
||||||
|
/// The primary user of Low Priority work is the asynchronous work system (Suspense).
|
||||||
|
///
|
||||||
|
/// This is considered "idle" work or "background" work.
|
||||||
|
Low = 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VirtualDom {
|
||||||
|
/// Returns None if no element could be found
|
||||||
|
pub fn handle_event<T: 'static>(&mut self, event: &UiEvent<T>) -> Option<()> {
|
||||||
|
let path = self.elements.get(event.element.0)?;
|
||||||
|
|
||||||
|
let location = unsafe { &mut *path.template }
|
||||||
|
.dynamic_attrs
|
||||||
|
.iter_mut()
|
||||||
|
.position(|attr| attr.mounted_element.get() == event.element)?;
|
||||||
|
|
||||||
|
let mut index = Some((path.template, location));
|
||||||
|
|
||||||
|
let mut listeners = Vec::<&mut Listener>::new();
|
||||||
|
|
||||||
|
while let Some((raw_parent, dyn_index)) = index {
|
||||||
|
let parent = unsafe { &mut *raw_parent };
|
||||||
|
let path = parent.dynamic_nodes[dyn_index].path;
|
||||||
|
|
||||||
|
listeners.extend(
|
||||||
|
parent
|
||||||
|
.dynamic_attrs
|
||||||
|
.iter_mut()
|
||||||
|
.filter(|attr| is_path_ascendant(attr.path, path))
|
||||||
|
.map(|f| f.listeners.iter_mut().filter(|f| f.name == event.name))
|
||||||
|
.flatten(),
|
||||||
|
);
|
||||||
|
|
||||||
|
index = parent.parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
for listener in listeners {
|
||||||
|
(listener.callback)(&event.event);
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensures a strict descendant relationship
|
||||||
|
// returns false if the paths are equal
|
||||||
|
fn is_path_ascendant(small: &[u8], big: &[u8]) -> bool {
|
||||||
|
if small.len() >= big.len() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
small == &big[..small.len()]
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn matches_slice() {
|
||||||
|
let left = &[1, 2, 3];
|
||||||
|
let right = &[1, 2, 3, 4, 5];
|
||||||
|
assert!(is_path_ascendant(left, right));
|
||||||
|
assert!(!is_path_ascendant(right, left));
|
||||||
|
assert!(!is_path_ascendant(left, left));
|
||||||
|
|
||||||
|
assert!(is_path_ascendant(&[1, 2], &[1, 2, 3, 4, 5]));
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
use std::fmt::Arguments;
|
||||||
|
|
||||||
|
use crate::{innerlude::DynamicNode, LazyNodes, ScopeState, VNode};
|
||||||
|
|
||||||
|
impl ScopeState {
|
||||||
|
/// Create some text that's allocated along with the other vnodes
|
||||||
|
///
|
||||||
|
pub fn text(&self, args: Arguments) -> DynamicNode {
|
||||||
|
// let (text, _is_static) = self.raw_text(args);
|
||||||
|
|
||||||
|
// VNode::Text(self.bump.alloc(VText {
|
||||||
|
// text,
|
||||||
|
// id: Default::default(),
|
||||||
|
// }))
|
||||||
|
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fragment_from_iter<'a, I, F: IntoVnode<'a, I>>(
|
||||||
|
&'a self,
|
||||||
|
it: impl IntoIterator<Item = F>,
|
||||||
|
) -> DynamicNode {
|
||||||
|
let mut bump_vec = bumpalo::vec![in self.bump();];
|
||||||
|
|
||||||
|
for item in it {
|
||||||
|
bump_vec.push(item.into_dynamic_node(self));
|
||||||
|
}
|
||||||
|
|
||||||
|
DynamicNode {
|
||||||
|
path: &[0, 0],
|
||||||
|
kind: crate::innerlude::DynamicNodeKind::Fragment {
|
||||||
|
children: bump_vec.into_bump_slice(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait IntoVnode<'a, A = ()> {
|
||||||
|
fn into_dynamic_node(self, cx: &'a ScopeState) -> VNode<'a>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b> IntoVnode<'a> for LazyNodes<'a, 'b> {
|
||||||
|
fn into_dynamic_node(self, cx: &'a ScopeState) -> VNode<'a> {
|
||||||
|
self.call(cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
use futures_channel::mpsc::UnboundedSender;
|
||||||
|
use futures_util::Future;
|
||||||
|
use std::{cell::RefCell, rc::Rc};
|
||||||
|
|
||||||
|
use crate::innerlude::ScopeId;
|
||||||
|
/// The type of message that can be sent to the scheduler.
|
||||||
|
///
|
||||||
|
/// These messages control how the scheduler will process updates to the UI.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum SchedulerMsg {
|
||||||
|
/// Events from athe Renderer
|
||||||
|
Event,
|
||||||
|
|
||||||
|
/// Immediate updates from Components that mark them as dirty
|
||||||
|
Immediate(ScopeId),
|
||||||
|
|
||||||
|
/// Mark all components as dirty and update them
|
||||||
|
DirtyAll,
|
||||||
|
|
||||||
|
/// New tasks from components that should be polled when the next poll is ready
|
||||||
|
NewTask(ScopeId),
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo extract this so spawning doesn't require refcell and rc doesnt need to be tracked
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct FutureQueue {
|
||||||
|
pub sender: UnboundedSender<SchedulerMsg>,
|
||||||
|
pub queue: Rc<RefCell<Vec<Box<dyn Future<Output = ()>>>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FutureQueue {
|
||||||
|
pub fn new(sender: UnboundedSender<SchedulerMsg>) -> Self {
|
||||||
|
Self {
|
||||||
|
sender,
|
||||||
|
queue: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn spawn(&self, id: ScopeId, fut: impl Future<Output = ()> + 'static) -> TaskId {
|
||||||
|
self.sender
|
||||||
|
.unbounded_send(SchedulerMsg::NewTask(id))
|
||||||
|
.unwrap();
|
||||||
|
self.queue.borrow_mut().push(Box::new(fut));
|
||||||
|
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove(&self, id: TaskId) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A task's unique identifier.
|
||||||
|
///
|
||||||
|
/// `TaskId` is a `usize` that is unique across the entire [`VirtualDom`] and across time. [`TaskID`]s will never be reused
|
||||||
|
/// once a Task has been completed.
|
||||||
|
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
|
||||||
|
pub struct TaskId {
|
||||||
|
/// The global ID of the task
|
||||||
|
pub id: usize,
|
||||||
|
|
||||||
|
/// The original scope that this task was scheduled in
|
||||||
|
pub scope: ScopeId,
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
use crate::{
|
||||||
|
nodes::{DynamicNodeKind, VNode},
|
||||||
|
scopes::ScopeId,
|
||||||
|
virtualdom::VirtualDom,
|
||||||
|
};
|
||||||
|
|
||||||
|
impl VirtualDom {
|
||||||
|
pub fn drop_scope(&mut self, id: ScopeId) {
|
||||||
|
let scope = self.scopes.get(id.0).unwrap();
|
||||||
|
|
||||||
|
let root = scope.root_node();
|
||||||
|
let root = unsafe { std::mem::transmute(root) };
|
||||||
|
|
||||||
|
self.drop_template(root);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn drop_template<'a>(&'a mut self, template: &'a VNode<'a>) {
|
||||||
|
for node in template.dynamic_nodes.iter() {
|
||||||
|
match &node.kind {
|
||||||
|
DynamicNodeKind::Text { id, .. } => {}
|
||||||
|
|
||||||
|
DynamicNodeKind::Component { .. } => {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
DynamicNodeKind::Fragment { children } => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,324 @@
|
|||||||
|
//! Support for storing lazy-nodes on the stack
|
||||||
|
//!
|
||||||
|
//! This module provides support for a type called `LazyNodes` which is a micro-heap located on the stack to make calls
|
||||||
|
//! to `rsx!` more efficient.
|
||||||
|
//!
|
||||||
|
//! To support returning rsx! from branches in match statements, we need to use dynamic dispatch on [`NodeFactory`] closures.
|
||||||
|
//!
|
||||||
|
//! This can be done either through boxing directly, or by using dynamic-sized-types and a custom allocator. In our case,
|
||||||
|
//! we build a tiny alloactor in the stack and allocate the closure into that.
|
||||||
|
//!
|
||||||
|
//! The logic for this was borrowed from <https://docs.rs/stack_dst/0.6.1/stack_dst/>. Unfortunately, this crate does not
|
||||||
|
//! support non-static closures, so we've implemented the core logic of `ValueA` in this module.
|
||||||
|
|
||||||
|
use crate::{innerlude::VNode, ScopeState};
|
||||||
|
use std::mem;
|
||||||
|
|
||||||
|
/// A concrete type provider for closures that build [`VNode`] structures.
|
||||||
|
///
|
||||||
|
/// This struct wraps lazy structs that build [`VNode`] trees Normally, we cannot perform a blanket implementation over
|
||||||
|
/// closures, but if we wrap the closure in a concrete type, we can maintain separate implementations of [`IntoVNode`].
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// ```rust, ignore
|
||||||
|
/// LazyNodes::new(|f| f.element("div", [], [], [] None))
|
||||||
|
/// ```
|
||||||
|
pub struct LazyNodes<'a, 'b> {
|
||||||
|
inner: StackNodeStorage<'a, 'b>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type NodeFactory<'a> = &'a ScopeState;
|
||||||
|
|
||||||
|
type StackHeapSize = [usize; 16];
|
||||||
|
|
||||||
|
enum StackNodeStorage<'a, 'b> {
|
||||||
|
Stack(LazyStack),
|
||||||
|
Heap(Box<dyn FnMut(Option<NodeFactory<'a>>) -> Option<VNode<'a>> + 'b>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b> LazyNodes<'a, 'b> {
|
||||||
|
/// Create a new [`LazyNodes`] closure, optimistically placing it onto the stack.
|
||||||
|
///
|
||||||
|
/// If the closure cannot fit into the stack allocation (16 bytes), then it
|
||||||
|
/// is placed on the heap. Most closures will fit into the stack, and is
|
||||||
|
/// the most optimal way to use the creation function.
|
||||||
|
pub fn new(val: impl FnOnce(NodeFactory<'a>) -> VNode<'a> + 'b) -> Self {
|
||||||
|
// there's no way to call FnOnce without a box, so we need to store it in a slot and use static dispatch
|
||||||
|
let mut slot = Some(val);
|
||||||
|
|
||||||
|
let val = move |fac: Option<NodeFactory<'a>>| {
|
||||||
|
fac.map(
|
||||||
|
slot.take()
|
||||||
|
.expect("LazyNodes closure to be called only once"),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
// miri does not know how to work with mucking directly into bytes
|
||||||
|
// just use a heap allocated type when miri is running
|
||||||
|
if cfg!(miri) {
|
||||||
|
Self {
|
||||||
|
inner: StackNodeStorage::Heap(Box::new(val)),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
unsafe { LazyNodes::new_inner(val) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new [`LazyNodes`] closure, but force it onto the heap.
|
||||||
|
pub fn new_boxed<F>(inner: F) -> Self
|
||||||
|
where
|
||||||
|
F: FnOnce(NodeFactory<'a>) -> VNode<'a> + 'b,
|
||||||
|
{
|
||||||
|
// there's no way to call FnOnce without a box, so we need to store it in a slot and use static dispatch
|
||||||
|
let mut slot = Some(inner);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
inner: StackNodeStorage::Heap(Box::new(move |fac: Option<NodeFactory<'a>>| {
|
||||||
|
fac.map(
|
||||||
|
slot.take()
|
||||||
|
.expect("LazyNodes closure to be called only once"),
|
||||||
|
)
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn new_inner<F>(val: F) -> Self
|
||||||
|
where
|
||||||
|
F: FnMut(Option<NodeFactory<'a>>) -> Option<VNode<'a>> + 'b,
|
||||||
|
{
|
||||||
|
let mut ptr: *const _ = &val as &dyn FnMut(Option<NodeFactory<'a>>) -> Option<VNode<'a>>;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
ptr as *const u8, &val as *const _ as *const u8,
|
||||||
|
"MISUSE: Closure returned different pointer"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
std::mem::size_of_val(&*ptr),
|
||||||
|
std::mem::size_of::<F>(),
|
||||||
|
"MISUSE: Closure returned a subset pointer"
|
||||||
|
);
|
||||||
|
|
||||||
|
let words = ptr_as_slice(&mut ptr);
|
||||||
|
assert!(
|
||||||
|
words[0] == &val as *const _ as usize,
|
||||||
|
"BUG: Pointer layout is not (data_ptr, info...)"
|
||||||
|
);
|
||||||
|
|
||||||
|
// - Ensure that Self is aligned same as data requires
|
||||||
|
assert!(
|
||||||
|
std::mem::align_of::<F>() <= std::mem::align_of::<Self>(),
|
||||||
|
"TODO: Enforce alignment >{} (requires {})",
|
||||||
|
std::mem::align_of::<Self>(),
|
||||||
|
std::mem::align_of::<F>()
|
||||||
|
);
|
||||||
|
|
||||||
|
let info = &words[1..];
|
||||||
|
let data = words[0] as *mut ();
|
||||||
|
let size = mem::size_of::<F>();
|
||||||
|
|
||||||
|
let stored_size = info.len() * mem::size_of::<usize>() + size;
|
||||||
|
let max_size = mem::size_of::<StackHeapSize>();
|
||||||
|
|
||||||
|
if stored_size > max_size {
|
||||||
|
Self {
|
||||||
|
inner: StackNodeStorage::Heap(Box::new(val)),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let mut buf: StackHeapSize = StackHeapSize::default();
|
||||||
|
|
||||||
|
assert!(info.len() + round_to_words(size) <= buf.as_ref().len());
|
||||||
|
|
||||||
|
// Place pointer information at the end of the region
|
||||||
|
// - Allows the data to be at the start for alignment purposes
|
||||||
|
{
|
||||||
|
let info_ofs = buf.as_ref().len() - info.len();
|
||||||
|
let info_dst = &mut buf.as_mut()[info_ofs..];
|
||||||
|
for (d, v) in Iterator::zip(info_dst.iter_mut(), info.iter()) {
|
||||||
|
*d = *v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let src_ptr = data as *const u8;
|
||||||
|
let dataptr = buf.as_mut_ptr().cast::<u8>();
|
||||||
|
|
||||||
|
for i in 0..size {
|
||||||
|
*dataptr.add(i) = *src_ptr.add(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::mem::forget(val);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
inner: StackNodeStorage::Stack(LazyStack {
|
||||||
|
_align: [],
|
||||||
|
buf,
|
||||||
|
dropped: false,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Call the closure with the given factory to produce real [`VNode`].
|
||||||
|
///
|
||||||
|
/// ```rust, ignore
|
||||||
|
/// let f = LazyNodes::new(move |f| f.element("div", [], [], [] None));
|
||||||
|
///
|
||||||
|
/// let fac = NodeFactory::new(&cx);
|
||||||
|
///
|
||||||
|
/// let node = f.call(cac);
|
||||||
|
/// ```
|
||||||
|
#[must_use]
|
||||||
|
pub fn call(self, f: NodeFactory<'a>) -> VNode<'a> {
|
||||||
|
match self.inner {
|
||||||
|
StackNodeStorage::Heap(mut lazy) => {
|
||||||
|
lazy(Some(f)).expect("Closure should not be called twice")
|
||||||
|
}
|
||||||
|
StackNodeStorage::Stack(mut stack) => stack.call(f),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct LazyStack {
|
||||||
|
_align: [u64; 0],
|
||||||
|
buf: StackHeapSize,
|
||||||
|
dropped: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LazyStack {
|
||||||
|
fn call<'a>(&mut self, f: NodeFactory<'a>) -> VNode<'a> {
|
||||||
|
let LazyStack { buf, .. } = self;
|
||||||
|
let data = buf.as_ref();
|
||||||
|
|
||||||
|
let info_size =
|
||||||
|
mem::size_of::<*mut dyn FnMut(Option<NodeFactory<'a>>) -> Option<VNode<'a>>>()
|
||||||
|
/ mem::size_of::<usize>()
|
||||||
|
- 1;
|
||||||
|
|
||||||
|
let info_ofs = data.len() - info_size;
|
||||||
|
|
||||||
|
let g: *mut dyn FnMut(Option<NodeFactory<'a>>) -> Option<VNode<'a>> =
|
||||||
|
unsafe { make_fat_ptr(data[..].as_ptr() as usize, &data[info_ofs..]) };
|
||||||
|
|
||||||
|
self.dropped = true;
|
||||||
|
|
||||||
|
let clos = unsafe { &mut *g };
|
||||||
|
clos(Some(f)).unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Drop for LazyStack {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if !self.dropped {
|
||||||
|
let LazyStack { buf, .. } = self;
|
||||||
|
let data = buf.as_ref();
|
||||||
|
|
||||||
|
let info_size = mem::size_of::<
|
||||||
|
*mut dyn FnMut(Option<NodeFactory<'_>>) -> Option<VNode<'_>>,
|
||||||
|
>() / mem::size_of::<usize>()
|
||||||
|
- 1;
|
||||||
|
|
||||||
|
let info_ofs = data.len() - info_size;
|
||||||
|
|
||||||
|
let g: *mut dyn FnMut(Option<NodeFactory<'_>>) -> Option<VNode<'_>> =
|
||||||
|
unsafe { make_fat_ptr(data[..].as_ptr() as usize, &data[info_ofs..]) };
|
||||||
|
|
||||||
|
self.dropped = true;
|
||||||
|
|
||||||
|
let clos = unsafe { &mut *g };
|
||||||
|
clos(None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Obtain mutable access to a pointer's words
|
||||||
|
fn ptr_as_slice<T>(ptr: &mut T) -> &mut [usize] {
|
||||||
|
assert!(mem::size_of::<T>() % mem::size_of::<usize>() == 0);
|
||||||
|
let words = mem::size_of::<T>() / mem::size_of::<usize>();
|
||||||
|
// SAFE: Points to valid memory (a raw pointer)
|
||||||
|
unsafe { core::slice::from_raw_parts_mut(ptr as *mut _ as *mut usize, words) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Re-construct a fat pointer
|
||||||
|
unsafe fn make_fat_ptr<T: ?Sized>(data_ptr: usize, meta_vals: &[usize]) -> *mut T {
|
||||||
|
let mut rv = mem::MaybeUninit::<*mut T>::uninit();
|
||||||
|
{
|
||||||
|
let s = ptr_as_slice(&mut rv);
|
||||||
|
s[0] = data_ptr;
|
||||||
|
s[1..].copy_from_slice(meta_vals);
|
||||||
|
}
|
||||||
|
let rv = rv.assume_init();
|
||||||
|
assert_eq!(rv as *const (), data_ptr as *const ());
|
||||||
|
rv
|
||||||
|
}
|
||||||
|
|
||||||
|
fn round_to_words(len: usize) -> usize {
|
||||||
|
(len + mem::size_of::<usize>() - 1) / mem::size_of::<usize>()
|
||||||
|
}
|
||||||
|
|
||||||
|
// #[cfg(test)]
|
||||||
|
// mod tests {
|
||||||
|
// use super::*;
|
||||||
|
// use crate::innerlude::{Element, Scope, VirtualDom};
|
||||||
|
|
||||||
|
// #[test]
|
||||||
|
// fn it_works() {
|
||||||
|
// fn app(cx: Scope<()>) -> Element {
|
||||||
|
// cx.render(LazyNodes::new(|f| f.text(format_args!("hello world!"))))
|
||||||
|
// }
|
||||||
|
|
||||||
|
// let mut dom = VirtualDom::new(app);
|
||||||
|
// dom.rebuild();
|
||||||
|
|
||||||
|
// let g = dom.base_scope().root_node();
|
||||||
|
// dbg!(g);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// #[test]
|
||||||
|
// fn it_drops() {
|
||||||
|
// use std::rc::Rc;
|
||||||
|
|
||||||
|
// struct AppProps {
|
||||||
|
// inner: Rc<i32>,
|
||||||
|
// }
|
||||||
|
|
||||||
|
// fn app(cx: Scope<AppProps>) -> Element {
|
||||||
|
// struct DropInner {
|
||||||
|
// id: i32,
|
||||||
|
// }
|
||||||
|
// impl Drop for DropInner {
|
||||||
|
// fn drop(&mut self) {
|
||||||
|
// eprintln!("dropping inner");
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// let caller = {
|
||||||
|
// let it = (0..10).map(|i| {
|
||||||
|
// let val = cx.props.inner.clone();
|
||||||
|
// LazyNodes::new(move |f| {
|
||||||
|
// eprintln!("hell closure");
|
||||||
|
// let inner = DropInner { id: i };
|
||||||
|
// f.text(format_args!("hello world {:?}, {:?}", inner.id, val))
|
||||||
|
// })
|
||||||
|
// });
|
||||||
|
|
||||||
|
// LazyNodes::new(|f| {
|
||||||
|
// eprintln!("main closure");
|
||||||
|
// f.fragment_from_iter(it)
|
||||||
|
// })
|
||||||
|
// };
|
||||||
|
|
||||||
|
// cx.render(caller)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// let inner = Rc::new(0);
|
||||||
|
// let mut dom = VirtualDom::new_with_props(
|
||||||
|
// app,
|
||||||
|
// AppProps {
|
||||||
|
// inner: inner.clone(),
|
||||||
|
// },
|
||||||
|
// );
|
||||||
|
// dom.rebuild();
|
||||||
|
|
||||||
|
// drop(dom);
|
||||||
|
|
||||||
|
// assert_eq!(Rc::strong_count(&inner), 1);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
+126
-55
@@ -1,20 +1,3 @@
|
|||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
mod any_props;
|
mod any_props;
|
||||||
mod arena;
|
mod arena;
|
||||||
mod bump_frame;
|
mod bump_frame;
|
||||||
@@ -22,54 +5,142 @@ mod component;
|
|||||||
mod create;
|
mod create;
|
||||||
mod diff;
|
mod diff;
|
||||||
mod element;
|
mod element;
|
||||||
|
mod events;
|
||||||
|
mod factory;
|
||||||
|
mod future_container;
|
||||||
|
mod garbage;
|
||||||
|
mod lazynodes;
|
||||||
mod mutations;
|
mod mutations;
|
||||||
mod nodes;
|
mod nodes;
|
||||||
mod scope_arena;
|
mod scope_arena;
|
||||||
mod scopes;
|
mod scopes;
|
||||||
|
mod virtualdom;
|
||||||
|
|
||||||
pub struct VirtualDom {
|
pub(crate) mod innerlude {
|
||||||
templates: HashMap<TemplateId, Template>,
|
pub use crate::element::Element;
|
||||||
elements: ElementArena,
|
pub use crate::events::*;
|
||||||
scopes: Slab<ScopeState>,
|
pub use crate::future_container::*;
|
||||||
scope_stack: Vec<ScopeId>,
|
pub use crate::mutations::*;
|
||||||
|
pub use crate::nodes::*;
|
||||||
|
// pub use crate::properties::*;
|
||||||
|
pub use crate::lazynodes::*;
|
||||||
|
pub use crate::scopes::*;
|
||||||
|
pub use crate::virtualdom::*;
|
||||||
|
|
||||||
|
// /// 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<VNodea<'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>]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VirtualDom {
|
pub use crate::innerlude::{
|
||||||
pub fn new(app: Component) -> Self {
|
// AnyAttributeValue, AnyEvent, Attribute, AttributeValue, Component, Element, ElementId,
|
||||||
let mut res = Self {
|
Attribute,
|
||||||
templates: Default::default(),
|
AttributeValue,
|
||||||
scopes: Slab::default(),
|
Element,
|
||||||
elements: ElementArena::default(),
|
EventPriority,
|
||||||
scope_stack: Vec::new(),
|
LazyNodes,
|
||||||
|
Listener,
|
||||||
|
NodeFactory,
|
||||||
|
Scope,
|
||||||
|
ScopeId,
|
||||||
|
ScopeState,
|
||||||
|
TaskId,
|
||||||
|
Template,
|
||||||
|
TemplateAttribute,
|
||||||
|
TemplateNode,
|
||||||
|
UiEvent,
|
||||||
|
VNode,
|
||||||
|
VirtualDom,
|
||||||
};
|
};
|
||||||
|
// EventHandler, EventPriority, IntoVNode, LazyNodes, Listener, NodeFactory, Properties, Renderer,
|
||||||
|
// SchedulerMsg, Scope, ScopeId, ScopeState, TaskId, Template, TemplateAttribute, TemplateNode,
|
||||||
|
// UiEvent, UserEvent, VComponent, VElement, VNode, VTemplate, VText, VirtualDom,
|
||||||
|
|
||||||
res.new_scope(
|
/// The purpose of this module is to alleviate imports of many common types
|
||||||
app as _,
|
|
||||||
None,
|
|
||||||
ElementId(0),
|
|
||||||
Box::new(VComponentProps::new_empty(app)),
|
|
||||||
);
|
|
||||||
|
|
||||||
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.
|
/// This includes types like [`Scope`], [`Element`], and [`Component`].
|
||||||
|
pub mod prelude {
|
||||||
|
pub use crate::innerlude::{
|
||||||
|
Attribute, Element, EventPriority, LazyNodes, Listener, NodeFactory, Scope, ScopeId,
|
||||||
|
ScopeState, TaskId, Template, TemplateAttribute, TemplateNode, UiEvent, 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
/// A helper macro for using hooks in async environements.
|
||||||
///
|
///
|
||||||
/// Use other methods to update the virtualdom incrementally.
|
/// # Usage
|
||||||
pub fn render_all<'a>(&'a mut self, mutations: &mut Vec<Mutation<'a>>) {
|
///
|
||||||
let root = self.root_scope();
|
///
|
||||||
|
/// ```ignore
|
||||||
let root_template = root.current_arena();
|
/// let (data) = use_ref(&cx, || {});
|
||||||
|
///
|
||||||
let root_node: &'a VTemplate = unsafe { &*root_template.node.get() };
|
/// let handle_thing = move |_| {
|
||||||
let root_node: &'a VTemplate<'a> = unsafe { std::mem::transmute(root_node) };
|
/// to_owned![data]
|
||||||
|
/// cx.spawn(async move {
|
||||||
self.create(mutations, root_node);
|
/// // 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")
|
||||||
|
)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
@@ -17,6 +17,11 @@ pub enum Mutation<'a> {
|
|||||||
index: usize,
|
index: usize,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
SaveTemplate {
|
||||||
|
name: &'static str,
|
||||||
|
m: usize,
|
||||||
|
},
|
||||||
|
|
||||||
HydrateText {
|
HydrateText {
|
||||||
path: &'static [u8],
|
path: &'static [u8],
|
||||||
value: &'a str,
|
value: &'a str,
|
||||||
@@ -30,7 +35,7 @@ pub enum Mutation<'a> {
|
|||||||
|
|
||||||
ReplacePlaceholder {
|
ReplacePlaceholder {
|
||||||
path: &'static [u8],
|
path: &'static [u8],
|
||||||
id: ElementId,
|
m: usize,
|
||||||
},
|
},
|
||||||
|
|
||||||
AssignId {
|
AssignId {
|
||||||
@@ -42,4 +47,20 @@ pub enum Mutation<'a> {
|
|||||||
Replace {
|
Replace {
|
||||||
id: ElementId,
|
id: ElementId,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
CreateElement {
|
||||||
|
name: &'a str,
|
||||||
|
namespace: Option<&'a str>,
|
||||||
|
id: ElementId,
|
||||||
|
},
|
||||||
|
|
||||||
|
CreateText {
|
||||||
|
value: &'a str,
|
||||||
|
},
|
||||||
|
|
||||||
|
CreatePlaceholder,
|
||||||
|
|
||||||
|
AppendChildren {
|
||||||
|
m: usize,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
+12
-11
@@ -1,4 +1,4 @@
|
|||||||
use crate::{any_props::AnyProps, arena::ElementId, scopes::ComponentPtr};
|
use crate::{any_props::AnyProps, arena::ElementId};
|
||||||
use std::{
|
use std::{
|
||||||
any::{Any, TypeId},
|
any::{Any, TypeId},
|
||||||
cell::Cell,
|
cell::Cell,
|
||||||
@@ -8,11 +8,14 @@ use std::{
|
|||||||
pub type TemplateId = &'static str;
|
pub type TemplateId = &'static str;
|
||||||
|
|
||||||
/// A reference to a template along with any context needed to hydrate it
|
/// A reference to a template along with any context needed to hydrate it
|
||||||
pub struct VTemplate<'a> {
|
pub struct VNode<'a> {
|
||||||
// The ID assigned for the root of this template
|
// The ID assigned for the root of this template
|
||||||
pub node_id: Cell<ElementId>,
|
pub node_id: Cell<ElementId>,
|
||||||
|
|
||||||
pub template: &'static Template,
|
// When rendered, this template will be linked to its parent
|
||||||
|
pub parent: Option<(*mut VNode<'static>, usize)>,
|
||||||
|
|
||||||
|
pub template: Template,
|
||||||
|
|
||||||
pub root_ids: &'a [Cell<ElementId>],
|
pub root_ids: &'a [Cell<ElementId>],
|
||||||
|
|
||||||
@@ -25,7 +28,6 @@ pub struct VTemplate<'a> {
|
|||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct Template {
|
pub struct Template {
|
||||||
pub id: &'static str,
|
pub id: &'static str,
|
||||||
|
|
||||||
pub roots: &'static [TemplateNode<'static>],
|
pub roots: &'static [TemplateNode<'static>],
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,8 +56,7 @@ pub enum DynamicNodeKind<'a> {
|
|||||||
// IE in caps or with underscores
|
// IE in caps or with underscores
|
||||||
Component {
|
Component {
|
||||||
name: &'static str,
|
name: &'static str,
|
||||||
fn_ptr: ComponentPtr,
|
props: *mut dyn AnyProps,
|
||||||
props: Box<dyn AnyProps>,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Comes in with string interpolation or from format_args, include_str, etc
|
// Comes in with string interpolation or from format_args, include_str, etc
|
||||||
@@ -66,7 +67,7 @@ pub enum DynamicNodeKind<'a> {
|
|||||||
|
|
||||||
// Anything that's coming in as an iterator
|
// Anything that's coming in as an iterator
|
||||||
Fragment {
|
Fragment {
|
||||||
children: &'a [VTemplate<'a>],
|
children: &'a [VNode<'a>],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,8 +81,8 @@ pub struct TemplateAttribute<'a> {
|
|||||||
|
|
||||||
pub struct AttributeLocation<'a> {
|
pub struct AttributeLocation<'a> {
|
||||||
pub mounted_element: Cell<ElementId>,
|
pub mounted_element: Cell<ElementId>,
|
||||||
pub attrs: &'a [Attribute<'a>],
|
pub attrs: &'a mut [Attribute<'a>],
|
||||||
pub listeners: &'a [Listener<'a>],
|
pub listeners: &'a mut [Listener<'a>],
|
||||||
pub path: &'static [u8],
|
pub path: &'static [u8],
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,12 +119,12 @@ where
|
|||||||
|
|
||||||
pub struct Listener<'a> {
|
pub struct Listener<'a> {
|
||||||
pub name: &'static str,
|
pub name: &'static str,
|
||||||
pub callback: &'a dyn Fn(),
|
pub callback: &'a mut dyn FnMut(&dyn Any),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn what_are_the_sizes() {
|
fn what_are_the_sizes() {
|
||||||
dbg!(std::mem::size_of::<VTemplate>());
|
dbg!(std::mem::size_of::<VNode>());
|
||||||
dbg!(std::mem::size_of::<Template>());
|
dbg!(std::mem::size_of::<Template>());
|
||||||
dbg!(std::mem::size_of::<TemplateNode>());
|
dbg!(std::mem::size_of::<TemplateNode>());
|
||||||
}
|
}
|
||||||
@@ -1,50 +1,61 @@
|
|||||||
use slab::Slab;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
any_props::AnyProps,
|
any_props::AnyProps,
|
||||||
arena::ElementId,
|
arena::ElementId,
|
||||||
bump_frame::BumpFrame,
|
bump_frame::BumpFrame,
|
||||||
nodes::VTemplate,
|
nodes::VNode,
|
||||||
scopes::{ComponentPtr, ScopeId, ScopeState},
|
scopes::{ScopeId, ScopeState},
|
||||||
VirtualDom,
|
virtualdom::VirtualDom,
|
||||||
};
|
};
|
||||||
|
|
||||||
impl VirtualDom {
|
impl VirtualDom {
|
||||||
pub fn new_scope(
|
pub fn new_scope(&mut self, props: *mut dyn AnyProps) -> ScopeId {
|
||||||
&mut self,
|
let parent = self.acquire_current_scope_raw();
|
||||||
fn_ptr: ComponentPtr,
|
let container = self.acquire_current_container();
|
||||||
parent: Option<*mut ScopeState>,
|
|
||||||
container: ElementId,
|
|
||||||
props: Box<dyn AnyProps>,
|
|
||||||
) -> ScopeId {
|
|
||||||
let entry = self.scopes.vacant_entry();
|
let entry = self.scopes.vacant_entry();
|
||||||
let our_arena_idx = entry.key();
|
|
||||||
let height = unsafe { parent.map(|f| (*f).height).unwrap_or(0) + 1 };
|
let height = unsafe { parent.map(|f| (*f).height).unwrap_or(0) + 1 };
|
||||||
|
let id = ScopeId(entry.key());
|
||||||
|
|
||||||
entry.insert(ScopeState {
|
entry.insert(ScopeState {
|
||||||
parent,
|
parent,
|
||||||
container,
|
container,
|
||||||
our_arena_idx,
|
id,
|
||||||
height,
|
height,
|
||||||
fn_ptr,
|
|
||||||
props,
|
props,
|
||||||
|
tasks: self.pending_futures.clone(),
|
||||||
node_arena_1: BumpFrame::new(50),
|
node_arena_1: BumpFrame::new(50),
|
||||||
node_arena_2: BumpFrame::new(50),
|
node_arena_2: BumpFrame::new(50),
|
||||||
render_cnt: Default::default(),
|
render_cnt: Default::default(),
|
||||||
hook_arena: Default::default(),
|
hook_arena: Default::default(),
|
||||||
hook_vals: Default::default(),
|
hook_vals: Default::default(),
|
||||||
hook_idx: Default::default(),
|
hook_idx: Default::default(),
|
||||||
|
shared_contexts: Default::default(),
|
||||||
});
|
});
|
||||||
|
|
||||||
our_arena_idx
|
id
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_scope<'a>(&'a mut self, id: ScopeId) -> &'a VTemplate<'a> {
|
pub fn acquire_current_container(&self) -> ElementId {
|
||||||
let scope = &mut self.scopes[id];
|
self.element_stack
|
||||||
|
.last()
|
||||||
|
.copied()
|
||||||
|
.expect("Always have a container")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn acquire_current_scope_raw(&mut self) -> Option<*mut ScopeState> {
|
||||||
|
self.scope_stack
|
||||||
|
.last()
|
||||||
|
.copied()
|
||||||
|
.and_then(|id| self.scopes.get_mut(id.0).map(|f| f as *mut ScopeState))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_scope<'a>(&'a mut self, id: ScopeId) -> &'a VNode<'a> {
|
||||||
|
let scope = &mut self.scopes[id.0];
|
||||||
scope.hook_idx.set(0);
|
scope.hook_idx.set(0);
|
||||||
|
|
||||||
let res = scope.props.render(scope).unwrap();
|
let props = unsafe { &mut *scope.props };
|
||||||
let res: VTemplate<'static> = unsafe { std::mem::transmute(res) };
|
|
||||||
|
let res = props.render(scope).unwrap();
|
||||||
|
let res: VNode<'static> = unsafe { std::mem::transmute(res) };
|
||||||
|
|
||||||
let frame = match scope.render_cnt % 2 {
|
let frame = match scope.render_cnt % 2 {
|
||||||
0 => &mut scope.node_arena_1,
|
0 => &mut scope.node_arena_1,
|
||||||
|
|||||||
+352
-15
@@ -1,13 +1,50 @@
|
|||||||
use std::{
|
use std::{
|
||||||
any::Any,
|
any::{Any, TypeId},
|
||||||
cell::{Cell, RefCell},
|
cell::{Cell, RefCell},
|
||||||
|
collections::HashMap,
|
||||||
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
|
||||||
use bumpalo::Bump;
|
use bumpalo::Bump;
|
||||||
|
use futures_channel::mpsc::UnboundedSender;
|
||||||
|
use futures_util::Future;
|
||||||
|
|
||||||
use crate::{any_props::AnyProps, arena::ElementId, bump_frame::BumpFrame, nodes::VTemplate};
|
use crate::{
|
||||||
|
any_props::AnyProps, arena::ElementId, bump_frame::BumpFrame, future_container::FutureQueue,
|
||||||
|
innerlude::SchedulerMsg, lazynodes::LazyNodes, nodes::VNode, TaskId,
|
||||||
|
};
|
||||||
|
|
||||||
pub type ScopeId = usize;
|
pub struct Scope<'a, T = ()> {
|
||||||
|
pub scope: &'a ScopeState,
|
||||||
|
pub props: &'a T,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Copy for Scope<'_, T> {}
|
||||||
|
|
||||||
|
impl<T> Clone for Scope<'_, T> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
props: self.props,
|
||||||
|
scope: self.scope,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T> std::ops::Deref for Scope<'a, T> {
|
||||||
|
type Target = &'a ScopeState;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.scope
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A component's unique identifier.
|
||||||
|
///
|
||||||
|
/// `ScopeId` is a `usize` that is unique across the entire [`VirtualDom`] and across time. [`ScopeID`]s will never be reused
|
||||||
|
/// once a component has been unmounted.
|
||||||
|
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)]
|
||||||
|
pub struct ScopeId(pub usize);
|
||||||
|
|
||||||
pub struct ScopeState {
|
pub struct ScopeState {
|
||||||
pub render_cnt: usize,
|
pub render_cnt: usize,
|
||||||
@@ -17,16 +54,19 @@ pub struct ScopeState {
|
|||||||
|
|
||||||
pub parent: Option<*mut ScopeState>,
|
pub parent: Option<*mut ScopeState>,
|
||||||
pub container: ElementId,
|
pub container: ElementId,
|
||||||
pub our_arena_idx: ScopeId,
|
pub id: ScopeId,
|
||||||
|
|
||||||
pub height: u32,
|
pub height: u32,
|
||||||
pub fn_ptr: ComponentPtr,
|
|
||||||
|
|
||||||
pub hook_arena: Bump,
|
pub hook_arena: Bump,
|
||||||
pub hook_vals: RefCell<Vec<*mut dyn Any>>,
|
pub hook_vals: RefCell<Vec<*mut dyn Any>>,
|
||||||
pub hook_idx: Cell<usize>,
|
pub hook_idx: Cell<usize>,
|
||||||
|
|
||||||
pub props: Box<dyn AnyProps>,
|
pub(crate) shared_contexts: RefCell<HashMap<TypeId, Box<dyn Any>>>,
|
||||||
|
|
||||||
|
pub tasks: FutureQueue,
|
||||||
|
|
||||||
|
pub props: *mut dyn AnyProps,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ScopeState {
|
impl ScopeState {
|
||||||
@@ -37,19 +77,316 @@ impl ScopeState {
|
|||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn bump(&self) -> &Bump {
|
||||||
|
&self.current_arena().bump
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) type ComponentPtr = *mut std::os::raw::c_void;
|
pub fn root_node<'a>(&'a self) -> &'a VNode<'a> {
|
||||||
|
let r = unsafe { &*self.current_arena().node.get() };
|
||||||
pub struct Scope<T = ()> {
|
unsafe { std::mem::transmute(r) }
|
||||||
pub props: T,
|
|
||||||
pub state: Cell<*const ScopeState>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> std::ops::Deref for Scope<T> {
|
/// Get the height of this Scope - IE the number of scopes above it.
|
||||||
type Target = ScopeState;
|
///
|
||||||
|
/// A Scope with a height of `0` is the root scope - there are no other scopes above it.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust, ignore
|
||||||
|
/// let mut dom = VirtualDom::new(|cx| cx.render(rsx!{ div {} }));
|
||||||
|
/// dom.rebuild();
|
||||||
|
///
|
||||||
|
/// let base = dom.base_scope();
|
||||||
|
///
|
||||||
|
/// assert_eq!(base.height(), 0);
|
||||||
|
/// ```
|
||||||
|
pub fn height(&self) -> u32 {
|
||||||
|
self.height
|
||||||
|
}
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
/// Get the Parent of this [`Scope`] within this Dioxus [`VirtualDom`].
|
||||||
unsafe { &*self.state.get() }
|
///
|
||||||
|
/// This ID is not unique across Dioxus [`VirtualDom`]s or across time. IDs will be reused when components are unmounted.
|
||||||
|
///
|
||||||
|
/// The base component will not have a parent, and will return `None`.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust, ignore
|
||||||
|
/// let mut dom = VirtualDom::new(|cx| cx.render(rsx!{ div {} }));
|
||||||
|
/// dom.rebuild();
|
||||||
|
///
|
||||||
|
/// let base = dom.base_scope();
|
||||||
|
///
|
||||||
|
/// assert_eq!(base.parent(), None);
|
||||||
|
/// ```
|
||||||
|
pub fn parent(&self) -> Option<ScopeId> {
|
||||||
|
// safety: the pointer to our parent is *always* valid thanks to the bump arena
|
||||||
|
self.parent.map(|p| unsafe { &*p }.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the ID of this Scope within this Dioxus [`VirtualDom`].
|
||||||
|
///
|
||||||
|
/// This ID is not unique across Dioxus [`VirtualDom`]s or across time. IDs will be reused when components are unmounted.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust, ignore
|
||||||
|
/// let mut dom = VirtualDom::new(|cx| cx.render(rsx!{ div {} }));
|
||||||
|
/// dom.rebuild();
|
||||||
|
/// let base = dom.base_scope();
|
||||||
|
///
|
||||||
|
/// assert_eq!(base.scope_id(), 0);
|
||||||
|
/// ```
|
||||||
|
pub fn scope_id(&self) -> ScopeId {
|
||||||
|
self.id
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a handle to the raw update scheduler channel
|
||||||
|
pub fn scheduler_channel(&self) -> UnboundedSender<SchedulerMsg> {
|
||||||
|
self.tasks.sender.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a subscription that schedules a future render for the reference component
|
||||||
|
///
|
||||||
|
/// ## Notice: you should prefer using [`schedule_update_any`] and [`scope_id`]
|
||||||
|
pub fn schedule_update(&self) -> Arc<dyn Fn() + Send + Sync + 'static> {
|
||||||
|
let (chan, id) = (self.tasks.sender.clone(), self.scope_id());
|
||||||
|
Arc::new(move || drop(chan.unbounded_send(SchedulerMsg::Immediate(id))))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Schedule an update for any component given its [`ScopeId`].
|
||||||
|
///
|
||||||
|
/// A component's [`ScopeId`] can be obtained from `use_hook` or the [`ScopeState::scope_id`] method.
|
||||||
|
///
|
||||||
|
/// This method should be used when you want to schedule an update for a component
|
||||||
|
pub fn schedule_update_any(&self) -> Arc<dyn Fn(ScopeId) + Send + Sync> {
|
||||||
|
let chan = self.tasks.sender.clone();
|
||||||
|
Arc::new(move |id| drop(chan.unbounded_send(SchedulerMsg::Immediate(id))))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn needs_update(&self) {
|
||||||
|
self.needs_update_any(self.scope_id());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the [`ScopeId`] of a mounted component.
|
||||||
|
///
|
||||||
|
/// `ScopeId` is not unique for the lifetime of the [`VirtualDom`] - a [`ScopeId`] will be reused if a component is unmounted.
|
||||||
|
pub fn needs_update_any(&self, id: ScopeId) {
|
||||||
|
self.tasks
|
||||||
|
.sender
|
||||||
|
.unbounded_send(SchedulerMsg::Immediate(id))
|
||||||
|
.expect("Scheduler to exist if scope exists");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This method enables the ability to expose state to children further down the [`VirtualDom`] Tree.
|
||||||
|
///
|
||||||
|
/// This is a "fundamental" operation and should only be called during initialization of a hook.
|
||||||
|
///
|
||||||
|
/// For a hook that provides the same functionality, use `use_provide_context` and `use_consume_context` instead.
|
||||||
|
///
|
||||||
|
/// When the component is dropped, so is the context. Be aware of this behavior when consuming
|
||||||
|
/// the context via Rc/Weak.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust, ignore
|
||||||
|
/// struct SharedState(&'static str);
|
||||||
|
///
|
||||||
|
/// static App: Component = |cx| {
|
||||||
|
/// cx.use_hook(|| cx.provide_context(SharedState("world")));
|
||||||
|
/// render!(Child {})
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// static Child: Component = |cx| {
|
||||||
|
/// let state = cx.consume_state::<SharedState>();
|
||||||
|
/// render!(div { "hello {state.0}" })
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub fn provide_context<T: 'static + Clone>(&self, value: T) -> T {
|
||||||
|
self.shared_contexts
|
||||||
|
.borrow_mut()
|
||||||
|
.insert(TypeId::of::<T>(), Box::new(value.clone()))
|
||||||
|
.and_then(|f| f.downcast::<T>().ok());
|
||||||
|
value
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Provide a context for the root component from anywhere in your app.
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust, ignore
|
||||||
|
/// struct SharedState(&'static str);
|
||||||
|
///
|
||||||
|
/// static App: Component = |cx| {
|
||||||
|
/// cx.use_hook(|| cx.provide_root_context(SharedState("world")));
|
||||||
|
/// render!(Child {})
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// static Child: Component = |cx| {
|
||||||
|
/// let state = cx.consume_state::<SharedState>();
|
||||||
|
/// render!(div { "hello {state.0}" })
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub fn provide_root_context<T: 'static + Clone>(&self, value: T) -> T {
|
||||||
|
// if we *are* the root component, then we can just provide the context directly
|
||||||
|
if self.scope_id() == ScopeId(0) {
|
||||||
|
self.shared_contexts
|
||||||
|
.borrow_mut()
|
||||||
|
.insert(TypeId::of::<T>(), Box::new(value.clone()))
|
||||||
|
.and_then(|f| f.downcast::<T>().ok());
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut search_parent = self.parent;
|
||||||
|
|
||||||
|
while let Some(parent) = search_parent.take() {
|
||||||
|
let parent = unsafe { &*parent };
|
||||||
|
|
||||||
|
if parent.scope_id() == ScopeId(0) {
|
||||||
|
let exists = parent
|
||||||
|
.shared_contexts
|
||||||
|
.borrow_mut()
|
||||||
|
.insert(TypeId::of::<T>(), Box::new(value.clone()));
|
||||||
|
|
||||||
|
if exists.is_some() {
|
||||||
|
log::warn!("Context already provided to parent scope - replacing it");
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
search_parent = parent.parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
unreachable!("all apps have a root scope")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to retrieve a shared state with type T from the any parent Scope.
|
||||||
|
pub fn consume_context<T: 'static + Clone>(&self) -> Option<T> {
|
||||||
|
if let Some(shared) = self.shared_contexts.borrow().get(&TypeId::of::<T>()) {
|
||||||
|
Some(
|
||||||
|
(*shared
|
||||||
|
.downcast_ref::<T>()
|
||||||
|
.expect("Context of type T should exist"))
|
||||||
|
.clone(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
let mut search_parent = self.parent;
|
||||||
|
|
||||||
|
while let Some(parent_ptr) = search_parent {
|
||||||
|
// safety: all parent pointers are valid thanks to the bump arena
|
||||||
|
let parent = unsafe { &*parent_ptr };
|
||||||
|
if let Some(shared) = parent.shared_contexts.borrow().get(&TypeId::of::<T>()) {
|
||||||
|
return Some(
|
||||||
|
shared
|
||||||
|
.downcast_ref::<T>()
|
||||||
|
.expect("Context of type T should exist")
|
||||||
|
.clone(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
search_parent = parent.parent;
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pushes the future onto the poll queue to be polled after the component renders.
|
||||||
|
pub fn push_future(&self, fut: impl Future<Output = ()> + 'static) -> TaskId {
|
||||||
|
// wake up the scheduler if it is sleeping
|
||||||
|
self.tasks
|
||||||
|
.sender
|
||||||
|
.unbounded_send(SchedulerMsg::NewTask(self.id))
|
||||||
|
.expect("Scheduler should exist");
|
||||||
|
|
||||||
|
self.tasks.spawn(self.id, fut)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Spawns the future but does not return the [`TaskId`]
|
||||||
|
pub fn spawn(&self, fut: impl Future<Output = ()> + 'static) {
|
||||||
|
self.push_future(fut);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Spawn a future that Dioxus will never clean up
|
||||||
|
///
|
||||||
|
/// This is good for tasks that need to be run after the component has been dropped.
|
||||||
|
pub fn spawn_forever(&self, fut: impl Future<Output = ()> + 'static) -> TaskId {
|
||||||
|
// wake up the scheduler if it is sleeping
|
||||||
|
self.tasks
|
||||||
|
.sender
|
||||||
|
.unbounded_send(SchedulerMsg::NewTask(self.id))
|
||||||
|
.expect("Scheduler should exist");
|
||||||
|
|
||||||
|
// The root scope will never be unmounted so we can just add the task at the top of the app
|
||||||
|
self.tasks.spawn(ScopeId(0), fut)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Informs the scheduler that this task is no longer needed and should be removed
|
||||||
|
/// on next poll.
|
||||||
|
pub fn remove_future(&self, id: TaskId) {
|
||||||
|
self.tasks.remove(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Take a lazy [`VNode`] structure and actually build it with the context of the Vdoms efficient [`VNode`] allocator.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// fn Component(cx: Scope<Props>) -> Element {
|
||||||
|
/// // Lazy assemble the VNode tree
|
||||||
|
/// let lazy_nodes = rsx!("hello world");
|
||||||
|
///
|
||||||
|
/// // Actually build the tree and allocate it
|
||||||
|
/// cx.render(lazy_tree)
|
||||||
|
/// }
|
||||||
|
///```
|
||||||
|
pub fn render<'src>(&'src self, rsx: LazyNodes<'src, '_>) -> Option<VNode<'src>> {
|
||||||
|
Some(rsx.call(self))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Store a value between renders. The foundational hook for all other hooks.
|
||||||
|
///
|
||||||
|
/// Accepts an `initializer` closure, which is run on the first use of the hook (typically the initial render). The return value of this closure is stored for the lifetime of the component, and a mutable reference to it is provided on every render as the return value of `use_hook`.
|
||||||
|
///
|
||||||
|
/// When the component is unmounted (removed from the UI), the value is dropped. This means you can return a custom type and provide cleanup code by implementing the [`Drop`] trait
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use dioxus_core::ScopeState;
|
||||||
|
///
|
||||||
|
/// // prints a greeting on the initial render
|
||||||
|
/// pub fn use_hello_world(cx: &ScopeState) {
|
||||||
|
/// cx.use_hook(|| println!("Hello, world!"));
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
#[allow(clippy::mut_from_ref)]
|
||||||
|
pub fn use_hook<State: 'static>(&self, initializer: impl FnOnce() -> State) -> &mut State {
|
||||||
|
let mut vals = self.hook_vals.borrow_mut();
|
||||||
|
|
||||||
|
let hook_len = vals.len();
|
||||||
|
let cur_idx = self.hook_idx.get();
|
||||||
|
|
||||||
|
if cur_idx >= hook_len {
|
||||||
|
vals.push(self.hook_arena.alloc(initializer()));
|
||||||
|
}
|
||||||
|
|
||||||
|
vals
|
||||||
|
.get(cur_idx)
|
||||||
|
.and_then(|inn| {
|
||||||
|
self.hook_idx.set(cur_idx + 1);
|
||||||
|
let raw_box = unsafe { &mut **inn };
|
||||||
|
raw_box.downcast_mut::<State>()
|
||||||
|
})
|
||||||
|
.expect(
|
||||||
|
r###"
|
||||||
|
Unable to retrieve the hook that was initialized at this index.
|
||||||
|
Consult the `rules of hooks` to understand how to use hooks properly.
|
||||||
|
|
||||||
|
You likely used the hook in a conditional. Hooks rely on consistent ordering between renders.
|
||||||
|
Functions prefixed with "use" should never be called conditionally.
|
||||||
|
"###,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
use crate::any_props::VComponentProps;
|
||||||
|
use crate::arena::ElementPath;
|
||||||
|
use crate::component::{Component, IntoComponent};
|
||||||
|
use crate::diff::DirtyScope;
|
||||||
|
use crate::future_container::FutureQueue;
|
||||||
|
use crate::innerlude::SchedulerMsg;
|
||||||
|
use crate::mutations::Mutation;
|
||||||
|
use crate::nodes::{Template, TemplateId};
|
||||||
|
use crate::{
|
||||||
|
arena::ElementId,
|
||||||
|
scopes::{ScopeId, ScopeState},
|
||||||
|
};
|
||||||
|
use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender};
|
||||||
|
use slab::Slab;
|
||||||
|
use std::collections::{BTreeSet, HashMap};
|
||||||
|
|
||||||
|
pub struct VirtualDom {
|
||||||
|
pub(crate) templates: HashMap<TemplateId, Template>,
|
||||||
|
pub(crate) elements: Slab<ElementPath>,
|
||||||
|
pub(crate) scopes: Slab<ScopeState>,
|
||||||
|
pub(crate) scope_stack: Vec<ScopeId>,
|
||||||
|
pub(crate) element_stack: Vec<ElementId>,
|
||||||
|
pub(crate) dirty_scopes: BTreeSet<DirtyScope>,
|
||||||
|
pub(crate) pending_futures: FutureQueue,
|
||||||
|
pub(crate) sender: UnboundedSender<SchedulerMsg>,
|
||||||
|
pub(crate) receiver: UnboundedReceiver<SchedulerMsg>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VirtualDom {
|
||||||
|
pub fn new(app: Component<()>) -> Self {
|
||||||
|
let (sender, receiver) = futures_channel::mpsc::unbounded();
|
||||||
|
|
||||||
|
let mut res = Self {
|
||||||
|
templates: Default::default(),
|
||||||
|
scopes: Slab::default(),
|
||||||
|
elements: Default::default(),
|
||||||
|
scope_stack: Vec::new(),
|
||||||
|
element_stack: vec![ElementId(0)],
|
||||||
|
dirty_scopes: BTreeSet::new(),
|
||||||
|
pending_futures: FutureQueue::new(sender.clone()),
|
||||||
|
receiver,
|
||||||
|
sender,
|
||||||
|
};
|
||||||
|
|
||||||
|
let props = Box::into_raw(Box::new(VComponentProps::new_empty(app)));
|
||||||
|
|
||||||
|
let root = res.new_scope(props);
|
||||||
|
|
||||||
|
assert_eq!(root, ScopeId(0));
|
||||||
|
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Render the virtualdom, without processing any suspense.
|
||||||
|
pub fn rebuild<'a>(&'a mut self, mutations: &mut Vec<Mutation<'a>>) {
|
||||||
|
// let root = self.scopes.get(0).unwrap();
|
||||||
|
|
||||||
|
let root_node = unsafe { std::mem::transmute(self.run_scope(ScopeId(0))) };
|
||||||
|
|
||||||
|
// let root_node = unsafe { std::mem::transmute(root.root_node()) };
|
||||||
|
|
||||||
|
self.scope_stack.push(ScopeId(0));
|
||||||
|
self.create(mutations, root_node);
|
||||||
|
self.scope_stack.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Render what you can given the timeline and then move on
|
||||||
|
pub async fn render_with_deadline<'a>(
|
||||||
|
&'a mut self,
|
||||||
|
future: impl std::future::Future<Output = ()>,
|
||||||
|
mutations: &mut Vec<Mutation<'a>>,
|
||||||
|
) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Whenever the future is canceled, the VirtualDom will be
|
||||||
|
pub async fn render<'a>(&'a mut self, mutations: &mut Vec<Mutation<'a>>) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wait for futures internal to the virtualdom
|
||||||
|
///
|
||||||
|
/// This is cancel safe, so if the future is dropped, you can push events into the virtualdom
|
||||||
|
pub async fn wait_for_work(&mut self) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for VirtualDom {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
// self.drop_scope(ScopeId(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,114 +0,0 @@
|
|||||||
use dioxus_core::{prelude::*, TemplateNode, VTemplate, VText};
|
|
||||||
|
|
||||||
// #[test]
|
|
||||||
// fn simple_static() {
|
|
||||||
// fn app(cx: Scope) -> Element {
|
|
||||||
// static MyTemplate: TemplateDef = TemplateDef {
|
|
||||||
// id: "my-template",
|
|
||||||
// static_nodes: &[TemplateNode::Element {
|
|
||||||
// attributes: &[],
|
|
||||||
// nodes: &[TemplateNode::StaticText("Hello, world!")],
|
|
||||||
// tag: "div",
|
|
||||||
// }],
|
|
||||||
// dynamic_nodes: &[],
|
|
||||||
// };
|
|
||||||
|
|
||||||
// Some(VNode::Template(NodeFactory::new(&cx).bump().alloc(
|
|
||||||
// VTemplate {
|
|
||||||
// def: &MyTemplate,
|
|
||||||
// dynamic_nodes: &[],
|
|
||||||
// rendered_nodes: &[],
|
|
||||||
// },
|
|
||||||
// )))
|
|
||||||
// }
|
|
||||||
|
|
||||||
// let mut dom = VirtualDom::new(app);
|
|
||||||
// let edits = dom.rebuild();
|
|
||||||
// dbg!(edits);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[test]
|
|
||||||
// fn mixed_dynamic() {
|
|
||||||
// fn app(cx: Scope) -> Element {
|
|
||||||
// static MyTemplate: TemplateDef = TemplateDef {
|
|
||||||
// id: "my-template",
|
|
||||||
// static_nodes: &[TemplateNode::Element {
|
|
||||||
// tag: "div",
|
|
||||||
// attributes: &[],
|
|
||||||
// nodes: &[
|
|
||||||
// TemplateNode::StaticText("Hello, world!"),
|
|
||||||
// TemplateNode::DynamicText,
|
|
||||||
// ],
|
|
||||||
// }],
|
|
||||||
// dynamic_nodes: &[],
|
|
||||||
// };
|
|
||||||
|
|
||||||
// let val = cx.use_hook(|| 0);
|
|
||||||
// *val += 1;
|
|
||||||
|
|
||||||
// let fact = NodeFactory::new(&cx);
|
|
||||||
|
|
||||||
// Some(VNode::Template(fact.bump().alloc(VTemplateRef {
|
|
||||||
// def: &MyTemplate,
|
|
||||||
// dynamic_nodes: fact.bump().alloc([fact.text(format_args!("{val}"))]),
|
|
||||||
// })))
|
|
||||||
// }
|
|
||||||
|
|
||||||
// let mut dom = VirtualDom::new(app);
|
|
||||||
// let edits = dom.rebuild();
|
|
||||||
// dbg!(edits);
|
|
||||||
|
|
||||||
// let edits = dom.hard_diff(ScopeId(0));
|
|
||||||
// dbg!(edits);
|
|
||||||
|
|
||||||
// let edits = dom.hard_diff(ScopeId(0));
|
|
||||||
// dbg!(edits);
|
|
||||||
|
|
||||||
// let edits = dom.hard_diff(ScopeId(0));
|
|
||||||
// dbg!(edits);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[test]
|
|
||||||
// fn mixes() {
|
|
||||||
// fn app(cx: Scope) -> Element {
|
|
||||||
// static MyTemplate: TemplateDef = TemplateDef {
|
|
||||||
// id: "my-template",
|
|
||||||
// static_nodes: &[TemplateNode::Element {
|
|
||||||
// tag: "div",
|
|
||||||
// attributes: &[],
|
|
||||||
// nodes: &[
|
|
||||||
// TemplateNode::StaticText("Hello, world!"),
|
|
||||||
// TemplateNode::DynamicText,
|
|
||||||
// ],
|
|
||||||
// }],
|
|
||||||
// dynamic_nodes: &[],
|
|
||||||
// };
|
|
||||||
|
|
||||||
// let val = cx.use_hook(|| 1);
|
|
||||||
// *val += 1;
|
|
||||||
|
|
||||||
// let fact = NodeFactory::new(&cx);
|
|
||||||
|
|
||||||
// if *val % 2 == 0 {
|
|
||||||
// Some(VNode::Template(fact.bump().alloc(VTemplateRef {
|
|
||||||
// def: &MyTemplate,
|
|
||||||
// dynamic_nodes: fact.bump().alloc([fact.text(format_args!("{val}"))]),
|
|
||||||
// })))
|
|
||||||
// } else {
|
|
||||||
// Some(fact.text(format_args!("Hello, world! {val}")))
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// let mut dom = VirtualDom::new(app);
|
|
||||||
// let edits = dom.rebuild();
|
|
||||||
// dbg!(edits);
|
|
||||||
|
|
||||||
// let edits = dom.hard_diff(ScopeId(0));
|
|
||||||
// dbg!(edits);
|
|
||||||
|
|
||||||
// let edits = dom.hard_diff(ScopeId(0));
|
|
||||||
// dbg!(edits);
|
|
||||||
|
|
||||||
// let edits = dom.hard_diff(ScopeId(0));
|
|
||||||
// dbg!(edits);
|
|
||||||
// }
|
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
use dioxus_core::prelude::*;
|
||||||
|
|
||||||
|
fn app(cx: Scope) -> Element {
|
||||||
|
todo!();
|
||||||
|
// render! {
|
||||||
|
// Suspend {
|
||||||
|
// delay: Duration::from_millis(100),
|
||||||
|
// fallback: rsx! { "Loading..." },
|
||||||
|
// ChildAsync {}
|
||||||
|
// ChildAsync {}
|
||||||
|
// ChildAsync {}
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn ChildAsync(cx: Scope<'_>) -> Element {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn it_works() {
|
||||||
|
let mut dom = VirtualDom::new(app);
|
||||||
|
|
||||||
|
let mut mutations = vec![];
|
||||||
|
dom.rebuild(&mut mutations);
|
||||||
|
}
|
||||||
@@ -32,7 +32,7 @@ rand = { version = "0.8.4", features = ["small_rng"] }
|
|||||||
criterion = "0.3.5"
|
criterion = "0.3.5"
|
||||||
thiserror = "1.0.30"
|
thiserror = "1.0.30"
|
||||||
env_logger = "0.9.0"
|
env_logger = "0.9.0"
|
||||||
dioxus-edit-stream = { path = "../edit-stream" }
|
# dioxus-edit-stream = { path = "../edit-stream" }
|
||||||
|
|
||||||
[[bench]]
|
[[bench]]
|
||||||
name = "create"
|
name = "create"
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
use dioxus_core::{Attribute, TemplateAttribute};
|
|
||||||
use dioxus_edit_stream::*;
|
|
||||||
|
|
||||||
fn basic_syntax_is_a_template(cx: Scope) -> Element {
|
fn basic_syntax_is_a_template(cx: Scope) -> Element {
|
||||||
let asd = 123;
|
let asd = 123;
|
||||||
let var = 123;
|
let var = 123;
|
||||||
|
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
div { class: "asd", class: "{asd}",
|
div {
|
||||||
|
class: "asd",
|
||||||
|
class: "{asd}",
|
||||||
onclick: move |_| {},
|
onclick: move |_| {},
|
||||||
|
|
||||||
div { "{var}" }
|
div { "{var}" }
|
||||||
div {
|
div {
|
||||||
h1 { "var" }
|
h1 { "var" }
|
||||||
p { "you're great!" }
|
p { "you're great!" }
|
||||||
div {
|
div { background_color: "red",
|
||||||
background_color: "red",
|
|
||||||
h1 { "var" }
|
h1 { "var" }
|
||||||
div {
|
div {
|
||||||
b { "asd" }
|
b { "asd" }
|
||||||
@@ -29,16 +29,24 @@ fn basic_syntax_is_a_template(cx: Scope) -> Element {
|
|||||||
|
|
||||||
fn basic_template(cx: Scope) -> Element {
|
fn basic_template(cx: Scope) -> Element {
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
div {"hi!"}
|
div {
|
||||||
|
(0..2).map(|i| rsx! {
|
||||||
|
div { "asd" }
|
||||||
|
})
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn basic_prints() {
|
fn basic_prints() {
|
||||||
let dom = VirtualDom::new(basic_template);
|
let mut dom = VirtualDom::new(basic_template);
|
||||||
|
|
||||||
let renderer = dioxus_edit_stream::Mutations::default();
|
let mut edits = Vec::new();
|
||||||
dom.rebuild(&mut renderer);
|
|
||||||
|
|
||||||
dbg!(renderer.edits);
|
dom.rebuild(&mut edits);
|
||||||
|
|
||||||
|
dbg!(edits);
|
||||||
|
// let renderer = dioxus_edit_stream::Mutations::default();
|
||||||
|
//
|
||||||
|
// dbg!(renderer.edits);
|
||||||
}
|
}
|
||||||
+18
-17
@@ -40,28 +40,29 @@ pub mod on {
|
|||||||
// mut callback: impl FnMut(UiEvent<$data>) + 'a,
|
// mut callback: impl FnMut(UiEvent<$data>) + 'a,
|
||||||
) -> Listener<'a>
|
) -> Listener<'a>
|
||||||
{
|
{
|
||||||
let bump = &factory.bump();
|
// let bump = &factory.bump();
|
||||||
|
|
||||||
|
|
||||||
use dioxus_core::{AnyEvent};
|
// // we can't allocate unsized in bumpalo's box, so we need to craft the box manually
|
||||||
// we can't allocate unsized in bumpalo's box, so we need to craft the box manually
|
// // safety: this is essentially the same as calling Box::new() but manually
|
||||||
// safety: this is essentially the same as calling Box::new() but manually
|
// // The box is attached to the lifetime of the bumpalo allocator
|
||||||
// The box is attached to the lifetime of the bumpalo allocator
|
// let cb: &mut dyn FnMut(AnyEvent) = bump.alloc(move |evt: AnyEvent| {
|
||||||
let cb: &mut dyn FnMut(AnyEvent) = bump.alloc(move |evt: AnyEvent| {
|
// let event = evt.downcast::<$data>().unwrap();
|
||||||
let event = evt.downcast::<$data>().unwrap();
|
// callback(event)
|
||||||
callback(event)
|
// });
|
||||||
});
|
|
||||||
|
|
||||||
let callback: BumpBox<dyn FnMut(AnyEvent) + 'a> = unsafe { BumpBox::from_raw(cb) };
|
// let callback: BumpBox<dyn FnMut(AnyEvent) + 'a> = unsafe { BumpBox::from_raw(cb) };
|
||||||
|
|
||||||
// ie oncopy
|
// // ie oncopy
|
||||||
let event_name = stringify!($name);
|
// let event_name = stringify!($name);
|
||||||
|
|
||||||
// ie copy
|
// // ie copy
|
||||||
let shortname: &'static str = &event_name[2..];
|
// let shortname: &'static str = &event_name[2..];
|
||||||
|
|
||||||
let handler = bump.alloc(std::cell::RefCell::new(Some(callback)));
|
// let handler = bump.alloc(std::cell::RefCell::new(Some(callback)));
|
||||||
factory.listener(shortname, handler)
|
// factory.listener(shortname, handler)
|
||||||
|
|
||||||
|
todo!()
|
||||||
}
|
}
|
||||||
)*
|
)*
|
||||||
)*
|
)*
|
||||||
@@ -1423,7 +1424,7 @@ impl KeyCode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn _event_meta(event: &UserEvent) -> (bool, EventPriority) {
|
pub(crate) fn _event_meta<T>(event: &UiEvent<T>) -> (bool, EventPriority) {
|
||||||
use EventPriority::*;
|
use EventPriority::*;
|
||||||
|
|
||||||
match event.name {
|
match event.name {
|
||||||
|
|||||||
+41
-34
@@ -17,10 +17,6 @@ mod errors;
|
|||||||
// mod attributes;
|
// mod attributes;
|
||||||
mod component;
|
mod component;
|
||||||
mod element;
|
mod element;
|
||||||
// #[cfg(any(feature = "hot-reload", debug_assertions))]
|
|
||||||
// mod elements;
|
|
||||||
// #[cfg(any(feature = "hot-reload", debug_assertions))]
|
|
||||||
// mod error;
|
|
||||||
mod ifmt;
|
mod ifmt;
|
||||||
mod node;
|
mod node;
|
||||||
mod template;
|
mod template;
|
||||||
@@ -173,23 +169,40 @@ impl ToTokens for CallBody {
|
|||||||
let listener_printer = context.dynamic_listeners.iter();
|
let listener_printer = context.dynamic_listeners.iter();
|
||||||
|
|
||||||
out_tokens.append_all(quote! {
|
out_tokens.append_all(quote! {
|
||||||
LazyNodes::new(move | __cx: ::dioxus::core::NodeFactory| -> ::dioxus::core::VNode {
|
// LazyNodes::new(move | __cx: ::dioxus::core::NodeFactory| -> ::dioxus::core::VNode {
|
||||||
__cx.template_ref(
|
// __cx.template_ref(
|
||||||
::dioxus::core::Template {
|
// ::dioxus::core::Template {
|
||||||
|
// id: ::dioxus::core::get_line_num!(),
|
||||||
|
// roots: &[ #roots ]
|
||||||
|
// },
|
||||||
|
// __cx.bump().alloc([
|
||||||
|
// #( #node_printer ),*
|
||||||
|
// ]),
|
||||||
|
// __cx.bump().alloc([
|
||||||
|
// #( #attr_printer ),*
|
||||||
|
// ]),
|
||||||
|
// __cx.bump().alloc([
|
||||||
|
// #( #listener_printer ),*
|
||||||
|
// ]),
|
||||||
|
// None
|
||||||
|
// )
|
||||||
|
// })
|
||||||
|
|
||||||
|
|
||||||
|
::dioxus::core::LazyNodes::new( move | __cx: ::dioxus::core::NodeFactory| -> ::dioxus::core::VNode {
|
||||||
|
static TEMPLATE: ::dioxus::core::Template = ::dioxus::core::Template {
|
||||||
id: ::dioxus::core::get_line_num!(),
|
id: ::dioxus::core::get_line_num!(),
|
||||||
roots: &[ #roots ]
|
roots: &[ #roots ]
|
||||||
},
|
};
|
||||||
__cx.bump().alloc([
|
|
||||||
#( #node_printer ),*
|
::dioxus::core::VNode {
|
||||||
]),
|
node_id: Default::default(),
|
||||||
__cx.bump().alloc([
|
parent: None,
|
||||||
#( #attr_printer ),*
|
template: TEMPLATE,
|
||||||
]),
|
root_ids: __cx.bump().alloc([]),
|
||||||
__cx.bump().alloc([
|
dynamic_nodes: __cx.bump().alloc([ #( #node_printer ),* ]),
|
||||||
#( #listener_printer ),*
|
dynamic_attrs: __cx.bump().alloc([]),
|
||||||
]),
|
}
|
||||||
None
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -221,22 +234,16 @@ impl CallBody {
|
|||||||
roots: &[]
|
roots: &[]
|
||||||
};
|
};
|
||||||
|
|
||||||
LazyNodes::new(TEMPLATE, move | __cx: ::dioxus::core::NodeFactory| -> ::dioxus::core::VNode {
|
LazyNodes::new( move | __cx: ::dioxus::core::NodeFactory| -> ::dioxus::core::VNode {
|
||||||
todo!()
|
::dioxus::core::VNode {
|
||||||
|
node_id: Default::default(),
|
||||||
|
parent: None,
|
||||||
|
template: &TEMPLATE,
|
||||||
|
root_ids: __cx.bump().alloc([]),
|
||||||
|
dynamic_nodes: __cx.bump().alloc([]),
|
||||||
|
dynamic_attrs: __cx.bump().alloc([]),
|
||||||
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
// let template = TemplateBuilder::from_roots(self.roots.clone());
|
|
||||||
// let inner = if let Some(template) = template {
|
|
||||||
// quote! { #template }
|
|
||||||
// } else {
|
|
||||||
// let children = &self.roots;
|
|
||||||
// if children.len() == 1 {
|
|
||||||
// let inner = &self.roots[0];
|
|
||||||
// quote! { #inner }
|
|
||||||
// } else {
|
|
||||||
// quote! { __cx.fragment_root([ #(#children),* ]) }
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
// out_tokens.append_all(inner);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user