diff --git a/Cargo.toml b/Cargo.toml index 95900a1d2..a1e16bada 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,7 @@ dioxus-tui = { path = "./packages/tui", version = "^0.2.0", optional = true } dioxus-liveview = { path = "./packages/liveview", optional = true } dioxus-native-core = { path = "./packages/native-core", optional = true } +dioxus-native-core-macro = { path = "./packages/native-core-macro", optional = true } # dioxus-mobile = { path = "./packages/mobile", version = "^0.2.0", optional = true } # dioxus-rsx = { path = "./packages/rsx", optional = true } @@ -47,7 +48,7 @@ ayatana = ["dioxus-desktop/ayatana"] router = ["dioxus-router"] tui = ["dioxus-tui"] liveview = ["dioxus-liveview"] -native-core = ["dioxus-native-core"] +native-core = ["dioxus-native-core", "dioxus-native-core-macro"] [workspace] diff --git a/packages/native-core-macro/Cargo.toml b/packages/native-core-macro/Cargo.toml new file mode 100644 index 000000000..d71b03246 --- /dev/null +++ b/packages/native-core-macro/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "dioxus-native-core-macro" +version = "0.2.0" +edition = "2021" + +[lib] +proc-macro = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[dependencies] +syn = { version = "1.0.11", features = ["extra-traits"] } +quote = "1.0" diff --git a/packages/native-core-macro/src/lib.rs b/packages/native-core-macro/src/lib.rs new file mode 100644 index 000000000..883c12b2e --- /dev/null +++ b/packages/native-core-macro/src/lib.rs @@ -0,0 +1,285 @@ +extern crate proc_macro; + +use proc_macro::TokenStream; +use quote::{quote, ToTokens}; +use syn::{ + self, + parse::{Parse, ParseStream}, + Field, Ident, Token, Type, +}; + +#[derive(PartialEq)] +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(); + impl_derive_macro(&ast) +} + +fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream { + let type_name = &ast.ident; + let fields: Vec<_> = match &ast.data { + 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"), + } + .iter() + .collect(), + _ => unimplemented!(), + }; + let strct = Struct::parse(&fields); + let state_strct = StateStruct::parse(&fields, &strct); + 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 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.type_id()); + let child_types = state_strct + .state_members + .iter() + .filter(|f| f.dep_kind == DepKind::ChildDepState) + .map(|f| f.type_id()); + let parent_types = state_strct + .state_members + .iter() + .filter(|f| f.dep_kind == DepKind::ParentDepState) + .map(|f| f.type_id()); + + let type_name_str = type_name.to_string(); + + let gen = quote! { + impl State for #type_name{ + fn update_node_dep_state(&mut self, ty: std::any::TypeId, node: dioxus_native_core::real_dom_new_api::NodeRef, ctx: &anymap::AnyMap){ + use dioxus_native_core::real_dom_new_api::NodeDepState; + if false {} + #node_dep_state_fields + else{ + panic!("{:?} not in {}", ty, #type_name_str); + } + } + + fn update_parent_dep_state(&mut self, ty: std::any::TypeId, node: dioxus_native_core::real_dom_new_api::NodeRef, parent: &Self, ctx: &anymap::AnyMap){ + use dioxus_native_core::real_dom_new_api::ParentDepState; + if false {} + #parent_dep_state_fields + else{ + panic!("{:?} not in {}", ty, #type_name_str); + } + } + + fn update_child_dep_state(&mut self, ty: std::any::TypeId, node: dioxus_native_core::real_dom_new_api::NodeRef, children: Vec<&Self>, ctx: &anymap::AnyMap){ + use dioxus_native_core::real_dom_new_api::ChildDepState; + if false {} + #child_dep_state_fields + else{ + panic!("{:?} not in {}", ty, #type_name_str); + } + } + + fn child_dep_types(&self) -> Vec{ + // todo: order should depend on order of dependencies + vec![ + #(#child_types,)* + ] + } + + fn parent_dep_types(&self) -> Vec{ + // todo: order should depend on order of dependencies + vec![ + #(#parent_types,)* + ] + } + + fn node_dep_types(&self) -> Vec{ + vec![ + #(#node_types,)* + ] + } + } + }; + gen.into() +} + +struct Struct { + members: Vec, +} + +impl Struct { + fn parse(fields: &[&Field]) -> Self { + let members = fields.iter().filter_map(|f| Member::parse(f)).collect(); + Self { members } + } +} + +struct StateStruct<'a> { + state_members: Vec>, +} + +impl<'a> StateStruct<'a> { + fn parse(fields: &[&'a Field], strct: &'a Struct) -> Self { + let state_members = strct + .members + .iter() + .zip(fields.iter()) + .filter_map(|(m, f)| StateMember::parse(f, m, &strct)) + .collect(); + Self { state_members } + } +} + +struct DepTypes { + ctx_ty: Option, + dep_ty: Option, +} + +impl Parse for DepTypes { + fn parse(input: ParseStream) -> Result { + let ctx_ty = input.parse::().ok(); + let comma = input.parse::().ok(); + let dep_ty = input.parse::().ok(); + let dep_ty = comma.and(dep_ty); + Ok(DepTypes { ctx_ty, dep_ty }) + } +} + +struct Member { + ty: Type, + ident: Ident, +} + +impl Member { + fn parse(field: &Field) -> Option { + Some(Self { + ty: field.ty.clone(), + ident: field.ident.as_ref()?.clone(), + }) + } +} + +struct StateMember<'a> { + mem: &'a Member, + dep_kind: DepKind, + dep_mem: Option<&'a Member>, + ctx_ty: Option, +} + +impl<'a> StateMember<'a> { + fn parse(field: &Field, mem: &'a Member, parent: &'a Struct) -> Option> { + field.attrs.iter().find_map(|a| { + let dep_kind = a + .path + .get_ident() + .map(|i| match i.to_string().as_str() { + "node_dep_state" => Some(DepKind::NodeDepState), + "child_dep_state" => Some(DepKind::ChildDepState), + "parent_dep_state" => Some(DepKind::ParentDepState), + _ => None, + }) + .flatten()?; + let deps: DepTypes = 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, + }) + }) + } + + fn reduce_self(&self) -> quote::__private::TokenStream { + let ident = &self.mem.ident; + let get_ctx = if let Some(ctx_ty) = &self.ctx_ty { + let msg = ctx_ty.to_token_stream().to_string() + " not found in context"; + quote! {ctx.get().expect(#msg)} + } else { + quote! {&()} + }; + if let Some(dep_ident) = &self.dep_mem.map(|m| &m.ident) { + match self.dep_kind { + DepKind::NodeDepState => { + quote!(self.#ident.reduce(node, #get_ctx);) + } + DepKind::ChildDepState => { + quote!(self.#ident.reduce(node, children.iter().map(|s| &s.#dep_ident).collect(), #get_ctx);) + } + DepKind::ParentDepState => { + quote!(self.#ident.reduce(node, &parent.#dep_ident, #get_ctx);) + } + } + } else { + match self.dep_kind { + DepKind::NodeDepState => { + quote!(self.#ident.reduce(node, #get_ctx);) + } + DepKind::ChildDepState => { + quote!(self.#ident.reduce(node, &(), #get_ctx);) + } + DepKind::ParentDepState => { + quote!(self.#ident.reduce(node, &(), #get_ctx);) + } + } + } + } + + fn type_id(&self) -> quote::__private::TokenStream { + let ty = &self.mem.ty; + quote!(std::any::TypeId::of::<#ty>()) + } +} diff --git a/packages/native-core/Cargo.toml b/packages/native-core/Cargo.toml index 83e403487..3e37d7e8a 100644 --- a/packages/native-core/Cargo.toml +++ b/packages/native-core/Cargo.toml @@ -10,10 +10,12 @@ 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" fxhash = "0.2" +anymap = "0.12.1" [dev-dependencies] rand = "0.8.5" \ No newline at end of file diff --git a/packages/native-core/src/lib.rs b/packages/native-core/src/lib.rs index 26c629446..0ad573834 100644 --- a/packages/native-core/src/lib.rs +++ b/packages/native-core/src/lib.rs @@ -1,2 +1,3 @@ pub mod layout_attributes; pub mod real_dom; +pub mod real_dom_new_api; diff --git a/packages/native-core/src/real_dom_new_api.rs b/packages/native-core/src/real_dom_new_api.rs new file mode 100644 index 000000000..7740a97d4 --- /dev/null +++ b/packages/native-core/src/real_dom_new_api.rs @@ -0,0 +1,58 @@ +use std::any::TypeId; + +use anymap::AnyMap; +use dioxus_core::{Attribute, VElement}; + +#[repr(transparent)] +pub struct NodeRef<'a>(&'a VElement<'a>); +impl<'a> NodeRef<'a> { + pub fn new(velement: &'a VElement<'a>) -> Self { + Self(velement) + } + + pub fn tag(&self) -> &'a str { + self.0.tag + } + + pub fn namespace(&self) -> Option<&'a str> { + self.0.namespace + } + + pub fn attributes(&self) -> &'a [Attribute<'a>] { + self.0.attributes + } +} + +pub trait ChildDepState: PartialEq { + type Ctx; + type DepState: ChildDepState; + fn reduce(&mut self, node: NodeRef, children: Vec<&Self::DepState>, ctx: &Self::Ctx); +} + +pub trait ParentDepState: PartialEq { + type Ctx; + type DepState: ParentDepState; + fn reduce(&mut self, node: NodeRef, parent: &Self::DepState, ctx: &Self::Ctx); +} + +pub trait NodeDepState: PartialEq { + type Ctx; + fn reduce(&mut self, node: NodeRef, ctx: &Self::Ctx); +} + +pub trait State { + fn update_node_dep_state(&mut self, ty: TypeId, node: NodeRef, ctx: &AnyMap); + fn child_dep_types(&self) -> Vec; + + fn update_parent_dep_state(&mut self, ty: TypeId, node: NodeRef, parent: &Self, ctx: &AnyMap); + fn parent_dep_types(&self) -> Vec; + + fn update_child_dep_state( + &mut self, + ty: TypeId, + node: NodeRef, + children: Vec<&Self>, + ctx: &AnyMap, + ); + fn node_dep_types(&self) -> Vec; +} diff --git a/packages/native-core/tests/parse.rs b/packages/native-core/tests/parse.rs new file mode 100644 index 000000000..c22e897df --- /dev/null +++ b/packages/native-core/tests/parse.rs @@ -0,0 +1,62 @@ +use dioxus_native_core::real_dom_new_api::*; +use dioxus_native_core_macro::*; + +#[derive(State)] +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(i32, B)] + y: B, + // depends on attributes, the C component of it's parent and a u8 context + #[parent_dep_state(u8, C)] + z: C, +} + +// struct Z { +// x: A, +// y: B, +// z: C, +// } + +use dioxus_native_core::real_dom_new_api::NodeDepState; + +#[derive(PartialEq)] +struct A; +impl NodeDepState for A { + type Ctx = (); + fn reduce(&mut self, _: NodeRef, _: &()) { + todo!() + } +} + +#[derive(PartialEq)] +struct B; +impl ChildDepState for B { + type Ctx = i32; + type DepState = Self; + fn reduce( + &mut self, + _: dioxus_native_core::real_dom_new_api::NodeRef, + _: Vec<&Self::DepState>, + _: &i32, + ) { + todo!() + } +} + +#[derive(PartialEq)] +struct C; +impl ParentDepState for C { + type Ctx = u8; + type DepState = Self; + fn reduce( + &mut self, + _: dioxus_native_core::real_dom_new_api::NodeRef, + _: &Self::DepState, + _: &u8, + ) { + todo!() + } +}