chore: more refactoring, docs
This commit is contained in:
1 parent
f7df6a9893
commit
c44bd11fe5
27 files changed
+657
-382
No files matched your search
@@ -1,7 +1,5 @@
|
|||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
use std::future::Future;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
factory::{ComponentReturn, RenderReturn},
|
factory::{ComponentReturn, RenderReturn},
|
||||||
innerlude::Scoped,
|
innerlude::Scoped,
|
||||||
@@ -25,17 +23,6 @@ where
|
|||||||
pub _marker: PhantomData<A>,
|
pub _marker: PhantomData<A>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> VComponentProps<'a, (), ()> {
|
|
||||||
pub fn new_empty(render_fn: fn(Scope) -> Element) -> Self {
|
|
||||||
Self {
|
|
||||||
render_fn,
|
|
||||||
memo: <() as PartialEq>::eq,
|
|
||||||
props: (),
|
|
||||||
_marker: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, P, A, F: ComponentReturn<'a, A>> VComponentProps<'a, P, A, F> {
|
impl<'a, P, A, F: ComponentReturn<'a, A>> VComponentProps<'a, P, A, F> {
|
||||||
pub(crate) fn new(
|
pub(crate) fn new(
|
||||||
render_fn: fn(Scope<'a, P>) -> F,
|
render_fn: fn(Scope<'a, P>) -> F,
|
||||||
|
|||||||
@@ -8,6 +8,15 @@ pub struct ElementPath {
|
|||||||
pub element: usize,
|
pub element: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ElementPath {
|
||||||
|
pub fn null() -> Self {
|
||||||
|
Self {
|
||||||
|
template: std::ptr::null_mut(),
|
||||||
|
element: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl VirtualDom {
|
impl VirtualDom {
|
||||||
pub fn next_element(&mut self, template: &VNode) -> ElementId {
|
pub fn next_element(&mut self, template: &VNode) -> ElementId {
|
||||||
let entry = self.elements.vacant_entry();
|
let entry = self.elements.vacant_entry();
|
||||||
|
|||||||
@@ -21,4 +21,8 @@ impl BumpFrame {
|
|||||||
self.bump.reset();
|
self.bump.reset();
|
||||||
self.node.set(std::ptr::null_mut());
|
self.node.set(std::ptr::null_mut());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub unsafe fn load_node<'b>(&self) -> &'b RenderReturn<'b> {
|
||||||
|
unsafe { std::mem::transmute(&*self.node.get()) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
// pub trait IntoComponentType<T> {
|
|
||||||
// fn into_component_type(self) -> ComponentType;
|
|
||||||
// }
|
|
||||||
|
|
||||||
use std::marker::PhantomData;
|
|
||||||
|
|
||||||
use std::future::Future;
|
|
||||||
|
|
||||||
use crate::{scopes::Scope, Element};
|
|
||||||
|
|
||||||
pub type Component<'a, T = ()> = fn(Scope<'a, T>) -> Element<'a>;
|
|
||||||
@@ -130,7 +130,10 @@ impl VirtualDom {
|
|||||||
) {
|
) {
|
||||||
match *node {
|
match *node {
|
||||||
// Todo: create the children's template
|
// Todo: create the children's template
|
||||||
TemplateNode::Dynamic(_) => mutations.push(CreatePlaceholder { id: ElementId(0) }),
|
TemplateNode::Dynamic(_) => {
|
||||||
|
let id = self.next_element(template);
|
||||||
|
mutations.push(CreatePlaceholder { id })
|
||||||
|
}
|
||||||
TemplateNode::Text(value) => mutations.push(CreateText { value }),
|
TemplateNode::Text(value) => mutations.push(CreateText { value }),
|
||||||
TemplateNode::DynamicText { .. } => mutations.push(CreateText {
|
TemplateNode::DynamicText { .. } => mutations.push(CreateText {
|
||||||
value: "placeholder",
|
value: "placeholder",
|
||||||
@@ -220,7 +223,7 @@ impl VirtualDom {
|
|||||||
|
|
||||||
let split_off = unsafe { std::mem::transmute(split_off) };
|
let split_off = unsafe { std::mem::transmute(split_off) };
|
||||||
|
|
||||||
boundary_mut.mutations.mutations = split_off;
|
boundary_mut.mutations.edits = split_off;
|
||||||
boundary_mut
|
boundary_mut
|
||||||
.waiting_on
|
.waiting_on
|
||||||
.extend(self.collected_leaves.drain(..));
|
.extend(self.collected_leaves.drain(..));
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
|
|
||||||
|
use crate::factory::RenderReturn;
|
||||||
use crate::innerlude::Mutations;
|
use crate::innerlude::Mutations;
|
||||||
use crate::virtual_dom::VirtualDom;
|
use crate::virtual_dom::VirtualDom;
|
||||||
use crate::{Attribute, AttributeValue, TemplateNode};
|
use crate::{Attribute, AttributeValue, TemplateNode};
|
||||||
|
|
||||||
use crate::any_props::VComponentProps;
|
use crate::any_props::VComponentProps;
|
||||||
|
|
||||||
use crate::component::Component;
|
|
||||||
use crate::mutations::Mutation;
|
use crate::mutations::Mutation;
|
||||||
use crate::nodes::{DynamicNode, Template, TemplateId};
|
use crate::nodes::{DynamicNode, Template, TemplateId};
|
||||||
use crate::scopes::Scope;
|
use crate::scopes::Scope;
|
||||||
@@ -41,6 +41,62 @@ impl Ord for DirtyScope {
|
|||||||
impl<'b> VirtualDom {
|
impl<'b> VirtualDom {
|
||||||
pub fn diff_scope(&mut self, mutations: &mut Mutations<'b>, scope: ScopeId) {
|
pub fn diff_scope(&mut self, mutations: &mut Mutations<'b>, scope: ScopeId) {
|
||||||
let scope_state = &mut self.scopes[scope.0];
|
let scope_state = &mut self.scopes[scope.0];
|
||||||
|
|
||||||
|
let cur_arena = scope_state.current_frame();
|
||||||
|
let prev_arena = scope_state.previous_frame();
|
||||||
|
|
||||||
|
// relax the borrow checker
|
||||||
|
let cur_arena: &BumpFrame = unsafe { std::mem::transmute(cur_arena) };
|
||||||
|
let prev_arena: &BumpFrame = unsafe { std::mem::transmute(prev_arena) };
|
||||||
|
|
||||||
|
// Make sure the nodes arent null (they've been set properly)
|
||||||
|
assert_ne!(
|
||||||
|
cur_arena.node.get(),
|
||||||
|
std::ptr::null_mut(),
|
||||||
|
"Call rebuild before diffing"
|
||||||
|
);
|
||||||
|
assert_ne!(
|
||||||
|
prev_arena.node.get(),
|
||||||
|
std::ptr::null_mut(),
|
||||||
|
"Call rebuild before diffing"
|
||||||
|
);
|
||||||
|
|
||||||
|
self.scope_stack.push(scope);
|
||||||
|
let left = unsafe { prev_arena.load_node() };
|
||||||
|
let right = unsafe { cur_arena.load_node() };
|
||||||
|
self.diff_maybe_node(mutations, left, right);
|
||||||
|
self.scope_stack.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn diff_maybe_node(
|
||||||
|
&mut self,
|
||||||
|
m: &mut Mutations<'b>,
|
||||||
|
left: &'b RenderReturn<'b>,
|
||||||
|
right: &'b RenderReturn<'b>,
|
||||||
|
) {
|
||||||
|
use RenderReturn::{Async, Sync};
|
||||||
|
match (left, right) {
|
||||||
|
// diff
|
||||||
|
(Sync(Some(l)), Sync(Some(r))) => self.diff_node(m, l, r),
|
||||||
|
|
||||||
|
// remove old with placeholder
|
||||||
|
(Sync(Some(l)), Sync(None)) | (Sync(Some(l)), Async(_)) => {
|
||||||
|
//
|
||||||
|
let id = self.next_element(l); // todo!
|
||||||
|
m.push(Mutation::CreatePlaceholder { id });
|
||||||
|
self.drop_template(m, l, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove placeholder with nodes
|
||||||
|
(Sync(None), Sync(Some(_))) => {}
|
||||||
|
(Async(_), Sync(Some(v))) => {}
|
||||||
|
|
||||||
|
// nothing...
|
||||||
|
(Async(_), Async(_))
|
||||||
|
| (Sync(None), Sync(None))
|
||||||
|
| (Sync(None), Async(_))
|
||||||
|
| (Async(_), Sync(None)) => {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn diff_node(
|
pub fn diff_node(
|
||||||
|
|||||||
@@ -98,53 +98,9 @@ pub enum EventPriority {
|
|||||||
Low = 0,
|
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()
|
|
||||||
.position(|attr| attr.mounted_element.get() == event.element)?;
|
|
||||||
|
|
||||||
let mut index = Some((path.template, location));
|
|
||||||
|
|
||||||
let mut listeners = Vec::<&Attribute>::new();
|
|
||||||
|
|
||||||
while let Some((raw_parent, dyn_index)) = index {
|
|
||||||
let parent = unsafe { &mut *raw_parent };
|
|
||||||
let path = parent.template.node_paths[dyn_index];
|
|
||||||
|
|
||||||
listeners.extend(
|
|
||||||
parent
|
|
||||||
.dynamic_attrs
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.filter_map(|(idx, attr)| {
|
|
||||||
match is_path_ascendant(parent.template.node_paths[idx], path) {
|
|
||||||
true if attr.name == event.name => Some(attr),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
index = parent.parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
for listener in listeners {
|
|
||||||
if let AttributeValue::Listener(listener) = &listener.value {
|
|
||||||
(listener.borrow_mut())(&event.event)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ensures a strict descendant relationship
|
// ensures a strict descendant relationship
|
||||||
// returns false if the paths are equal
|
// returns false if the paths are equal
|
||||||
fn is_path_ascendant(small: &[u8], big: &[u8]) -> bool {
|
pub fn is_path_ascendant(small: &[u8], big: &[u8]) -> bool {
|
||||||
if small.len() >= big.len() {
|
if small.len() >= big.len() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use std::{cell::Cell, fmt::Arguments, pin::Pin};
|
use std::{cell::Cell, fmt::Arguments};
|
||||||
|
|
||||||
use bumpalo::boxed::Box as BumpBox;
|
use bumpalo::boxed::Box as BumpBox;
|
||||||
use bumpalo::Bump;
|
use bumpalo::Bump;
|
||||||
@@ -123,13 +123,6 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn takes_it() {
|
|
||||||
fn demo(cx: Scope) -> Element {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum RenderReturn<'a> {
|
pub enum RenderReturn<'a> {
|
||||||
Sync(Element<'a>),
|
Sync(Element<'a>),
|
||||||
Async(BumpBox<'a, dyn Future<Output = Element<'a>> + 'a>),
|
Async(BumpBox<'a, dyn Future<Output = Element<'a>> + 'a>),
|
||||||
@@ -144,8 +137,6 @@ impl<'a> RenderReturn<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type FiberLeaf<'a> = Pin<BumpBox<'a, dyn Future<Output = Element<'a>> + 'a>>;
|
|
||||||
|
|
||||||
pub trait IntoVnode<'a, A = ()> {
|
pub trait IntoVnode<'a, A = ()> {
|
||||||
fn into_dynamic_node(self, cx: &'a ScopeState) -> VNode<'a>;
|
fn into_dynamic_node(self, cx: &'a ScopeState) -> VNode<'a>;
|
||||||
}
|
}
|
||||||
@@ -162,6 +153,12 @@ impl<'a, 'b> IntoVnode<'a> for VNode<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b> IntoVnode<'a> for Option<VNode<'a>> {
|
||||||
|
fn into_dynamic_node(self, _cx: &'a ScopeState) -> VNode<'a> {
|
||||||
|
self.expect("please allow optional nodes in")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a, 'b> IntoVnode<'a> for &'a VNode<'a> {
|
impl<'a, 'b> IntoVnode<'a> for &'a VNode<'a> {
|
||||||
fn into_dynamic_node(self, _cx: &'a ScopeState) -> VNode<'a> {
|
fn into_dynamic_node(self, _cx: &'a ScopeState) -> VNode<'a> {
|
||||||
VNode {
|
VNode {
|
||||||
|
|||||||
@@ -1,16 +1,22 @@
|
|||||||
use crate::{nodes::VNode, scopes::ScopeId, virtual_dom::VirtualDom, DynamicNode};
|
use crate::{nodes::VNode, scopes::ScopeId, virtual_dom::VirtualDom, DynamicNode, Mutations};
|
||||||
|
|
||||||
impl VirtualDom {
|
impl<'b> VirtualDom {
|
||||||
pub fn drop_scope(&mut self, id: ScopeId) {
|
pub fn drop_scope(&mut self, id: ScopeId) {
|
||||||
let scope = self.scopes.get(id.0).unwrap();
|
// let scope = self.scopes.get(id.0).unwrap();
|
||||||
|
|
||||||
let root = scope.root_node();
|
// let root = scope.root_node();
|
||||||
let root = unsafe { std::mem::transmute(root) };
|
// let root = unsafe { std::mem::transmute(root) };
|
||||||
|
|
||||||
self.drop_template(root);
|
// self.drop_template(root, false);
|
||||||
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn drop_template<'a>(&'a mut self, template: &'a VNode<'a>) {
|
pub fn drop_template(
|
||||||
|
&mut self,
|
||||||
|
mutations: &mut Mutations,
|
||||||
|
template: &'b VNode<'b>,
|
||||||
|
gen_roots: bool,
|
||||||
|
) {
|
||||||
for node in template.dynamic_nodes.iter() {
|
for node in template.dynamic_nodes.iter() {
|
||||||
match node {
|
match node {
|
||||||
DynamicNode::Text { id, .. } => {}
|
DynamicNode::Text { id, .. } => {}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
mod any_props;
|
mod any_props;
|
||||||
mod arena;
|
mod arena;
|
||||||
mod bump_frame;
|
mod bump_frame;
|
||||||
mod component;
|
|
||||||
mod create;
|
mod create;
|
||||||
mod diff;
|
mod diff;
|
||||||
mod events;
|
mod events;
|
||||||
@@ -77,7 +76,10 @@ pub use crate::innerlude::{
|
|||||||
ElementId,
|
ElementId,
|
||||||
ElementPath,
|
ElementPath,
|
||||||
EventPriority,
|
EventPriority,
|
||||||
|
Fragment,
|
||||||
LazyNodes,
|
LazyNodes,
|
||||||
|
Mutation,
|
||||||
|
Mutations,
|
||||||
NodeFactory,
|
NodeFactory,
|
||||||
Properties,
|
Properties,
|
||||||
Scope,
|
Scope,
|
||||||
@@ -100,9 +102,9 @@ pub use crate::innerlude::{
|
|||||||
/// This includes types like [`Scope`], [`Element`], and [`Component`].
|
/// This includes types like [`Scope`], [`Element`], and [`Component`].
|
||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
pub use crate::innerlude::{
|
pub use crate::innerlude::{
|
||||||
fc_to_builder, Attribute, DynamicNode, Element, EventPriority, LazyNodes, NodeFactory,
|
fc_to_builder, Element, EventPriority, Fragment, LazyNodes, NodeFactory, Properties, Scope,
|
||||||
Properties, Scope, ScopeId, ScopeState, Scoped, SuspenseBoundary, SuspenseContext, TaskId,
|
ScopeId, ScopeState, Scoped, TaskId, Template, TemplateAttribute, TemplateNode, UiEvent,
|
||||||
Template, TemplateAttribute, TemplateNode, UiEvent, VNode, VirtualDom,
|
VNode, VirtualDom,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,24 +137,3 @@ macro_rules! to_owned {
|
|||||||
let mut $es = $es.to_owned();
|
let mut $es = $es.to_owned();
|
||||||
)*}
|
)*}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A helper macro for values into callbacks for async environements.
|
|
||||||
///
|
|
||||||
///
|
|
||||||
macro_rules! callback {
|
|
||||||
() => {};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Convert a hook into a hook with an implicit dependency list by analyzing the closure.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// // Convert hooks with annoying dependencies into...
|
|
||||||
///
|
|
||||||
/// let val = use_effect(cx, (val,) |(val,)| println!("thing {val}"))
|
|
||||||
///
|
|
||||||
/// // a simple closure
|
|
||||||
/// let val = use_effect!(cx, |val| async { println!("thing {val}")) });
|
|
||||||
/// ```
|
|
||||||
macro_rules! make_dep_fn {
|
|
||||||
() => {};
|
|
||||||
}
|
|
||||||
@@ -4,14 +4,14 @@ use crate::arena::ElementId;
|
|||||||
pub struct Mutations<'a> {
|
pub struct Mutations<'a> {
|
||||||
pub subtree: usize,
|
pub subtree: usize,
|
||||||
pub template_mutations: Vec<Mutation<'a>>,
|
pub template_mutations: Vec<Mutation<'a>>,
|
||||||
pub mutations: Vec<Mutation<'a>>,
|
pub edits: Vec<Mutation<'a>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Mutations<'a> {
|
impl<'a> Mutations<'a> {
|
||||||
pub fn new(subtree: usize) -> Self {
|
pub fn new(subtree: usize) -> Self {
|
||||||
Self {
|
Self {
|
||||||
subtree,
|
subtree,
|
||||||
mutations: Vec::new(),
|
edits: Vec::new(),
|
||||||
template_mutations: Vec::new(),
|
template_mutations: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -21,13 +21,13 @@ impl<'a> std::ops::Deref for Mutations<'a> {
|
|||||||
type Target = Vec<Mutation<'a>>;
|
type Target = Vec<Mutation<'a>>;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
&self.mutations
|
&self.edits
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::ops::DerefMut for Mutations<'_> {
|
impl std::ops::DerefMut for Mutations<'_> {
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
&mut self.mutations
|
&mut self.edits
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,7 +35,7 @@ impl std::ops::DerefMut for Mutations<'_> {
|
|||||||
each subtree has its own numbering scheme
|
each subtree has its own numbering scheme
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub enum Mutation<'a> {
|
pub enum Mutation<'a> {
|
||||||
SetAttribute {
|
SetAttribute {
|
||||||
name: &'a str,
|
name: &'a str,
|
||||||
|
|||||||
@@ -64,36 +64,37 @@ impl<'a> Properties for FragmentProps<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// /// Create inline fragments using Component syntax.
|
/// Create inline fragments using Component syntax.
|
||||||
// ///
|
///
|
||||||
// /// ## Details
|
/// ## Details
|
||||||
// ///
|
///
|
||||||
// /// Fragments capture a series of children without rendering extra nodes.
|
/// Fragments capture a series of children without rendering extra nodes.
|
||||||
// ///
|
///
|
||||||
// /// Creating fragments explicitly with the Fragment component is particularly useful when rendering lists or tables and
|
/// Creating fragments explicitly with the Fragment component is particularly useful when rendering lists or tables and
|
||||||
// /// a key is needed to identify each item.
|
/// a key is needed to identify each item.
|
||||||
// ///
|
///
|
||||||
// /// ## Example
|
/// ## Example
|
||||||
// ///
|
///
|
||||||
// /// ```rust, ignore
|
/// ```rust, ignore
|
||||||
// /// rsx!{
|
/// rsx!{
|
||||||
// /// Fragment { key: "abc" }
|
/// Fragment { key: "abc" }
|
||||||
// /// }
|
/// }
|
||||||
// /// ```
|
/// ```
|
||||||
// ///
|
///
|
||||||
// /// ## Usage
|
/// ## Usage
|
||||||
// ///
|
///
|
||||||
// /// Fragments are incredibly useful when necessary, but *do* add cost in the diffing phase.
|
/// Fragments are incredibly useful when necessary, but *do* add cost in the diffing phase.
|
||||||
// /// Try to avoid highly nested fragments if you can. Unlike React, there is no protection against infinitely nested fragments.
|
/// Try to avoid highly nested fragments if you can. Unlike React, there is no protection against infinitely nested fragments.
|
||||||
// ///
|
///
|
||||||
// /// This function defines a dedicated `Fragment` component that can be used to create inline fragments in the RSX macro.
|
/// This function defines a dedicated `Fragment` component that can be used to create inline fragments in the RSX macro.
|
||||||
// ///
|
///
|
||||||
// /// You want to use this free-function when your fragment needs a key and simply returning multiple nodes from rsx! won't cut it.
|
/// You want to use this free-function when your fragment needs a key and simply returning multiple nodes from rsx! won't cut it.
|
||||||
// #[allow(non_upper_case_globals, non_snake_case)]
|
#[allow(non_upper_case_globals, non_snake_case)]
|
||||||
// pub fn Fragment<'a>(cx: Scope<'a, FragmentProps<'a>>) -> Element {
|
pub fn Fragment<'a>(cx: Scope<'a, FragmentProps<'a>>) -> Element {
|
||||||
// let i = cx.props.0.as_ref().map(|f| f.decouple());
|
// let i = cx.props.0.as_ref().map(|f| f.decouple());
|
||||||
// cx.render(LazyNodes::new(|f| f.fragment_from_iter(i)))
|
// cx.render(LazyNodes::new(|f| f.fragment_from_iter(i)))
|
||||||
// }
|
todo!("Fragment")
|
||||||
|
}
|
||||||
|
|
||||||
/// Every "Props" used for a component must implement the `Properties` trait. This trait gives some hints to Dioxus
|
/// Every "Props" used for a component must implement the `Properties` trait. This trait gives some hints to Dioxus
|
||||||
/// on how to memoize the props and some additional optimizations that can be made. We strongly encourage using the
|
/// on how to memoize the props and some additional optimizations that can be made. We strongly encourage using the
|
||||||
|
|||||||
@@ -14,16 +14,10 @@ pub use waker::RcWake;
|
|||||||
///
|
///
|
||||||
/// These messages control how the scheduler will process updates to the UI.
|
/// These messages control how the scheduler will process updates to the UI.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum SchedulerMsg {
|
pub(crate) enum SchedulerMsg {
|
||||||
/// Events from the Renderer
|
|
||||||
Event,
|
|
||||||
|
|
||||||
/// Immediate updates from Components that mark them as dirty
|
/// Immediate updates from Components that mark them as dirty
|
||||||
Immediate(ScopeId),
|
Immediate(ScopeId),
|
||||||
|
|
||||||
/// Mark all components as dirty and update them
|
|
||||||
DirtyAll,
|
|
||||||
|
|
||||||
/// A task has woken and needs to be progressed
|
/// A task has woken and needs to be progressed
|
||||||
TaskNotified(TaskId),
|
TaskNotified(TaskId),
|
||||||
|
|
||||||
|
|||||||
@@ -29,12 +29,11 @@ impl SuspenseBoundary {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SuspenseLeaf {
|
pub(crate) struct SuspenseLeaf {
|
||||||
pub id: SuspenseId,
|
pub id: SuspenseId,
|
||||||
pub scope_id: ScopeId,
|
pub scope_id: ScopeId,
|
||||||
pub tx: futures_channel::mpsc::UnboundedSender<SchedulerMsg>,
|
pub tx: futures_channel::mpsc::UnboundedSender<SchedulerMsg>,
|
||||||
pub notified: Cell<bool>,
|
pub notified: Cell<bool>,
|
||||||
|
|
||||||
pub task: *mut dyn Future<Output = Element<'static>>,
|
pub task: *mut dyn Future<Output = Element<'static>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -57,11 +57,6 @@ impl Scheduler {
|
|||||||
pub fn remove(&self, id: TaskId) {
|
pub fn remove(&self, id: TaskId) {
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
|
|
||||||
// Aborts the future
|
|
||||||
pub fn abort(&self, id: TaskId) {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RcWake for LocalTask {
|
impl RcWake for LocalTask {
|
||||||
|
|||||||
@@ -1,110 +1,92 @@
|
|||||||
use futures_util::{FutureExt, StreamExt};
|
use futures_util::FutureExt;
|
||||||
use std::task::{Context, Poll};
|
use std::task::{Context, Poll};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
diff::DirtyScope,
|
|
||||||
factory::RenderReturn,
|
factory::RenderReturn,
|
||||||
innerlude::{Mutation, Mutations, SuspenseContext},
|
innerlude::{Mutation, Mutations, SuspenseContext},
|
||||||
VNode, VirtualDom,
|
ScopeId, TaskId, VNode, VirtualDom,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{waker::RcWake, SchedulerMsg, SuspenseLeaf};
|
use super::{waker::RcWake, SuspenseId, SuspenseLeaf};
|
||||||
|
|
||||||
impl VirtualDom {
|
impl VirtualDom {
|
||||||
/// Wait for futures internal to the virtualdom
|
/// Handle notifications by tasks inside the scheduler
|
||||||
///
|
///
|
||||||
/// This is cancel safe, so if the future is dropped, you can push events into the virtualdom
|
/// This is precise, meaning we won't poll every task, just tasks that have woken up as notified to use by the
|
||||||
pub async fn wait_for_work(&mut self) {
|
/// queue
|
||||||
// todo: make sure the scheduler queue is completely drained
|
pub fn handle_task_wakeup(&mut self, id: TaskId) {
|
||||||
loop {
|
let mut tasks = self.scheduler.tasks.borrow_mut();
|
||||||
match self.rx.next().await.unwrap() {
|
let task = &tasks[id.0];
|
||||||
SchedulerMsg::Event => break,
|
|
||||||
|
|
||||||
SchedulerMsg::Immediate(id) => {
|
// If the task completes...
|
||||||
let height = self.scopes[id.0].height;
|
if task.progress() {
|
||||||
self.dirty_scopes.insert(DirtyScope { height, id });
|
// Remove it from the scope so we dont try to double drop it when the scope dropes
|
||||||
break;
|
self.scopes[task.scope.0].spawned_tasks.remove(&id);
|
||||||
}
|
|
||||||
|
|
||||||
SchedulerMsg::DirtyAll => todo!(),
|
// Remove it from the scheduler
|
||||||
|
tasks.remove(id.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SchedulerMsg::TaskNotified(id) => {
|
pub fn handle_suspense_wakeup(&mut self, id: SuspenseId) {
|
||||||
let mut tasks = self.scheduler.tasks.borrow_mut();
|
println!("suspense notified");
|
||||||
let task = &tasks[id.0];
|
|
||||||
|
|
||||||
// If the task completes...
|
let leaf = self
|
||||||
if task.progress() {
|
.scheduler
|
||||||
// Remove it from the scope so we dont try to double drop it when the scope dropes
|
.leaves
|
||||||
self.scopes[task.scope.0].spawned_tasks.remove(&id);
|
.borrow_mut()
|
||||||
|
.get(id.0)
|
||||||
|
.unwrap()
|
||||||
|
.clone();
|
||||||
|
|
||||||
// Remove it from the scheduler
|
let scope_id = leaf.scope_id;
|
||||||
tasks.remove(id.0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SchedulerMsg::SuspenseNotified(id) => {
|
// todo: cache the waker
|
||||||
println!("suspense notified");
|
let waker = leaf.waker();
|
||||||
|
let mut cx = Context::from_waker(&waker);
|
||||||
|
|
||||||
let leaf = self
|
// Safety: the future is always pinned to the bump arena
|
||||||
.scheduler
|
let mut pinned = unsafe { std::pin::Pin::new_unchecked(&mut *leaf.task) };
|
||||||
.leaves
|
let as_pinned_mut = &mut pinned;
|
||||||
.borrow_mut()
|
|
||||||
.get(id.0)
|
|
||||||
.unwrap()
|
|
||||||
.clone();
|
|
||||||
|
|
||||||
let scope_id = leaf.scope_id;
|
// the component finished rendering and gave us nodes
|
||||||
|
// we should attach them to that component and then render its children
|
||||||
|
// continue rendering the tree until we hit yet another suspended component
|
||||||
|
if let Poll::Ready(new_nodes) = as_pinned_mut.poll_unpin(&mut cx) {
|
||||||
|
let boundary = &self.scopes[leaf.scope_id.0]
|
||||||
|
.consume_context::<SuspenseContext>()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
// todo: cache the waker
|
println!("ready pool");
|
||||||
let waker = leaf.waker();
|
|
||||||
let mut cx = Context::from_waker(&waker);
|
|
||||||
|
|
||||||
// Safety: the future is always pinned to the bump arena
|
let mut fiber = boundary.borrow_mut();
|
||||||
let mut pinned = unsafe { std::pin::Pin::new_unchecked(&mut *leaf.task) };
|
|
||||||
let as_pinned_mut = &mut pinned;
|
|
||||||
|
|
||||||
// the component finished rendering and gave us nodes
|
println!(
|
||||||
// we should attach them to that component and then render its children
|
"Existing mutations {:?}, scope {:?}",
|
||||||
// continue rendering the tree until we hit yet another suspended component
|
fiber.mutations, fiber.id
|
||||||
if let Poll::Ready(new_nodes) = as_pinned_mut.poll_unpin(&mut cx) {
|
);
|
||||||
let boundary = &self.scopes[leaf.scope_id.0]
|
|
||||||
.consume_context::<SuspenseContext>()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
println!("ready pool");
|
let scope = &mut self.scopes[scope_id.0];
|
||||||
|
let arena = scope.current_frame();
|
||||||
|
|
||||||
let mut fiber = boundary.borrow_mut();
|
let ret = arena.bump.alloc(RenderReturn::Sync(new_nodes));
|
||||||
|
arena.node.set(ret);
|
||||||
|
|
||||||
println!(
|
if let RenderReturn::Sync(Some(template)) = ret {
|
||||||
"Existing mutations {:?}, scope {:?}",
|
let mutations = &mut fiber.mutations;
|
||||||
fiber.mutations, fiber.id
|
let template: &VNode = unsafe { std::mem::transmute(template) };
|
||||||
);
|
let mutations: &mut Mutations = unsafe { std::mem::transmute(mutations) };
|
||||||
|
|
||||||
let scope = &mut self.scopes[scope_id.0];
|
self.scope_stack.push(scope_id);
|
||||||
let arena = scope.current_arena();
|
self.create(mutations, template);
|
||||||
|
self.scope_stack.pop();
|
||||||
|
|
||||||
let ret = arena.bump.alloc(RenderReturn::Sync(new_nodes));
|
println!("{:#?}", mutations);
|
||||||
arena.node.set(ret);
|
} else {
|
||||||
|
println!("nodes arent right");
|
||||||
if let RenderReturn::Sync(Some(template)) = ret {
|
|
||||||
let mutations = &mut fiber.mutations;
|
|
||||||
let template: &VNode = unsafe { std::mem::transmute(template) };
|
|
||||||
let mutations: &mut Mutations =
|
|
||||||
unsafe { std::mem::transmute(mutations) };
|
|
||||||
|
|
||||||
self.scope_stack.push(scope_id);
|
|
||||||
self.create(mutations, template);
|
|
||||||
self.scope_stack.pop();
|
|
||||||
|
|
||||||
println!("{:#?}", mutations);
|
|
||||||
} else {
|
|
||||||
println!("nodes arent right");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
println!("not ready");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
println!("not ready");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7,7 +7,6 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use bumpalo::Bump;
|
use bumpalo::Bump;
|
||||||
use futures_channel::mpsc::UnboundedSender;
|
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -69,20 +68,27 @@ pub struct ScopeState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ScopeState {
|
impl ScopeState {
|
||||||
pub fn current_arena(&self) -> &BumpFrame {
|
pub fn current_frame(&self) -> &BumpFrame {
|
||||||
match self.render_cnt % 2 {
|
match self.render_cnt % 2 {
|
||||||
0 => &self.node_arena_1,
|
0 => &self.node_arena_1,
|
||||||
1 => &self.node_arena_2,
|
1 => &self.node_arena_2,
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn previous_frame(&self) -> &BumpFrame {
|
||||||
|
match self.render_cnt % 2 {
|
||||||
|
1 => &self.node_arena_1,
|
||||||
|
0 => &self.node_arena_2,
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn bump(&self) -> &Bump {
|
pub fn bump(&self) -> &Bump {
|
||||||
&self.current_arena().bump
|
&self.current_frame().bump
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn root_node<'a>(&'a self) -> &'a VNode<'a> {
|
pub fn root_node<'a>(&'a self) -> &'a VNode<'a> {
|
||||||
let r = unsafe { &*self.current_arena().node.get() };
|
let r = unsafe { &*self.current_frame().node.get() };
|
||||||
unsafe { std::mem::transmute(r) }
|
unsafe { std::mem::transmute(r) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,11 +148,6 @@ impl ScopeState {
|
|||||||
self.id
|
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
|
/// Create a subscription that schedules a future render for the reference component
|
||||||
///
|
///
|
||||||
/// ## Notice: you should prefer using [`schedule_update_any`] and [`scope_id`]
|
/// ## Notice: you should prefer using [`schedule_update_any`] and [`scope_id`]
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
use crate::any_props::VComponentProps;
|
|
||||||
use crate::arena::ElementPath;
|
|
||||||
use crate::diff::DirtyScope;
|
|
||||||
use crate::factory::RenderReturn;
|
|
||||||
use crate::innerlude::{Mutations, Scheduler, SchedulerMsg};
|
|
||||||
use crate::mutations::Mutation;
|
|
||||||
use crate::nodes::{Template, TemplateId};
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
any_props::VComponentProps,
|
||||||
arena::ElementId,
|
arena::ElementId,
|
||||||
|
arena::ElementPath,
|
||||||
|
diff::DirtyScope,
|
||||||
|
factory::RenderReturn,
|
||||||
|
innerlude::{is_path_ascendant, Mutations, Scheduler, SchedulerMsg},
|
||||||
|
mutations::Mutation,
|
||||||
|
nodes::{Template, TemplateId},
|
||||||
|
scheduler::{SuspenseBoundary, SuspenseId},
|
||||||
scopes::{ScopeId, ScopeState},
|
scopes::{ScopeId, ScopeState},
|
||||||
|
Attribute, AttributeValue, Element, Scope, SuspenseContext, UiEvent,
|
||||||
};
|
};
|
||||||
use crate::{scheduler, Element, Scope};
|
use futures_util::{pin_mut, FutureExt, StreamExt};
|
||||||
use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender};
|
|
||||||
use scheduler::{SuspenseBoundary, SuspenseId};
|
|
||||||
use slab::Slab;
|
use slab::Slab;
|
||||||
use std::collections::{BTreeSet, HashMap};
|
use std::collections::{BTreeSet, HashMap};
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
@@ -56,56 +56,88 @@ use std::rc::Rc;
|
|||||||
/// let edits = vdom.rebuild();
|
/// let edits = vdom.rebuild();
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// To inject UserEvents into the VirtualDom, call [`VirtualDom::get_scheduler_channel`] to get access to the scheduler.
|
/// To call listeners inside the VirtualDom, call [`VirtualDom::handle_event`] with the appropriate event data.
|
||||||
///
|
///
|
||||||
/// ```rust, ignore
|
/// ```rust, ignore
|
||||||
/// let channel = vdom.get_scheduler_channel();
|
/// vdom.handle_event(event);
|
||||||
/// channel.send_unbounded(SchedulerMsg::UserEvent(UserEvent {
|
|
||||||
/// // ...
|
|
||||||
/// }))
|
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// While waiting for UserEvents to occur, call [`VirtualDom::wait_for_work`] to poll any futures inside the VirtualDom.
|
/// While no events are ready, call [`VirtualDom::wait_for_work`] to poll any futures inside the VirtualDom.
|
||||||
///
|
///
|
||||||
/// ```rust, ignore
|
/// ```rust, ignore
|
||||||
/// vdom.wait_for_work().await;
|
/// vdom.wait_for_work().await;
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// Once work is ready, call [`VirtualDom::work_with_deadline`] to compute the differences between the previous and
|
/// Once work is ready, call [`VirtualDom::render_with_deadline`] to compute the differences between the previous and
|
||||||
/// current UI trees. This will return a [`Mutations`] object that contains Edits, Effects, and NodeRefs that need to be
|
/// current UI trees. This will return a [`Mutations`] object that contains Edits, Effects, and NodeRefs that need to be
|
||||||
/// handled by the renderer.
|
/// handled by the renderer.
|
||||||
///
|
///
|
||||||
/// ```rust, ignore
|
/// ```rust, ignore
|
||||||
/// let mutations = vdom.work_with_deadline(|| false);
|
/// let mutations = vdom.work_with_deadline(tokio::time::sleep(Duration::from_millis(100)));
|
||||||
/// for edit in mutations {
|
///
|
||||||
/// apply(edit);
|
/// for edit in mutations.edits {
|
||||||
|
/// real_dom.apply(edit);
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
|
/// To not wait for suspense while diffing the VirtualDom, call [`VirtualDom::render_immediate`] or pass an immediately
|
||||||
|
/// ready future to [`VirtualDom::render_with_deadline`].
|
||||||
|
///
|
||||||
|
///
|
||||||
/// ## Building an event loop around Dioxus:
|
/// ## Building an event loop around Dioxus:
|
||||||
///
|
///
|
||||||
/// Putting everything together, you can build an event loop around Dioxus by using the methods outlined above.
|
/// Putting everything together, you can build an event loop around Dioxus by using the methods outlined above.
|
||||||
///
|
/// ```rust
|
||||||
/// ```rust, ignore
|
/// fn app(cx: Scope) -> Element {
|
||||||
/// fn App(cx: Scope) -> Element {
|
|
||||||
/// cx.render(rsx!{
|
/// cx.render(rsx!{
|
||||||
/// div { "Hello World" }
|
/// div { "Hello World" }
|
||||||
/// })
|
/// })
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// async fn main() {
|
/// let dom = VirtualDom::new(app);
|
||||||
/// let mut dom = VirtualDom::new(App);
|
|
||||||
///
|
///
|
||||||
/// let mut inital_edits = dom.rebuild();
|
/// real_dom.apply(dom.rebuild());
|
||||||
/// apply_edits(inital_edits);
|
|
||||||
///
|
///
|
||||||
/// loop {
|
/// loop {
|
||||||
/// dom.wait_for_work().await;
|
/// select! {
|
||||||
/// let frame_timeout = TimeoutFuture::new(Duration::from_millis(16));
|
/// _ = dom.wait_for_work() => {}
|
||||||
/// let deadline = || (&mut frame_timeout).now_or_never();
|
/// evt = real_dom.wait_for_event() => dom.handle_event(evt),
|
||||||
/// let edits = dom.run_with_deadline(deadline).await;
|
|
||||||
/// apply_edits(edits);
|
|
||||||
/// }
|
/// }
|
||||||
|
///
|
||||||
|
/// real_dom.apply(dom.render_immediate());
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ## Waiting for suspense
|
||||||
|
///
|
||||||
|
/// Because Dioxus supports suspense, you can use it for server-side rendering, static site generation, and other usecases
|
||||||
|
/// where waiting on portions of the UI to finish rendering is important. To wait for suspense, use the
|
||||||
|
/// [`VirtualDom::render_with_deadline`] method:
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// let dom = VirtualDom::new(app);
|
||||||
|
///
|
||||||
|
/// let deadline = tokio::time::sleep(Duration::from_millis(100));
|
||||||
|
/// let edits = dom.render_with_deadline(deadline).await;
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ## Use with streaming
|
||||||
|
///
|
||||||
|
/// If not all rendering is done by the deadline, it might be worthwhile to stream the rest later. To do this, we
|
||||||
|
/// suggest rendering with a deadline, and then looping between [`VirtualDom::wait_for_work`] and render_immediate until
|
||||||
|
/// no suspended work is left.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let dom = VirtualDom::new(app);
|
||||||
|
///
|
||||||
|
/// let deadline = tokio::time::sleep(Duration::from_millis(20));
|
||||||
|
/// let edits = dom.render_with_deadline(deadline).await;
|
||||||
|
///
|
||||||
|
/// real_dom.apply(edits);
|
||||||
|
///
|
||||||
|
/// while dom.has_suspended_work() {
|
||||||
|
/// dom.wait_for_work().await;
|
||||||
|
/// real_dom.apply(dom.render_immediate());
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
pub struct VirtualDom {
|
pub struct VirtualDom {
|
||||||
@@ -186,27 +218,7 @@ impl VirtualDom {
|
|||||||
where
|
where
|
||||||
P: 'static,
|
P: 'static,
|
||||||
{
|
{
|
||||||
let channel = futures_channel::mpsc::unbounded();
|
let (tx, rx) = futures_channel::mpsc::unbounded();
|
||||||
Self::new_with_props_and_scheduler(root, root_props, channel)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Launch the VirtualDom, but provide your own channel for receiving and sending messages into the scheduler
|
|
||||||
///
|
|
||||||
/// This is useful when the VirtualDom must be driven from outside a thread and it doesn't make sense to wait for the
|
|
||||||
/// VirtualDom to be created just to retrieve its channel receiver.
|
|
||||||
///
|
|
||||||
/// ```rust, ignore
|
|
||||||
/// let channel = futures_channel::mpsc::unbounded();
|
|
||||||
/// let dom = VirtualDom::new_with_scheduler(Example, (), channel);
|
|
||||||
/// ```
|
|
||||||
pub fn new_with_props_and_scheduler<P: 'static>(
|
|
||||||
root: fn(Scope<P>) -> Element,
|
|
||||||
root_props: P,
|
|
||||||
(tx, rx): (
|
|
||||||
UnboundedSender<SchedulerMsg>,
|
|
||||||
UnboundedReceiver<SchedulerMsg>,
|
|
||||||
),
|
|
||||||
) -> Self {
|
|
||||||
let mut dom = Self {
|
let mut dom = Self {
|
||||||
rx,
|
rx,
|
||||||
scheduler: Scheduler::new(tx),
|
scheduler: Scheduler::new(tx),
|
||||||
@@ -220,18 +232,138 @@ impl VirtualDom {
|
|||||||
finished_fibers: Vec::new(),
|
finished_fibers: Vec::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
dom.new_scope(Box::into_raw(Box::new(VComponentProps::new(
|
let root = dom.new_scope(Box::into_raw(Box::new(VComponentProps::new(
|
||||||
root,
|
root,
|
||||||
|_, _| unreachable!(),
|
|_, _| unreachable!(),
|
||||||
root_props,
|
root_props,
|
||||||
))))
|
))));
|
||||||
|
|
||||||
// The root component is always a suspense boundary for any async children
|
// The root component is always a suspense boundary for any async children
|
||||||
// This could be unexpected, so we might rethink this behavior
|
// This could be unexpected, so we might rethink this behavior
|
||||||
.provide_context(SuspenseBoundary::new(ScopeId(0)));
|
root.provide_context(SuspenseBoundary::new(ScopeId(0)));
|
||||||
|
|
||||||
|
// the root element is always given element 0
|
||||||
|
dom.elements.insert(ElementPath::null());
|
||||||
|
|
||||||
dom
|
dom
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_scope(&self, id: ScopeId) -> Option<&ScopeState> {
|
||||||
|
self.scopes.get(id.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn base_scope(&self) -> &ScopeState {
|
||||||
|
self.scopes.get(0).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mark_dirty_scope(&mut self, id: ScopeId) {
|
||||||
|
let height = self.scopes[id.0].height;
|
||||||
|
self.dirty_scopes.insert(DirtyScope { height, id });
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_scope_suspended(&self, id: ScopeId) -> bool {
|
||||||
|
!self.scopes[id.0]
|
||||||
|
.consume_context::<SuspenseContext>()
|
||||||
|
.unwrap()
|
||||||
|
.borrow()
|
||||||
|
.waiting_on
|
||||||
|
.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if there is any suspended work left to be done.
|
||||||
|
pub fn has_suspended_work(&self) -> bool {
|
||||||
|
!self.scheduler.leaves.borrow().is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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()
|
||||||
|
.position(|attr| attr.mounted_element.get() == event.element)?;
|
||||||
|
|
||||||
|
let mut index = Some((path.template, location));
|
||||||
|
|
||||||
|
let mut listeners = Vec::<&Attribute>::new();
|
||||||
|
|
||||||
|
while let Some((raw_parent, dyn_index)) = index {
|
||||||
|
let parent = unsafe { &mut *raw_parent };
|
||||||
|
let path = parent.template.node_paths[dyn_index];
|
||||||
|
|
||||||
|
listeners.extend(
|
||||||
|
parent
|
||||||
|
.dynamic_attrs
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter_map(|(idx, attr)| {
|
||||||
|
match is_path_ascendant(parent.template.node_paths[idx], path) {
|
||||||
|
true if attr.name == event.name => Some(attr),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
index = parent.parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
for listener in listeners {
|
||||||
|
if let AttributeValue::Listener(listener) = &listener.value {
|
||||||
|
(listener.borrow_mut())(&event.event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wait for the scheduler to have any work.
|
||||||
|
///
|
||||||
|
/// This method polls the internal future queue, waiting for suspense nodes, tasks, or other work. This completes when
|
||||||
|
/// any work is ready. If multiple scopes are marked dirty from a task or a suspense tree is finished, this method
|
||||||
|
/// will exit.
|
||||||
|
///
|
||||||
|
/// This method is cancel-safe, so you're fine to discard the future in a select block.
|
||||||
|
///
|
||||||
|
/// This lets us poll async tasks during idle periods without blocking the main thread.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust, ignore
|
||||||
|
/// let dom = VirtualDom::new(App);
|
||||||
|
/// let sender = dom.get_scheduler_channel();
|
||||||
|
/// ```
|
||||||
|
pub async fn wait_for_work(&mut self) {
|
||||||
|
let mut some_msg = None;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
match some_msg.take() {
|
||||||
|
// If a bunch of messages are ready in a sequence, try to pop them off synchronously
|
||||||
|
Some(msg) => match msg {
|
||||||
|
SchedulerMsg::Immediate(id) => self.mark_dirty_scope(id),
|
||||||
|
SchedulerMsg::TaskNotified(task) => self.handle_task_wakeup(task),
|
||||||
|
SchedulerMsg::SuspenseNotified(id) => self.handle_suspense_wakeup(id),
|
||||||
|
},
|
||||||
|
|
||||||
|
// If they're not ready, then we should wait for them to be ready
|
||||||
|
None => {
|
||||||
|
match self.rx.try_next() {
|
||||||
|
Ok(Some(val)) => some_msg = Some(val),
|
||||||
|
Ok(None) => return,
|
||||||
|
Err(_) => {
|
||||||
|
// If we have any dirty scopes, or finished fiber trees then we should exit
|
||||||
|
if !self.dirty_scopes.is_empty() || !self.finished_fibers.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
some_msg = self.rx.next().await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom from scratch.
|
/// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom from scratch.
|
||||||
///
|
///
|
||||||
/// The diff machine expects the RealDom's stack to be the root of the application.
|
/// The diff machine expects the RealDom's stack to be the root of the application.
|
||||||
@@ -268,22 +400,71 @@ impl VirtualDom {
|
|||||||
mutations
|
mutations
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Render whatever the VirtualDom has ready as fast as possible without requiring an executor to progress
|
||||||
|
/// suspended subtrees.
|
||||||
|
pub fn render_immediate(&mut self) -> Mutations {
|
||||||
|
// Build a waker that won't wake up since our deadline is already expired when it's polled
|
||||||
|
let waker = futures_util::task::noop_waker();
|
||||||
|
let mut cx = std::task::Context::from_waker(&waker);
|
||||||
|
|
||||||
|
// Now run render with deadline but dont even try to poll any async tasks
|
||||||
|
let fut = self.render_with_deadline(std::future::ready(()));
|
||||||
|
pin_mut!(fut);
|
||||||
|
match fut.poll_unpin(&mut cx) {
|
||||||
|
std::task::Poll::Ready(mutations) => mutations,
|
||||||
|
std::task::Poll::Pending => panic!("render_immediate should never return pending"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Render what you can given the timeline and then move on
|
/// Render what you can given the timeline and then move on
|
||||||
///
|
///
|
||||||
/// It's generally a good idea to put some sort of limit on the suspense process in case a future is having issues.
|
/// It's generally a good idea to put some sort of limit on the suspense process in case a future is having issues.
|
||||||
pub async fn render_with_deadline(
|
///
|
||||||
&mut self,
|
/// If no suspense trees are present
|
||||||
|
pub async fn render_with_deadline<'a>(
|
||||||
|
&'a mut self,
|
||||||
deadline: impl Future<Output = ()>,
|
deadline: impl Future<Output = ()>,
|
||||||
) -> Vec<Mutation> {
|
) -> Mutations<'a> {
|
||||||
todo!()
|
use futures_util::future::{select, Either};
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_scope(&self, id: ScopeId) -> Option<&ScopeState> {
|
let mut mutations = Mutations::new(0);
|
||||||
self.scopes.get(id.0)
|
pin_mut!(deadline);
|
||||||
}
|
|
||||||
|
|
||||||
pub fn base_scope(&self) -> &ScopeState {
|
loop {
|
||||||
self.scopes.get(0).unwrap()
|
// first, unload any complete suspense trees
|
||||||
|
for finished_fiber in self.finished_fibers.drain(..) {
|
||||||
|
let scope = &mut self.scopes[finished_fiber.0];
|
||||||
|
let context = scope.has_context::<SuspenseContext>().unwrap();
|
||||||
|
let mut context = context.borrow_mut();
|
||||||
|
mutations.extend(context.mutations.drain(..));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next, diff any dirty scopes
|
||||||
|
// We choose not to poll the deadline since we complete pretty quickly anyways
|
||||||
|
if let Some(dirty) = self.dirty_scopes.iter().next().cloned() {
|
||||||
|
self.dirty_scopes.remove(&dirty);
|
||||||
|
|
||||||
|
// if the scope is currently suspended, then we should skip it, ignoring any tasks calling for an update
|
||||||
|
if !self.is_scope_suspended(dirty.id) {
|
||||||
|
self.run_scope(dirty.id);
|
||||||
|
self.diff_scope(&mut mutations, dirty.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for suspense, or a deadline
|
||||||
|
if self.dirty_scopes.is_empty() {
|
||||||
|
if self.scheduler.leaves.borrow().is_empty() {
|
||||||
|
return mutations;
|
||||||
|
}
|
||||||
|
|
||||||
|
let (work, deadline) = (self.wait_for_work(), &mut deadline);
|
||||||
|
pin_mut!(work);
|
||||||
|
|
||||||
|
if let Either::Left((_, _)) = select(deadline, work).await {
|
||||||
|
return mutations;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
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 = dom.rebuild();
|
|
||||||
}
|
|
||||||
+18
-15
@@ -6,26 +6,29 @@ use dioxus_core::*;
|
|||||||
async fn it_works() {
|
async fn it_works() {
|
||||||
let mut dom = VirtualDom::new(app);
|
let mut dom = VirtualDom::new(app);
|
||||||
|
|
||||||
let mutations = dom.rebuild();
|
let _ = dom.rebuild();
|
||||||
|
|
||||||
println!("mutations: {:?}", mutations);
|
tokio::select! {
|
||||||
|
_ = dom.wait_for_work() => {}
|
||||||
dom.wait_for_work().await;
|
_ = tokio::time::sleep(Duration::from_millis(1000)) => {}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn app(cx: Scope) -> Element {
|
fn app(cx: Scope) -> Element {
|
||||||
cx.spawn(async {
|
cx.use_hook(|| {
|
||||||
for x in 0..10 {
|
cx.spawn(async {
|
||||||
tokio::time::sleep(Duration::from_millis(500)).await;
|
for x in 0..10 {
|
||||||
println!("Hello, world! {x}");
|
tokio::time::sleep(Duration::from_millis(50)).await;
|
||||||
}
|
println!("Hello, world! {x}");
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
|
||||||
cx.spawn(async {
|
cx.spawn(async {
|
||||||
for x in 0..10 {
|
for x in 0..10 {
|
||||||
tokio::time::sleep(Duration::from_millis(250)).await;
|
tokio::time::sleep(Duration::from_millis(25)).await;
|
||||||
println!("Hello, world does! {x}");
|
println!("Hello, world from second thread! {x}");
|
||||||
}
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
None
|
None
|
||||||
|
|||||||
@@ -35,9 +35,6 @@ env_logger = "0.9.0"
|
|||||||
tokio = { version = "1.21.2", features = ["full"] }
|
tokio = { version = "1.21.2", features = ["full"] }
|
||||||
# dioxus-edit-stream = { path = "../edit-stream" }
|
# dioxus-edit-stream = { path = "../edit-stream" }
|
||||||
|
|
||||||
[[bench]]
|
|
||||||
name = "create"
|
|
||||||
harness = false
|
|
||||||
|
|
||||||
[[bench]]
|
[[bench]]
|
||||||
name = "jsframework"
|
name = "jsframework"
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
fn main() {}
|
|
||||||
@@ -5,9 +5,14 @@
|
|||||||
//! to be made, but the change application phase will be just as performant as the vanilla wasm_bindgen code. In essence,
|
//! to be made, but the change application phase will be just as performant as the vanilla wasm_bindgen code. In essence,
|
||||||
//! we are measuring the overhead of Dioxus, not the performance of the "apply" phase.
|
//! we are measuring the overhead of Dioxus, not the performance of the "apply" phase.
|
||||||
//!
|
//!
|
||||||
//! On my MBP 2019:
|
//!
|
||||||
//! - Dioxus takes 3ms to create 1_000 rows
|
//! Pre-templates (Mac M1):
|
||||||
//! - Dioxus takes 30ms to create 10_000 rows
|
//! - 3ms to create 1_000 rows
|
||||||
|
//! - 30ms to create 10_000 rows
|
||||||
|
//!
|
||||||
|
//! Post-templates
|
||||||
|
//! - 580us to create 1_000 rows
|
||||||
|
//! - 6.2ms to create 10_000 rows
|
||||||
//!
|
//!
|
||||||
//! As pure "overhead", these are amazing good numbers, mostly slowed down by hitting the global allocator.
|
//! As pure "overhead", these are amazing good numbers, mostly slowed down by hitting the global allocator.
|
||||||
//! These numbers don't represent Dioxus with the heuristic engine installed, so I assume it'll be even faster.
|
//! These numbers don't represent Dioxus with the heuristic engine installed, so I assume it'll be even faster.
|
||||||
@@ -20,25 +25,26 @@ criterion_group!(mbenches, create_rows);
|
|||||||
criterion_main!(mbenches);
|
criterion_main!(mbenches);
|
||||||
|
|
||||||
fn create_rows(c: &mut Criterion) {
|
fn create_rows(c: &mut Criterion) {
|
||||||
static App: Component = |cx| {
|
fn app(cx: Scope) -> Element {
|
||||||
let mut rng = SmallRng::from_entropy();
|
let mut rng = SmallRng::from_entropy();
|
||||||
|
|
||||||
render!(table {
|
render!(
|
||||||
tbody {
|
table {
|
||||||
(0..10_000_usize).map(|f| {
|
tbody {
|
||||||
let label = Label::new(&mut rng);
|
(0..10_000_usize).map(|f| {
|
||||||
rsx!(Row {
|
let label = Label::new(&mut rng);
|
||||||
row_id: f,
|
rsx!( Row { row_id: f, label: label } )
|
||||||
label: label
|
|
||||||
})
|
})
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
})
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
c.bench_function("create rows", |b| {
|
c.bench_function("create rows", |b| {
|
||||||
|
let mut dom = VirtualDom::new(app);
|
||||||
|
dom.rebuild();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
let mut dom = VirtualDom::new(App);
|
|
||||||
let g = dom.rebuild();
|
let g = dom.rebuild();
|
||||||
assert!(g.edits.len() > 1);
|
assert!(g.edits.len() > 1);
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -0,0 +1,101 @@
|
|||||||
|
use dioxus::prelude::*;
|
||||||
|
use rand::prelude::*;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut dom = VirtualDom::new(app);
|
||||||
|
|
||||||
|
dom.rebuild();
|
||||||
|
|
||||||
|
for _ in 0..1000 {
|
||||||
|
dom.rebuild();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn app(cx: Scope) -> Element {
|
||||||
|
let mut rng = SmallRng::from_entropy();
|
||||||
|
|
||||||
|
render! (
|
||||||
|
table {
|
||||||
|
tbody {
|
||||||
|
(0..10_000_usize).map(|f| {
|
||||||
|
let label = Label::new(&mut rng);
|
||||||
|
rsx!( Row { row_id: f, label: label } )
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Props)]
|
||||||
|
struct RowProps {
|
||||||
|
row_id: usize,
|
||||||
|
label: Label,
|
||||||
|
}
|
||||||
|
fn Row(cx: Scope<RowProps>) -> Element {
|
||||||
|
let [adj, col, noun] = cx.props.label.0;
|
||||||
|
render! {
|
||||||
|
tr {
|
||||||
|
td { class:"col-md-1", "{cx.props.row_id}" }
|
||||||
|
td { class:"col-md-1", onclick: move |_| { /* run onselect */ },
|
||||||
|
a { class: "lbl", "{adj}" "{col}" "{noun}" }
|
||||||
|
}
|
||||||
|
td { class: "col-md-1",
|
||||||
|
a { class: "remove", onclick: move |_| {/* remove */},
|
||||||
|
span { class: "glyphicon glyphicon-remove remove", aria_hidden: "true" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
td { class: "col-md-6" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq)]
|
||||||
|
struct Label([&'static str; 3]);
|
||||||
|
|
||||||
|
impl Label {
|
||||||
|
fn new(rng: &mut SmallRng) -> Self {
|
||||||
|
Label([
|
||||||
|
ADJECTIVES.choose(rng).unwrap(),
|
||||||
|
COLOURS.choose(rng).unwrap(),
|
||||||
|
NOUNS.choose(rng).unwrap(),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static ADJECTIVES: &[&str] = &[
|
||||||
|
"pretty",
|
||||||
|
"large",
|
||||||
|
"big",
|
||||||
|
"small",
|
||||||
|
"tall",
|
||||||
|
"short",
|
||||||
|
"long",
|
||||||
|
"handsome",
|
||||||
|
"plain",
|
||||||
|
"quaint",
|
||||||
|
"clean",
|
||||||
|
"elegant",
|
||||||
|
"easy",
|
||||||
|
"angry",
|
||||||
|
"crazy",
|
||||||
|
"helpful",
|
||||||
|
"mushy",
|
||||||
|
"odd",
|
||||||
|
"unsightly",
|
||||||
|
"adorable",
|
||||||
|
"important",
|
||||||
|
"inexpensive",
|
||||||
|
"cheap",
|
||||||
|
"expensive",
|
||||||
|
"fancy",
|
||||||
|
];
|
||||||
|
|
||||||
|
static COLOURS: &[&str] = &[
|
||||||
|
"red", "yellow", "blue", "green", "pink", "brown", "purple", "brown", "white", "black",
|
||||||
|
"orange",
|
||||||
|
];
|
||||||
|
|
||||||
|
static NOUNS: &[&str] = &[
|
||||||
|
"table", "chair", "house", "bbq", "desk", "car", "pony", "cookie", "sandwich", "burger",
|
||||||
|
"pizza", "mouse", "keyboard",
|
||||||
|
];
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
use dioxus::prelude::*;
|
|
||||||
|
|
||||||
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;
|
||||||
@@ -23,8 +21,63 @@ fn basic_syntax_is_a_template(cx: Scope) -> Element {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// imports come after the test since the rsx! macro is sensitive to its location in the file
|
||||||
|
// (the byte index is used to differentiate sub templates)
|
||||||
|
use dioxus::core::{ElementId, Mutation};
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn dual_stream() {
|
fn dual_stream() {
|
||||||
let mut dom = VirtualDom::new(basic_syntax_is_a_template);
|
let mut dom = VirtualDom::new(basic_syntax_is_a_template);
|
||||||
dbg!(dom.rebuild());
|
let edits = dom.rebuild();
|
||||||
|
|
||||||
|
use Mutation::*;
|
||||||
|
assert_eq!(
|
||||||
|
edits.template_mutations,
|
||||||
|
vec![
|
||||||
|
CreateElement { name: "div", namespace: None, id: ElementId(1) },
|
||||||
|
SetAttribute { name: "class", value: "asd", id: ElementId(1) },
|
||||||
|
CreateElement { name: "div", namespace: None, id: ElementId(2) },
|
||||||
|
CreatePlaceholder { id: ElementId(3) },
|
||||||
|
AppendChildren { m: 1 },
|
||||||
|
CreateElement { name: "div", namespace: None, id: ElementId(4) },
|
||||||
|
CreateElement { name: "h1", namespace: None, id: ElementId(5) },
|
||||||
|
CreateText { value: "var" },
|
||||||
|
AppendChildren { m: 1 },
|
||||||
|
CreateElement { name: "p", namespace: None, id: ElementId(6) },
|
||||||
|
CreateText { value: "you're great!" },
|
||||||
|
AppendChildren { m: 1 },
|
||||||
|
CreateElement { name: "div", namespace: None, id: ElementId(7) },
|
||||||
|
SetAttribute { name: "background-color", value: "red", id: ElementId(7) },
|
||||||
|
CreateElement { name: "h1", namespace: None, id: ElementId(8) },
|
||||||
|
CreateText { value: "var" },
|
||||||
|
AppendChildren { m: 1 },
|
||||||
|
CreateElement { name: "div", namespace: None, id: ElementId(9) },
|
||||||
|
CreateElement { name: "b", namespace: None, id: ElementId(10) },
|
||||||
|
CreateText { value: "asd" },
|
||||||
|
AppendChildren { m: 1 },
|
||||||
|
CreateText { value: "not great" },
|
||||||
|
AppendChildren { m: 2 },
|
||||||
|
AppendChildren { m: 2 },
|
||||||
|
CreateElement { name: "p", namespace: None, id: ElementId(11) },
|
||||||
|
CreateText { value: "you're great!" },
|
||||||
|
AppendChildren { m: 1 },
|
||||||
|
AppendChildren { m: 4 },
|
||||||
|
AppendChildren { m: 2 },
|
||||||
|
SaveTemplate { name: "packages/dioxus/tests/rsx_syntax.rs:5:15:122", m: 1 }
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
edits.edits,
|
||||||
|
vec![
|
||||||
|
LoadTemplate { name: "packages/dioxus/tests/rsx_syntax.rs:5:15:122", index: 0 },
|
||||||
|
AssignId { path: &[], id: ElementId(12) },
|
||||||
|
SetAttribute { name: "class", value: "123", id: ElementId(12) },
|
||||||
|
SetAttribute { name: "onclick", value: "asd", id: ElementId(12) }, // ---- todo: listeners
|
||||||
|
HydrateText { path: &[0, 0], value: "123", id: ElementId(13) },
|
||||||
|
ReplacePlaceholder { m: 1, path: &[0, 0] },
|
||||||
|
AppendChildren { m: 1 }
|
||||||
|
]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
use std::future::IntoFuture;
|
use std::future::IntoFuture;
|
||||||
|
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
|
use dioxus_core::SuspenseBoundary;
|
||||||
|
|
||||||
#[inline_props]
|
#[inline_props]
|
||||||
fn suspense_boundary<'a>(cx: Scope<'a>, children: Element<'a>) -> Element {
|
fn suspense_boundary<'a>(cx: Scope<'a>, children: Element<'a>) -> Element {
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
fn main() {}
|
|
||||||
Reference in new issue
Block a user