feat: simple tests passing

This commit is contained in:
Jonathan Kelley committed 2022-11-01 18:42:29 -07:00
1 parent 23603aaaf5
commit 4a31b29703
27 files changed
+1565 -372

No files matched your search

+1 -1
View File
@@ -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",
] ]
+1 -2
View File
@@ -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()
} }
-4
View File
@@ -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());
} }
+20 -13
View File
@@ -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
View File
@@ -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 -2
View File
@@ -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 {
+39 -1
View File
@@ -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!()
}
}
+99 -41
View File
@@ -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
.iter()
.fold(0, |acc, child| acc + self.create(mutations, child))
} }
DynamicNodeKind::Fragment { children } => children
.iter()
.fold(0, |acc, child| acc + self.create(mutations, child)),
} }
} }
} }
+14 -5
View File
@@ -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];
}
}
+2 -2
View File
@@ -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>>;
+159
View File
@@ -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]));
}
+46
View File
@@ -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)
}
}
+65
View File
@@ -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,
}
+30
View File
@@ -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 } => {}
}
}
}
}
+324
View File
@@ -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);
// }
// }
+127 -56
View File
@@ -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, /// This includes types like [`Scope`], [`Element`], and [`Component`].
ElementId(0), pub mod prelude {
Box::new(VComponentProps::new_empty(app)), pub use crate::innerlude::{
); Attribute, Element, EventPriority, LazyNodes, Listener, NodeFactory, Scope, ScopeId,
ScopeState, TaskId, Template, TemplateAttribute, TemplateNode, UiEvent, VNode, VirtualDom,
};
}
res 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;
}
fn root_scope(&self) -> &ScopeState { #[macro_export]
todo!() /// 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();
)*}
}
/// Render the virtualdom, waiting for all suspended nodes to complete before moving on /// get the code location of the code that called this function
/// #[macro_export]
/// Forces a full render of the virtualdom from scratch. macro_rules! get_line_num {
/// () => {
/// Use other methods to update the virtualdom incrementally. concat!(
pub fn render_all<'a>(&'a mut self, mutations: &mut Vec<Mutation<'a>>) { file!(),
let root = self.root_scope(); ":",
line!(),
let root_template = root.current_arena(); ":",
column!(),
let root_node: &'a VTemplate = unsafe { &*root_template.node.get() }; ":",
let root_node: &'a VTemplate<'a> = unsafe { std::mem::transmute(root_node) }; env!("CARGO_MANIFEST_DIR")
)
self.create(mutations, root_node); };
}
} }
+22 -1
View File
@@ -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
View File
@@ -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>());
} }
+31 -20
View File
@@ -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,
+353 -16
View File
@@ -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(crate) type ComponentPtr = *mut std::os::raw::c_void; pub fn bump(&self) -> &Bump {
&self.current_arena().bump
}
pub struct Scope<T = ()> { pub fn root_node<'a>(&'a self) -> &'a VNode<'a> {
pub props: T, let r = unsafe { &*self.current_arena().node.get() };
pub state: Cell<*const ScopeState>, unsafe { std::mem::transmute(r) }
} }
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.
"###,
)
} }
} }
+91
View File
@@ -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));
}
}
-114
View File
@@ -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);
// }
+26
View File
@@ -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);
}
+1 -1
View File
@@ -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"
+18 -10
View File
@@ -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
View File
@@ -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 {
+43 -36
View File
@@ -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!(), // id: ::dioxus::core::get_line_num!(),
roots: &[ #roots ] // roots: &[ #roots ]
}, // },
__cx.bump().alloc([ // __cx.bump().alloc([
#( #node_printer ),* // #( #node_printer ),*
]), // ]),
__cx.bump().alloc([ // __cx.bump().alloc([
#( #attr_printer ),* // #( #attr_printer ),*
]), // ]),
__cx.bump().alloc([ // __cx.bump().alloc([
#( #listener_printer ),* // #( #listener_printer ),*
]), // ]),
None // 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!(),
roots: &[ #roots ]
};
::dioxus::core::VNode {
node_id: Default::default(),
parent: None,
template: TEMPLATE,
root_ids: __cx.bump().alloc([]),
dynamic_nodes: __cx.bump().alloc([ #( #node_printer ),* ]),
dynamic_attrs: __cx.bump().alloc([]),
}
}) })
}) })
} }
@@ -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);
} }
} }