mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-23 12:43:08 +00:00
add node dependancy
This commit is contained in:
parent
0fb9aed273
commit
436c6a02f7
18 changed files with 1312 additions and 575 deletions
|
@ -10,3 +10,13 @@ proc-macro = true
|
|||
[dependencies]
|
||||
syn = { version = "1.0.11", features = ["extra-traits"] }
|
||||
quote = "1.0"
|
||||
dioxus-native-core = { path = "../native-core" }
|
||||
|
||||
[dev-dependencies]
|
||||
dioxus-core = { path = "../core", version = "^0.2.0" }
|
||||
dioxus-html = { path = "../html", version = "^0.2.0" }
|
||||
dioxus-core-macro = { path = "../core-macro", version = "^0.2.0" }
|
||||
|
||||
smallvec = "1.6"
|
||||
fxhash = "0.2"
|
||||
anymap = "0.12.1"
|
|
@ -1,38 +1,17 @@
|
|||
extern crate proc_macro;
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
mod sorted_slice;
|
||||
|
||||
use dioxus_native_core::state::MemberId;
|
||||
use proc_macro::TokenStream;
|
||||
use quote::{quote, ToTokens};
|
||||
use quote::{quote, ToTokens, __private::Span};
|
||||
use sorted_slice::StrSlice;
|
||||
use syn::{
|
||||
self, bracketed,
|
||||
self,
|
||||
parse::{Parse, ParseStream, Result},
|
||||
parse_macro_input,
|
||||
punctuated::Punctuated,
|
||||
token::Paren,
|
||||
Field, Ident, LitStr, Token, Type, TypeTuple,
|
||||
parse_macro_input, parse_quote, Error, Field, Ident, Token, Type,
|
||||
};
|
||||
|
||||
struct StrSlice {
|
||||
map: BTreeMap<String, LitStr>,
|
||||
}
|
||||
|
||||
impl Parse for StrSlice {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let content;
|
||||
bracketed!(content in input);
|
||||
let mut map = BTreeMap::new();
|
||||
while let Ok(s) = content.parse::<LitStr>() {
|
||||
map.insert(s.value(), s);
|
||||
#[allow(unused_must_use)]
|
||||
{
|
||||
content.parse::<Token![,]>();
|
||||
}
|
||||
}
|
||||
Ok(StrSlice { map })
|
||||
}
|
||||
}
|
||||
|
||||
#[proc_macro]
|
||||
pub fn sorted_str_slice(input: TokenStream) -> TokenStream {
|
||||
let slice: StrSlice = parse_macro_input!(input as StrSlice);
|
||||
|
@ -40,14 +19,13 @@ pub fn sorted_str_slice(input: TokenStream) -> TokenStream {
|
|||
quote!([#(#strings, )*]).into()
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
#[derive(PartialEq, Debug, Clone)]
|
||||
enum DepKind {
|
||||
NodeDepState,
|
||||
ChildDepState,
|
||||
ParentDepState,
|
||||
}
|
||||
|
||||
// macro that streams data from the State for any attributes that end with _
|
||||
#[proc_macro_derive(State, attributes(node_dep_state, child_dep_state, parent_dep_state))]
|
||||
pub fn state_macro_derive(input: TokenStream) -> TokenStream {
|
||||
let ast = syn::parse(input).unwrap();
|
||||
|
@ -60,153 +38,149 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream {
|
|||
syn::Data::Struct(data) => match &data.fields {
|
||||
syn::Fields::Named(e) => &e.named,
|
||||
syn::Fields::Unnamed(_) => todo!("unnamed fields"),
|
||||
syn::Fields::Unit => todo!("unit fields"),
|
||||
syn::Fields::Unit => todo!("unit structs"),
|
||||
}
|
||||
.iter()
|
||||
.collect(),
|
||||
_ => unimplemented!(),
|
||||
};
|
||||
let strct = Struct::parse(&fields);
|
||||
let state_strct = StateStruct::parse(&fields, &strct);
|
||||
let strct = Struct::new(type_name.clone(), &fields);
|
||||
match StateStruct::parse(&fields, &strct) {
|
||||
Ok(state_strct) => {
|
||||
let node_dep_state_fields = state_strct
|
||||
.state_members
|
||||
.iter()
|
||||
.filter(|f| f.dep_kind == DepKind::NodeDepState)
|
||||
.map(|f| f.reduce_self());
|
||||
let child_dep_state_fields = state_strct
|
||||
.state_members
|
||||
.iter()
|
||||
.filter(|f| f.dep_kind == DepKind::ChildDepState)
|
||||
.map(|f| f.reduce_self());
|
||||
let parent_dep_state_fields = state_strct
|
||||
.state_members
|
||||
.iter()
|
||||
.filter(|f| f.dep_kind == DepKind::ParentDepState)
|
||||
.map(|f| f.reduce_self());
|
||||
|
||||
let node_dep_state_fields = quote::__private::TokenStream::from_iter(
|
||||
state_strct
|
||||
.state_members
|
||||
.iter()
|
||||
.filter(|f| f.dep_kind == DepKind::NodeDepState)
|
||||
.map(|f| {
|
||||
let ty_id = &f.type_id();
|
||||
let reduce = &f.reduce_self();
|
||||
quote! {
|
||||
else if ty == #ty_id {
|
||||
#reduce
|
||||
let node_iter = state_strct
|
||||
.state_members
|
||||
.iter()
|
||||
.filter(|m| m.dep_kind == DepKind::NodeDepState);
|
||||
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::ChildDepState);
|
||||
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::ParentDepState);
|
||||
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 gen = quote! {
|
||||
impl State for #type_name{
|
||||
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,
|
||||
ctx: &anymap::AnyMap,
|
||||
) -> Option<dioxus_native_core::state::NodeStatesChanged>{
|
||||
use dioxus_native_core::state::NodeDepState as _;
|
||||
match ty.0{
|
||||
#(
|
||||
#node_ids => #node_dep_state_fields,
|
||||
)*
|
||||
_ => panic!("{:?} not in {}", ty, #type_name_str),
|
||||
}
|
||||
}
|
||||
|
||||
fn update_parent_dep_state<'a>(
|
||||
&'a mut self,
|
||||
ty: dioxus_native_core::state::MemberId,
|
||||
node: &'a dioxus_core::VNode<'a>,
|
||||
vdom: &'a dioxus_core::VirtualDom,
|
||||
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,
|
||||
)*
|
||||
_ => panic!("{:?} not in {}", ty, #type_name_str),
|
||||
}
|
||||
}
|
||||
|
||||
fn update_child_dep_state<'a>(
|
||||
&'a mut self,
|
||||
ty: dioxus_native_core::state::MemberId,
|
||||
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},
|
||||
)*
|
||||
_ => 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>{
|
||||
let mut dep_types = Vec::new();
|
||||
#(if #child_types::NODE_MASK.overlaps(mask) {
|
||||
dep_types.push(dioxus_native_core::state::MemberId(#child_ids_clone));
|
||||
})*
|
||||
dep_types
|
||||
}
|
||||
|
||||
fn parent_dep_types(&self, mask: &dioxus_native_core::node_ref::NodeMask) -> Vec<dioxus_native_core::state::MemberId>{
|
||||
let mut dep_types = Vec::new();
|
||||
#(if #parent_types::NODE_MASK.overlaps(mask) {
|
||||
dep_types.push(dioxus_native_core::state::MemberId(#parent_ids_clone));
|
||||
})*
|
||||
dep_types
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}),
|
||||
);
|
||||
let child_dep_state_fields = quote::__private::TokenStream::from_iter(
|
||||
state_strct
|
||||
.state_members
|
||||
.iter()
|
||||
.filter(|f| f.dep_kind == DepKind::ChildDepState)
|
||||
.map(|f| {
|
||||
let ty_id = &f.type_id();
|
||||
let reduce = &f.reduce_self();
|
||||
quote! {
|
||||
else if ty == #ty_id {
|
||||
#reduce
|
||||
}
|
||||
}
|
||||
}),
|
||||
);
|
||||
let parent_dep_state_fields = quote::__private::TokenStream::from_iter(
|
||||
state_strct
|
||||
.state_members
|
||||
.iter()
|
||||
.filter(|f| f.dep_kind == DepKind::ParentDepState)
|
||||
.map(|f| {
|
||||
let ty_id = &f.type_id();
|
||||
let reduce = &f.reduce_self();
|
||||
quote! {
|
||||
else if ty == #ty_id {
|
||||
#reduce
|
||||
}
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
let node_types = state_strct
|
||||
.state_members
|
||||
.iter()
|
||||
.filter(|f| f.dep_kind == DepKind::NodeDepState)
|
||||
.map(|f| &f.mem.ty);
|
||||
let child_types = state_strct
|
||||
.state_members
|
||||
.iter()
|
||||
.filter(|f| f.dep_kind == DepKind::ChildDepState)
|
||||
.map(|f| &f.mem.ty);
|
||||
let parent_types = state_strct
|
||||
.state_members
|
||||
.iter()
|
||||
.filter(|f| f.dep_kind == DepKind::ParentDepState)
|
||||
.map(|f| &f.mem.ty);
|
||||
|
||||
let type_name_str = type_name.to_string();
|
||||
|
||||
let gen = quote! {
|
||||
impl State for #type_name{
|
||||
fn update_node_dep_state<'a>(&'a mut self, ty: std::any::TypeId, node: &'a dioxus_core::VNode<'a>, vdom: &'a dioxus_core::VirtualDom, ctx: &anymap::AnyMap) -> bool{
|
||||
use dioxus_native_core::state::NodeDepState as _;
|
||||
// println!("called update_node_dep_state with ty: {:?}", ty);
|
||||
if false {
|
||||
unreachable!();
|
||||
}
|
||||
#node_dep_state_fields
|
||||
else{
|
||||
panic!("{:?} not in {}", ty, #type_name_str)
|
||||
}
|
||||
}
|
||||
|
||||
fn update_parent_dep_state<'a>(&'a mut self, ty: std::any::TypeId, node: &'a dioxus_core::VNode<'a>, vdom: &'a dioxus_core::VirtualDom, parent: Option<&Self>, ctx: &anymap::AnyMap) -> bool{
|
||||
use dioxus_native_core::state::ParentDepState as _;
|
||||
// println!("called update_parent_dep_state with ty: {:?}", ty);
|
||||
if false {
|
||||
unreachable!();
|
||||
}
|
||||
#parent_dep_state_fields
|
||||
else{
|
||||
panic!("{:?} not in {}", ty, #type_name_str)
|
||||
}
|
||||
}
|
||||
|
||||
fn update_child_dep_state<'a>(&'a mut self, ty: std::any::TypeId, node: &'a dioxus_core::VNode<'a>, vdom: &'a dioxus_core::VirtualDom, children: &[&Self], ctx: &anymap::AnyMap) -> bool{
|
||||
use dioxus_native_core::state::ChildDepState as _;
|
||||
// println!("called update_child_dep_state with ty: {:?}", ty);
|
||||
if false {
|
||||
unreachable!()
|
||||
}
|
||||
#child_dep_state_fields
|
||||
else{
|
||||
panic!("{:?} not in {}", ty, #type_name_str)
|
||||
}
|
||||
}
|
||||
|
||||
fn child_dep_types(&self, mask: &dioxus_native_core::state::NodeMask) -> Vec<std::any::TypeId>{
|
||||
let mut dep_types = Vec::new();
|
||||
#(if #child_types::NODE_MASK.overlaps(mask) {
|
||||
dep_types.push(std::any::TypeId::of::<#child_types>());
|
||||
})*
|
||||
dep_types
|
||||
}
|
||||
|
||||
fn parent_dep_types(&self, mask: &dioxus_native_core::state::NodeMask) -> Vec<std::any::TypeId>{
|
||||
let mut dep_types = Vec::new();
|
||||
#(if #parent_types::NODE_MASK.overlaps(mask) {
|
||||
dep_types.push(std::any::TypeId::of::<#parent_types>());
|
||||
})*
|
||||
dep_types
|
||||
}
|
||||
|
||||
fn node_dep_types(&self, mask: &dioxus_native_core::state::NodeMask) -> Vec<std::any::TypeId>{
|
||||
let mut dep_types = Vec::new();
|
||||
#(if #node_types::NODE_MASK.overlaps(mask) {
|
||||
dep_types.push(std::any::TypeId::of::<#node_types>());
|
||||
})*
|
||||
dep_types
|
||||
}
|
||||
};
|
||||
gen.into()
|
||||
}
|
||||
};
|
||||
gen.into()
|
||||
Err(e) => e.into_compile_error().into(),
|
||||
}
|
||||
}
|
||||
|
||||
struct Struct {
|
||||
name: Ident,
|
||||
members: Vec<Member>,
|
||||
}
|
||||
|
||||
impl Struct {
|
||||
fn parse(fields: &[&Field]) -> Self {
|
||||
fn new(name: Ident, fields: &[&Field]) -> Self {
|
||||
let members = fields.iter().filter_map(|f| Member::parse(f)).collect();
|
||||
Self { members }
|
||||
Self { name, members }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -215,57 +189,198 @@ struct StateStruct<'a> {
|
|||
}
|
||||
|
||||
impl<'a> StateStruct<'a> {
|
||||
fn parse(fields: &[&'a Field], strct: &'a Struct) -> Self {
|
||||
let state_members = strct
|
||||
fn parse(fields: &[&'a Field], strct: &'a Struct) -> Result<Self> {
|
||||
let mut parse_err = Ok(());
|
||||
let state_members: Vec<_> = strct
|
||||
.members
|
||||
.iter()
|
||||
.zip(fields.iter())
|
||||
.filter_map(|(m, f)| StateMember::parse(f, m, &strct))
|
||||
.filter_map(|(m, f)| match StateMember::parse(f, m, &strct) {
|
||||
Ok(m) => m,
|
||||
Err(err) => {
|
||||
parse_err = Err(err);
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
parse_err?;
|
||||
|
||||
// todo: sort members
|
||||
#[derive(Debug, Clone)]
|
||||
struct DepNode<'a> {
|
||||
state_mem: StateMember<'a>,
|
||||
depandants: Vec<Box<DepNode<'a>>>,
|
||||
}
|
||||
impl<'a> DepNode<'a> {
|
||||
fn new(state_mem: StateMember<'a>) -> Self {
|
||||
Self {
|
||||
state_mem,
|
||||
depandants: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
Self { state_members }
|
||||
}
|
||||
}
|
||||
/// 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
|
||||
}
|
||||
|
||||
struct DepTypes {
|
||||
ctx_ty: Option<Type>,
|
||||
dep_ty: Option<Type>,
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for DepTypes {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let dep_ty = input.parse().ok();
|
||||
let comma: Option<Token![,]> = input.parse().ok();
|
||||
let ctx_ty = input.parse().ok();
|
||||
Ok(Self {
|
||||
ctx_ty: comma.and(ctx_ty),
|
||||
dep_ty,
|
||||
})
|
||||
}
|
||||
}
|
||||
fn contains_member(&self, member: &Member) -> bool {
|
||||
if self.state_mem.mem == member {
|
||||
true
|
||||
} else {
|
||||
self.depandants.iter().any(|d| d.contains_member(member))
|
||||
}
|
||||
}
|
||||
|
||||
struct NodeDepTypes {
|
||||
ctx_ty: Option<Type>,
|
||||
}
|
||||
// check if there are any mixed child/parent dependancies
|
||||
fn check(&self) -> Option<Error> {
|
||||
self.kind().err()
|
||||
}
|
||||
|
||||
impl Parse for NodeDepTypes {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let ctx_ty = input.parse().ok();
|
||||
Ok(Self { ctx_ty })
|
||||
}
|
||||
}
|
||||
fn kind(&self) -> Result<&DepKind> {
|
||||
fn reduce_kind<'a>(dk1: &'a DepKind, dk2: &'a DepKind) -> Result<&'a DepKind> {
|
||||
match (dk1, dk2) {
|
||||
(DepKind::ChildDepState, DepKind::ParentDepState)
|
||||
| (DepKind::ParentDepState, DepKind::ChildDepState) => Err(Error::new(
|
||||
Span::call_site(),
|
||||
"There is a ChildDepState that depends on a ParentDepState",
|
||||
)),
|
||||
// node dep state takes the lowest priority
|
||||
(DepKind::NodeDepState, important) | (important, DepKind::NodeDepState) => {
|
||||
Ok(important)
|
||||
}
|
||||
// they are the same
|
||||
(fst, _) => Ok(fst),
|
||||
}
|
||||
}
|
||||
reduce_kind(
|
||||
self.depandants
|
||||
.iter()
|
||||
.try_fold(&DepKind::NodeDepState, |dk1, dk2| {
|
||||
reduce_kind(dk1, dk2.kind()?)
|
||||
})?,
|
||||
&self.state_mem.dep_kind,
|
||||
)
|
||||
}
|
||||
|
||||
impl From<NodeDepTypes> for DepTypes {
|
||||
fn from(node_dep_types: NodeDepTypes) -> Self {
|
||||
Self {
|
||||
ctx_ty: node_dep_types.ctx_ty,
|
||||
dep_ty: None,
|
||||
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(Box::new(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
|
||||
let mut roots: Vec<DepNode> = vec![];
|
||||
for m in state_members.into_iter() {
|
||||
if let Some(dep) = m.dep_mem {
|
||||
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)) {
|
||||
let new = DepNode::new(m);
|
||||
if root_depends_on {
|
||||
return Err(Error::new(
|
||||
new.state_mem.mem.ident.span(),
|
||||
format!("{} has a circular dependancy", new.state_mem.mem.ident),
|
||||
));
|
||||
}
|
||||
// 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);
|
||||
}
|
||||
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()
|
||||
.map(|r| r.flatten().into_iter())
|
||||
.flatten()
|
||||
.collect();
|
||||
|
||||
Ok(Self { state_members })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Dependancy {
|
||||
ctx_ty: Option<Type>,
|
||||
dep: Option<Ident>,
|
||||
}
|
||||
|
||||
impl Parse for Dependancy {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let dep = input
|
||||
.parse()
|
||||
.ok()
|
||||
.filter(|i: &Ident| format!("{}", i) != "NONE");
|
||||
let comma: Option<Token![,]> = input.parse().ok();
|
||||
let ctx_ty = input.parse().ok();
|
||||
Ok(Self {
|
||||
ctx_ty: comma.and(ctx_ty),
|
||||
dep,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
struct Member {
|
||||
ty: Type,
|
||||
ident: Ident,
|
||||
|
@ -280,16 +395,25 @@ impl Member {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct StateMember<'a> {
|
||||
mem: &'a Member,
|
||||
dep_kind: DepKind,
|
||||
dep_mem: Option<&'a Member>,
|
||||
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> {
|
||||
fn parse(field: &Field, mem: &'a Member, parent: &'a Struct) -> Option<StateMember<'a>> {
|
||||
field.attrs.iter().find_map(|a| {
|
||||
fn parse(
|
||||
field: &Field,
|
||||
mem: &'a Member,
|
||||
parent: &'a Struct,
|
||||
) -> Result<Option<StateMember<'a>>> {
|
||||
let mut err = Ok(());
|
||||
let member = field.attrs.iter().find_map(|a| {
|
||||
let dep_kind = a
|
||||
.path
|
||||
.get_ident()
|
||||
|
@ -300,34 +424,44 @@ impl<'a> StateMember<'a> {
|
|||
_ => None,
|
||||
})
|
||||
.flatten()?;
|
||||
let deps: DepTypes = match dep_kind {
|
||||
DepKind::NodeDepState => a.parse_args::<NodeDepTypes>().ok()?.into(),
|
||||
_ => a.parse_args().ok()?,
|
||||
};
|
||||
|
||||
Some(Self {
|
||||
mem,
|
||||
dep_kind,
|
||||
dep_mem: deps
|
||||
.dep_ty
|
||||
.map(|ty| parent.members.iter().find(|m| m.ty == ty))
|
||||
.flatten(),
|
||||
ctx_ty: deps.ctx_ty,
|
||||
})
|
||||
})
|
||||
match a.parse_args::<Dependancy>() {
|
||||
Ok(dependancy) => {
|
||||
let dep_mem = if let Some(name) = &dependancy.dep {
|
||||
if let Some(found) = parent.members.iter().find(|m| &m.ident == name) {
|
||||
Some(found)
|
||||
} else {
|
||||
err = Err(Error::new(
|
||||
name.span(),
|
||||
format!("{} not found in {}", name, parent.name),
|
||||
));
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Some(Self {
|
||||
mem,
|
||||
dep_kind,
|
||||
dep_mem,
|
||||
ctx_ty: dependancy.ctx_ty,
|
||||
dependants: Vec::new(),
|
||||
member_id: dioxus_native_core::state::MemberId(0),
|
||||
})
|
||||
}
|
||||
Err(e) => {
|
||||
err = Err(e);
|
||||
None
|
||||
}
|
||||
}
|
||||
});
|
||||
err?;
|
||||
Ok(member)
|
||||
}
|
||||
|
||||
fn reduce_self(&self) -> quote::__private::TokenStream {
|
||||
let ident = &self.mem.ident;
|
||||
let get_ctx = if let Some(ctx_ty) = &self.ctx_ty {
|
||||
if ctx_ty
|
||||
== &Type::Tuple(TypeTuple {
|
||||
paren_token: Paren {
|
||||
span: quote::__private::Span::call_site(),
|
||||
},
|
||||
elems: Punctuated::new(),
|
||||
})
|
||||
{
|
||||
if ctx_ty == &parse_quote!(()) {
|
||||
quote! {&()}
|
||||
} else {
|
||||
let msg = ctx_ty.to_token_stream().to_string() + " not found in context";
|
||||
|
@ -336,40 +470,119 @@ impl<'a> StateMember<'a> {
|
|||
} else {
|
||||
quote! {&()}
|
||||
};
|
||||
let states_changed = {
|
||||
let child_dep = self
|
||||
.dependants
|
||||
.iter()
|
||||
.filter(|(_, kind)| kind == &DepKind::ChildDepState)
|
||||
.map(|(id, _)| id.0);
|
||||
let parent_dep = self
|
||||
.dependants
|
||||
.iter()
|
||||
.filter(|(_, kind)| kind == &DepKind::ParentDepState)
|
||||
.map(|(id, _)| id.0);
|
||||
let node_dep = self
|
||||
.dependants
|
||||
.iter()
|
||||
.filter(|(_, kind)| kind == &DepKind::NodeDepState)
|
||||
.map(|(id, _)| id.0);
|
||||
match self.dep_kind {
|
||||
DepKind::NodeDepState => {
|
||||
quote! {
|
||||
dioxus_native_core::state::NodeStatesChanged{
|
||||
node_dep: &[#(dioxus_native_core::state::MemberId(#node_dep), )*],
|
||||
}
|
||||
}
|
||||
}
|
||||
DepKind::ChildDepState => {
|
||||
quote! {
|
||||
dioxus_native_core::state::ChildStatesChanged{
|
||||
node_dep: &[#(dioxus_native_core::state::MemberId(#node_dep), )*],
|
||||
child_dep: &[#(dioxus_native_core::state::MemberId(#child_dep), )*],
|
||||
}
|
||||
}
|
||||
}
|
||||
DepKind::ParentDepState => {
|
||||
quote! {
|
||||
dioxus_native_core::state::ParentStatesChanged{
|
||||
node_dep: &[#(dioxus_native_core::state::MemberId(#node_dep), )*],
|
||||
parent_dep: &[#(dioxus_native_core::state::MemberId(#parent_dep), )*],
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let ty = &self.mem.ty;
|
||||
let node_view = quote!(NodeView::new(node, #ty::NODE_MASK, vdom));
|
||||
let node_view =
|
||||
quote!(dioxus_native_core::node_ref::NodeView::new(node, #ty::NODE_MASK, vdom));
|
||||
let id = self.member_id.0;
|
||||
if let Some(dep_ident) = &self.dep_mem.map(|m| &m.ident) {
|
||||
match self.dep_kind {
|
||||
DepKind::NodeDepState => {
|
||||
quote!(self.#ident.reduce(#node_view, #get_ctx))
|
||||
quote!({
|
||||
// println!("node: {:?} {:?} {:?}", self.#ident, #id, #node_view.id());
|
||||
if self.#ident.reduce(#node_view, &self.#dep_ident, #get_ctx){
|
||||
Some(#states_changed)
|
||||
} else{
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
DepKind::ChildDepState => {
|
||||
quote!(self.#ident.reduce(#node_view, children.iter().map(|s| &s.#dep_ident), #get_ctx))
|
||||
quote!({
|
||||
// println!("child: {:?} {:?} {:?}", self.#ident, #id, #node_view.id());
|
||||
if self.#ident.reduce(#node_view, children.iter().map(|s| &s.#dep_ident), #get_ctx){
|
||||
Some(#states_changed)
|
||||
} else{
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
DepKind::ParentDepState => {
|
||||
quote!(self.#ident.reduce(#node_view, parent.as_ref().map(|p| &p.#dep_ident), #get_ctx))
|
||||
quote!({
|
||||
// println!("parent: {:?} {:?} {:?}", self.#ident, #id, #node_view.id());
|
||||
if self.#ident.reduce(#node_view, parent.as_ref().map(|p| &p.#dep_ident), #get_ctx){
|
||||
Some(#states_changed)
|
||||
} else{
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match self.dep_kind {
|
||||
DepKind::NodeDepState => {
|
||||
quote!(self.#ident.reduce(#node_view, #get_ctx))
|
||||
quote!({
|
||||
// println!("node: {:?} {:?} {:?}", self.#ident, #id, #node_view.id());
|
||||
if self.#ident.reduce(#node_view, &(), #get_ctx){
|
||||
Some(#states_changed)
|
||||
} else{
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
DepKind::ChildDepState => {
|
||||
quote!(self.#ident.reduce(#node_view, &(), #get_ctx))
|
||||
quote!({
|
||||
// println!("child: {:?} {:?} {:?}", self.#ident, #id, #node_view.id());
|
||||
if self.#ident.reduce(#node_view, std::iter::empty(), #get_ctx){
|
||||
Some(#states_changed)
|
||||
} else{
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
DepKind::ParentDepState => {
|
||||
quote!(self.#ident.reduce(#node_view, Some(&()), #get_ctx))
|
||||
quote!({
|
||||
println!("parent: {:?} {:?} {:?}", self.#ident, #id, #node_view.id());
|
||||
if self.#ident.reduce(#node_view, Some(&()), #get_ctx){
|
||||
Some(#states_changed)
|
||||
} else{
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn type_id(&self) -> quote::__private::TokenStream {
|
||||
let ty = &self.mem.ty;
|
||||
quote!({
|
||||
let type_id = std::any::TypeId::of::<#ty>();
|
||||
type_id
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
28
packages/native-core-macro/src/sorted_slice.rs
Normal file
28
packages/native-core-macro/src/sorted_slice.rs
Normal file
|
@ -0,0 +1,28 @@
|
|||
extern crate proc_macro;
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use syn::{
|
||||
self, bracketed,
|
||||
parse::{Parse, ParseStream, Result},
|
||||
LitStr, Token,
|
||||
};
|
||||
pub struct StrSlice {
|
||||
pub map: BTreeMap<String, LitStr>,
|
||||
}
|
||||
|
||||
impl Parse for StrSlice {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let content;
|
||||
bracketed!(content in input);
|
||||
let mut map = BTreeMap::new();
|
||||
while let Ok(s) = content.parse::<LitStr>() {
|
||||
map.insert(s.value(), s);
|
||||
#[allow(unused_must_use)]
|
||||
{
|
||||
content.parse::<Token![,]>();
|
||||
}
|
||||
}
|
||||
Ok(StrSlice { map })
|
||||
}
|
||||
}
|
|
@ -8,7 +8,7 @@ use dioxus_native_core::real_dom::RealDom;
|
|||
use dioxus_native_core::state::State;
|
||||
use dioxus_native_core_macro::State;
|
||||
|
||||
#[derive(State, Default, Clone)]
|
||||
#[derive(Default, Clone, State)]
|
||||
struct Empty {}
|
||||
|
||||
#[test]
|
74
packages/native-core-macro/tests/parse.rs
Normal file
74
packages/native-core-macro/tests/parse.rs
Normal file
|
@ -0,0 +1,74 @@
|
|||
use dioxus_native_core::node_ref::*;
|
||||
use dioxus_native_core::state::*;
|
||||
use dioxus_native_core_macro::*;
|
||||
|
||||
#[derive(State, Default, Clone)]
|
||||
#[allow(dead_code)]
|
||||
struct Z {
|
||||
// depends on text, the C component of it's parent and a u16 context
|
||||
#[parent_dep_state(c, u16)]
|
||||
d: D,
|
||||
// depends on just attributes and no context
|
||||
#[node_dep_state()]
|
||||
a: A,
|
||||
// depends on the B component of children and i32 context
|
||||
#[child_dep_state(b, i32)]
|
||||
b: B,
|
||||
// depends on the C component of it's parent and a u8 context
|
||||
#[parent_dep_state(c, u8)]
|
||||
c: C,
|
||||
// this will remain uneffected on updates
|
||||
n: i32,
|
||||
}
|
||||
|
||||
use dioxus_native_core::state::NodeDepState;
|
||||
|
||||
#[derive(Default, Clone, Debug)]
|
||||
struct A;
|
||||
impl NodeDepState for A {
|
||||
type Ctx = ();
|
||||
type DepState = ();
|
||||
const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::All, false, false, false);
|
||||
fn reduce(&mut self, _: NodeView, _: &Self::DepState, _: &()) -> bool {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Debug)]
|
||||
struct B;
|
||||
impl ChildDepState for B {
|
||||
type Ctx = i32;
|
||||
type DepState = Self;
|
||||
fn reduce<'a>(
|
||||
&mut self,
|
||||
_: NodeView,
|
||||
_: impl Iterator<Item = &'a Self::DepState>,
|
||||
_: &i32,
|
||||
) -> bool
|
||||
where
|
||||
Self::DepState: 'a,
|
||||
{
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Debug)]
|
||||
struct C;
|
||||
impl ParentDepState for C {
|
||||
type Ctx = u8;
|
||||
type DepState = Self;
|
||||
fn reduce(&mut self, _: NodeView, _: Option<&Self::DepState>, _: &u8) -> bool {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Debug)]
|
||||
struct D;
|
||||
impl ParentDepState for D {
|
||||
type Ctx = u16;
|
||||
type DepState = C;
|
||||
const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::NONE, false, false, true);
|
||||
fn reduce(&mut self, _: NodeView, _: Option<&Self::DepState>, _: &u16) -> bool {
|
||||
todo!()
|
||||
}
|
||||
}
|
70
packages/native-core-macro/tests/test.rs
Normal file
70
packages/native-core-macro/tests/test.rs
Normal file
|
@ -0,0 +1,70 @@
|
|||
use dioxus_native_core::node_ref::*;
|
||||
use dioxus_native_core::state::{ChildDepState, NodeDepState, ParentDepState, State};
|
||||
use dioxus_native_core_macro::State;
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
struct BubbledUpStateTester(Option<String>, Vec<Box<BubbledUpStateTester>>);
|
||||
impl ChildDepState for BubbledUpStateTester {
|
||||
type Ctx = u32;
|
||||
type DepState = Self;
|
||||
const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::NONE, true, false, false);
|
||||
fn reduce<'a>(
|
||||
&mut self,
|
||||
node: NodeView,
|
||||
children: impl Iterator<Item = &'a Self::DepState>,
|
||||
ctx: &Self::Ctx,
|
||||
) -> bool
|
||||
where
|
||||
Self::DepState: 'a,
|
||||
{
|
||||
assert_eq!(*ctx, 42);
|
||||
*self = BubbledUpStateTester(
|
||||
node.tag().map(|s| s.to_string()),
|
||||
children.into_iter().map(|c| Box::new(c.clone())).collect(),
|
||||
);
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
struct PushedDownStateTester(Option<String>, Option<Box<PushedDownStateTester>>);
|
||||
impl ParentDepState for PushedDownStateTester {
|
||||
type Ctx = u32;
|
||||
type DepState = Self;
|
||||
const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::NONE, true, false, false);
|
||||
fn reduce(&mut self, node: NodeView, parent: Option<&Self::DepState>, ctx: &Self::Ctx) -> bool {
|
||||
assert_eq!(*ctx, 42);
|
||||
*self = PushedDownStateTester(
|
||||
node.tag().map(|s| s.to_string()),
|
||||
parent.map(|c| Box::new(c.clone())),
|
||||
);
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
struct NodeStateTester(Option<String>, Vec<(String, String)>);
|
||||
impl NodeDepState for NodeStateTester {
|
||||
type Ctx = u32;
|
||||
type DepState = ();
|
||||
const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::All, true, false, false);
|
||||
fn reduce(&mut self, node: NodeView, _sibling: &Self::DepState, ctx: &Self::Ctx) -> bool {
|
||||
assert_eq!(*ctx, 42);
|
||||
*self = NodeStateTester(
|
||||
node.tag().map(|s| s.to_string()),
|
||||
node.attributes()
|
||||
.map(|a| (a.name.to_string(), a.value.to_string()))
|
||||
.collect(),
|
||||
);
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(State, Clone, Default, Debug)]
|
||||
struct StateTester {
|
||||
#[child_dep_state(bubbled, u32)]
|
||||
bubbled: BubbledUpStateTester,
|
||||
#[parent_dep_state(pushed, u32)]
|
||||
pushed: PushedDownStateTester,
|
||||
#[node_dep_state(NONE, u32)]
|
||||
node: NodeStateTester,
|
||||
}
|
|
@ -3,17 +3,16 @@ use dioxus_core::VNode;
|
|||
use dioxus_core::*;
|
||||
use dioxus_core_macro::*;
|
||||
use dioxus_html as dioxus_elements;
|
||||
use dioxus_native_core::node_ref::*;
|
||||
use dioxus_native_core::real_dom::*;
|
||||
use dioxus_native_core::state::{
|
||||
AttributeMask, ChildDepState, NodeDepState, NodeMask, NodeView, ParentDepState, State,
|
||||
};
|
||||
use dioxus_native_core::state::{ChildDepState, NodeDepState, ParentDepState, State};
|
||||
use dioxus_native_core_macro::State;
|
||||
|
||||
#[derive(Debug, Clone, Default, State)]
|
||||
struct CallCounterState {
|
||||
#[child_dep_state(ChildDepCallCounter)]
|
||||
#[child_dep_state(child_counter)]
|
||||
child_counter: ChildDepCallCounter,
|
||||
#[parent_dep_state(ParentDepCallCounter)]
|
||||
#[parent_dep_state(parent_counter)]
|
||||
parent_counter: ParentDepCallCounter,
|
||||
#[node_dep_state()]
|
||||
node_counter: NodeDepCallCounter,
|
||||
|
@ -60,8 +59,9 @@ impl ParentDepState for ParentDepCallCounter {
|
|||
struct NodeDepCallCounter(u32);
|
||||
impl NodeDepState for NodeDepCallCounter {
|
||||
type Ctx = ();
|
||||
type DepState = ();
|
||||
const NODE_MASK: NodeMask = NodeMask::ALL;
|
||||
fn reduce(&mut self, _node: NodeView, _ctx: &Self::Ctx) -> bool {
|
||||
fn reduce(&mut self, _node: NodeView, _sibling: &Self::DepState, _ctx: &Self::Ctx) -> bool {
|
||||
self.0 += 1;
|
||||
true
|
||||
}
|
||||
|
@ -107,22 +107,13 @@ impl ParentDepState for PushedDownStateTester {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(State, Clone, Default, Debug)]
|
||||
struct StateTester {
|
||||
#[child_dep_state(BubbledUpStateTester, u32)]
|
||||
bubbled: BubbledUpStateTester,
|
||||
#[parent_dep_state(PushedDownStateTester, u32)]
|
||||
pushed: PushedDownStateTester,
|
||||
#[node_dep_state(u32)]
|
||||
node: NodeStateTester,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
struct NodeStateTester(Option<String>, Vec<(String, String)>);
|
||||
impl NodeDepState for NodeStateTester {
|
||||
type Ctx = u32;
|
||||
type DepState = ();
|
||||
const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::All, true, false, false);
|
||||
fn reduce(&mut self, node: NodeView, ctx: &Self::Ctx) -> bool {
|
||||
fn reduce(&mut self, node: NodeView, _sibling: &Self::DepState, ctx: &Self::Ctx) -> bool {
|
||||
assert_eq!(*ctx, 42);
|
||||
*self = NodeStateTester(
|
||||
node.tag().map(|s| s.to_string()),
|
||||
|
@ -134,6 +125,16 @@ impl NodeDepState for NodeStateTester {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(State, Clone, Default, Debug)]
|
||||
struct StateTester {
|
||||
#[child_dep_state(bubbled, u32)]
|
||||
bubbled: BubbledUpStateTester,
|
||||
#[parent_dep_state(pushed, u32)]
|
||||
pushed: PushedDownStateTester,
|
||||
#[node_dep_state(NONE, u32)]
|
||||
node: NodeStateTester,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn state_initial() {
|
||||
#[allow(non_snake_case)]
|
||||
|
@ -172,7 +173,10 @@ fn state_initial() {
|
|||
]
|
||||
);
|
||||
assert_eq!(root_div.state.pushed.0, Some("div".to_string()));
|
||||
assert_eq!(root_div.state.pushed.1, None);
|
||||
assert_eq!(
|
||||
root_div.state.pushed.1,
|
||||
Some(Box::new(PushedDownStateTester(None, None)))
|
||||
);
|
||||
assert_eq!(root_div.state.node.0, Some("div".to_string()));
|
||||
assert_eq!(root_div.state.node.1, vec![]);
|
||||
|
||||
|
@ -184,7 +188,7 @@ fn state_initial() {
|
|||
child_p.state.pushed.1,
|
||||
Some(Box::new(PushedDownStateTester(
|
||||
Some("div".to_string()),
|
||||
None
|
||||
Some(Box::new(PushedDownStateTester(None, None)))
|
||||
)))
|
||||
);
|
||||
assert_eq!(child_p.state.node.0, Some("p".to_string()));
|
||||
|
@ -201,7 +205,7 @@ fn state_initial() {
|
|||
child_h1.state.pushed.1,
|
||||
Some(Box::new(PushedDownStateTester(
|
||||
Some("div".to_string()),
|
||||
None
|
||||
Some(Box::new(PushedDownStateTester(None, None)))
|
||||
)))
|
||||
);
|
||||
assert_eq!(child_h1.state.node.0, Some("h1".to_string()));
|
||||
|
@ -256,10 +260,6 @@ fn state_reduce_initally_called_minimally() {
|
|||
let nodes_updated = dom.apply_mutations(vec![mutations]);
|
||||
let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new());
|
||||
|
||||
dom.traverse_depth_first(|n| {
|
||||
println!("{:#?}", n.state);
|
||||
});
|
||||
|
||||
dom.traverse_depth_first(|n| {
|
||||
assert_eq!(n.state.child_counter.0, 1);
|
||||
assert_eq!(n.state.parent_counter.0, 1);
|
||||
|
@ -268,7 +268,7 @@ fn state_reduce_initally_called_minimally() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn state_reduce_down_called_minimally_on_update() {
|
||||
fn state_reduce_parent_called_minimally_on_update() {
|
||||
#[allow(non_snake_case)]
|
||||
fn Base(cx: Scope) -> Element {
|
||||
rsx!(cx, div {
|
||||
|
@ -329,21 +329,20 @@ fn state_reduce_down_called_minimally_on_update() {
|
|||
let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new());
|
||||
|
||||
dom.traverse_depth_first(|n| {
|
||||
// println!("{:?}", n.state);
|
||||
assert_eq!(n.state.parent_counter.0, 2);
|
||||
});
|
||||
// panic!()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn state_reduce_up_called_minimally_on_update() {
|
||||
fn state_reduce_child_called_minimally_on_update() {
|
||||
#[allow(non_snake_case)]
|
||||
fn Base(cx: Scope) -> Element {
|
||||
rsx!(cx, div {
|
||||
width: "100%",
|
||||
div{
|
||||
div{
|
||||
p{}
|
||||
p{
|
||||
width: "100%",
|
||||
}
|
||||
}
|
||||
p{
|
||||
"hello"
|
||||
|
@ -362,10 +361,11 @@ fn state_reduce_up_called_minimally_on_update() {
|
|||
|
||||
let mutations = vdom.create_vnodes(rsx! {
|
||||
div {
|
||||
width: "100%",
|
||||
div{
|
||||
div{
|
||||
p{}
|
||||
p{
|
||||
width: "100%",
|
||||
}
|
||||
}
|
||||
p{
|
||||
"hello"
|
||||
|
@ -397,6 +397,93 @@ fn state_reduce_up_called_minimally_on_update() {
|
|||
let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new());
|
||||
|
||||
dom.traverse_depth_first(|n| {
|
||||
println!("{:?}", n);
|
||||
assert_eq!(n.state.child_counter.0, if n.id.0 > 4 { 1 } else { 2 });
|
||||
});
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, State)]
|
||||
struct UnorderedDependanciesState {
|
||||
#[node_dep_state(c)]
|
||||
b: BDepCallCounter,
|
||||
#[node_dep_state()]
|
||||
c: CDepCallCounter,
|
||||
#[node_dep_state(b)]
|
||||
a: ADepCallCounter,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq)]
|
||||
struct ADepCallCounter(usize, BDepCallCounter);
|
||||
impl NodeDepState for ADepCallCounter {
|
||||
type Ctx = ();
|
||||
type DepState = BDepCallCounter;
|
||||
const NODE_MASK: NodeMask = NodeMask::NONE;
|
||||
fn reduce(&mut self, _node: NodeView, sibling: &Self::DepState, _ctx: &Self::Ctx) -> bool {
|
||||
self.0 += 1;
|
||||
self.1 = sibling.clone();
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq)]
|
||||
struct BDepCallCounter(usize, CDepCallCounter);
|
||||
impl NodeDepState for BDepCallCounter {
|
||||
type Ctx = ();
|
||||
type DepState = CDepCallCounter;
|
||||
const NODE_MASK: NodeMask = NodeMask::NONE;
|
||||
fn reduce(&mut self, _node: NodeView, sibling: &Self::DepState, _ctx: &Self::Ctx) -> bool {
|
||||
self.0 += 1;
|
||||
self.1 = sibling.clone();
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq)]
|
||||
struct CDepCallCounter(usize);
|
||||
impl NodeDepState for CDepCallCounter {
|
||||
type Ctx = ();
|
||||
type DepState = ();
|
||||
const NODE_MASK: NodeMask = NodeMask::ALL;
|
||||
fn reduce(&mut self, _node: NodeView, _sibling: &Self::DepState, _ctx: &Self::Ctx) -> bool {
|
||||
self.0 += 1;
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dependancies_order_independant() {
|
||||
#[allow(non_snake_case)]
|
||||
fn Base(cx: Scope) -> Element {
|
||||
rsx!(cx, div {
|
||||
width: "100%",
|
||||
p{
|
||||
"hello"
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
let vdom = VirtualDom::new(Base);
|
||||
|
||||
let mutations = vdom.create_vnodes(rsx! {
|
||||
div {
|
||||
width: "100%",
|
||||
p{
|
||||
"hello"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let mut dom: RealDom<UnorderedDependanciesState> = RealDom::new();
|
||||
|
||||
let nodes_updated = dom.apply_mutations(vec![mutations]);
|
||||
let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new());
|
||||
|
||||
let c = CDepCallCounter(1);
|
||||
let b = BDepCallCounter(1, c.clone());
|
||||
let a = ADepCallCounter(1, b.clone());
|
||||
dom.traverse_depth_first(|n| {
|
||||
assert_eq!(&n.state.a, &a);
|
||||
assert_eq!(&n.state.b, &b);
|
||||
assert_eq!(&n.state.c, &c);
|
||||
});
|
||||
}
|
|
@ -10,7 +10,6 @@ homepage = "https://dioxuslabs.com"
|
|||
dioxus-core = { path = "../core", version = "^0.2.0" }
|
||||
dioxus-html = { path = "../html", version = "^0.2.0" }
|
||||
dioxus-core-macro = { path = "../core-macro", version = "^0.2.0" }
|
||||
dioxus-native-core-macro = { path = "../native-core-macro", version = "^0.2.0" }
|
||||
|
||||
stretch2 = { git = "https://github.com/DioxusLabs/stretch" }
|
||||
smallvec = "1.6"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
pub mod layout_attributes;
|
||||
pub mod node_ref;
|
||||
pub mod real_dom;
|
||||
pub mod state;
|
||||
pub use dioxus_native_core_macro;
|
||||
|
|
212
packages/native-core/src/node_ref.rs
Normal file
212
packages/native-core/src/node_ref.rs
Normal file
|
@ -0,0 +1,212 @@
|
|||
use dioxus_core::*;
|
||||
|
||||
use crate::state::union_ordered_iter;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct NodeView<'a> {
|
||||
inner: &'a VNode<'a>,
|
||||
mask: NodeMask,
|
||||
}
|
||||
impl<'a> NodeView<'a> {
|
||||
pub fn new(mut vnode: &'a VNode<'a>, view: NodeMask, vdom: &'a VirtualDom) -> Self {
|
||||
if let VNode::Component(sc) = vnode {
|
||||
let scope = vdom.get_scope(sc.scope.get().unwrap()).unwrap();
|
||||
vnode = scope.root_node();
|
||||
}
|
||||
Self {
|
||||
inner: vnode,
|
||||
mask: view,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn id(&self) -> ElementId {
|
||||
self.inner.mounted_id()
|
||||
}
|
||||
|
||||
pub fn tag(&self) -> Option<&'a str> {
|
||||
self.mask.tag.then(|| self.el().map(|el| el.tag)).flatten()
|
||||
}
|
||||
|
||||
pub fn namespace(&self) -> Option<&'a str> {
|
||||
self.mask
|
||||
.namespace
|
||||
.then(|| self.el().map(|el| el.namespace).flatten())
|
||||
.flatten()
|
||||
}
|
||||
|
||||
pub fn attributes(&self) -> impl Iterator<Item = &Attribute<'a>> {
|
||||
self.el()
|
||||
.map(|el| el.attributes)
|
||||
.unwrap_or_default()
|
||||
.iter()
|
||||
.filter(|a| self.mask.attritutes.contains_attribute(&a.name))
|
||||
}
|
||||
|
||||
pub fn text(&self) -> Option<&str> {
|
||||
self.mask
|
||||
.text
|
||||
.then(|| self.txt().map(|txt| txt.text))
|
||||
.flatten()
|
||||
}
|
||||
|
||||
fn el(&self) -> Option<&'a VElement<'a>> {
|
||||
if let VNode::Element(el) = &self.inner {
|
||||
Some(el)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn txt(&self) -> Option<&'a VText<'a>> {
|
||||
if let VNode::Text(txt) = &self.inner {
|
||||
Some(txt)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Debug)]
|
||||
pub enum AttributeMask {
|
||||
All,
|
||||
Dynamic(Vec<&'static str>),
|
||||
Static(&'static [&'static str]),
|
||||
}
|
||||
|
||||
impl AttributeMask {
|
||||
pub const NONE: Self = Self::Static(&[]);
|
||||
|
||||
fn contains_attribute(&self, attr: &'static str) -> bool {
|
||||
match self {
|
||||
AttributeMask::All => true,
|
||||
AttributeMask::Dynamic(l) => l.binary_search(&attr).is_ok(),
|
||||
AttributeMask::Static(l) => l.binary_search(&attr).is_ok(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn single(new: &'static str) -> Self {
|
||||
Self::Dynamic(vec![new])
|
||||
}
|
||||
|
||||
pub fn verify(&self) {
|
||||
match &self {
|
||||
AttributeMask::Static(attrs) => debug_assert!(
|
||||
attrs.windows(2).all(|w| w[0] < w[1]),
|
||||
"attritutes must be increasing"
|
||||
),
|
||||
AttributeMask::Dynamic(attrs) => debug_assert!(
|
||||
attrs.windows(2).all(|w| w[0] < w[1]),
|
||||
"attritutes must be increasing"
|
||||
),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn union(&self, other: &Self) -> Self {
|
||||
let new = match (self, other) {
|
||||
(AttributeMask::Dynamic(s), AttributeMask::Dynamic(o)) => AttributeMask::Dynamic(
|
||||
union_ordered_iter(s.iter().copied(), o.iter().copied(), s.len() + o.len()),
|
||||
),
|
||||
(AttributeMask::Static(s), AttributeMask::Dynamic(o)) => AttributeMask::Dynamic(
|
||||
union_ordered_iter(s.iter().copied(), o.iter().copied(), s.len() + o.len()),
|
||||
),
|
||||
(AttributeMask::Dynamic(s), AttributeMask::Static(o)) => AttributeMask::Dynamic(
|
||||
union_ordered_iter(s.iter().copied(), o.iter().copied(), s.len() + o.len()),
|
||||
),
|
||||
(AttributeMask::Static(s), AttributeMask::Static(o)) => AttributeMask::Dynamic(
|
||||
union_ordered_iter(s.iter().copied(), o.iter().copied(), s.len() + o.len()),
|
||||
),
|
||||
_ => AttributeMask::All,
|
||||
};
|
||||
new.verify();
|
||||
new
|
||||
}
|
||||
|
||||
fn overlaps(&self, other: &Self) -> bool {
|
||||
fn overlaps_iter(
|
||||
mut self_iter: impl Iterator<Item = &'static str>,
|
||||
mut other_iter: impl Iterator<Item = &'static str>,
|
||||
) -> bool {
|
||||
if let Some(mut other_attr) = other_iter.next() {
|
||||
while let Some(self_attr) = self_iter.next() {
|
||||
while other_attr < self_attr {
|
||||
if let Some(attr) = other_iter.next() {
|
||||
other_attr = attr;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if other_attr == self_attr {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
match (self, other) {
|
||||
(AttributeMask::All, AttributeMask::All) => true,
|
||||
(AttributeMask::All, AttributeMask::Dynamic(v)) => !v.is_empty(),
|
||||
(AttributeMask::All, AttributeMask::Static(s)) => !s.is_empty(),
|
||||
(AttributeMask::Dynamic(v), AttributeMask::All) => !v.is_empty(),
|
||||
(AttributeMask::Static(s), AttributeMask::All) => !s.is_empty(),
|
||||
(AttributeMask::Dynamic(v1), AttributeMask::Dynamic(v2)) => {
|
||||
overlaps_iter(v1.iter().copied(), v2.iter().copied())
|
||||
}
|
||||
(AttributeMask::Dynamic(v), AttributeMask::Static(s)) => {
|
||||
overlaps_iter(v.iter().copied(), s.iter().copied())
|
||||
}
|
||||
(AttributeMask::Static(s), AttributeMask::Dynamic(v)) => {
|
||||
overlaps_iter(v.iter().copied(), s.iter().copied())
|
||||
}
|
||||
(AttributeMask::Static(s1), AttributeMask::Static(s2)) => {
|
||||
overlaps_iter(s1.iter().copied(), s2.iter().copied())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for AttributeMask {
|
||||
fn default() -> Self {
|
||||
AttributeMask::Static(&[])
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, PartialEq, Clone, Debug)]
|
||||
pub struct NodeMask {
|
||||
// must be sorted
|
||||
attritutes: AttributeMask,
|
||||
tag: bool,
|
||||
namespace: bool,
|
||||
text: bool,
|
||||
}
|
||||
|
||||
impl NodeMask {
|
||||
pub const NONE: Self = Self::new(AttributeMask::Static(&[]), false, false, false);
|
||||
pub const ALL: Self = Self::new(AttributeMask::All, true, true, true);
|
||||
|
||||
/// attritutes must be sorted!
|
||||
pub const fn new(attritutes: AttributeMask, tag: bool, namespace: bool, text: bool) -> Self {
|
||||
Self {
|
||||
attritutes,
|
||||
tag,
|
||||
namespace,
|
||||
text,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn overlaps(&self, other: &Self) -> bool {
|
||||
(self.tag && other.tag)
|
||||
|| (self.namespace && other.namespace)
|
||||
|| self.attritutes.overlaps(&other.attritutes)
|
||||
|| (self.text && other.text)
|
||||
}
|
||||
|
||||
pub fn union(&self, other: &Self) -> Self {
|
||||
Self {
|
||||
attritutes: self.attritutes.union(&other.attritutes),
|
||||
tag: self.tag | other.tag,
|
||||
namespace: self.namespace | other.namespace,
|
||||
text: self.text | other.text,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,14 +1,17 @@
|
|||
use anymap::AnyMap;
|
||||
use fxhash::{FxHashMap, FxHashSet};
|
||||
use std::{
|
||||
any::TypeId,
|
||||
collections::VecDeque,
|
||||
ops::{Index, IndexMut},
|
||||
};
|
||||
|
||||
use dioxus_core::{ElementId, Mutations, VNode, VirtualDom};
|
||||
|
||||
use crate::state::{union_ordered_iter, AttributeMask, NodeMask, State};
|
||||
use crate::state::{union_ordered_iter, State};
|
||||
use crate::{
|
||||
node_ref::{AttributeMask, NodeMask},
|
||||
state::MemberId,
|
||||
};
|
||||
|
||||
/// 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.
|
||||
|
@ -211,7 +214,7 @@ impl<S: State> RealDom<S> {
|
|||
#[derive(PartialEq, Clone, Debug)]
|
||||
enum StatesToCheck {
|
||||
All,
|
||||
Some(Vec<TypeId>),
|
||||
Some(Vec<MemberId>),
|
||||
}
|
||||
impl StatesToCheck {
|
||||
fn union(&self, other: &Self) -> Self {
|
||||
|
@ -294,14 +297,28 @@ impl<S: State> RealDom<S> {
|
|||
for node_ref in &nodes_updated {
|
||||
let mut changed = false;
|
||||
let node = &mut self[node_ref.id];
|
||||
let ids = match &node_ref.to_check {
|
||||
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!(),
|
||||
};
|
||||
for ty in ids {
|
||||
let mut i = 0;
|
||||
while i < ids.len() {
|
||||
let id = ids[i];
|
||||
let node = &mut self[node_ref.id];
|
||||
let vnode = node.element(vdom);
|
||||
changed |= node.state.update_node_dep_state(ty, vnode, vdom, &ctx);
|
||||
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);
|
||||
|
@ -319,19 +336,30 @@ impl<S: State> RealDom<S> {
|
|||
} = node_ref;
|
||||
let (node, children) = self.get_node_children_mut(id).unwrap();
|
||||
let children_state: Vec<_> = children.iter().map(|c| &c.state).collect();
|
||||
let ids = match to_check {
|
||||
let mut ids = match to_check {
|
||||
StatesToCheck::All => node.state.child_dep_types(&node_mask),
|
||||
StatesToCheck::Some(ids) => ids,
|
||||
};
|
||||
let mut changed = Vec::new();
|
||||
for ty in ids {
|
||||
let mut i = 0;
|
||||
while i < ids.len() {
|
||||
let id = ids[i];
|
||||
let vnode = node.element(vdom);
|
||||
if node
|
||||
.state
|
||||
.update_child_dep_state(ty, vnode, vdom, &children_state, &ctx)
|
||||
if let Some(members_effected) =
|
||||
node.state
|
||||
.update_child_dep_state(id, vnode, vdom, &children_state, &ctx)
|
||||
{
|
||||
changed.push(ty);
|
||||
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() {
|
||||
|
@ -374,19 +402,32 @@ impl<S: State> RealDom<S> {
|
|||
to_check,
|
||||
} = node_ref;
|
||||
let node = &self[id];
|
||||
let ids = match to_check {
|
||||
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();
|
||||
for ty in ids {
|
||||
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 state.update_parent_dep_state(ty, vnode, vdom, parent.map(|n| &n.state), &ctx) {
|
||||
changed.push(ty);
|
||||
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);
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
use std::{any::TypeId, fmt::Debug};
|
||||
use std::fmt::Debug;
|
||||
|
||||
use anymap::AnyMap;
|
||||
use dioxus_core::{Attribute, ElementId, VElement, VNode, VText, VirtualDom};
|
||||
use dioxus_core::VNode;
|
||||
|
||||
use crate::node_ref::{NodeMask, NodeView};
|
||||
|
||||
pub(crate) fn union_ordered_iter<T: Ord + Debug>(
|
||||
s_iter: impl Iterator<Item = T>,
|
||||
|
@ -34,215 +36,6 @@ pub(crate) fn union_ordered_iter<T: Ord + Debug>(
|
|||
v
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct NodeView<'a> {
|
||||
inner: &'a VNode<'a>,
|
||||
mask: NodeMask,
|
||||
}
|
||||
impl<'a> NodeView<'a> {
|
||||
pub fn new(mut vnode: &'a VNode<'a>, view: NodeMask, vdom: &'a VirtualDom) -> Self {
|
||||
if let VNode::Component(sc) = vnode {
|
||||
let scope = vdom.get_scope(sc.scope.get().unwrap()).unwrap();
|
||||
vnode = scope.root_node();
|
||||
}
|
||||
Self {
|
||||
inner: vnode,
|
||||
mask: view,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn id(&self) -> ElementId {
|
||||
self.inner.mounted_id()
|
||||
}
|
||||
|
||||
pub fn tag(&self) -> Option<&'a str> {
|
||||
self.mask.tag.then(|| self.el().map(|el| el.tag)).flatten()
|
||||
}
|
||||
|
||||
pub fn namespace(&self) -> Option<&'a str> {
|
||||
self.mask
|
||||
.namespace
|
||||
.then(|| self.el().map(|el| el.namespace).flatten())
|
||||
.flatten()
|
||||
}
|
||||
|
||||
pub fn attributes(&self) -> impl Iterator<Item = &Attribute<'a>> {
|
||||
self.el()
|
||||
.map(|el| el.attributes)
|
||||
.unwrap_or_default()
|
||||
.iter()
|
||||
.filter(|a| self.mask.attritutes.contains_attribute(&a.name))
|
||||
}
|
||||
|
||||
pub fn text(&self) -> Option<&str> {
|
||||
self.mask
|
||||
.text
|
||||
.then(|| self.txt().map(|txt| txt.text))
|
||||
.flatten()
|
||||
}
|
||||
|
||||
fn el(&self) -> Option<&'a VElement<'a>> {
|
||||
if let VNode::Element(el) = &self.inner {
|
||||
Some(el)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn txt(&self) -> Option<&'a VText<'a>> {
|
||||
if let VNode::Text(txt) = &self.inner {
|
||||
Some(txt)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Debug)]
|
||||
pub enum AttributeMask {
|
||||
All,
|
||||
Dynamic(Vec<&'static str>),
|
||||
Static(&'static [&'static str]),
|
||||
}
|
||||
|
||||
impl AttributeMask {
|
||||
pub const NONE: Self = Self::Static(&[]);
|
||||
|
||||
fn contains_attribute(&self, attr: &'static str) -> bool {
|
||||
match self {
|
||||
AttributeMask::All => true,
|
||||
AttributeMask::Dynamic(l) => l.binary_search(&attr).is_ok(),
|
||||
AttributeMask::Static(l) => l.binary_search(&attr).is_ok(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn single(new: &'static str) -> Self {
|
||||
Self::Dynamic(vec![new])
|
||||
}
|
||||
|
||||
pub fn verify(&self) {
|
||||
match &self {
|
||||
AttributeMask::Static(attrs) => debug_assert!(
|
||||
attrs.windows(2).all(|w| w[0] < w[1]),
|
||||
"attritutes must be increasing"
|
||||
),
|
||||
AttributeMask::Dynamic(attrs) => debug_assert!(
|
||||
attrs.windows(2).all(|w| w[0] < w[1]),
|
||||
"attritutes must be increasing"
|
||||
),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn union(&self, other: &Self) -> Self {
|
||||
let new = match (self, other) {
|
||||
(AttributeMask::Dynamic(s), AttributeMask::Dynamic(o)) => AttributeMask::Dynamic(
|
||||
union_ordered_iter(s.iter().copied(), o.iter().copied(), s.len() + o.len()),
|
||||
),
|
||||
(AttributeMask::Static(s), AttributeMask::Dynamic(o)) => AttributeMask::Dynamic(
|
||||
union_ordered_iter(s.iter().copied(), o.iter().copied(), s.len() + o.len()),
|
||||
),
|
||||
(AttributeMask::Dynamic(s), AttributeMask::Static(o)) => AttributeMask::Dynamic(
|
||||
union_ordered_iter(s.iter().copied(), o.iter().copied(), s.len() + o.len()),
|
||||
),
|
||||
(AttributeMask::Static(s), AttributeMask::Static(o)) => AttributeMask::Dynamic(
|
||||
union_ordered_iter(s.iter().copied(), o.iter().copied(), s.len() + o.len()),
|
||||
),
|
||||
_ => AttributeMask::All,
|
||||
};
|
||||
new.verify();
|
||||
new
|
||||
}
|
||||
|
||||
fn overlaps(&self, other: &Self) -> bool {
|
||||
fn overlaps_iter(
|
||||
mut self_iter: impl Iterator<Item = &'static str>,
|
||||
mut other_iter: impl Iterator<Item = &'static str>,
|
||||
) -> bool {
|
||||
if let Some(mut other_attr) = other_iter.next() {
|
||||
while let Some(self_attr) = self_iter.next() {
|
||||
while other_attr < self_attr {
|
||||
if let Some(attr) = other_iter.next() {
|
||||
other_attr = attr;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if other_attr == self_attr {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
match (self, other) {
|
||||
(AttributeMask::All, AttributeMask::All) => true,
|
||||
(AttributeMask::All, AttributeMask::Dynamic(v)) => !v.is_empty(),
|
||||
(AttributeMask::All, AttributeMask::Static(s)) => !s.is_empty(),
|
||||
(AttributeMask::Dynamic(v), AttributeMask::All) => !v.is_empty(),
|
||||
(AttributeMask::Static(s), AttributeMask::All) => !s.is_empty(),
|
||||
(AttributeMask::Dynamic(v1), AttributeMask::Dynamic(v2)) => {
|
||||
overlaps_iter(v1.iter().copied(), v2.iter().copied())
|
||||
}
|
||||
(AttributeMask::Dynamic(v), AttributeMask::Static(s)) => {
|
||||
overlaps_iter(v.iter().copied(), s.iter().copied())
|
||||
}
|
||||
(AttributeMask::Static(s), AttributeMask::Dynamic(v)) => {
|
||||
overlaps_iter(v.iter().copied(), s.iter().copied())
|
||||
}
|
||||
(AttributeMask::Static(s1), AttributeMask::Static(s2)) => {
|
||||
overlaps_iter(s1.iter().copied(), s2.iter().copied())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for AttributeMask {
|
||||
fn default() -> Self {
|
||||
AttributeMask::Static(&[])
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, PartialEq, Clone, Debug)]
|
||||
pub struct NodeMask {
|
||||
// must be sorted
|
||||
attritutes: AttributeMask,
|
||||
tag: bool,
|
||||
namespace: bool,
|
||||
text: bool,
|
||||
}
|
||||
|
||||
impl NodeMask {
|
||||
pub const NONE: Self = Self::new(AttributeMask::Static(&[]), false, false, false);
|
||||
pub const ALL: Self = Self::new(AttributeMask::All, true, true, true);
|
||||
|
||||
/// attritutes must be sorted!
|
||||
pub const fn new(attritutes: AttributeMask, tag: bool, namespace: bool, text: bool) -> Self {
|
||||
Self {
|
||||
attritutes,
|
||||
tag,
|
||||
namespace,
|
||||
text,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn overlaps(&self, other: &Self) -> bool {
|
||||
(self.tag && other.tag)
|
||||
|| (self.namespace && other.namespace)
|
||||
|| self.attritutes.overlaps(&other.attritutes)
|
||||
|| (self.text && other.text)
|
||||
}
|
||||
|
||||
pub fn union(&self, other: &Self) -> Self {
|
||||
Self {
|
||||
attritutes: self.attritutes.union(&other.attritutes),
|
||||
tag: self.tag | other.tag,
|
||||
namespace: self.namespace | other.namespace,
|
||||
text: self.text | other.text,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This state is derived from children. For example a node's size could be derived from the size of children.
|
||||
/// Called when the current node's node properties are modified, a child's [BubbledUpState] is modified or a child is removed.
|
||||
/// Called at most once per update.
|
||||
|
@ -250,7 +43,8 @@ pub trait ChildDepState {
|
|||
/// The context is passed to the [PushedDownState::reduce] when it is pushed down.
|
||||
/// This is sometimes nessisary for lifetime purposes.
|
||||
type Ctx;
|
||||
type DepState: ChildDepState;
|
||||
/// This must be either a [ChildDepState] or [NodeDepState]
|
||||
type DepState;
|
||||
const NODE_MASK: NodeMask = NodeMask::NONE;
|
||||
fn reduce<'a>(
|
||||
&mut self,
|
||||
|
@ -269,7 +63,8 @@ pub trait ParentDepState {
|
|||
/// The context is passed to the [PushedDownState::reduce] when it is pushed down.
|
||||
/// This is sometimes nessisary for lifetime purposes.
|
||||
type Ctx;
|
||||
type DepState: ParentDepState;
|
||||
/// This must be either a [ParentDepState] or [NodeDepState]
|
||||
type DepState;
|
||||
const NODE_MASK: NodeMask = NodeMask::NONE;
|
||||
fn reduce(&mut self, node: NodeView, parent: Option<&Self::DepState>, ctx: &Self::Ctx) -> bool;
|
||||
}
|
||||
|
@ -279,40 +74,93 @@ pub trait ParentDepState {
|
|||
/// Called at most once per update.
|
||||
pub trait NodeDepState {
|
||||
type Ctx;
|
||||
type DepState: NodeDepState;
|
||||
const NODE_MASK: NodeMask = NodeMask::NONE;
|
||||
fn reduce(&mut self, node: NodeView, ctx: &Self::Ctx) -> bool;
|
||||
fn reduce(&mut self, node: NodeView, sibling: &Self::DepState, ctx: &Self::Ctx) -> bool;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ChildStatesChanged {
|
||||
pub node_dep: &'static [MemberId],
|
||||
pub child_dep: &'static [MemberId],
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ParentStatesChanged {
|
||||
pub node_dep: &'static [MemberId],
|
||||
pub parent_dep: &'static [MemberId],
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct NodeStatesChanged {
|
||||
pub node_dep: &'static [MemberId],
|
||||
}
|
||||
|
||||
pub trait State: Default + Clone {
|
||||
fn update_node_dep_state<'a>(
|
||||
&'a mut self,
|
||||
ty: TypeId,
|
||||
ty: MemberId,
|
||||
node: &'a VNode<'a>,
|
||||
vdom: &'a dioxus_core::VirtualDom,
|
||||
ctx: &AnyMap,
|
||||
) -> bool;
|
||||
) -> Option<NodeStatesChanged>;
|
||||
/// This must be a valid resolution order. (no nodes updated before a state they rely on)
|
||||
fn child_dep_types(&self, mask: &NodeMask) -> Vec<TypeId>;
|
||||
fn child_dep_types(&self, mask: &NodeMask) -> Vec<MemberId>;
|
||||
|
||||
fn update_parent_dep_state<'a>(
|
||||
&'a mut self,
|
||||
ty: TypeId,
|
||||
ty: MemberId,
|
||||
node: &'a VNode<'a>,
|
||||
vdom: &'a dioxus_core::VirtualDom,
|
||||
parent: Option<&Self>,
|
||||
ctx: &AnyMap,
|
||||
) -> bool;
|
||||
) -> 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<TypeId>;
|
||||
fn parent_dep_types(&self, mask: &NodeMask) -> Vec<MemberId>;
|
||||
|
||||
fn update_child_dep_state<'a>(
|
||||
&'a mut self,
|
||||
ty: TypeId,
|
||||
ty: MemberId,
|
||||
node: &'a VNode<'a>,
|
||||
vdom: &'a dioxus_core::VirtualDom,
|
||||
children: &[&Self],
|
||||
ctx: &AnyMap,
|
||||
) -> bool;
|
||||
) -> 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<TypeId>;
|
||||
fn node_dep_types(&self, mask: &NodeMask) -> Vec<MemberId>;
|
||||
}
|
||||
|
||||
impl ChildDepState for () {
|
||||
type Ctx = ();
|
||||
type DepState = ();
|
||||
fn reduce<'a>(
|
||||
&mut self,
|
||||
_: NodeView,
|
||||
_: impl Iterator<Item = &'a Self::DepState>,
|
||||
_: &Self::Ctx,
|
||||
) -> bool
|
||||
where
|
||||
Self::DepState: 'a,
|
||||
{
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl ParentDepState for () {
|
||||
type Ctx = ();
|
||||
type DepState = ();
|
||||
fn reduce(&mut self, _: NodeView, _: Option<&Self::DepState>, _: &Self::Ctx) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl NodeDepState for () {
|
||||
type Ctx = ();
|
||||
type DepState = ();
|
||||
fn reduce(&mut self, _: NodeView, _sibling: &Self::DepState, _: &Self::Ctx) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct MemberId(pub usize);
|
||||
|
|
|
@ -1,59 +0,0 @@
|
|||
use dioxus_native_core::state::*;
|
||||
use dioxus_native_core_macro::*;
|
||||
|
||||
#[derive(State, Default, Clone)]
|
||||
struct Z {
|
||||
// depends on just attributes and no context
|
||||
#[node_dep_state()]
|
||||
x: A,
|
||||
// depends on attributes, the B component of children and i32 context
|
||||
#[child_dep_state(B, i32)]
|
||||
y: B,
|
||||
// depends on attributes, the C component of it's parent and a u8 context
|
||||
#[parent_dep_state(C, u8)]
|
||||
z: C,
|
||||
}
|
||||
|
||||
use dioxus_native_core::state::NodeDepState;
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
struct A;
|
||||
impl NodeDepState for A {
|
||||
type Ctx = ();
|
||||
fn reduce(&mut self, _: NodeView, _: &()) -> bool {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
struct B;
|
||||
impl ChildDepState for B {
|
||||
type Ctx = i32;
|
||||
type DepState = Self;
|
||||
fn reduce<'a>(
|
||||
&mut self,
|
||||
_: dioxus_native_core::state::NodeView,
|
||||
_: impl Iterator<Item = &'a Self::DepState>,
|
||||
_: &i32,
|
||||
) -> bool
|
||||
where
|
||||
Self::DepState: 'a,
|
||||
{
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
struct C;
|
||||
impl ParentDepState for C {
|
||||
type Ctx = u8;
|
||||
type DepState = Self;
|
||||
fn reduce(
|
||||
&mut self,
|
||||
_: dioxus_native_core::state::NodeView,
|
||||
_: Option<&Self::DepState>,
|
||||
_: &u8,
|
||||
) -> bool {
|
||||
todo!()
|
||||
}
|
||||
}
|
|
@ -16,6 +16,7 @@ license = "MIT/Apache-2.0"
|
|||
dioxus-core = { path = "../core", version = "^0.2.0" }
|
||||
dioxus-html = { path = "../html", version = "^0.2.0" }
|
||||
dioxus-native-core = { path = "../native-core", version = "^0.2.0" }
|
||||
dioxus-native-core-macro = { path = "../native-core-macro", version = "^0.2.0" }
|
||||
|
||||
tui = "0.17.0"
|
||||
crossterm = "0.23.0"
|
||||
|
|
|
@ -2,9 +2,10 @@ use std::cell::RefCell;
|
|||
use std::rc::Rc;
|
||||
|
||||
use dioxus_core::*;
|
||||
use dioxus_native_core::dioxus_native_core_macro::sorted_str_slice;
|
||||
use dioxus_native_core::layout_attributes::apply_layout_attributes;
|
||||
use dioxus_native_core::state::{AttributeMask, ChildDepState, NodeMask, NodeView};
|
||||
use dioxus_native_core::node_ref::{AttributeMask, NodeMask, NodeView};
|
||||
use dioxus_native_core::state::ChildDepState;
|
||||
use dioxus_native_core_macro::sorted_str_slice;
|
||||
use stretch2::prelude::*;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
|
|
|
@ -7,7 +7,8 @@ use crossterm::{
|
|||
};
|
||||
use dioxus_core::exports::futures_channel::mpsc::unbounded;
|
||||
use dioxus_core::*;
|
||||
use dioxus_native_core::{dioxus_native_core_macro::State, real_dom::RealDom, state::*};
|
||||
use dioxus_native_core::{real_dom::RealDom, state::*};
|
||||
use dioxus_native_core_macro::State;
|
||||
use futures::{
|
||||
channel::mpsc::{UnboundedReceiver, UnboundedSender},
|
||||
pin_mut, StreamExt,
|
||||
|
@ -36,10 +37,10 @@ type Node = dioxus_native_core::real_dom::Node<NodeState>;
|
|||
|
||||
#[derive(Debug, Clone, State, Default)]
|
||||
struct NodeState {
|
||||
#[child_dep_state(StretchLayout, RefCell<Stretch>)]
|
||||
#[child_dep_state(layout, RefCell<Stretch>)]
|
||||
layout: StretchLayout,
|
||||
// depends on attributes, the C component of it's parent and a u8 context
|
||||
#[parent_dep_state(StyleModifier)]
|
||||
#[parent_dep_state(style)]
|
||||
style: StyleModifier,
|
||||
}
|
||||
|
||||
|
|
|
@ -32,8 +32,10 @@
|
|||
use dioxus_core::Attribute;
|
||||
use dioxus_native_core::{
|
||||
layout_attributes::{parse_value, UnitSystem},
|
||||
state::{AttributeMask, NodeMask, NodeView, ParentDepState},
|
||||
node_ref::{AttributeMask, NodeMask, NodeView},
|
||||
state::ParentDepState,
|
||||
};
|
||||
use dioxus_native_core_macro::sorted_str_slice;
|
||||
|
||||
use crate::style::{RinkColor, RinkStyle};
|
||||
|
||||
|
@ -47,7 +49,8 @@ impl ParentDepState for StyleModifier {
|
|||
type Ctx = ();
|
||||
type DepState = Self;
|
||||
// todo: seperate each attribute into it's own class
|
||||
const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::All, true, true, false);
|
||||
const NODE_MASK: NodeMask =
|
||||
NodeMask::new(AttributeMask::Static(SORTED_STYLE_ATTRS), true, true, false);
|
||||
|
||||
fn reduce(&mut self, node: NodeView, parent: Option<&Self::DepState>, _: &Self::Ctx) -> bool {
|
||||
let mut new = StyleModifier::default();
|
||||
|
@ -586,3 +589,211 @@ fn apply_text(name: &str, value: &str, style: &mut StyleModifier) {
|
|||
fn apply_transition(_name: &str, _value: &str, _style: &mut StyleModifier) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
const SORTED_STYLE_ATTRS: &'static [&'static str] = &sorted_str_slice!([
|
||||
"animation",
|
||||
"animation-delay",
|
||||
"animation-direction",
|
||||
"animation-duration",
|
||||
"animation-fill-mode",
|
||||
"animation-iteration-count",
|
||||
"animation-name",
|
||||
"animation-play-state",
|
||||
"animation-timing-function",
|
||||
"backface-visibility",
|
||||
"background",
|
||||
"background-attachment",
|
||||
"background-clip",
|
||||
"background-color",
|
||||
"background-image",
|
||||
"background-origin",
|
||||
"background-position",
|
||||
"background-repeat",
|
||||
"background-size",
|
||||
"border",
|
||||
"border-bottom",
|
||||
"border-bottom-color",
|
||||
"border-bottom-left-radius",
|
||||
"border-bottom-right-radius",
|
||||
"border-bottom-style",
|
||||
"border-bottom-width",
|
||||
"border-collapse",
|
||||
"border-color",
|
||||
"border-image",
|
||||
"border-image-outset",
|
||||
"border-image-repeat",
|
||||
"border-image-slice",
|
||||
"border-image-source",
|
||||
"border-image-width",
|
||||
"border-left",
|
||||
"border-left-color",
|
||||
"border-left-style",
|
||||
"border-left-width",
|
||||
"border-radius",
|
||||
"border-right",
|
||||
"border-right-color",
|
||||
"border-right-style",
|
||||
"border-right-width",
|
||||
"border-spacing",
|
||||
"border-style",
|
||||
"border-top",
|
||||
"border-top-color",
|
||||
"border-top-left-radius",
|
||||
"border-top-right-radius",
|
||||
"border-top-style",
|
||||
"border-top-width",
|
||||
"border-width",
|
||||
"bottom",
|
||||
"box-shadow",
|
||||
"box-sizing",
|
||||
"caption-side",
|
||||
"clear",
|
||||
"clip",
|
||||
"color",
|
||||
"columns",
|
||||
"content",
|
||||
"counter-increment",
|
||||
"counter-reset",
|
||||
"cursor",
|
||||
"empty-cells",
|
||||
"float",
|
||||
"font",
|
||||
"font-family",
|
||||
"font-size",
|
||||
"font-size-adjust",
|
||||
"font-stretch",
|
||||
"font-style",
|
||||
"font-variant",
|
||||
"font-weight",
|
||||
"letter-spacing",
|
||||
"line-height",
|
||||
"list-style",
|
||||
"list-style-image",
|
||||
"list-style-position",
|
||||
"list-style-type",
|
||||
"opacity",
|
||||
"order",
|
||||
"outline",
|
||||
"outline-color",
|
||||
"outline-offset",
|
||||
"outline-style",
|
||||
"outline-width",
|
||||
"page-break-after",
|
||||
"page-break-before",
|
||||
"page-break-inside",
|
||||
"perspective",
|
||||
"perspective-origin",
|
||||
"pointer-events",
|
||||
"quotes",
|
||||
"resize",
|
||||
"tab-size",
|
||||
"table-layout",
|
||||
"text-align",
|
||||
"text-align-last",
|
||||
"text-decoration",
|
||||
"text-decoration-color",
|
||||
"text-decoration-line",
|
||||
"text-decoration-style",
|
||||
"text-indent",
|
||||
"text-justify",
|
||||
"text-overflow",
|
||||
"text-shadow",
|
||||
"text-transform",
|
||||
"transition",
|
||||
"transition-delay",
|
||||
"transition-duration",
|
||||
"transition-property",
|
||||
"transition-timing-function",
|
||||
"visibility",
|
||||
"white-space",
|
||||
"background-color",
|
||||
"background",
|
||||
"background-attachment",
|
||||
"background-clip",
|
||||
"background-image",
|
||||
"background-origin",
|
||||
"background-position",
|
||||
"background-repeat",
|
||||
"background-size",
|
||||
"dotted",
|
||||
"dashed",
|
||||
"solid",
|
||||
"double",
|
||||
"groove",
|
||||
"ridge",
|
||||
"inset",
|
||||
"outset",
|
||||
"none",
|
||||
"hidden",
|
||||
"border",
|
||||
"border-bottom",
|
||||
"border-bottom-color",
|
||||
"border-bottom-left-radius",
|
||||
"border-bottom-right-radius",
|
||||
"border-bottom-style",
|
||||
"border-bottom-width",
|
||||
"border-collapse",
|
||||
"border-color",
|
||||
" ",
|
||||
" ",
|
||||
"border-image",
|
||||
"border-image-outset",
|
||||
"border-image-repeat",
|
||||
"border-image-slice",
|
||||
"border-image-source",
|
||||
"border-image-width",
|
||||
"border-left",
|
||||
"border-left-color",
|
||||
"border-left-style",
|
||||
"border-left-width",
|
||||
"border-radius",
|
||||
" ",
|
||||
"border-right",
|
||||
"border-right-color",
|
||||
"border-right-style",
|
||||
"border-right-width",
|
||||
"border-spacing",
|
||||
"border-style",
|
||||
"border-top",
|
||||
"border-top-color",
|
||||
"border-top-left-radius",
|
||||
"border-top-right-radius",
|
||||
"border-top-style",
|
||||
"border-top-width",
|
||||
"border-width",
|
||||
" ",
|
||||
"animation",
|
||||
"animation-delay",
|
||||
"animation-direction",
|
||||
"animation-duration",
|
||||
"animation-fill-mode",
|
||||
"animation-itera ",
|
||||
"animation-name",
|
||||
"animation-play-state",
|
||||
"animation-timing-function",
|
||||
"font",
|
||||
"font-family",
|
||||
"font-size",
|
||||
"font-size-adjust",
|
||||
"font-stretch",
|
||||
"font-style",
|
||||
"italic",
|
||||
"oblique",
|
||||
"font-variant",
|
||||
"font-weight",
|
||||
"bold",
|
||||
"normal",
|
||||
"text-align",
|
||||
"text-align-last",
|
||||
"text-decoration",
|
||||
"text-decoration-line",
|
||||
"line-through",
|
||||
"underline",
|
||||
"text-decoration-color",
|
||||
"text-decoration-style",
|
||||
"text-indent",
|
||||
"text-justify",
|
||||
"text-overflow",
|
||||
"text-shadow",
|
||||
"text-transform"
|
||||
]);
|
||||
|
|
Loading…
Reference in a new issue