Merge branch 'master' of github.com:DioxusLabs/dioxus

This commit is contained in:
Jonathan Kelley committed 2022-07-07 01:46:48 -04:00
commit 2b9888627b
22 files changed
+994 -1113

No files matched your search

+47 -38
View File
@@ -243,11 +243,11 @@ You've probably noticed that many elements in the `rsx!` macros support on-hover
# Native Core # Native Core
Renderers take a lot of work. If you are creating a renderer in rust, native core provides some utilites to implement a renderer. It provides an abstraction over DomEdits and handles layout for you. If you are creating a renderer in rust, native core provides some utilites to implement a renderer. It provides an abstraction over DomEdits and handles layout for you.
## RealDom ## RealDom
The `RealDom` is a higher level abstraction over updating the Dom. It updates with `DomEdits` and provides a way to lazily update the state of nodes based on what attributes change. The `RealDom` is a higher level abstraction over updating the Dom. It updates with `DomEdits` and provides a way to incrementally update the state of nodes based on what attributes change.
### Example ### Example
@@ -267,43 +267,52 @@ cx.render(rsx!{
In this tree the color depends on the parent's color. The size depends on the childrens size, the current text, and a text size. The border depends on only the current node. In this tree the color depends on the parent's color. The size depends on the childrens size, the current text, and a text size. The border depends on only the current node.
```mermaid In the following diagram arrows represent dataflow:
flowchart TB
subgraph context
text_width(text width)
end
subgraph div
state1(state)-->color1(color)
state1(state)-->border1(border)
border1-.->text_width
linkStyle 2 stroke:#5555ff,stroke-width:4px;
state1(state)-->layout_width1(layout width)
end
subgraph p
state2(state)-->color2(color)
color2-.->color1(color)
linkStyle 5 stroke:#0000ff,stroke-width:4px;
state2(state)-->border2(border)
border2-.->text_width
linkStyle 7 stroke:#5555ff,stroke-width:4px;
state2(state)-->layout_width2(layout width)
layout_width1-.->layout_width2
linkStyle 9 stroke:#aaaaff,stroke-width:4px;
end
subgraph hello world
state3(state)-->color3(color)
color3-.->color2(color)
linkStyle 11 stroke:#0000ff,stroke-width:4px;
state3(state)-->border3(border)
border3-.->text_width
linkStyle 13 stroke:#5555ff,stroke-width:4px;
state3(state)-->layout_width3(layout width)
layout_width2-.->layout_width3
linkStyle 15 stroke:#aaaaff,stroke-width:4px;
end
```
To help in building a Dom, native core provides four traits: State, ChildDepState, ParentDepState, and NodeDepState and a RealDom struct. [![](https://mermaid.ink/img/pako:eNqdVNFqgzAU_RXJXizUUZPJmIM-jO0LukdhpCbO0JhIGteW0n9fNK1Oa0brfUnu9VxyzzkXjyCVhIIYZFzu0hwr7X2-JcIzsa3W3wqXuZdKoele22oddfa1Y0Tnfn31muvMfqeCDNq3GmvaNROmaKqZFO1DPTRhP8MOd1fTWYNDvzlmQbBMJZcq9JtjNgY1mLVUhBqQPQeojl3wGCw5PsjqnIe-zXqEL8GZ2Kz0gVMPmoeU3ND4IcuiaLGY2zRouuKncv_qGKv3VodpJe0JVU6QCQ5kgqMyWQVr8hbk4hm1PBcmsuwmnrCVH94rP7xN_ucp8sOB_EPSfz9drYVrkpc_AmH8_yTjJueUc-ntpOJkgt2os9tKjcYlt-DLUiD3UsB2KZCLcwjv3Aq33-g2v0M0xXA0MBy5DUdXi-gcJZriuLmAOSioKjAj5ld8rMsJ0DktaAJicyVYbRKQiJPBVSUx438QpqUCcYb5ls4BrrRcHUTaFizqnWGzR8W5evoFI-bJdw)](https://mermaid-js.github.io/mermaid-live-editor/edit#pako:eNqdVNFqgzAU_RXJXizUUZPJmIM-jO0LukdhpCbO0JhIGteW0n9fNK1Oa0brfUnu9VxyzzkXjyCVhIIYZFzu0hwr7X2-JcIzsa3W3wqXuZdKoele22oddfa1Y0Tnfn31muvMfqeCDNq3GmvaNROmaKqZFO1DPTRhP8MOd1fTWYNDvzlmQbBMJZcq9JtjNgY1mLVUhBqQPQeojl3wGCw5PsjqnIe-zXqEL8GZ2Kz0gVMPmoeU3ND4IcuiaLGY2zRouuKncv_qGKv3VodpJe0JVU6QCQ5kgqMyWQVr8hbk4hm1PBcmsuwmnrCVH94rP7xN_ucp8sOB_EPSfz9drYVrkpc_AmH8_yTjJueUc-ntpOJkgt2os9tKjcYlt-DLUiD3UsB2KZCLcwjv3Aq33-g2v0M0xXA0MBy5DUdXi-gcJZriuLmAOSioKjAj5ld8rMsJ0DktaAJicyVYbRKQiJPBVSUx438QpqUCcYb5ls4BrrRcHUTaFizqnWGzR8W5evoFI-bJdw)
[//]: # "%% mermaid flow chart"
[//]: # "flowchart TB"
[//]: # " subgraph context"
[//]: # " text_width(text width)"
[//]: # " end"
[//]: # " subgraph state"
[//]: # " direction TB"
[//]: # " subgraph div state"
[//]: # " direction TB"
[//]: # " state1(state)-->color1(color)"
[//]: # " state1-->border1(border)"
[//]: # " text_width-.->layout_width1(layout width)"
[//]: # " linkStyle 2 stroke:#ff5500,stroke-width:4px;"
[//]: # " state1-->layout_width1"
[//]: # " end"
[//]: # " subgraph p state"
[//]: # " direction TB"
[//]: # " state2(state)-->color2(color)"
[//]: # " color1-.->color2"
[//]: # " linkStyle 5 stroke:#0000ff,stroke-width:4px;"
[//]: # " state2-->border2(border)"
[//]: # " text_width-.->layout_width2(layout width)"
[//]: # " linkStyle 7 stroke:#ff5500,stroke-width:4px;"
[//]: # " state2-->layout_width2"
[//]: # " layout_width2-.->layout_width1"
[//]: # " linkStyle 9 stroke:#00aa00,stroke-width:4px;"
[//]: # " end"
[//]: # " subgraph hello world state"
[//]: # " direction TB"
[//]: # " state3(state)-->border3(border)"
[//]: # " state3-->color3(color)"
[//]: # " color2-.->color3"
[//]: # " linkStyle 12 stroke:#0000ff,stroke-width:4px;"
[//]: # " text_width-.->layout_width3(layout width)"
[//]: # " linkStyle 13 stroke:#ff5500,stroke-width:4px;"
[//]: # " state3-->layout_width3"
[//]: # " layout_width3-.->layout_width2"
[//]: # " linkStyle 15 stroke:#00aa00,stroke-width:4px;"
[//]: # " end"
[//]: # " end"
To help in building a Dom, native core provides four traits: State, ChildDepState, ParentDepState, and NodeDepState and a RealDom struct. The ChildDepState, ParentDepState, and NodeDepState provide a way to discribe how some information in a node relates to that of its relatives. By providing how to build a single node from its relations, native-core will derive a way to update the state of all nodes for you with ```#[derive(State)]```. Once you have a state you can provide it as a generic to RealDom. RealDom provides all of the methods to interact and update your new dom.
```rust ```rust
use dioxus_native_core::node_ref::*; use dioxus_native_core::node_ref::*;
+3 -4
View File
@@ -83,7 +83,7 @@ mod util {
} }
pub fn expr_to_single_string(expr: &syn::Expr) -> Option<String> { pub fn expr_to_single_string(expr: &syn::Expr) -> Option<String> {
if let syn::Expr::Path(path) = &*expr { if let syn::Expr::Path(path) = expr {
path_to_single_string(&path.path) path_to_single_string(&path.path)
} else { } else {
None None
@@ -779,13 +779,12 @@ Finally, call `.build()` to create the instance of `{name}`.
// NOTE: both auto_into and strip_option affect `arg_type` and `arg_expr`, but the order of // NOTE: both auto_into and strip_option affect `arg_type` and `arg_expr`, but the order of
// nesting is different so we have to do this little dance. // nesting is different so we have to do this little dance.
let arg_type = if field.builder_attr.strip_option { let arg_type = if field.builder_attr.strip_option {
let internal_type = field.type_from_inside_option(false).ok_or_else(|| { field.type_from_inside_option(false).ok_or_else(|| {
Error::new_spanned( Error::new_spanned(
&field_type, &field_type,
"can't `strip_option` - field is not `Option<...>`", "can't `strip_option` - field is not `Option<...>`",
) )
})?; })?
internal_type
} else { } else {
field_type field_type
}; };
+1 -1
View File
@@ -21,7 +21,7 @@ serde = "1.0.136"
serde_json = "1.0.79" serde_json = "1.0.79"
thiserror = "1.0.30" thiserror = "1.0.30"
log = "0.4.14" log = "0.4.14"
wry = { version = "0.16.0" } wry = { version = "0.19.0" }
futures-channel = "0.3.21" futures-channel = "0.3.21"
tokio = { version = "1.16.1", features = [ tokio = { version = "1.16.1", features = [
"sync", "sync",
+3 -2
View File
@@ -6,8 +6,9 @@ use std::sync::Arc;
use dioxus_core::{ElementId, EventPriority, UserEvent}; use dioxus_core::{ElementId, EventPriority, UserEvent};
use dioxus_html::event_bubbles; use dioxus_html::event_bubbles;
use dioxus_html::on::*; use dioxus_html::on::*;
use serde::{Deserialize, Serialize};
#[derive(serde::Serialize, serde::Deserialize)] #[derive(Deserialize, Serialize)]
pub(crate) struct IpcMessage { pub(crate) struct IpcMessage {
method: String, method: String,
params: serde_json::Value, params: serde_json::Value,
@@ -33,7 +34,7 @@ pub(crate) fn parse_ipc_message(payload: &str) -> Option<IpcMessage> {
} }
} }
#[derive(serde::Serialize, serde::Deserialize)] #[derive(Deserialize, Serialize)]
struct ImEvent { struct ImEvent {
event: String, event: String,
mounted_dom_id: u64, mounted_dom_id: u64,
-7
View File
@@ -213,13 +213,6 @@ pub fn launch_with_props<P: 'static + Send>(
} => match event { } => match event {
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
WindowEvent::Destroyed { .. } => desktop.close_window(window_id, control_flow), WindowEvent::Destroyed { .. } => desktop.close_window(window_id, control_flow),
WindowEvent::Resized(_) | WindowEvent::Moved(_) => {
if let Some(view) = desktop.webviews.get_mut(&window_id) {
let _ = view.resize();
}
}
_ => {} _ => {}
}, },
+1 -1
View File
@@ -937,7 +937,7 @@ pub mod on {
feature = "serialize", feature = "serialize",
derive(serde_repr::Serialize_repr, serde_repr::Deserialize_repr) derive(serde_repr::Serialize_repr, serde_repr::Deserialize_repr)
)] )]
#[derive(Clone, Copy, Debug, PartialEq)] #[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[repr(u8)] #[repr(u8)]
pub enum KeyCode { pub enum KeyCode {
// That key has no keycode, = 0 // That key has no keycode, = 0
+364 -479
View File
@@ -2,11 +2,12 @@ extern crate proc_macro;
mod sorted_slice; mod sorted_slice;
use dioxus_native_core::state::MemberId;
use proc_macro::TokenStream; use proc_macro::TokenStream;
use quote::format_ident; use quote::{quote, ToTokens};
use quote::{quote, ToTokens, __private::Span};
use sorted_slice::StrSlice; use sorted_slice::StrSlice;
use syn::parenthesized;
use syn::parse::ParseBuffer;
use syn::punctuated::Punctuated;
use syn::{ use syn::{
self, self,
parse::{Parse, ParseStream, Result}, parse::{Parse, ParseStream, Result},
@@ -51,228 +52,99 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream {
let strct = Struct::new(type_name.clone(), &fields); let strct = Struct::new(type_name.clone(), &fields);
match StateStruct::parse(&fields, &strct) { match StateStruct::parse(&fields, &strct) {
Ok(state_strct) => { Ok(state_strct) => {
let node_dep_state_fields = state_strct let members: Vec<_> = state_strct
.state_members .state_members
.iter() .iter()
.filter(|f| f.dep_kind == DepKind::Node) .map(|m| &m.mem.ident)
.map(|f| f.reduce_self());
let child_dep_state_fields = state_strct
.state_members
.iter()
.filter(|f| f.dep_kind == DepKind::Child)
.map(|f| f.reduce_self());
let parent_dep_state_fields = state_strct
.state_members
.iter()
.filter(|f| f.dep_kind == DepKind::Parent)
.map(|f| f.reduce_self());
let node_iter = state_strct
.state_members
.iter()
.filter(|m| m.dep_kind == DepKind::Node);
let node_ids = node_iter.clone().map(|m| m.member_id.0);
let node_ids_clone = node_ids.clone();
let node_types = node_iter.map(|f| &f.mem.ty);
let child_iter = state_strct
.state_members
.iter()
.filter(|m| m.dep_kind == DepKind::Child);
let child_ids = child_iter.clone().map(|m| m.member_id.0);
let child_ids_clone = child_ids.clone();
let child_types = child_iter.map(|f| &f.mem.ty);
let parent_iter = state_strct
.state_members
.iter()
.filter(|m| m.dep_kind == DepKind::Parent);
let parent_ids = parent_iter.clone().map(|m| m.member_id.0);
let parent_ids_clone = parent_ids.clone();
let parent_types = parent_iter.map(|f| &f.mem.ty);
let type_name_str = type_name.to_string();
let child_states = &state_strct.child_states;
let member_size = state_strct.state_members.len();
let child_state_ty = child_states.iter().map(|m| &m.ty);
let child_state_idents: Vec<_> = child_states.iter().map(|m| &m.ident).collect();
let sum_const_declarations = child_state_ty.clone().enumerate().map(|(i, ty)| {
let ident = format_ident!("__{}_SUM_{}", i, type_name.to_string());
let ident_minus = format_ident!("__{}_SUM_{}_minus", i, type_name.to_string());
if i == 0 {
quote!(const #ident_minus: usize = #member_size + #ty::SIZE - 1;
const #ident: usize = #member_size + #ty::SIZE;)
} else {
let prev_ident = format_ident!("__{}_SUM_{}", i - 1, type_name.to_string());
quote!(const #ident_minus: usize = #prev_ident + #ty::SIZE - 1;
const #ident: usize = #prev_ident + #ty::SIZE;)
}
});
let sum_idents: Vec<_> = std::iter::once(quote!(#member_size))
.chain((0..child_states.len()).map(|i| {
let ident = format_ident!("__{}_SUM_{}", i, type_name.to_string());
quote!(#ident)
}))
.collect(); .collect();
let member_types = state_strct.state_members.iter().map(|m| &m.mem.ty);
let resolve_members = state_strct
.state_members
.iter()
.map(|m| state_strct.resolve(m));
let child_state_ranges: Vec<_> = (0..child_state_ty.len()) let child_types = state_strct.child_states.iter().map(|s| &s.ty);
.map(|i| { let child_members = state_strct.child_states.iter().map(|s| &s.ident);
let current = format_ident!("__{}_SUM_{}_minus", i, type_name.to_string());
let previous = if i == 0 {
quote!(#member_size)
} else {
let ident = format_ident!("__{}_SUM_{}", i - 1, type_name.to_string());
quote!(#ident)
};
quote!(#previous..=#current)
})
.collect();
let gen = quote! { let gen = quote! {
#( impl State for #type_name {
#sum_const_declarations fn update<'a, T: dioxus_native_core::traversable::Traversable<Node = Self, Id = dioxus_core::ElementId>>(
)* dirty: &[(dioxus_core::ElementId, dioxus_native_core::node_ref::NodeMask)],
impl State for #type_name{ state_tree: &'a mut T,
const SIZE: usize = #member_size #( + #child_state_ty::SIZE)*;
fn update_node_dep_state<'a>(
&'a mut self,
ty: dioxus_native_core::state::MemberId,
node: &'a dioxus_core::VNode<'a>,
vdom: &'a dioxus_core::VirtualDom, vdom: &'a dioxus_core::VirtualDom,
ctx: &anymap::AnyMap, ctx: &anymap::AnyMap,
) -> Option<dioxus_native_core::state::NodeStatesChanged>{ ) -> fxhash::FxHashSet<dioxus_core::ElementId>{
use dioxus_native_core::state::NodeDepState as _; #[derive(Eq, PartialEq)]
use dioxus_native_core::state::State as _; struct HeightOrdering {
match ty.0{ height: u16,
#( id: dioxus_core::ElementId,
#node_ids => #node_dep_state_fields,
)*
#(
#child_state_ranges => {
self.#child_state_idents.update_node_dep_state(
ty - #sum_idents,
node,
vdom,
ctx,
).map(|mut changed|{
for id in &mut changed.node_dep{
*id += #sum_idents;
}
changed
})
}
)*
_ => panic!("{:?} not in {}", ty, #type_name_str),
} }
}
fn update_parent_dep_state<'a>( impl HeightOrdering {
&'a mut self, fn new(height: u16, id: dioxus_core::ElementId) -> Self {
ty: dioxus_native_core::state::MemberId, HeightOrdering {
node: &'a dioxus_core::VNode<'a>, height,
vdom: &'a dioxus_core::VirtualDom, id,
parent: Option<&Self>,
ctx: &anymap::AnyMap,
) -> Option<dioxus_native_core::state::ParentStatesChanged>{
use dioxus_native_core::state::ParentDepState as _;
match ty.0{
#(
#parent_ids => #parent_dep_state_fields,
)*
#(
#child_state_ranges => {
self.#child_state_idents.update_parent_dep_state(
ty - #sum_idents,
node,
vdom,
parent.map(|p| &p.#child_state_idents),
ctx,
).map(|mut changed|{
for id in &mut changed.node_dep{
*id += #sum_idents;
}
for id in &mut changed.parent_dep{
*id += #sum_idents;
}
changed
})
} }
)* }
_ => panic!("{:?} not in {}", ty, #type_name_str),
} }
}
fn update_child_dep_state<'a>( impl Ord for HeightOrdering {
&'a mut self, fn cmp(&self, other: &Self) -> std::cmp::Ordering {
ty: dioxus_native_core::state::MemberId, self.height.cmp(&other.height).then(self.id.0.cmp(&other.id.0))
node: &'a dioxus_core::VNode<'a>, }
vdom: &'a dioxus_core::VirtualDom,
children: &[&Self],
ctx: &anymap::AnyMap,
) -> Option<dioxus_native_core::state::ChildStatesChanged>{
use dioxus_native_core::state::ChildDepState as _;
match ty.0{
#(
#child_ids => #child_dep_state_fields,
)*
#(
#child_state_ranges => {
self.#child_state_idents.update_child_dep_state(
ty - #sum_idents,
node,
vdom,
&children.iter().map(|p| &p.#child_state_idents).collect::<Vec<_>>(),
ctx,
).map(|mut changed|{
for id in &mut changed.node_dep{
*id += #sum_idents;
}
for id in &mut changed.child_dep{
*id += #sum_idents;
}
changed
})
}
)*
_ => panic!("{:?} not in {}", ty, #type_name_str),
} }
}
fn child_dep_types(&self, mask: &dioxus_native_core::node_ref::NodeMask) -> Vec<dioxus_native_core::state::MemberId>{ impl PartialOrd for HeightOrdering {
let mut dep_types = Vec::new(); fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
#(if #child_types::NODE_MASK.overlaps(mask) { Some(self.cmp(&other))
dep_types.push(dioxus_native_core::state::MemberId(#child_ids_clone)); }
})* }
#(
dep_types.extend(self.#child_state_idents.child_dep_types(mask).into_iter().map(|id| id + #sum_idents));
)*
dep_types
}
fn parent_dep_types(&self, mask: &dioxus_native_core::node_ref::NodeMask) -> Vec<dioxus_native_core::state::MemberId>{ struct MembersDirty {
let mut dep_types = Vec::new(); #(#members: bool, )*
#(if #parent_types::NODE_MASK.overlaps(mask) { }
dep_types.push(dioxus_native_core::state::MemberId(#parent_ids_clone));
})* impl MembersDirty {
#( fn new() -> Self {
dep_types.extend(self.#child_state_idents.parent_dep_types(mask).into_iter().map(|id| id + #sum_idents)); Self {#(#members: false),*}
)* }
dep_types
} fn any(&self) -> bool {
#(self.#members || )* false
}
}
let mut dirty_elements = fxhash::FxHashSet::default();
// the states of any elements that are dirty
let mut states = fxhash::FxHashMap::default();
for (id, mask) in dirty {
let members_dirty = MembersDirty {
#(#members: #member_types::NODE_MASK.overlaps(mask),)*
};
if members_dirty.any(){
states.insert(*id, members_dirty);
}
dirty_elements.insert(*id);
}
fn node_dep_types(&self, mask: &dioxus_native_core::node_ref::NodeMask) -> Vec<dioxus_native_core::state::MemberId>{
let mut dep_types = Vec::new();
#(if #node_types::NODE_MASK.overlaps(mask) {
dep_types.push(dioxus_native_core::state::MemberId(#node_ids_clone));
})*
#( #(
dep_types.extend(self.#child_state_idents.node_dep_types(mask).into_iter().map(|id| id + #sum_idents)); #resolve_members;
)* )*
dep_types
#(
dirty_elements.extend(
<#child_types as dioxus_native_core::state::State>::update(
dirty,
&mut state_tree.map(|n| &n.#child_members, |n| &mut n.#child_members),
vdom,
ctx,
)
);
)*
dirty_elements
} }
} }
}; };
@@ -282,6 +154,12 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream {
} }
} }
struct Depenadants<'a> {
node: Vec<&'a Member>,
child: Vec<&'a Member>,
parent: Vec<&'a Member>,
}
struct Struct { struct Struct {
name: Ident, name: Ident,
members: Vec<Member>, members: Vec<Member>,
@@ -302,7 +180,7 @@ struct StateStruct<'a> {
impl<'a> StateStruct<'a> { impl<'a> StateStruct<'a> {
fn parse(fields: &[&'a Field], strct: &'a Struct) -> Result<Self> { fn parse(fields: &[&'a Field], strct: &'a Struct) -> Result<Self> {
let mut parse_err = Ok(()); let mut parse_err = Ok(());
let state_members = strct let mut unordered_state_members: Vec<_> = strct
.members .members
.iter() .iter()
.zip(fields.iter()) .zip(fields.iter())
@@ -312,7 +190,44 @@ impl<'a> StateStruct<'a> {
parse_err = Err(err); parse_err = Err(err);
None None
} }
}); })
.collect();
parse_err?;
let mut state_members = Vec::new();
while !unordered_state_members.is_empty() {
let mut resolved = false;
for i in 0..unordered_state_members.len() {
let mem = &mut unordered_state_members[i];
if mem.dep_mems.iter().all(|(dep, resolved)| {
*resolved || (*dep == mem.mem && mem.dep_kind != DepKind::Node)
}) {
let mem = unordered_state_members.remove(i);
// mark any dependancy that depends on this member as resolved
for member in unordered_state_members.iter_mut() {
for (dep, resolved) in &mut member.dep_mems {
*resolved |= *dep == mem.mem;
}
}
state_members.push(mem);
resolved = true;
break;
}
}
if !resolved {
return Err(Error::new(
strct.name.span(),
format!(
"{} has circular dependacy in {:?}",
strct.name,
unordered_state_members
.iter()
.map(|m| format!("{}", &m.mem.ident))
.collect::<Vec<_>>()
),
));
}
}
let child_states = strct let child_states = strct
.members .members
@@ -328,177 +243,234 @@ impl<'a> StateStruct<'a> {
}) })
.map(|(m, _)| m); .map(|(m, _)| m);
#[derive(Debug, Clone)]
struct DepNode<'a> {
state_mem: StateMember<'a>,
depandants: Vec<DepNode<'a>>,
}
impl<'a> DepNode<'a> {
fn new(state_mem: StateMember<'a>) -> Self {
Self {
state_mem,
depandants: Vec::new(),
}
}
/// flattens the node in pre order
fn flatten(self) -> Vec<StateMember<'a>> {
let DepNode {
state_mem,
depandants,
} = self;
let mut flat = vec![state_mem];
for d in depandants {
flat.append(&mut d.flatten());
}
flat
}
fn set_ids(&mut self, current_id: &mut usize) {
self.state_mem.member_id = dioxus_native_core::state::MemberId(*current_id);
// if the node depends on itself, we need to add the dependency seperately
if let Some(dep) = self.state_mem.dep_mem {
if dep == self.state_mem.mem {
self.state_mem
.dependants
.push((MemberId(*current_id), self.state_mem.dep_kind.clone()));
}
}
*current_id += 1;
for d in &mut self.depandants {
self.state_mem
.dependants
.push((MemberId(*current_id), d.state_mem.dep_kind.clone()));
d.set_ids(current_id);
}
}
fn contains_member(&self, member: &Member) -> bool {
if self.state_mem.mem == member {
true
} else {
self.depandants.iter().any(|d| d.contains_member(member))
}
}
// check if there are any mixed child/parent dependancies
fn check(&self) -> Option<Error> {
self.kind().err()
}
fn kind(&self) -> Result<&DepKind> {
fn reduce_kind<'a>(dk1: &'a DepKind, dk2: &'a DepKind) -> Result<&'a DepKind> {
match (dk1, dk2) {
(DepKind::Child, DepKind::Parent) | (DepKind::Parent, DepKind::Child) => {
Err(Error::new(
Span::call_site(),
"There is a ChildDepState that depends on a ParentDepState",
))
}
// node dep state takes the lowest priority
(DepKind::Node, important) | (important, DepKind::Node) => Ok(important),
// they are the same
(fst, _) => Ok(fst),
}
}
reduce_kind(
self.depandants
.iter()
.try_fold(&DepKind::Node, |dk1, dk2| reduce_kind(dk1, dk2.kind()?))?,
&self.state_mem.dep_kind,
)
}
fn insert_dependant(&mut self, other: DepNode<'a>) -> bool {
let dep = other.state_mem.dep_mem.unwrap();
if self.contains_member(dep) {
if self.state_mem.mem == dep {
self.depandants.push(other);
true
} else {
self.depandants
.iter_mut()
.find(|d| d.contains_member(dep))
.unwrap()
.insert_dependant(other)
}
} else {
false
}
}
}
// members need to be sorted so that members are updated after the members they depend on // members need to be sorted so that members are updated after the members they depend on
let mut roots: Vec<DepNode> = vec![]; Ok(Self {
for m in state_members { state_members,
if let Some(dep) = m.dep_mem { child_states: child_states.collect(),
let root_depends_on = roots })
.iter() }
.filter_map(|m| m.state_mem.dep_mem)
.any(|d| m.mem == d);
if let Some(r) = roots.iter_mut().find(|r| r.contains_member(dep)) { fn get_depenadants(&self, mem: &Member) -> Depenadants {
let new = DepNode::new(m); let mut dependants = Depenadants {
if root_depends_on { node: Vec::new(),
return Err(Error::new( child: Vec::new(),
new.state_mem.mem.ident.span(), parent: Vec::new(),
format!("{} has a circular dependancy", new.state_mem.mem.ident), };
)); for member in &self.state_members {
for (dep, _) in &member.dep_mems {
if *dep == mem {
match member.dep_kind {
DepKind::Node => dependants.node.push(member.mem),
DepKind::Child => dependants.child.push(member.mem),
DepKind::Parent => dependants.parent.push(member.mem),
} }
// return Err(Error::new(new.state_mem.mem.ident.span(), "stuff"));
r.insert_dependant(new);
continue;
} }
} }
let mut new = DepNode::new(m);
let mut i = 0;
while i < roots.len() {
if roots[i].state_mem.dep_mem == Some(new.state_mem.mem) {
let child = roots.remove(i);
new.insert_dependant(child);
} else {
i += 1;
}
}
roots.push(new);
} }
parse_err?; dependants
let mut current_id = 0; }
for r in &mut roots {
r.set_ids(&mut current_id);
}
if let Some(err) = roots.iter().find_map(DepNode::check) {
Err(err)
} else {
let state_members: Vec<_> = roots
.into_iter()
.flat_map(|r| r.flatten().into_iter())
.collect();
Ok(Self { fn update_dependants(&self, mem: &Member) -> impl ToTokens {
state_members, let dep = self.get_depenadants(mem);
child_states: child_states.collect(), let update_child_dependants = if dep.child.is_empty() {
}) quote!()
} else {
let insert = dep.child.iter().map(|d|{
if *d == mem {
quote! {
let seeking = HeightOrdering::new(state_tree.height(parent_id).unwrap(), parent_id);
if let Err(idx) = resolution_order
.binary_search_by(|ordering| ordering.cmp(&seeking).reverse()){
resolution_order.insert(
idx,
seeking,
);
}
}
} else {
quote! {}
}
});
let update: Vec<_> = dep
.child
.iter()
.map(|d| {
let ident = &d.ident;
quote! {
dirty.#ident = true;
}
})
.collect();
quote! {
if let Some(parent_id) = state_tree.parent(id) {
#(#insert)*
if let Some(dirty) = states.get_mut(&parent_id) {
#(#update)*
}
else {
let mut dirty = MembersDirty::new();
#(#update)*
states.insert(parent_id, dirty);
}
}
}
};
let node_dependants: Vec<_> = dep.node.iter().map(|d| &d.ident).collect();
let update_node_dependants = quote! {#(members_dirty.#node_dependants = true;)*};
let update_parent_dependants = if dep.parent.is_empty() {
quote!()
} else {
let insert = dep.parent.iter().map(|d| {
if *d == mem {
quote! {
let seeking = HeightOrdering::new(state_tree.height(*child_id).unwrap(), *child_id);
if let Err(idx) = resolution_order
.binary_search(&seeking){
resolution_order.insert(
idx,
seeking,
);
}
}
} else {
quote! {}
}
});
let update: Vec<_> = dep
.parent
.iter()
.map(|d| {
let ident = &d.ident;
quote! {
dirty.#ident = true;
}
})
.collect();
quote! {
for child_id in state_tree.children(id) {
#(#insert)*
if let Some(dirty) = states.get_mut(&child_id) {
#(#update)*
}
else {
let mut dirty = MembersDirty::new();
#(#update)*
states.insert(*child_id, dirty);
}
}
}
};
quote! {
#update_node_dependants
#update_child_dependants
#update_parent_dependants
}
}
fn resolve(&self, mem: &StateMember) -> impl ToTokens {
let reduce_member = mem.reduce_self();
let update_dependant = self.update_dependants(mem.mem);
let member = &mem.mem.ident;
match mem.dep_kind {
DepKind::Parent => {
quote! {
// resolve parent dependant state
let mut resolution_order = states.keys().copied().map(|id| HeightOrdering::new(state_tree.height(id).unwrap(), id)).collect::<Vec<_>>();
resolution_order.sort();
let mut i = 0;
while i < resolution_order.len(){
let id = resolution_order[i].id;
let vnode = vdom.get_element(id).unwrap();
let members_dirty = states.get_mut(&id).unwrap();
let (current_state, parent) = state_tree.get_node_parent_mut(id);
let current_state = current_state.unwrap();
if members_dirty.#member && #reduce_member {
dirty_elements.insert(id);
#update_dependant
}
i += 1;
}
}
}
DepKind::Child => {
quote! {
// resolve child dependant state
let mut resolution_order = states.keys().copied().map(|id| HeightOrdering::new(state_tree.height(id).unwrap(), id)).collect::<Vec<_>>();
resolution_order.sort_by(|height_ordering1, height_ordering2| {
height_ordering1.cmp(&height_ordering2).reverse()
});
let mut i = 0;
while i < resolution_order.len(){
let id = resolution_order[i].id;
let vnode = vdom.get_element(id).unwrap();
let members_dirty = states.get_mut(&id).unwrap();
let (current_state, children) = state_tree.get_node_children_mut(id);
let current_state = current_state.unwrap();
if members_dirty.#member && #reduce_member {
dirty_elements.insert(id);
#update_dependant
}
i += 1;
}
}
}
DepKind::Node => {
quote! {
// resolve node dependant state
let mut resolution_order = states.keys().copied().collect::<Vec<_>>();
let mut i = 0;
while i < resolution_order.len(){
let id = resolution_order[i];
let vnode = vdom.get_element(id).unwrap();
let members_dirty = states.get_mut(&id).unwrap();
let current_state = state_tree.get_mut(id).unwrap();
if members_dirty.#member && #reduce_member {
dirty_elements.insert(id);
#update_dependant
}
i += 1;
}
}
}
} }
} }
} }
fn try_parenthesized(input: ParseStream) -> Result<ParseBuffer> {
let inside;
parenthesized!(inside in input);
Ok(inside)
}
struct Dependancy { struct Dependancy {
ctx_ty: Option<Type>, ctx_ty: Option<Type>,
dep: Option<Ident>, deps: Vec<Ident>,
} }
impl Parse for Dependancy { impl Parse for Dependancy {
fn parse(input: ParseStream) -> Result<Self> { fn parse(input: ParseStream) -> Result<Self> {
let dep = input let deps: Option<Punctuated<Ident, Token![,]>> = {
.parse() try_parenthesized(input)
.ok() .ok()
.filter(|i: &Ident| format!("{}", i) != "NONE"); .and_then(|inside| inside.parse_terminated(Ident::parse).ok())
};
let deps: Vec<_> = deps
.map(|deps| deps.into_iter().collect())
.or_else(|| {
input
.parse::<Ident>()
.ok()
.filter(|i: &Ident| format!("{}", i) != "NONE")
.map(|i| vec![i])
})
.unwrap_or_default();
let comma: Option<Token![,]> = input.parse().ok(); let comma: Option<Token![,]> = input.parse().ok();
let ctx_ty = input.parse().ok(); let ctx_ty = input.parse().ok();
Ok(Self { Ok(Self {
ctx_ty: comma.and(ctx_ty), ctx_ty: comma.and(ctx_ty),
dep, deps,
}) })
} }
} }
@@ -522,11 +494,9 @@ impl Member {
struct StateMember<'a> { struct StateMember<'a> {
mem: &'a Member, mem: &'a Member,
dep_kind: DepKind, dep_kind: DepKind,
dep_mem: Option<&'a Member>, // the depenancy and if it is satified
dep_mems: Vec<(&'a Member, bool)>,
ctx_ty: Option<Type>, ctx_ty: Option<Type>,
dependants: Vec<(dioxus_native_core::state::MemberId, DepKind)>,
// This is just the index of the final order of the struct it is used to communicate which parts need updated and what order to update them in.
member_id: dioxus_native_core::state::MemberId,
} }
impl<'a> StateMember<'a> { impl<'a> StateMember<'a> {
@@ -548,26 +518,26 @@ impl<'a> StateMember<'a> {
})?; })?;
match a.parse_args::<Dependancy>() { match a.parse_args::<Dependancy>() {
Ok(dependancy) => { Ok(dependancy) => {
let dep_mem = if let Some(name) = &dependancy.dep { let dep_mems = dependancy
if let Some(found) = parent.members.iter().find(|m| &m.ident == name) { .deps
Some(found) .iter()
} else { .filter_map(|name| {
err = Err(Error::new( if let Some(found) = parent.members.iter().find(|m| &m.ident == name) {
name.span(), Some((found, false))
format!("{} not found in {}", name, parent.name), } else {
)); err = Err(Error::new(
None name.span(),
} format!("{} not found in {}", name, parent.name),
} else { ));
None None
}; }
})
.collect();
Some(Self { Some(Self {
mem, mem,
dep_kind, dep_kind,
dep_mem, dep_mems,
ctx_ty: dependancy.ctx_ty, ctx_ty: dependancy.ctx_ty,
dependants: Vec::new(),
member_id: dioxus_native_core::state::MemberId(0),
}) })
} }
Err(e) => { Err(e) => {
@@ -592,111 +562,26 @@ impl<'a> StateMember<'a> {
} else { } else {
quote! {&()} quote! {&()}
}; };
let states_changed = {
let child_dep = self
.dependants
.iter()
.filter(|(_, kind)| kind == &DepKind::Child)
.map(|(id, _)| id.0);
let parent_dep = self
.dependants
.iter()
.filter(|(_, kind)| kind == &DepKind::Parent)
.map(|(id, _)| id.0);
let node_dep = self
.dependants
.iter()
.filter(|(_, kind)| kind == &DepKind::Node)
.map(|(id, _)| id.0);
match self.dep_kind {
DepKind::Node => {
quote! {
dioxus_native_core::state::NodeStatesChanged{
node_dep: vec![#(dioxus_native_core::state::MemberId(#node_dep), )*],
}
}
}
DepKind::Child => {
quote! {
dioxus_native_core::state::ChildStatesChanged{
node_dep: vec![#(dioxus_native_core::state::MemberId(#node_dep), )*],
child_dep: vec![#(dioxus_native_core::state::MemberId(#child_dep), )*],
}
}
}
DepKind::Parent => {
quote! {
dioxus_native_core::state::ParentStatesChanged{
node_dep: vec![#(dioxus_native_core::state::MemberId(#node_dep), )*],
parent_dep: vec![#(dioxus_native_core::state::MemberId(#parent_dep), )*],
}
}
}
}
};
let ty = &self.mem.ty; let ty = &self.mem.ty;
let node_view = let node_view =
quote!(dioxus_native_core::node_ref::NodeView::new(node, #ty::NODE_MASK, vdom)); quote!(dioxus_native_core::node_ref::NodeView::new(vnode, #ty::NODE_MASK, vdom));
if let Some(dep_ident) = &self.dep_mem.map(|m| &m.ident) { let dep_idents = self.dep_mems.iter().map(|m| &m.0.ident);
match self.dep_kind { match self.dep_kind {
DepKind::Node => { DepKind::Node => {
quote!({ quote!({
if self.#ident.reduce(#node_view, &self.#dep_ident, #get_ctx){ current_state.#ident.reduce(#node_view, (#(&current_state.#dep_idents,)*), #get_ctx)
Some(#states_changed) })
} else{
None
}
})
}
DepKind::Child => {
quote!({
if self.#ident.reduce(#node_view, children.iter().map(|s| &s.#dep_ident), #get_ctx){
Some(#states_changed)
} else{
None
}
})
}
DepKind::Parent => {
quote!({
if self.#ident.reduce(#node_view, parent.as_ref().map(|p| &p.#dep_ident), #get_ctx){
Some(#states_changed)
} else{
None
}
})
}
} }
} else { DepKind::Child => {
match self.dep_kind { quote!({
DepKind::Node => { current_state.#ident.reduce(#node_view, children.iter().map(|c| (#(&c.#dep_idents)*)), #get_ctx)
quote!({ })
if self.#ident.reduce(#node_view, &(), #get_ctx){ }
Some(#states_changed) DepKind::Parent => {
} else{ quote!({
None current_state.#ident.reduce(#node_view, parent.as_ref().map(|p| (#(&p.#dep_idents)*)), #get_ctx)
} })
})
}
DepKind::Child => {
quote!({
if self.#ident.reduce(#node_view, std::iter::empty(), #get_ctx){
Some(#states_changed)
} else{
None
}
})
}
DepKind::Parent => {
quote!({
if self.#ident.reduce(#node_view, Some(&()), #get_ctx){
Some(#states_changed)
} else{
None
}
})
}
} }
} }
} }
@@ -0,0 +1,256 @@
use anymap::AnyMap;
use dioxus::core as dioxus_core;
use dioxus::prelude::*;
use dioxus_native_core::node_ref::*;
use dioxus_native_core::real_dom::*;
use dioxus_native_core::state::{ChildDepState, NodeDepState, ParentDepState, State};
use dioxus_native_core_macro::State;
macro_rules! dep {
( child( $name:ty, $dep:ty ) ) => {
impl ChildDepState for $name {
type Ctx = ();
type DepState = $dep;
const NODE_MASK: NodeMask = NodeMask::ALL;
fn reduce<'a>(
&mut self,
_: NodeView,
_: impl Iterator<Item = &'a Self::DepState>,
_: &Self::Ctx,
) -> bool
where
Self::DepState: 'a,
{
self.0 += 1;
true
}
}
};
( parent( $name:ty, $dep:ty ) ) => {
impl ParentDepState for $name {
type Ctx = ();
type DepState = $dep;
const NODE_MASK: NodeMask = NodeMask::ALL;
fn reduce(
&mut self,
_: NodeView,
_: Option<&Self::DepState>,
_: &Self::Ctx,
) -> bool {
self.0 += 1;
true
}
}
};
( node( $name:ty, ($($l:lifetime),*), $dep:ty ) ) => {
impl<$($l),*> NodeDepState<$dep> for $name {
type Ctx = ();
const NODE_MASK: NodeMask = NodeMask::ALL;
fn reduce(
&mut self,
_: NodeView,
_: $dep,
_: &Self::Ctx,
) -> bool {
self.0 += 1;
true
}
}
};
}
macro_rules! test_state{
( $s:ty, child: ( $( $child:ident ),* ), node: ( $( $node:ident ),* ), parent: ( $( $parent:ident ),* ) ) => {
#[test]
fn state_reduce_initally_called_minimally() {
#[allow(non_snake_case)]
fn Base(cx: Scope) -> Element {
rsx!(cx, div {
div{
div{
p{}
}
p{
"hello"
}
div{
h1{}
}
p{
"world"
}
}
})
}
let vdom = VirtualDom::new(Base);
let mutations = vdom.create_vnodes(rsx! {
div {
div{
div{
p{}
}
p{
"hello"
}
div{
h1{}
}
p{
"world"
}
}
}
});
let mut dom: RealDom<$s> = RealDom::new();
let nodes_updated = dom.apply_mutations(vec![mutations]);
let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new());
dom.traverse_depth_first(|n| {
$(
assert_eq!(n.state.$child.0, 1);
)*
$(
assert_eq!(n.state.$node.0, 1);
)*
$(
assert_eq!(n.state.$parent.0, 1);
)*
});
}
}
}
mod node_depends_on_child_and_parent {
use super::*;
#[derive(Debug, Clone, Default, PartialEq)]
struct Node(i32);
dep!(node(Node, ('a, 'b), (&'a Child, &'b Parent)));
#[derive(Debug, Clone, Default, PartialEq)]
struct Child(i32);
dep!(child(Child, Child));
#[derive(Debug, Clone, Default, PartialEq)]
struct Parent(i32);
dep!(parent(Parent, Parent));
#[derive(Debug, Clone, Default, State)]
struct StateTester {
#[node_dep_state((child, parent))]
node: Node,
#[child_dep_state(child)]
child: Child,
#[parent_dep_state(parent)]
parent: Parent,
}
test_state!(StateTester, child: (child), node: (node), parent: (parent));
}
mod child_depends_on_node_that_depends_on_parent {
use super::*;
#[derive(Debug, Clone, Default, PartialEq)]
struct Node(i32);
dep!(node(Node, ('a), (&'a Parent,)));
#[derive(Debug, Clone, Default, PartialEq)]
struct Child(i32);
dep!(child(Child, Node));
#[derive(Debug, Clone, Default, PartialEq)]
struct Parent(i32);
dep!(parent(Parent, Parent));
#[derive(Debug, Clone, Default, State)]
struct StateTester {
#[node_dep_state(parent)]
node: Node,
#[child_dep_state(node)]
child: Child,
#[parent_dep_state(parent)]
parent: Parent,
}
test_state!(StateTester, child: (child), node: (node), parent: (parent));
}
mod parent_depends_on_node_that_depends_on_child {
use super::*;
#[derive(Debug, Clone, Default, PartialEq)]
struct Node(i32);
dep!(node(Node, ('a), (&'a Child,)));
#[derive(Debug, Clone, Default, PartialEq)]
struct Child(i32);
dep!(child(Child, Child));
#[derive(Debug, Clone, Default, PartialEq)]
struct Parent(i32);
dep!(parent(Parent, Node));
#[derive(Debug, Clone, Default, State)]
struct StateTester {
#[node_dep_state(child)]
node: Node,
#[child_dep_state(child)]
child: Child,
#[parent_dep_state(node)]
parent: Parent,
}
test_state!(StateTester, child: (child), node: (node), parent: (parent));
}
mod node_depends_on_other_node_state {
use super::*;
#[derive(Debug, Clone, Default, PartialEq)]
struct Node1(i32);
dep!(node(Node1, ('a), (&'a Node2,)));
#[derive(Debug, Clone, Default, PartialEq)]
struct Node2(i32);
dep!(node(Node2, (), ()));
#[derive(Debug, Clone, Default, State)]
struct StateTester {
#[node_dep_state((node2))]
node1: Node1,
#[node_dep_state()]
node2: Node2,
}
test_state!(StateTester, child: (), node: (node1, node2), parent: ());
}
mod node_child_and_parent_state_depends_on_self {
use super::*;
#[derive(Debug, Clone, Default, PartialEq)]
struct Node(i32);
dep!(node(Node, (), ()));
#[derive(Debug, Clone, Default, PartialEq)]
struct Child(i32);
dep!(child(Child, Child));
#[derive(Debug, Clone, Default, PartialEq)]
struct Parent(i32);
dep!(parent(Parent, Parent));
#[derive(Debug, Clone, Default, State)]
struct StateTester {
#[node_dep_state()]
node: Node,
#[child_dep_state(child)]
child: Child,
#[parent_dep_state(parent)]
parent: Parent,
}
test_state!(StateTester, child: (child), node: (node), parent: (parent));
}
@@ -51,8 +51,8 @@ fn remove_node() {
assert_eq!(dom.size(), 2); assert_eq!(dom.size(), 2);
assert!(&dom.contains_node(&VNode::Element(&root_div))); assert!(&dom.contains_node(&VNode::Element(&root_div)));
assert_eq!(dom[1].height, 1); assert_eq!(dom[ElementId(1)].height, 1);
assert_eq!(dom[2].height, 2); assert_eq!(dom[ElementId(2)].height, 2);
let vdom = VirtualDom::new(Base); let vdom = VirtualDom::new(Base);
let mutations = vdom.diff_lazynodes( let mutations = vdom.diff_lazynodes(
@@ -80,7 +80,7 @@ fn remove_node() {
assert_eq!(dom.size(), 1); assert_eq!(dom.size(), 1);
assert!(&dom.contains_node(&VNode::Element(&new_root_div))); assert!(&dom.contains_node(&VNode::Element(&new_root_div)));
assert_eq!(dom[1].height, 1); assert_eq!(dom[ElementId(1)].height, 1);
} }
#[test] #[test]
@@ -113,7 +113,7 @@ fn add_node() {
assert_eq!(dom.size(), 1); assert_eq!(dom.size(), 1);
assert!(&dom.contains_node(&VNode::Element(&root_div))); assert!(&dom.contains_node(&VNode::Element(&root_div)));
assert_eq!(dom[1].height, 1); assert_eq!(dom[ElementId(1)].height, 1);
let vdom = VirtualDom::new(Base); let vdom = VirtualDom::new(Base);
let mutations = vdom.diff_lazynodes( let mutations = vdom.diff_lazynodes(
@@ -152,6 +152,6 @@ fn add_node() {
assert_eq!(dom.size(), 2); assert_eq!(dom.size(), 2);
assert!(&dom.contains_node(&VNode::Element(&new_root_div))); assert!(&dom.contains_node(&VNode::Element(&new_root_div)));
assert_eq!(dom[1].height, 1); assert_eq!(dom[ElementId(1)].height, 1);
assert_eq!(dom[2].height, 2); assert_eq!(dom[ElementId(2)].height, 2);
} }
@@ -40,7 +40,7 @@ fn initial_build_simple() {
}; };
assert_eq!(dom.size(), 1); assert_eq!(dom.size(), 1);
assert!(&dom.contains_node(&VNode::Element(&root_div))); assert!(&dom.contains_node(&VNode::Element(&root_div)));
assert_eq!(dom[1].height, 1); assert_eq!(dom[ElementId(1)].height, 1);
} }
#[test] #[test]
@@ -119,10 +119,10 @@ fn initial_build_with_children() {
}; };
assert_eq!(dom.size(), 6); assert_eq!(dom.size(), 6);
assert!(&dom.contains_node(&VNode::Element(&root_div))); assert!(&dom.contains_node(&VNode::Element(&root_div)));
assert_eq!(dom[1].height, 1); assert_eq!(dom[ElementId(1)].height, 1);
assert_eq!(dom[2].height, 2); assert_eq!(dom[ElementId(2)].height, 2);
assert_eq!(dom[3].height, 3); assert_eq!(dom[ElementId(3)].height, 3);
assert_eq!(dom[4].height, 3); assert_eq!(dom[ElementId(4)].height, 3);
assert_eq!(dom[5].height, 4); assert_eq!(dom[ElementId(5)].height, 4);
assert_eq!(dom[6].height, 3); assert_eq!(dom[ElementId(6)].height, 3);
} }
@@ -1,5 +1,6 @@
use anymap::AnyMap; use anymap::AnyMap;
use dioxus::core as dioxus_core; use dioxus::core as dioxus_core;
use dioxus::core::ElementId;
use dioxus::core::{AttributeValue, DomEdit, Mutations}; use dioxus::core::{AttributeValue, DomEdit, Mutations};
use dioxus::prelude::*; use dioxus::prelude::*;
use dioxus_native_core::node_ref::*; use dioxus_native_core::node_ref::*;
@@ -49,13 +50,14 @@ impl ChildDepState for ChildDepCallCounter {
const NODE_MASK: NodeMask = NodeMask::ALL; const NODE_MASK: NodeMask = NodeMask::ALL;
fn reduce<'a>( fn reduce<'a>(
&mut self, &mut self,
_: NodeView, node: NodeView,
_: impl Iterator<Item = &'a Self::DepState>, _: impl Iterator<Item = &'a Self::DepState>,
_: &Self::Ctx, _: &Self::Ctx,
) -> bool ) -> bool
where where
Self::DepState: 'a, Self::DepState: 'a,
{ {
println!("{self:?} {:?}: {} {:?}", node.tag(), node.id(), node.text());
self.0 += 1; self.0 += 1;
true true
} }
@@ -80,11 +82,10 @@ impl ParentDepState for ParentDepCallCounter {
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
struct NodeDepCallCounter(u32); struct NodeDepCallCounter(u32);
impl NodeDepState for NodeDepCallCounter { impl NodeDepState<()> for NodeDepCallCounter {
type Ctx = (); type Ctx = ();
type DepState = ();
const NODE_MASK: NodeMask = NodeMask::ALL; const NODE_MASK: NodeMask = NodeMask::ALL;
fn reduce(&mut self, _node: NodeView, _sibling: &Self::DepState, _ctx: &Self::Ctx) -> bool { fn reduce(&mut self, _node: NodeView, _sibling: (), _ctx: &Self::Ctx) -> bool {
self.0 += 1; self.0 += 1;
true true
} }
@@ -133,11 +134,10 @@ impl ParentDepState for PushedDownStateTester {
#[derive(Debug, Clone, PartialEq, Default)] #[derive(Debug, Clone, PartialEq, Default)]
struct NodeStateTester(Option<String>, Vec<(String, String)>); struct NodeStateTester(Option<String>, Vec<(String, String)>);
impl NodeDepState for NodeStateTester { impl NodeDepState<()> for NodeStateTester {
type Ctx = u32; type Ctx = u32;
type DepState = ();
const NODE_MASK: NodeMask = NodeMask::new_with_attrs(AttributeMask::All).with_tag(); const NODE_MASK: NodeMask = NodeMask::new_with_attrs(AttributeMask::All).with_tag();
fn reduce(&mut self, node: NodeView, _sibling: &Self::DepState, ctx: &Self::Ctx) -> bool { fn reduce(&mut self, node: NodeView, _sibling: (), ctx: &Self::Ctx) -> bool {
assert_eq!(*ctx, 42); assert_eq!(*ctx, 42);
*self = NodeStateTester( *self = NodeStateTester(
node.tag().map(|s| s.to_string()), node.tag().map(|s| s.to_string()),
@@ -187,7 +187,7 @@ fn state_initial() {
ctx.insert(42u32); ctx.insert(42u32);
let _to_rerender = dom.update_state(&vdom, nodes_updated, ctx); let _to_rerender = dom.update_state(&vdom, nodes_updated, ctx);
let root_div = &dom[1]; let root_div = &dom[ElementId(1)];
assert_eq!(root_div.state.bubbled.0, Some("div".to_string())); assert_eq!(root_div.state.bubbled.0, Some("div".to_string()));
assert_eq!( assert_eq!(
root_div.state.bubbled.1, root_div.state.bubbled.1,
@@ -204,7 +204,7 @@ fn state_initial() {
assert_eq!(root_div.state.node.0, Some("div".to_string())); assert_eq!(root_div.state.node.0, Some("div".to_string()));
assert_eq!(root_div.state.node.1, vec![]); assert_eq!(root_div.state.node.1, vec![]);
let child_p = &dom[2]; let child_p = &dom[ElementId(2)];
assert_eq!(child_p.state.bubbled.0, Some("p".to_string())); assert_eq!(child_p.state.bubbled.0, Some("p".to_string()));
assert_eq!(child_p.state.bubbled.1, Vec::new()); assert_eq!(child_p.state.bubbled.1, Vec::new());
assert_eq!(child_p.state.pushed.0, Some("p".to_string())); assert_eq!(child_p.state.pushed.0, Some("p".to_string()));
@@ -221,7 +221,7 @@ fn state_initial() {
vec![("color".to_string(), "red".to_string())] vec![("color".to_string(), "red".to_string())]
); );
let child_h1 = &dom[3]; let child_h1 = &dom[ElementId(3)];
assert_eq!(child_h1.state.bubbled.0, Some("h1".to_string())); assert_eq!(child_h1.state.bubbled.0, Some("h1".to_string()));
assert_eq!(child_h1.state.bubbled.1, Vec::new()); assert_eq!(child_h1.state.bubbled.1, Vec::new());
assert_eq!(child_h1.state.pushed.0, Some("h1".to_string())); assert_eq!(child_h1.state.pushed.0, Some("h1".to_string()));
@@ -236,64 +236,6 @@ fn state_initial() {
assert_eq!(child_h1.state.node.1, vec![]); assert_eq!(child_h1.state.node.1, vec![]);
} }
#[test]
fn state_reduce_initally_called_minimally() {
#[allow(non_snake_case)]
fn Base(cx: Scope) -> Element {
rsx!(cx, div {
div{
div{
p{}
}
p{
"hello"
}
div{
h1{}
}
p{
"world"
}
}
})
}
let vdom = VirtualDom::new(Base);
let mutations = vdom.create_vnodes(rsx! {
div {
div{
div{
p{}
}
p{
"hello"
}
div{
h1{}
}
p{
"world"
}
}
}
});
let mut dom: RealDom<CallCounterState> = RealDom::new();
let nodes_updated = dom.apply_mutations(vec![mutations]);
let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new());
dom.traverse_depth_first(|n| {
assert_eq!(n.state.part1.child_counter.0, 1);
assert_eq!(n.state.child_counter.0, 1);
assert_eq!(n.state.part2.parent_counter.0, 1);
assert_eq!(n.state.parent_counter.0, 1);
assert_eq!(n.state.part3.node_counter.0, 1);
assert_eq!(n.state.node_counter.0, 1);
});
}
#[test] #[test]
fn state_reduce_parent_called_minimally_on_update() { fn state_reduce_parent_called_minimally_on_update() {
#[allow(non_snake_case)] #[allow(non_snake_case)]
@@ -446,11 +388,15 @@ struct UnorderedDependanciesState {
#[derive(Debug, Clone, Default, PartialEq)] #[derive(Debug, Clone, Default, PartialEq)]
struct ADepCallCounter(usize, BDepCallCounter); struct ADepCallCounter(usize, BDepCallCounter);
impl NodeDepState for ADepCallCounter { impl<'a> NodeDepState<(&'a BDepCallCounter,)> for ADepCallCounter {
type Ctx = (); type Ctx = ();
type DepState = BDepCallCounter;
const NODE_MASK: NodeMask = NodeMask::NONE; const NODE_MASK: NodeMask = NodeMask::NONE;
fn reduce(&mut self, _node: NodeView, sibling: &Self::DepState, _ctx: &Self::Ctx) -> bool { fn reduce(
&mut self,
_node: NodeView,
(sibling,): (&'a BDepCallCounter,),
_ctx: &Self::Ctx,
) -> bool {
self.0 += 1; self.0 += 1;
self.1 = sibling.clone(); self.1 = sibling.clone();
true true
@@ -459,11 +405,15 @@ impl NodeDepState for ADepCallCounter {
#[derive(Debug, Clone, Default, PartialEq)] #[derive(Debug, Clone, Default, PartialEq)]
struct BDepCallCounter(usize, CDepCallCounter); struct BDepCallCounter(usize, CDepCallCounter);
impl NodeDepState for BDepCallCounter { impl<'a> NodeDepState<(&'a CDepCallCounter,)> for BDepCallCounter {
type Ctx = (); type Ctx = ();
type DepState = CDepCallCounter;
const NODE_MASK: NodeMask = NodeMask::NONE; const NODE_MASK: NodeMask = NodeMask::NONE;
fn reduce(&mut self, _node: NodeView, sibling: &Self::DepState, _ctx: &Self::Ctx) -> bool { fn reduce(
&mut self,
_node: NodeView,
(sibling,): (&'a CDepCallCounter,),
_ctx: &Self::Ctx,
) -> bool {
self.0 += 1; self.0 += 1;
self.1 = sibling.clone(); self.1 = sibling.clone();
true true
@@ -472,11 +422,10 @@ impl NodeDepState for BDepCallCounter {
#[derive(Debug, Clone, Default, PartialEq)] #[derive(Debug, Clone, Default, PartialEq)]
struct CDepCallCounter(usize); struct CDepCallCounter(usize);
impl NodeDepState for CDepCallCounter { impl NodeDepState<()> for CDepCallCounter {
type Ctx = (); type Ctx = ();
type DepState = ();
const NODE_MASK: NodeMask = NodeMask::ALL; const NODE_MASK: NodeMask = NodeMask::ALL;
fn reduce(&mut self, _node: NodeView, _sibling: &Self::DepState, _ctx: &Self::Ctx) -> bool { fn reduce(&mut self, _node: NodeView, _sibling: (), _ctx: &Self::Ctx) -> bool {
self.0 += 1; self.0 += 1;
true true
} }
+2
View File
@@ -2,4 +2,6 @@ pub mod layout_attributes;
pub mod node_ref; pub mod node_ref;
pub mod real_dom; pub mod real_dom;
pub mod state; pub mod state;
#[doc(hidden)]
pub mod traversable;
pub mod utils; pub mod utils;
+2 -2
View File
@@ -70,7 +70,7 @@ impl<'a> NodeView<'a> {
} }
} }
#[derive(PartialEq, Clone, Debug)] #[derive(PartialEq, Eq, Clone, Debug)]
pub enum AttributeMask { pub enum AttributeMask {
All, All,
Dynamic(Vec<&'static str>), Dynamic(Vec<&'static str>),
@@ -175,7 +175,7 @@ impl Default for AttributeMask {
} }
} }
#[derive(Default, PartialEq, Clone, Debug)] #[derive(Default, PartialEq, Eq, Clone, Debug)]
pub struct NodeMask { pub struct NodeMask {
// must be sorted // must be sorted
attritutes: AttributeMask, attritutes: AttributeMask,
+96 -382
View File
@@ -1,17 +1,12 @@
use anymap::AnyMap; use anymap::AnyMap;
use fxhash::{FxHashMap, FxHashSet}; use fxhash::{FxHashMap, FxHashSet};
use std::{ use std::ops::{Index, IndexMut};
collections::VecDeque,
ops::{Index, IndexMut},
};
use dioxus_core::{ElementId, Mutations, VNode, VirtualDom}; use dioxus_core::{ElementId, Mutations, VNode, VirtualDom};
use crate::state::{union_ordered_iter, State}; use crate::node_ref::{AttributeMask, NodeMask};
use crate::{ use crate::state::State;
node_ref::{AttributeMask, NodeMask}, use crate::traversable::Traversable;
state::MemberId,
};
/// A Dom that can sync with the VirtualDom mutations intended for use in lazy renderers. /// A Dom that can sync with the VirtualDom mutations intended for use in lazy renderers.
/// The render state passes from parent to children and or accumulates state from children to parents. /// The render state passes from parent to children and or accumulates state from children to parents.
@@ -20,7 +15,7 @@ use crate::{
pub struct RealDom<S: State> { pub struct RealDom<S: State> {
root: usize, root: usize,
nodes: Vec<Option<Node<S>>>, nodes: Vec<Option<Node<S>>>,
nodes_listening: FxHashMap<&'static str, FxHashSet<usize>>, nodes_listening: FxHashMap<&'static str, FxHashSet<ElementId>>,
node_stack: smallvec::SmallVec<[usize; 10]>, node_stack: smallvec::SmallVec<[usize; 10]>,
} }
@@ -51,7 +46,7 @@ impl<S: State> RealDom<S> {
} }
/// Updates the dom, up and down state and return a set of nodes that were updated pass this to update_state. /// Updates the dom, up and down state and return a set of nodes that were updated pass this to update_state.
pub fn apply_mutations(&mut self, mutations_vec: Vec<Mutations>) -> Vec<(usize, NodeMask)> { pub fn apply_mutations(&mut self, mutations_vec: Vec<Mutations>) -> Vec<(ElementId, NodeMask)> {
let mut nodes_updated = Vec::new(); let mut nodes_updated = Vec::new();
for mutations in mutations_vec { for mutations in mutations_vec {
for e in mutations.edits { for e in mutations.edits {
@@ -72,40 +67,44 @@ impl<S: State> RealDom<S> {
.drain(self.node_stack.len() - many as usize..) .drain(self.node_stack.len() - many as usize..)
.collect(); .collect();
for ns in drained { for ns in drained {
self.link_child(ns, target).unwrap(); let id = ElementId(ns);
nodes_updated.push((ns, NodeMask::ALL)); self.link_child(id, ElementId(target)).unwrap();
nodes_updated.push((id, NodeMask::ALL));
} }
} }
ReplaceWith { root, m } => { ReplaceWith { root, m } => {
let root = self.remove(root as usize).unwrap(); let root = self.remove(ElementId(root as usize)).unwrap();
let target = root.parent.unwrap().0; let target = root.parent.unwrap().0;
let drained: Vec<_> = self.node_stack.drain(0..m as usize).collect(); let drained: Vec<_> = self.node_stack.drain(0..m as usize).collect();
for ns in drained { for ns in drained {
nodes_updated.push((ns, NodeMask::ALL)); let id = ElementId(ns);
self.link_child(ns, target).unwrap(); nodes_updated.push((id, NodeMask::ALL));
self.link_child(id, ElementId(target)).unwrap();
} }
} }
InsertAfter { root, n } => { InsertAfter { root, n } => {
let target = self[root as usize].parent.unwrap().0; let target = self[ElementId(root as usize)].parent.unwrap().0;
let drained: Vec<_> = self.node_stack.drain(0..n as usize).collect(); let drained: Vec<_> = self.node_stack.drain(0..n as usize).collect();
for ns in drained { for ns in drained {
nodes_updated.push((ns, NodeMask::ALL)); let id = ElementId(ns);
self.link_child(ns, target).unwrap(); nodes_updated.push((id, NodeMask::ALL));
self.link_child(id, ElementId(target)).unwrap();
} }
} }
InsertBefore { root, n } => { InsertBefore { root, n } => {
let target = self[root as usize].parent.unwrap().0; let target = self[ElementId(root as usize)].parent.unwrap().0;
let drained: Vec<_> = self.node_stack.drain(0..n as usize).collect(); let drained: Vec<_> = self.node_stack.drain(0..n as usize).collect();
for ns in drained { for ns in drained {
nodes_updated.push((ns, NodeMask::ALL)); let id = ElementId(ns);
self.link_child(ns, target).unwrap(); nodes_updated.push((id, NodeMask::ALL));
self.link_child(id, ElementId(target)).unwrap();
} }
} }
Remove { root } => { Remove { root } => {
if let Some(parent) = self[root as usize].parent { if let Some(parent) = self[ElementId(root as usize)].parent {
nodes_updated.push((parent.0, NodeMask::NONE)); nodes_updated.push((parent, NodeMask::NONE));
} }
self.remove(root as usize).unwrap(); self.remove(ElementId(root as usize)).unwrap();
} }
CreateTextNode { root, text } => { CreateTextNode { root, text } => {
let n = Node::new( let n = Node::new(
@@ -152,26 +151,29 @@ impl<S: State> RealDom<S> {
scope: _, scope: _,
root, root,
} => { } => {
nodes_updated.push((root as usize, NodeMask::new().with_listeners())); let id = ElementId(root as usize);
nodes_updated.push((id, NodeMask::new().with_listeners()));
if let Some(v) = self.nodes_listening.get_mut(event_name) { if let Some(v) = self.nodes_listening.get_mut(event_name) {
v.insert(root as usize); v.insert(id);
} else { } else {
let mut hs = FxHashSet::default(); let mut hs = FxHashSet::default();
hs.insert(root as usize); hs.insert(id);
self.nodes_listening.insert(event_name, hs); self.nodes_listening.insert(event_name, hs);
} }
} }
RemoveEventListener { root, event } => { RemoveEventListener { root, event } => {
nodes_updated.push((root as usize, NodeMask::new().with_listeners())); let id = ElementId(root as usize);
nodes_updated.push((id, NodeMask::new().with_listeners()));
let v = self.nodes_listening.get_mut(event).unwrap(); let v = self.nodes_listening.get_mut(event).unwrap();
v.remove(&(root as usize)); v.remove(&id);
} }
SetText { SetText {
root, root,
text: new_text, text: new_text,
} => { } => {
let target = &mut self[root as usize]; let id = ElementId(root as usize);
nodes_updated.push((root as usize, NodeMask::new().with_text())); let target = &mut self[id];
nodes_updated.push((id, NodeMask::new().with_text()));
match &mut target.node_type { match &mut target.node_type {
NodeType::Text { text } => { NodeType::Text { text } => {
*text = new_text.to_string(); *text = new_text.to_string();
@@ -180,18 +182,16 @@ impl<S: State> RealDom<S> {
} }
} }
SetAttribute { root, field, .. } => { SetAttribute { root, field, .. } => {
nodes_updated.push(( let id = ElementId(root as usize);
root as usize, nodes_updated
NodeMask::new_with_attrs(AttributeMask::single(field)), .push((id, NodeMask::new_with_attrs(AttributeMask::single(field))));
));
} }
RemoveAttribute { RemoveAttribute {
root, name: field, .. root, name: field, ..
} => { } => {
nodes_updated.push(( let id = ElementId(root as usize);
root as usize, nodes_updated
NodeMask::new_with_attrs(AttributeMask::single(field)), .push((id, NodeMask::new_with_attrs(AttributeMask::single(field))));
));
} }
PopRoot {} => { PopRoot {} => {
self.node_stack.pop(); self.node_stack.pop();
@@ -203,309 +203,60 @@ impl<S: State> RealDom<S> {
nodes_updated nodes_updated
} }
/// Seperated from apply_mutations because Mutations require a mutable reference to the VirtualDom.
pub fn update_state( pub fn update_state(
&mut self, &mut self,
vdom: &VirtualDom, vdom: &VirtualDom,
nodes_updated: Vec<(usize, NodeMask)>, nodes_updated: Vec<(ElementId, NodeMask)>,
ctx: AnyMap, ctx: AnyMap,
) -> Option<FxHashSet<usize>> { ) -> FxHashSet<ElementId> {
#[derive(PartialEq, Clone, Debug)] S::update(
enum StatesToCheck { &nodes_updated,
All, &mut self.map(|n| &n.state, |n| &mut n.state),
Some(Vec<MemberId>), vdom,
} &ctx,
impl StatesToCheck { )
fn union(&self, other: &Self) -> Self {
match (self, other) {
(Self::Some(s), Self::Some(o)) => Self::Some(union_ordered_iter(
s.iter().copied(),
o.iter().copied(),
s.len() + o.len(),
)),
_ => Self::All,
}
}
}
#[derive(Debug, Clone)]
struct NodeRef {
id: usize,
height: u16,
node_mask: NodeMask,
to_check: StatesToCheck,
}
impl NodeRef {
fn union_with(&mut self, other: &Self) {
self.node_mask = self.node_mask.union(&other.node_mask);
self.to_check = self.to_check.union(&other.to_check);
}
}
impl Eq for NodeRef {}
impl PartialEq for NodeRef {
fn eq(&self, other: &Self) -> bool {
self.id == other.id && self.height == other.height
}
}
impl Ord for NodeRef {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.partial_cmp(other).unwrap()
}
}
impl PartialOrd for NodeRef {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
// Sort nodes first by height, then if the height is the same id.
// The order of the id does not matter it just helps with binary search later
Some(self.height.cmp(&other.height).then(self.id.cmp(&other.id)))
}
}
let mut to_rerender = FxHashSet::default();
to_rerender.extend(nodes_updated.iter().map(|(id, _)| id));
let mut nodes_updated: Vec<_> = nodes_updated
.into_iter()
.map(|(id, mask)| NodeRef {
id,
height: self[id].height,
node_mask: mask,
to_check: StatesToCheck::All,
})
.collect();
nodes_updated.sort();
// Combine mutations that affect the same node.
let mut last_node: Option<NodeRef> = None;
let mut new_nodes_updated = VecDeque::new();
for current in nodes_updated.into_iter() {
if let Some(node) = &mut last_node {
if *node == current {
node.union_with(&current);
} else {
new_nodes_updated.push_back(last_node.replace(current).unwrap());
}
} else {
last_node = Some(current);
}
}
if let Some(node) = last_node {
new_nodes_updated.push_back(node);
}
let nodes_updated = new_nodes_updated;
// update the state that only depends on nodes. The order does not matter.
for node_ref in &nodes_updated {
let mut changed = false;
let node = &mut self[node_ref.id];
let mut ids = match &node_ref.to_check {
StatesToCheck::All => node.state.node_dep_types(&node_ref.node_mask),
// this should only be triggered from the current node, so all members will need to be checked
StatesToCheck::Some(_) => unreachable!(),
};
let mut i = 0;
while i < ids.len() {
let id = ids[i];
let node = &mut self[node_ref.id];
let vnode = node.element(vdom);
if let Some(members_effected) =
node.state.update_node_dep_state(id, vnode, vdom, &ctx)
{
debug_assert!(members_effected.node_dep.iter().all(|i| i >= &id));
for m in &members_effected.node_dep {
if let Err(idx) = ids.binary_search(m) {
ids.insert(idx, *m);
}
}
changed = true;
}
i += 1;
}
if changed {
to_rerender.insert(node_ref.id);
}
}
// bubble up state. To avoid calling reduce more times than nessisary start from the bottom and go up.
let mut to_bubble = nodes_updated.clone();
while let Some(node_ref) = to_bubble.pop_back() {
let NodeRef {
id,
height,
node_mask,
to_check,
} = node_ref;
let (node, children) = self.get_node_children_mut(id).unwrap();
let children_state: Vec<_> = children.iter().map(|c| &c.state).collect();
let mut ids = match to_check {
StatesToCheck::All => node.state.child_dep_types(&node_mask),
StatesToCheck::Some(ids) => ids,
};
let mut changed = Vec::new();
let mut i = 0;
while i < ids.len() {
let id = ids[i];
let vnode = node.element(vdom);
if let Some(members_effected) =
node.state
.update_child_dep_state(id, vnode, vdom, &children_state, &ctx)
{
debug_assert!(members_effected.node_dep.iter().all(|i| i >= &id));
for m in members_effected.node_dep {
if let Err(idx) = ids.binary_search(&m) {
ids.insert(idx, m);
}
}
for m in members_effected.child_dep {
changed.push(m);
}
}
i += 1;
}
if let Some(parent_id) = node.parent {
if !changed.is_empty() {
to_rerender.insert(id);
let i = to_bubble.partition_point(
|NodeRef {
id: other_id,
height: h,
..
}| {
*h < height - 1 || (*h == height - 1 && *other_id < parent_id.0)
},
);
// make sure the parent is not already queued
if i < to_bubble.len() && to_bubble[i].id == parent_id.0 {
to_bubble[i].to_check =
to_bubble[i].to_check.union(&StatesToCheck::Some(changed));
} else {
to_bubble.insert(
i,
NodeRef {
id: parent_id.0,
height: height - 1,
node_mask: NodeMask::NONE,
to_check: StatesToCheck::Some(changed),
},
);
}
}
}
}
// push down state. To avoid calling reduce more times than nessisary start from the top and go down.
let mut to_push = nodes_updated;
while let Some(node_ref) = to_push.pop_front() {
let NodeRef {
id,
height,
node_mask,
to_check,
} = node_ref;
let node = &self[id];
let mut ids = match to_check {
StatesToCheck::All => node.state.parent_dep_types(&node_mask),
StatesToCheck::Some(ids) => ids,
};
let mut changed = Vec::new();
let (node, parent) = self.get_node_parent_mut(id).unwrap();
let mut i = 0;
while i < ids.len() {
let id = ids[i];
let vnode = node.element(vdom);
let parent = parent.as_deref();
let state = &mut node.state;
if let Some(members_effected) =
state.update_parent_dep_state(id, vnode, vdom, parent.map(|n| &n.state), &ctx)
{
debug_assert!(members_effected.node_dep.iter().all(|i| i >= &id));
for m in members_effected.node_dep {
if let Err(idx) = ids.binary_search(&m) {
ids.insert(idx, m);
}
}
for m in members_effected.parent_dep {
changed.push(m);
}
}
i += 1;
}
to_rerender.insert(id);
if !changed.is_empty() {
let node = &self[id];
if let NodeType::Element { children, .. } = &node.node_type {
for c in children {
let i = to_push.partition_point(
|NodeRef {
id: other_id,
height: h,
..
}| {
*h < height + 1 || (*h == height + 1 && *other_id < c.0)
},
);
if i < to_push.len() && to_push[i].id == c.0 {
to_push[i].to_check = to_push[i]
.to_check
.union(&StatesToCheck::Some(changed.clone()));
} else {
to_push.insert(
i,
NodeRef {
id: c.0,
height: height + 1,
node_mask: NodeMask::NONE,
to_check: StatesToCheck::Some(changed.clone()),
},
);
}
}
}
}
}
Some(to_rerender)
} }
fn link_child(&mut self, child_id: usize, parent_id: usize) -> Option<()> { fn link_child(&mut self, child_id: ElementId, parent_id: ElementId) -> Option<()> {
debug_assert_ne!(child_id, parent_id); debug_assert_ne!(child_id, parent_id);
let parent = &mut self[parent_id]; let parent = &mut self[parent_id];
parent.add_child(ElementId(child_id)); parent.add_child(child_id);
let parent_height = parent.height + 1; let parent_height = parent.height + 1;
self[child_id].set_parent(ElementId(parent_id)); self[child_id].set_parent(parent_id);
self.increase_height(child_id, parent_height); self.increase_height(child_id, parent_height);
Some(()) Some(())
} }
fn increase_height(&mut self, id: usize, amount: u16) { fn increase_height(&mut self, id: ElementId, amount: u16) {
let n = &mut self[id]; let n = &mut self[id];
n.height += amount; n.height += amount;
if let NodeType::Element { children, .. } = &n.node_type { if let NodeType::Element { children, .. } = &n.node_type {
for c in children.clone() { for c in children.clone() {
self.increase_height(c.0, amount); self.increase_height(c, amount);
} }
} }
} }
// remove a node and it's children from the dom. // remove a node and it's children from the dom.
fn remove(&mut self, id: usize) -> Option<Node<S>> { fn remove(&mut self, id: ElementId) -> Option<Node<S>> {
// We do not need to remove the node from the parent's children list for children. // We do not need to remove the node from the parent's children list for children.
fn inner<S: State>(dom: &mut RealDom<S>, id: usize) -> Option<Node<S>> { fn inner<S: State>(dom: &mut RealDom<S>, id: ElementId) -> Option<Node<S>> {
let mut node = dom.nodes[id as usize].take()?; let mut node = dom.nodes[id.0].take()?;
if let NodeType::Element { children, .. } = &mut node.node_type { if let NodeType::Element { children, .. } = &mut node.node_type {
for c in children { for c in children {
inner(dom, c.0)?; inner(dom, *c)?;
} }
} }
Some(node) Some(node)
} }
let mut node = self.nodes[id as usize].take()?; let mut node = self.nodes[id.0].take()?;
if let Some(parent) = node.parent { if let Some(parent) = node.parent {
let parent = &mut self[parent]; let parent = &mut self[parent];
parent.remove_child(ElementId(id)); parent.remove_child(id);
} }
if let NodeType::Element { children, .. } = &mut node.node_type { if let NodeType::Element { children, .. } = &mut node.node_type {
for c in children { for c in children {
inner(self, c.0)?; inner(self, *c)?;
} }
} }
Some(node) Some(node)
@@ -521,62 +272,6 @@ impl<S: State> RealDom<S> {
self.nodes[id] = Some(node); self.nodes[id] = Some(node);
} }
pub fn get(&self, id: usize) -> Option<&Node<S>> {
self.nodes.get(id)?.as_ref()
}
pub fn get_mut(&mut self, id: usize) -> Option<&mut Node<S>> {
self.nodes.get_mut(id)?.as_mut()
}
// this is safe because no node will have itself as a child
pub fn get_node_children_mut(
&mut self,
id: usize,
) -> Option<(&mut Node<S>, Vec<&mut Node<S>>)> {
let ptr = self.nodes.as_mut_ptr();
unsafe {
if id >= self.nodes.len() {
None
} else {
let node = &mut *ptr.add(id);
if let Some(node) = node.as_mut() {
let children = match &node.node_type {
NodeType::Element { children, .. } => children
.iter()
.map(|id| (&mut *ptr.add(id.0)).as_mut().unwrap())
.collect(),
_ => Vec::new(),
};
Some((node, children))
} else {
None
}
}
}
}
// this is safe because no node will have itself as a parent
pub fn get_node_parent_mut(
&mut self,
id: usize,
) -> Option<(&mut Node<S>, Option<&mut Node<S>>)> {
let ptr = self.nodes.as_mut_ptr();
unsafe {
let node = &mut *ptr.add(id);
if id >= self.nodes.len() {
None
} else if let Some(node) = node.as_mut() {
let parent = node
.parent
.map(|id| (&mut *ptr.add(id.0)).as_mut().unwrap());
Some((node, parent))
} else {
None
}
}
}
pub fn get_listening_sorted(&self, event: &'static str) -> Vec<&Node<S>> { pub fn get_listening_sorted(&self, event: &'static str) -> Vec<&Node<S>> {
if let Some(nodes) = self.nodes_listening.get(event) { if let Some(nodes) = self.nodes_listening.get(event) {
let mut listening: Vec<_> = nodes.iter().map(|id| &self[*id]).collect(); let mut listening: Vec<_> = nodes.iter().map(|id| &self[*id]).collect();
@@ -658,7 +353,7 @@ impl<S: State> RealDom<S> {
} }
} }
} }
if let NodeType::Element { children, .. } = &self[self.root].node_type { if let NodeType::Element { children, .. } = &self[ElementId(self.root)].node_type {
for c in children { for c in children {
inner(self, *c, &mut f); inner(self, *c, &mut f);
} }
@@ -677,7 +372,7 @@ impl<S: State> RealDom<S> {
} }
} }
let root = self.root; let root = self.root;
if let NodeType::Element { children, .. } = &mut self[root].node_type { if let NodeType::Element { children, .. } = &mut self[ElementId(root)].node_type {
for c in children.clone() { for c in children.clone() {
inner(self, c, &mut f); inner(self, c, &mut f);
} }
@@ -685,30 +380,17 @@ impl<S: State> RealDom<S> {
} }
} }
impl<S: State> Index<usize> for RealDom<S> {
type Output = Node<S>;
fn index(&self, idx: usize) -> &Self::Output {
self.get(idx).expect("Node does not exist")
}
}
impl<S: State> Index<ElementId> for RealDom<S> { impl<S: State> Index<ElementId> for RealDom<S> {
type Output = Node<S>; type Output = Node<S>;
fn index(&self, idx: ElementId) -> &Self::Output { fn index(&self, idx: ElementId) -> &Self::Output {
&self[idx.0] self.get(idx).unwrap()
} }
} }
impl<S: State> IndexMut<usize> for RealDom<S> {
fn index_mut(&mut self, idx: usize) -> &mut Self::Output {
self.get_mut(idx).expect("Node does not exist")
}
}
impl<S: State> IndexMut<ElementId> for RealDom<S> { impl<S: State> IndexMut<ElementId> for RealDom<S> {
fn index_mut(&mut self, idx: ElementId) -> &mut Self::Output { fn index_mut(&mut self, idx: ElementId) -> &mut Self::Output {
&mut self[idx.0] self.get_mut(idx).unwrap()
} }
} }
@@ -772,3 +454,35 @@ impl<S: State> Node<S> {
self.parent = Some(parent); self.parent = Some(parent);
} }
} }
impl<T: State> Traversable for RealDom<T> {
type Id = ElementId;
type Node = Node<T>;
fn height(&self, id: Self::Id) -> Option<u16> {
Some(<Self as Traversable>::get(self, id)?.height)
}
fn get(&self, id: Self::Id) -> Option<&Self::Node> {
self.nodes.get(id.0)?.as_ref()
}
fn get_mut(&mut self, id: Self::Id) -> Option<&mut Self::Node> {
self.nodes.get_mut(id.0)?.as_mut()
}
fn children(&self, id: Self::Id) -> &[Self::Id] {
if let Some(node) = <Self as Traversable>::get(self, id) {
match &node.node_type {
NodeType::Element { children, .. } => children,
_ => &[],
}
} else {
&[]
}
}
fn parent(&self, id: Self::Id) -> Option<Self::Id> {
<Self as Traversable>::get(self, id).and_then(|n| n.parent)
}
}
+67 -92
View File
@@ -1,13 +1,11 @@
use std::{ use std::{cmp::Ordering, fmt::Debug};
cmp::Ordering,
fmt::Debug,
ops::{Add, AddAssign, Sub, SubAssign},
};
use anymap::AnyMap; use anymap::AnyMap;
use dioxus_core::VNode; use dioxus_core::ElementId;
use fxhash::FxHashSet;
use crate::node_ref::{NodeMask, NodeView}; use crate::node_ref::{NodeMask, NodeView};
use crate::traversable::Traversable;
pub(crate) fn union_ordered_iter<T: Ord + Debug>( pub(crate) fn union_ordered_iter<T: Ord + Debug>(
s_iter: impl Iterator<Item = T>, s_iter: impl Iterator<Item = T>,
@@ -50,7 +48,7 @@ pub trait ChildDepState {
/// The context is passed to the [ChildDepState::reduce] when it is pushed down. /// The context is passed to the [ChildDepState::reduce] when it is pushed down.
/// This is sometimes nessisary for lifetime purposes. /// This is sometimes nessisary for lifetime purposes.
type Ctx; type Ctx;
/// This must be either a [ChildDepState] or [NodeDepState] /// This must be either a [ChildDepState], or [NodeDepState]
type DepState; type DepState;
const NODE_MASK: NodeMask = NodeMask::NONE; const NODE_MASK: NodeMask = NodeMask::NONE;
fn reduce<'a>( fn reduce<'a>(
@@ -73,70 +71,77 @@ pub trait ParentDepState {
/// This must be either a [ParentDepState] or [NodeDepState] /// This must be either a [ParentDepState] or [NodeDepState]
type DepState; type DepState;
const NODE_MASK: NodeMask = NodeMask::NONE; const NODE_MASK: NodeMask = NodeMask::NONE;
fn reduce(&mut self, node: NodeView, parent: Option<&Self::DepState>, ctx: &Self::Ctx) -> bool; fn reduce<'a>(
&mut self,
node: NodeView,
parent: Option<&'a Self::DepState>,
ctx: &Self::Ctx,
) -> bool;
} }
/// This state that is upadated lazily. For example any propertys that do not effect other parts of the dom like bg-color. /// This state that is upadated lazily. For example any propertys that do not effect other parts of the dom like bg-color.
/// Called when the current node's node properties are modified or a sibling's [NodeDepState] is modified. /// Called when the current node's node properties are modified or a sibling's [NodeDepState] is modified.
/// Called at most once per update. /// Called at most once per update.
pub trait NodeDepState { /// NodeDepState is the only state that can accept multiple dependancies, but only from the current node.
/// ```rust
/// impl<'a, 'b> NodeDepState<(&'a TextWrap, &'b ChildLayout)> for Layout {
/// type Ctx = LayoutCache;
/// const NODE_MASK: NodeMask =
/// NodeMask::new_with_attrs(AttributeMask::Static(&sorted_str_slice!([
/// "width", "height"
/// ])))
/// .with_text();
/// fn reduce<'a>(
/// &mut self,
/// node: NodeView,
/// siblings: (&'a TextWrap, &'b ChildLayout),
/// ctx: &Self::Ctx,
/// ) -> bool {
/// let old = self.clone();
/// let (text_wrap, child_layout) = siblings;
/// if TextWrap::Wrap == text_wrap {
/// if let Some(text) = node.text() {
/// let lines = text_wrap.get_lines(text);
/// self.width = lines.max_by(|l| l.len());
/// self.height = lines.len();
/// return old != self;
/// }
/// }
/// let mut width = child_layout.width;
/// let mut height = child_layout.width;
/// for attr in node.attributes() {
/// match attr.name {
/// "width" => {
/// width = attr.value.as_text().unwrap().parse().unwrap();
/// }
/// "height" => {
/// height = attr.value.as_text().unwrap().parse().unwrap();
/// }
/// _ => unreachable!(),
/// }
/// }
/// self.width = width;
/// self.height = height;
/// old != self
/// }
/// }
/// ```
/// The generic argument (Depstate) must be a tuple containing any number of borrowed elments that are either a [ChildDepState], [ParentDepState] or [NodeDepState].
pub trait NodeDepState<DepState> {
type Ctx; type Ctx;
type DepState: NodeDepState;
const NODE_MASK: NodeMask = NodeMask::NONE; const NODE_MASK: NodeMask = NodeMask::NONE;
fn reduce(&mut self, node: NodeView, sibling: &Self::DepState, ctx: &Self::Ctx) -> bool; fn reduce(&mut self, node: NodeView, siblings: DepState, ctx: &Self::Ctx) -> bool;
}
#[derive(Debug)]
pub struct ChildStatesChanged {
pub node_dep: Vec<MemberId>,
pub child_dep: Vec<MemberId>,
}
#[derive(Debug)]
pub struct ParentStatesChanged {
pub node_dep: Vec<MemberId>,
pub parent_dep: Vec<MemberId>,
}
#[derive(Debug)]
pub struct NodeStatesChanged {
pub node_dep: Vec<MemberId>,
} }
/// Do not implement this trait. It is only meant to be derived and used through [crate::real_dom::RealDom].
pub trait State: Default + Clone { pub trait State: Default + Clone {
const SIZE: usize; #[doc(hidden)]
fn update<'a, T: Traversable<Node = Self, Id = ElementId>>(
fn update_node_dep_state<'a>( dirty: &[(ElementId, NodeMask)],
&'a mut self, state_tree: &'a mut T,
ty: MemberId,
node: &'a VNode<'a>,
vdom: &'a dioxus_core::VirtualDom, vdom: &'a dioxus_core::VirtualDom,
ctx: &AnyMap, ctx: &AnyMap,
) -> Option<NodeStatesChanged>; ) -> FxHashSet<ElementId>;
/// This must be a valid resolution order. (no nodes updated before a state they rely on)
fn child_dep_types(&self, mask: &NodeMask) -> Vec<MemberId>;
fn update_parent_dep_state<'a>(
&'a mut self,
ty: MemberId,
node: &'a VNode<'a>,
vdom: &'a dioxus_core::VirtualDom,
parent: Option<&Self>,
ctx: &AnyMap,
) -> Option<ParentStatesChanged>;
/// This must be a valid resolution order. (no nodes updated before a state they rely on)
fn parent_dep_types(&self, mask: &NodeMask) -> Vec<MemberId>;
fn update_child_dep_state<'a>(
&'a mut self,
ty: MemberId,
node: &'a VNode<'a>,
vdom: &'a dioxus_core::VirtualDom,
children: &[&Self],
ctx: &AnyMap,
) -> Option<ChildStatesChanged>;
/// This must be a valid resolution order. (no nodes updated before a state they rely on)
fn node_dep_types(&self, mask: &NodeMask) -> Vec<MemberId>;
} }
// Todo: once GATs land we can model multable dependencies // Todo: once GATs land we can model multable dependencies
@@ -159,44 +164,14 @@ impl ChildDepState for () {
impl ParentDepState for () { impl ParentDepState for () {
type Ctx = (); type Ctx = ();
type DepState = (); type DepState = ();
fn reduce(&mut self, _: NodeView, _: Option<&Self::DepState>, _: &Self::Ctx) -> bool { fn reduce<'a>(&mut self, _: NodeView, _: Option<&'a Self::DepState>, _: &Self::Ctx) -> bool {
false false
} }
} }
impl NodeDepState for () { impl NodeDepState<()> for () {
type Ctx = (); type Ctx = ();
type DepState = (); fn reduce(&mut self, _: NodeView, _sibling: (), _: &Self::Ctx) -> bool {
fn reduce(&mut self, _: NodeView, _sibling: &Self::DepState, _: &Self::Ctx) -> bool {
false false
} }
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct MemberId(pub usize);
impl Sub<usize> for MemberId {
type Output = MemberId;
fn sub(self, rhs: usize) -> Self::Output {
MemberId(self.0 - rhs)
}
}
impl Add<usize> for MemberId {
type Output = MemberId;
fn add(self, rhs: usize) -> Self::Output {
MemberId(self.0 + rhs)
}
}
impl SubAssign<usize> for MemberId {
fn sub_assign(&mut self, rhs: usize) {
*self = *self - rhs;
}
}
impl AddAssign<usize> for MemberId {
fn add_assign(&mut self, rhs: usize) {
*self = *self + rhs;
}
}
+100
View File
@@ -0,0 +1,100 @@
pub trait Traversable {
type Id: Copy;
type Node;
fn height(&self, id: Self::Id) -> Option<u16>;
fn get(&self, id: Self::Id) -> Option<&Self::Node>;
fn get_mut(&mut self, id: Self::Id) -> Option<&mut Self::Node>;
fn children(&self, node: Self::Id) -> &[Self::Id];
fn parent(&self, node: Self::Id) -> Option<Self::Id>;
fn map<N, F: Fn(&Self::Node) -> &N, FMut: Fn(&mut Self::Node) -> &mut N>(
&mut self,
f: F,
f_mut: FMut,
) -> Map<Self, N, F, FMut>
where
Self: Sized,
{
Map {
tree: self,
f,
f_mut,
}
}
// this is safe because no node will have itself as it's parent
fn get_node_parent_mut(
&mut self,
id: Self::Id,
) -> (Option<&mut Self::Node>, Option<&mut Self::Node>) {
let node = self.get_mut(id).map(|n| n as *mut _);
let parent = self
.parent(id)
.and_then(|n| self.get_mut(n))
.map(|n| n as *mut _);
unsafe { (node.map(|n| &mut *n), parent.map(|n| &mut *n)) }
}
// this is safe because no node will have itself as a child
fn get_node_children_mut(
&mut self,
id: Self::Id,
) -> (Option<&mut Self::Node>, Vec<&mut Self::Node>) {
let node = self.get_mut(id).map(|n| n as *mut _);
let mut children = Vec::new();
let children_indexes = self.children(id).to_vec();
for id in children_indexes {
if let Some(n) = self.get_mut(id) {
children.push(unsafe { &mut *(n as *mut _) });
}
}
unsafe { (node.map(|n| &mut *n), children) }
}
}
pub struct Map<
'a,
T: Traversable,
N,
F: Fn(&<T as Traversable>::Node) -> &N,
FMut: Fn(&mut <T as Traversable>::Node) -> &mut N,
> {
f: F,
f_mut: FMut,
tree: &'a mut T,
}
impl<
'a,
T: Traversable,
N,
F: Fn(&<T as Traversable>::Node) -> &N,
FMut: Fn(&mut <T as Traversable>::Node) -> &mut N,
> Traversable for Map<'a, T, N, F, FMut>
{
type Id = <T as Traversable>::Id;
type Node = N;
fn height(&self, id: Self::Id) -> Option<u16> {
self.tree.height(id)
}
fn get(&self, id: Self::Id) -> Option<&Self::Node> {
self.tree.get(id).map(&self.f)
}
fn get_mut(&mut self, id: Self::Id) -> Option<&mut Self::Node> {
self.tree.get_mut(id).map(&self.f_mut)
}
fn children(&self, id: Self::Id) -> &[Self::Id] {
self.tree.children(id)
}
fn parent(&self, id: Self::Id) -> Option<Self::Id> {
self.tree.parent(id)
}
}
+2 -3
View File
@@ -62,13 +62,12 @@ pub(crate) struct Focus {
pub level: FocusLevel, pub level: FocusLevel,
} }
impl NodeDepState for Focus { impl NodeDepState<()> for Focus {
type Ctx = (); type Ctx = ();
type DepState = ();
const NODE_MASK: NodeMask = const NODE_MASK: NodeMask =
NodeMask::new_with_attrs(AttributeMask::Static(FOCUS_ATTRIBUTES)).with_listeners(); NodeMask::new_with_attrs(AttributeMask::Static(FOCUS_ATTRIBUTES)).with_listeners();
fn reduce(&mut self, node: NodeView<'_>, _sibling: &Self::DepState, _: &Self::Ctx) -> bool { fn reduce(&mut self, node: NodeView<'_>, _sibling: (), _: &Self::Ctx) -> bool {
let new = Focus { let new = Focus {
level: if let Some(a) = node.attributes().find(|a| a.name == "tabindex") { level: if let Some(a) = node.attributes().find(|a| a.name == "tabindex") {
if let Some(index) = a if let Some(index) = a
+1 -1
View File
@@ -253,7 +253,7 @@ impl InnerInputState {
let mut parent = node.parent; let mut parent = node.parent;
while let Some(parent_id) = parent { while let Some(parent_id) = parent {
will_bubble.insert(parent_id); will_bubble.insert(parent_id);
parent = dom[parent_id.0].parent; parent = dom[parent_id].parent;
} }
resolved_events.push(UserEvent { resolved_events.push(UserEvent {
scope_id: None, scope_id: None,
+6 -5
View File
@@ -94,7 +94,7 @@ pub fn launch_cfg(app: Component<()>, cfg: Config) {
let to_update = rdom.apply_mutations(vec![mutations]); let to_update = rdom.apply_mutations(vec![mutations]);
let mut any_map = AnyMap::new(); let mut any_map = AnyMap::new();
any_map.insert(taffy.clone()); any_map.insert(taffy.clone());
let _to_rerender = rdom.update_state(&dom, to_update, any_map).unwrap(); let _to_rerender = rdom.update_state(&dom, to_update, any_map);
} }
render_vdom( render_vdom(
@@ -133,7 +133,8 @@ fn render_vdom(
terminal.clear().unwrap(); terminal.clear().unwrap();
} }
let mut to_rerender: fxhash::FxHashSet<usize> = vec![0].into_iter().collect(); let mut to_rerender: fxhash::FxHashSet<ElementId> =
vec![ElementId(0)].into_iter().collect();
let mut updated = true; let mut updated = true;
loop { loop {
@@ -153,7 +154,7 @@ fn render_vdom(
fn resize(dims: Rect, taffy: &mut Taffy, rdom: &Dom) { fn resize(dims: Rect, taffy: &mut Taffy, rdom: &Dom) {
let width = dims.width; let width = dims.width;
let height = dims.height; let height = dims.height;
let root_node = rdom[0].state.layout.node.unwrap(); let root_node = rdom[ElementId(0)].state.layout.node.unwrap();
taffy taffy
.compute_layout( .compute_layout(
@@ -170,7 +171,7 @@ fn render_vdom(
let rdom = rdom.borrow(); let rdom = rdom.borrow();
// size is guaranteed to not change when rendering // size is guaranteed to not change when rendering
resize(frame.size(), &mut taffy.borrow_mut(), &rdom); resize(frame.size(), &mut taffy.borrow_mut(), &rdom);
let root = &rdom[0]; let root = &rdom[ElementId(0)];
render::render_vnode( render::render_vnode(
frame, frame,
&taffy.borrow(), &taffy.borrow(),
@@ -249,7 +250,7 @@ fn render_vdom(
// update the style and layout // update the style and layout
let mut any_map = AnyMap::new(); let mut any_map = AnyMap::new();
any_map.insert(taffy.clone()); any_map.insert(taffy.clone());
to_rerender = rdom.update_state(vdom, to_update, any_map).unwrap(); to_rerender = rdom.update_state(vdom, to_update, any_map);
} }
} }
+2 -4
View File
@@ -45,11 +45,9 @@ impl Default for PreventDefault {
} }
} }
impl NodeDepState for PreventDefault { impl NodeDepState<()> for PreventDefault {
type Ctx = (); type Ctx = ();
type DepState = ();
const NODE_MASK: dioxus_native_core::node_ref::NodeMask = const NODE_MASK: dioxus_native_core::node_ref::NodeMask =
dioxus_native_core::node_ref::NodeMask::new_with_attrs( dioxus_native_core::node_ref::NodeMask::new_with_attrs(
dioxus_native_core::node_ref::AttributeMask::Static(&sorted_str_slice!([ dioxus_native_core::node_ref::AttributeMask::Static(&sorted_str_slice!([
@@ -60,7 +58,7 @@ impl NodeDepState for PreventDefault {
fn reduce( fn reduce(
&mut self, &mut self,
node: dioxus_native_core::node_ref::NodeView, node: dioxus_native_core::node_ref::NodeView,
_sibling: &Self::DepState, _sibling: (),
_ctx: &Self::Ctx, _ctx: &Self::Ctx,
) -> bool { ) -> bool {
let new = match node let new = match node
+1 -1
View File
@@ -15,7 +15,7 @@ use crate::Dom;
/// Allows querying the layout of nodes after rendering. It will only provide a correct value after a node is rendered. /// Allows querying the layout of nodes after rendering. It will only provide a correct value after a node is rendered.
/// Provided as a root context for all tui applictions. /// Provided as a root context for all tui applictions.
/// # Example /// # Example
/// ```rust /// ```rust, ignore
/// use dioxus::prelude::*; /// use dioxus::prelude::*;
/// use dioxus::tui::query::Query; /// use dioxus::tui::query::Query;
/// use dioxus::tui::Size; /// use dioxus::tui::Size;
+1 -1
View File
@@ -78,7 +78,7 @@ pub(crate) fn render_vnode(
} }
for c in children { for c in children {
render_vnode(frame, layout, rdom, &rdom[c.0], cfg, location); render_vnode(frame, layout, rdom, &rdom[*c], cfg, location);
} }
} }
NodeType::Placeholder => unreachable!(), NodeType::Placeholder => unreachable!(),