mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-26 22:20:19 +00:00
convert T into signals automatically
This commit is contained in:
parent
1847c737e9
commit
50e3216d8b
14 changed files with 441 additions and 103 deletions
|
@ -57,5 +57,17 @@ fn app() -> Element {
|
||||||
} else {
|
} else {
|
||||||
"No saved values"
|
"No saved values"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// You can pass a value directly to any prop that accepts a signal
|
||||||
|
Child { count: 0 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
fn Child(mut count: Signal<i32>) -> Element {
|
||||||
|
rsx! {
|
||||||
|
h1 { "{count}" }
|
||||||
|
button { onclick: move |_| count += 1, "Up high!" }
|
||||||
|
button { onclick: move |_| count -= 1, "Down low!" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,10 +8,12 @@
|
||||||
|
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro2::TokenStream;
|
||||||
|
|
||||||
use syn::parse::Error;
|
use syn::punctuated::Punctuated;
|
||||||
use syn::spanned::Spanned;
|
use syn::spanned::Spanned;
|
||||||
|
use syn::{parse::Error, PathArguments};
|
||||||
|
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
|
use syn::{parse_quote, Type};
|
||||||
|
|
||||||
pub fn impl_my_derive(ast: &syn::DeriveInput) -> Result<TokenStream, Error> {
|
pub fn impl_my_derive(ast: &syn::DeriveInput) -> Result<TokenStream, Error> {
|
||||||
let data = match &ast.data {
|
let data = match &ast.data {
|
||||||
|
@ -515,6 +517,7 @@ mod struct_info {
|
||||||
use syn::{Expr, Ident};
|
use syn::{Expr, Ident};
|
||||||
|
|
||||||
use super::field_info::{FieldBuilderAttr, FieldInfo};
|
use super::field_info::{FieldBuilderAttr, FieldInfo};
|
||||||
|
use super::looks_like_signal_type;
|
||||||
use super::util::{
|
use super::util::{
|
||||||
empty_type, empty_type_tuple, expr_to_single_string, make_punctuated_single,
|
empty_type, empty_type_tuple, expr_to_single_string, make_punctuated_single,
|
||||||
modify_types_generics_hack, path_to_single_string, strip_raw_ident_prefix, type_tuple,
|
modify_types_generics_hack, path_to_single_string, strip_raw_ident_prefix, type_tuple,
|
||||||
|
@ -576,6 +579,89 @@ mod struct_info {
|
||||||
generics
|
generics
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn has_signal_fields(&self) -> bool {
|
||||||
|
self.fields.iter().any(|f| looks_like_signal_type(f.ty))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn memoize_impl(&self) -> Result<TokenStream, Error> {
|
||||||
|
// First check if there are any Signal fields, if there are not, we can just use the partialEq impl
|
||||||
|
let has_signal_fields = self.has_signal_fields();
|
||||||
|
|
||||||
|
if has_signal_fields {
|
||||||
|
let signal_fields: Vec<_> = self
|
||||||
|
.included_fields()
|
||||||
|
.filter(|f| looks_like_signal_type(f.ty))
|
||||||
|
.map(|f| {
|
||||||
|
let name = f.name;
|
||||||
|
quote!(#name)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(quote! {
|
||||||
|
// First check if the fields are equal
|
||||||
|
let exactly_equal = self == new;
|
||||||
|
if exactly_equal {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If they are not, move over the signal fields and check if they are equal
|
||||||
|
#(
|
||||||
|
let mut #signal_fields = self.#signal_fields;
|
||||||
|
self.#signal_fields = new.#signal_fields;
|
||||||
|
)*
|
||||||
|
|
||||||
|
// Then check if the fields are equal again
|
||||||
|
let non_signal_fields_equal = self == new;
|
||||||
|
|
||||||
|
// If they are not equal, we still need to rerun the component
|
||||||
|
if !non_signal_fields_equal {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
trait NonPartialEq: Sized {
|
||||||
|
fn compare(&self, other: &Self) -> bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> NonPartialEq for &&T {
|
||||||
|
fn compare(&self, other: &Self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trait CanPartialEq: PartialEq {
|
||||||
|
fn compare(&self, other: &Self) -> bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: PartialEq> CanPartialEq for T {
|
||||||
|
fn compare(&self, other: &Self) -> bool {
|
||||||
|
self == other
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If they are equal, we don't need to rerun the component we can just update the existing signals
|
||||||
|
#(
|
||||||
|
// Try to memo the signal
|
||||||
|
let field_eq = {
|
||||||
|
let old_value: &_ = &*#signal_fields.peek();
|
||||||
|
let new_value: &_ = &*new.#signal_fields.peek();
|
||||||
|
(&old_value).compare(&&new_value)
|
||||||
|
};
|
||||||
|
if !field_eq {
|
||||||
|
(#signal_fields).set(new.#signal_fields.take());
|
||||||
|
}
|
||||||
|
// Move the old value back
|
||||||
|
self.#signal_fields = #signal_fields;
|
||||||
|
)*
|
||||||
|
|
||||||
|
true
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Ok(quote! {
|
||||||
|
self == new
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn builder_creation_impl(&self) -> Result<TokenStream, Error> {
|
pub fn builder_creation_impl(&self) -> Result<TokenStream, Error> {
|
||||||
let StructInfo {
|
let StructInfo {
|
||||||
ref vis,
|
ref vis,
|
||||||
|
@ -584,12 +670,6 @@ mod struct_info {
|
||||||
..
|
..
|
||||||
} = *self;
|
} = *self;
|
||||||
|
|
||||||
// we're generating stuff that goes into unsafe code here
|
|
||||||
// we use the heuristic: are there *any* generic parameters?
|
|
||||||
// If so, then they might have non-static lifetimes and we can't compare two generic things that *might borrow*
|
|
||||||
// Therefore, we will generate code that shortcircuits the "comparison" in memoization
|
|
||||||
let are_there_generics = !self.generics.params.is_empty();
|
|
||||||
|
|
||||||
let generics = self.generics.clone();
|
let generics = self.generics.clone();
|
||||||
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
|
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
|
||||||
let (_, b_initial_generics, _) = self.generics.split_for_impl();
|
let (_, b_initial_generics, _) = self.generics.split_for_impl();
|
||||||
|
@ -669,20 +749,26 @@ Finally, call `.build()` to create the instance of `{name}`.
|
||||||
.extend(predicates.predicates.clone());
|
.extend(predicates.predicates.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
let can_memoize = match are_there_generics {
|
let memoize = self.memoize_impl()?;
|
||||||
true => quote! { false },
|
|
||||||
false => quote! { self == other },
|
|
||||||
};
|
|
||||||
|
|
||||||
let extend_fields = self.extend_fields().map(|f| {
|
let global_fields = self
|
||||||
let name = f.name;
|
.extend_fields()
|
||||||
let ty = f.ty;
|
.map(|f| {
|
||||||
quote!(#name: #ty)
|
let name = f.name;
|
||||||
});
|
let ty = f.ty;
|
||||||
let extend_fields_value = self.extend_fields().map(|f| {
|
quote!(#name: #ty)
|
||||||
let name = f.name;
|
})
|
||||||
quote!(#name: Vec::new())
|
.chain(self.has_signal_fields().then(|| quote!(owner: Owner)));
|
||||||
});
|
let global_fields_value = self
|
||||||
|
.extend_fields()
|
||||||
|
.map(|f| {
|
||||||
|
let name = f.name;
|
||||||
|
quote!(#name: Vec::new())
|
||||||
|
})
|
||||||
|
.chain(
|
||||||
|
self.has_signal_fields()
|
||||||
|
.then(|| quote!(owner: Owner::default())),
|
||||||
|
);
|
||||||
|
|
||||||
Ok(quote! {
|
Ok(quote! {
|
||||||
impl #impl_generics #name #ty_generics #where_clause {
|
impl #impl_generics #name #ty_generics #where_clause {
|
||||||
|
@ -690,7 +776,7 @@ Finally, call `.build()` to create the instance of `{name}`.
|
||||||
#[allow(dead_code, clippy::type_complexity)]
|
#[allow(dead_code, clippy::type_complexity)]
|
||||||
#vis fn builder() -> #builder_name #generics_with_empty {
|
#vis fn builder() -> #builder_name #generics_with_empty {
|
||||||
#builder_name {
|
#builder_name {
|
||||||
#(#extend_fields_value,)*
|
#(#global_fields_value,)*
|
||||||
fields: #empties_tuple,
|
fields: #empties_tuple,
|
||||||
_phantom: ::core::default::Default::default(),
|
_phantom: ::core::default::Default::default(),
|
||||||
}
|
}
|
||||||
|
@ -701,7 +787,7 @@ Finally, call `.build()` to create the instance of `{name}`.
|
||||||
#builder_type_doc
|
#builder_type_doc
|
||||||
#[allow(dead_code, non_camel_case_types, non_snake_case)]
|
#[allow(dead_code, non_camel_case_types, non_snake_case)]
|
||||||
#vis struct #builder_name #b_generics {
|
#vis struct #builder_name #b_generics {
|
||||||
#(#extend_fields,)*
|
#(#global_fields,)*
|
||||||
fields: #all_fields_param,
|
fields: #all_fields_param,
|
||||||
_phantom: (#( #phantom_generics ),*),
|
_phantom: (#( #phantom_generics ),*),
|
||||||
}
|
}
|
||||||
|
@ -713,11 +799,10 @@ Finally, call `.build()` to create the instance of `{name}`.
|
||||||
fn builder() -> Self::Builder {
|
fn builder() -> Self::Builder {
|
||||||
#name::builder()
|
#name::builder()
|
||||||
}
|
}
|
||||||
fn memoize(&self, other: &Self) -> bool {
|
fn memoize(&mut self, new: &Self) -> bool {
|
||||||
#can_memoize
|
#memoize
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -966,22 +1051,29 @@ Finally, call `.build()` to create the instance of `{name}`.
|
||||||
let arg_type = field_type;
|
let arg_type = field_type;
|
||||||
// If the field is auto_into, we need to add a generic parameter to the builder for specialization
|
// If the field is auto_into, we need to add a generic parameter to the builder for specialization
|
||||||
let mut marker = None;
|
let mut marker = None;
|
||||||
let (arg_type, arg_expr) =
|
let (arg_type, arg_expr) = if looks_like_signal_type(arg_type) {
|
||||||
if field.builder_attr.auto_into || field.builder_attr.strip_option {
|
let marker_ident = syn::Ident::new("__Marker", proc_macro2::Span::call_site());
|
||||||
let marker_ident = syn::Ident::new("__Marker", proc_macro2::Span::call_site());
|
marker = Some(marker_ident.clone());
|
||||||
marker = Some(marker_ident.clone());
|
(
|
||||||
(
|
quote!(impl dioxus_core::prelude::SuperInto<#arg_type, #marker_ident>),
|
||||||
quote!(impl dioxus_core::prelude::SuperInto<#arg_type, #marker_ident>),
|
// If this looks like a signal type, we automatically convert it with SuperInto and use the props struct as the owner
|
||||||
quote!(dioxus_core::prelude::SuperInto::super_into(#field_name)),
|
quote!(with_owner(self.owner.clone(), move || dioxus_core::prelude::SuperInto::super_into(#field_name))),
|
||||||
)
|
)
|
||||||
} else if field.builder_attr.from_displayable {
|
} else if field.builder_attr.auto_into || field.builder_attr.strip_option {
|
||||||
(
|
let marker_ident = syn::Ident::new("__Marker", proc_macro2::Span::call_site());
|
||||||
quote!(impl ::core::fmt::Display),
|
marker = Some(marker_ident.clone());
|
||||||
quote!(#field_name.to_string()),
|
(
|
||||||
)
|
quote!(impl dioxus_core::prelude::SuperInto<#arg_type, #marker_ident>),
|
||||||
} else {
|
quote!(dioxus_core::prelude::SuperInto::super_into(#field_name)),
|
||||||
(quote!(#arg_type), quote!(#field_name))
|
)
|
||||||
};
|
} else if field.builder_attr.from_displayable {
|
||||||
|
(
|
||||||
|
quote!(impl ::core::fmt::Display),
|
||||||
|
quote!(#field_name.to_string()),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(quote!(#arg_type), quote!(#field_name))
|
||||||
|
};
|
||||||
|
|
||||||
let repeated_fields_error_type_name = syn::Ident::new(
|
let repeated_fields_error_type_name = syn::Ident::new(
|
||||||
&format!(
|
&format!(
|
||||||
|
@ -993,10 +1085,13 @@ Finally, call `.build()` to create the instance of `{name}`.
|
||||||
);
|
);
|
||||||
let repeated_fields_error_message = format!("Repeated field {field_name}");
|
let repeated_fields_error_message = format!("Repeated field {field_name}");
|
||||||
|
|
||||||
let forward_extended_fields = self.extend_fields().map(|f| {
|
let forward_fields = self
|
||||||
let name = f.name;
|
.extend_fields()
|
||||||
quote!(#name: self.#name)
|
.map(|f| {
|
||||||
});
|
let name = f.name;
|
||||||
|
quote!(#name: self.#name)
|
||||||
|
})
|
||||||
|
.chain(self.has_signal_fields().then(|| quote!(owner: self.owner)));
|
||||||
|
|
||||||
Ok(quote! {
|
Ok(quote! {
|
||||||
#[allow(dead_code, non_camel_case_types, missing_docs)]
|
#[allow(dead_code, non_camel_case_types, missing_docs)]
|
||||||
|
@ -1007,7 +1102,7 @@ Finally, call `.build()` to create the instance of `{name}`.
|
||||||
let #field_name = (#arg_expr,);
|
let #field_name = (#arg_expr,);
|
||||||
let ( #(#descructuring,)* ) = self.fields;
|
let ( #(#descructuring,)* ) = self.fields;
|
||||||
#builder_name {
|
#builder_name {
|
||||||
#(#forward_extended_fields,)*
|
#(#forward_fields,)*
|
||||||
fields: ( #(#reconstructing,)* ),
|
fields: ( #(#reconstructing,)* ),
|
||||||
_phantom: self._phantom,
|
_phantom: self._phantom,
|
||||||
}
|
}
|
||||||
|
@ -1132,7 +1227,7 @@ Finally, call `.build()` to create the instance of `{name}`.
|
||||||
note = #early_build_error_message
|
note = #early_build_error_message
|
||||||
)]
|
)]
|
||||||
pub fn build(self, _: #early_build_error_type_name) -> #name #ty_generics {
|
pub fn build(self, _: #early_build_error_type_name) -> #name #ty_generics {
|
||||||
panic!();
|
panic!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -1229,26 +1324,84 @@ Finally, call `.build()` to create the instance of `{name}`.
|
||||||
// I’d prefer “a” or “an” to “its”, but determining which is grammatically
|
// I’d prefer “a” or “an” to “its”, but determining which is grammatically
|
||||||
// correct is roughly impossible.
|
// correct is roughly impossible.
|
||||||
let doc =
|
let doc =
|
||||||
format!("Finalise the builder and create its [`{name}`] instance");
|
format!("Finalize the builder and create its [`{name}`] instance");
|
||||||
quote!(#[doc = #doc])
|
quote!(#[doc = #doc])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
quote!()
|
quote!()
|
||||||
};
|
};
|
||||||
quote!(
|
|
||||||
#[allow(dead_code, non_camel_case_types, missing_docs)]
|
if self.has_signal_fields() {
|
||||||
impl #impl_generics #builder_name #modified_ty_generics #where_clause {
|
let name = Ident::new(&format!("{}WithOwner", name), name.span());
|
||||||
#doc
|
let original_name = &self.name;
|
||||||
pub fn build(self) -> #name #ty_generics {
|
quote! {
|
||||||
let ( #(#descructuring,)* ) = self.fields;
|
#[doc(hidden)]
|
||||||
#( #assignments )*
|
#[allow(dead_code, non_camel_case_types, missing_docs)]
|
||||||
#name {
|
#[derive(Clone)]
|
||||||
#( #field_names ),*
|
struct #name #ty_generics {
|
||||||
|
inner: #original_name #ty_generics,
|
||||||
|
owner: Owner,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl #impl_generics PartialEq for #name #ty_generics #where_clause {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.inner.eq(&other.inner)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl #impl_generics #name #ty_generics #where_clause {
|
||||||
|
/// Create a component from the props.
|
||||||
|
fn into_vcomponent<M: 'static>(
|
||||||
|
self,
|
||||||
|
render_fn: impl dioxus_core::prelude::ComponentFunction<#original_name #ty_generics, M>,
|
||||||
|
component_name: &'static str,
|
||||||
|
) -> dioxus_core::VComponent {
|
||||||
|
use dioxus_core::prelude::ComponentFunction;
|
||||||
|
dioxus_core::VComponent::new(move |wrapper: Self| render_fn.rebuild(wrapper.inner), self, component_name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl #impl_generics dioxus_core::prelude::Properties for #name #ty_generics #where_clause {
|
||||||
|
type Builder = ();
|
||||||
|
fn builder() -> Self::Builder {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
fn memoize(&mut self, new: &Self) -> bool {
|
||||||
|
self.inner.memoize(&new.inner)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code, non_camel_case_types, missing_docs)]
|
||||||
|
impl #impl_generics #builder_name #modified_ty_generics #where_clause {
|
||||||
|
#doc
|
||||||
|
pub fn build(self) -> #name #ty_generics {
|
||||||
|
let ( #(#descructuring,)* ) = self.fields;
|
||||||
|
#( #assignments )*
|
||||||
|
#name {
|
||||||
|
inner: #original_name {
|
||||||
|
#( #field_names ),*
|
||||||
|
},
|
||||||
|
owner: self.owner,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
} else {
|
||||||
|
quote!(
|
||||||
|
#[allow(dead_code, non_camel_case_types, missing_docs)]
|
||||||
|
impl #impl_generics #builder_name #modified_ty_generics #where_clause {
|
||||||
|
#doc
|
||||||
|
pub fn build(self) -> #name #ty_generics {
|
||||||
|
let ( #(#descructuring,)* ) = self.fields;
|
||||||
|
#( #assignments )*
|
||||||
|
#name {
|
||||||
|
#( #field_names ),*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1374,3 +1527,47 @@ Finally, call `.build()` to create the instance of `{name}`.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn looks_like_signal_type(ty: &Type) -> bool {
|
||||||
|
match ty {
|
||||||
|
Type::Path(ty) => {
|
||||||
|
if ty.qself.is_some() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let path = &ty.path;
|
||||||
|
|
||||||
|
let mut path_segments_without_generics = Vec::new();
|
||||||
|
|
||||||
|
let mut generic_arg_count = 0;
|
||||||
|
|
||||||
|
for segment in &path.segments {
|
||||||
|
let mut segment = segment.clone();
|
||||||
|
match segment.arguments {
|
||||||
|
PathArguments::AngleBracketed(_) => generic_arg_count += 1,
|
||||||
|
PathArguments::Parenthesized(_) => {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
segment.arguments = syn::PathArguments::None;
|
||||||
|
path_segments_without_generics.push(segment);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there is more than the type and the send/sync generic, it doesn't look like our signal
|
||||||
|
if generic_arg_count > 2 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let path_without_generics = syn::Path {
|
||||||
|
leading_colon: None,
|
||||||
|
segments: Punctuated::from_iter(path_segments_without_generics),
|
||||||
|
};
|
||||||
|
|
||||||
|
path_without_generics == parse_quote!(dioxus_core::prelude::Signal)
|
||||||
|
|| path_without_generics == parse_quote!(prelude::Signal)
|
||||||
|
|| path_without_generics == parse_quote!(Signal)
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ pub(crate) trait AnyProps: 'static {
|
||||||
/// Render the component with the internal props.
|
/// Render the component with the internal props.
|
||||||
fn render(&self) -> RenderReturn;
|
fn render(&self) -> RenderReturn;
|
||||||
/// Check if the props are the same as the type erased props of another component.
|
/// Check if the props are the same as the type erased props of another component.
|
||||||
fn memoize(&self, other: &dyn Any) -> bool;
|
fn memoize(&mut self, other: &dyn Any) -> bool;
|
||||||
/// Get the props as a type erased `dyn Any`.
|
/// Get the props as a type erased `dyn Any`.
|
||||||
fn props(&self) -> &dyn Any;
|
fn props(&self) -> &dyn Any;
|
||||||
/// Duplicate this component into a new boxed component.
|
/// Duplicate this component into a new boxed component.
|
||||||
|
@ -18,7 +18,7 @@ pub(crate) trait AnyProps: 'static {
|
||||||
/// A component along with the props the component uses to render.
|
/// A component along with the props the component uses to render.
|
||||||
pub(crate) struct VProps<F: ComponentFunction<P, M>, P, M> {
|
pub(crate) struct VProps<F: ComponentFunction<P, M>, P, M> {
|
||||||
render_fn: F,
|
render_fn: F,
|
||||||
memo: fn(&P, &P) -> bool,
|
memo: fn(&mut P, &P) -> bool,
|
||||||
props: P,
|
props: P,
|
||||||
name: &'static str,
|
name: &'static str,
|
||||||
phantom: std::marker::PhantomData<M>,
|
phantom: std::marker::PhantomData<M>,
|
||||||
|
@ -40,7 +40,7 @@ impl<F: ComponentFunction<P, M> + Clone, P: Clone + 'static, M: 'static> VProps<
|
||||||
/// Create a [`VProps`] object.
|
/// Create a [`VProps`] object.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
render_fn: F,
|
render_fn: F,
|
||||||
memo: fn(&P, &P) -> bool,
|
memo: fn(&mut P, &P) -> bool,
|
||||||
props: P,
|
props: P,
|
||||||
name: &'static str,
|
name: &'static str,
|
||||||
) -> VProps<F, P, M> {
|
) -> VProps<F, P, M> {
|
||||||
|
@ -57,9 +57,9 @@ impl<F: ComponentFunction<P, M> + Clone, P: Clone + 'static, M: 'static> VProps<
|
||||||
impl<F: ComponentFunction<P, M> + Clone, P: Clone + 'static, M: 'static> AnyProps
|
impl<F: ComponentFunction<P, M> + Clone, P: Clone + 'static, M: 'static> AnyProps
|
||||||
for VProps<F, P, M>
|
for VProps<F, P, M>
|
||||||
{
|
{
|
||||||
fn memoize(&self, other: &dyn Any) -> bool {
|
fn memoize(&mut self, other: &dyn Any) -> bool {
|
||||||
match other.downcast_ref::<P>() {
|
match other.downcast_ref::<P>() {
|
||||||
Some(other) => (self.memo)(&self.props, other),
|
Some(other) => (self.memo)(&mut self.props, other),
|
||||||
None => false,
|
None => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use std::ops::Deref;
|
use std::ops::{Deref, DerefMut};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
any_props::AnyProps,
|
any_props::AnyProps,
|
||||||
|
@ -72,7 +72,7 @@ impl VNode {
|
||||||
|
|
||||||
// copy out the box for both
|
// copy out the box for both
|
||||||
let old_scope = &mut dom.scopes[scope_id.0];
|
let old_scope = &mut dom.scopes[scope_id.0];
|
||||||
let old_props: &dyn AnyProps = old_scope.props.deref();
|
let old_props: &mut dyn AnyProps = old_scope.props.deref_mut();
|
||||||
let new_props: &dyn AnyProps = new.props.deref();
|
let new_props: &dyn AnyProps = new.props.deref();
|
||||||
|
|
||||||
// If the props are static, then we try to memoize by setting the new with the old
|
// If the props are static, then we try to memoize by setting the new with the old
|
||||||
|
|
|
@ -330,7 +330,7 @@ impl Properties for ErrorBoundaryProps {
|
||||||
fn builder() -> Self::Builder {
|
fn builder() -> Self::Builder {
|
||||||
ErrorBoundaryProps::builder()
|
ErrorBoundaryProps::builder()
|
||||||
}
|
}
|
||||||
fn memoize(&self, _: &Self) -> bool {
|
fn memoize(&mut self, _: &Self) -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -90,7 +90,7 @@ impl Properties for FragmentProps {
|
||||||
fn builder() -> Self::Builder {
|
fn builder() -> Self::Builder {
|
||||||
FragmentBuilder(None)
|
FragmentBuilder(None)
|
||||||
}
|
}
|
||||||
fn memoize(&self, _other: &Self) -> bool {
|
fn memoize(&mut self, _other: &Self) -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,16 @@ pub trait Properties: Clone + Sized + 'static {
|
||||||
fn builder() -> Self::Builder;
|
fn builder() -> Self::Builder;
|
||||||
|
|
||||||
/// Compare two props to see if they are memoizable.
|
/// Compare two props to see if they are memoizable.
|
||||||
fn memoize(&self, other: &Self) -> bool;
|
fn memoize(&mut self, other: &Self) -> bool;
|
||||||
|
|
||||||
|
/// Create a component from the props.
|
||||||
|
fn into_vcomponent<M: 'static>(
|
||||||
|
self,
|
||||||
|
render_fn: impl ComponentFunction<Self, M>,
|
||||||
|
component_name: &'static str,
|
||||||
|
) -> VComponent {
|
||||||
|
VComponent::new(render_fn, self, component_name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Properties for () {
|
impl Properties for () {
|
||||||
|
@ -40,7 +49,7 @@ impl Properties for () {
|
||||||
fn builder() -> Self::Builder {
|
fn builder() -> Self::Builder {
|
||||||
EmptyBuilder {}
|
EmptyBuilder {}
|
||||||
}
|
}
|
||||||
fn memoize(&self, _other: &Self) -> bool {
|
fn memoize(&mut self, _other: &Self) -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -65,7 +74,7 @@ where
|
||||||
fn builder() -> Self::Builder {
|
fn builder() -> Self::Builder {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
fn memoize(&self, _other: &Self) -> bool {
|
fn memoize(&mut self, _other: &Self) -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -185,6 +185,15 @@ impl<T: 'static, S: Storage<T>> GenerationalBox<T, S> {
|
||||||
self.raw.0.data.data_ptr() == other.raw.0.data.data_ptr()
|
self.raw.0.data.data_ptr() == other.raw.0.data.data_ptr()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Take the value out of the generational box and invalidate the generational box. This will return the value if the value was taken.
|
||||||
|
pub fn take(&self) -> Option<T> {
|
||||||
|
if self.validate() {
|
||||||
|
Storage::take(&self.raw.0.data)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, S: 'static> Copy for GenerationalBox<T, S> {}
|
impl<T, S: 'static> Copy for GenerationalBox<T, S> {}
|
||||||
|
@ -211,6 +220,9 @@ pub trait Storage<Data = ()>: AnyStorage + 'static {
|
||||||
|
|
||||||
/// Set the value
|
/// Set the value
|
||||||
fn set(&'static self, value: Data);
|
fn set(&'static self, value: Data);
|
||||||
|
|
||||||
|
/// Take the value out of the storage. This will return the value if the value was taken.
|
||||||
|
fn take(&'static self) -> Option<Data>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A trait for any storage backing type.
|
/// A trait for any storage backing type.
|
||||||
|
@ -248,8 +260,8 @@ pub trait AnyStorage: Default {
|
||||||
/// Get the data pointer. No guarantees are made about the data pointer. It should only be used for debugging.
|
/// Get the data pointer. No guarantees are made about the data pointer. It should only be used for debugging.
|
||||||
fn data_ptr(&self) -> *const ();
|
fn data_ptr(&self) -> *const ();
|
||||||
|
|
||||||
/// Take the value out of the storage. This will return true if the value was taken.
|
/// Drop the value from the storage. This will return true if the value was taken.
|
||||||
fn take(&self) -> bool;
|
fn manually_drop(&self) -> bool;
|
||||||
|
|
||||||
/// Recycle a memory location. This will drop the memory location and return it to the runtime.
|
/// Recycle a memory location. This will drop the memory location and return it to the runtime.
|
||||||
fn recycle(location: &MemoryLocation<Self>);
|
fn recycle(location: &MemoryLocation<Self>);
|
||||||
|
@ -259,10 +271,9 @@ pub trait AnyStorage: Default {
|
||||||
|
|
||||||
/// Create a new owner. The owner will be responsible for dropping all of the generational boxes that it creates.
|
/// Create a new owner. The owner will be responsible for dropping all of the generational boxes that it creates.
|
||||||
fn owner() -> Owner<Self> {
|
fn owner() -> Owner<Self> {
|
||||||
Owner {
|
Owner(Arc::new(Mutex::new(OwnerInner {
|
||||||
owned: Default::default(),
|
owned: Default::default(),
|
||||||
phantom: PhantomData,
|
})))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -324,7 +335,7 @@ impl<S> MemoryLocation<S> {
|
||||||
where
|
where
|
||||||
S: AnyStorage,
|
S: AnyStorage,
|
||||||
{
|
{
|
||||||
let old = self.0.data.take();
|
let old = self.0.data.manually_drop();
|
||||||
#[cfg(any(debug_assertions, feature = "check_generation"))]
|
#[cfg(any(debug_assertions, feature = "check_generation"))]
|
||||||
if old {
|
if old {
|
||||||
let new_generation = self.0.generation.load(std::sync::atomic::Ordering::Relaxed) + 1;
|
let new_generation = self.0.generation.load(std::sync::atomic::Ordering::Relaxed) + 1;
|
||||||
|
@ -355,10 +366,31 @@ impl<S> MemoryLocation<S> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct OwnerInner<S: AnyStorage + 'static> {
|
||||||
|
owned: Vec<MemoryLocation<S>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S: AnyStorage> Drop for OwnerInner<S> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
for location in &self.owned {
|
||||||
|
S::recycle(location)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Owner: Handles dropping generational boxes. The owner acts like a runtime lifetime guard. Any states that you create with an owner will be dropped when that owner is dropped.
|
/// Owner: Handles dropping generational boxes. The owner acts like a runtime lifetime guard. Any states that you create with an owner will be dropped when that owner is dropped.
|
||||||
pub struct Owner<S: AnyStorage + 'static = UnsyncStorage> {
|
pub struct Owner<S: AnyStorage + 'static = UnsyncStorage>(Arc<Mutex<OwnerInner<S>>>);
|
||||||
owned: Arc<Mutex<Vec<MemoryLocation<S>>>>,
|
|
||||||
phantom: PhantomData<S>,
|
impl<S: AnyStorage> Default for Owner<S> {
|
||||||
|
fn default() -> Self {
|
||||||
|
S::owner()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S: AnyStorage> Clone for Owner<S> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self(self.0.clone())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S: AnyStorage> Owner<S> {
|
impl<S: AnyStorage> Owner<S> {
|
||||||
|
@ -391,7 +423,7 @@ impl<S: AnyStorage> Owner<S> {
|
||||||
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
|
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
|
||||||
caller,
|
caller,
|
||||||
);
|
);
|
||||||
self.owned.lock().push(location);
|
self.0.lock().owned.push(location);
|
||||||
key
|
key
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -409,15 +441,7 @@ impl<S: AnyStorage> Owner<S> {
|
||||||
created_at: std::panic::Location::caller(),
|
created_at: std::panic::Location::caller(),
|
||||||
_marker: PhantomData,
|
_marker: PhantomData,
|
||||||
};
|
};
|
||||||
self.owned.lock().push(location);
|
self.0.lock().owned.push(location);
|
||||||
generational_box
|
generational_box
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S: AnyStorage> Drop for Owner<S> {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
for location in self.owned.lock().iter() {
|
|
||||||
S::recycle(location)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -72,7 +72,7 @@ impl AnyStorage for SyncStorage {
|
||||||
self.0.data_ptr() as *const ()
|
self.0.data_ptr() as *const ()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn take(&self) -> bool {
|
fn manually_drop(&self) -> bool {
|
||||||
self.0.write().take().is_some()
|
self.0.write().take().is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,4 +162,11 @@ impl<T: Sync + Send + 'static> Storage<T> for SyncStorage {
|
||||||
fn set(&self, value: T) {
|
fn set(&self, value: T) {
|
||||||
*self.0.write() = Some(Box::new(value));
|
*self.0.write() = Some(Box::new(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn take(&'static self) -> Option<T> {
|
||||||
|
self.0
|
||||||
|
.write()
|
||||||
|
.take()
|
||||||
|
.and_then(|any| any.downcast().ok().map(|boxed| *boxed))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,6 +75,13 @@ impl<T: 'static> Storage<T> for UnsyncStorage {
|
||||||
fn set(&self, value: T) {
|
fn set(&self, value: T) {
|
||||||
*self.0.borrow_mut() = Some(Box::new(value));
|
*self.0.borrow_mut() = Some(Box::new(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn take(&'static self) -> Option<T> {
|
||||||
|
self.0
|
||||||
|
.borrow_mut()
|
||||||
|
.take()
|
||||||
|
.map(|any| *any.downcast().unwrap())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
thread_local! {
|
thread_local! {
|
||||||
|
@ -128,7 +135,7 @@ impl AnyStorage for UnsyncStorage {
|
||||||
self.0.as_ptr() as *const ()
|
self.0.as_ptr() as *const ()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn take(&self) -> bool {
|
fn manually_drop(&self) -> bool {
|
||||||
self.0.borrow_mut().take().is_some()
|
self.0.borrow_mut().take().is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -101,11 +101,13 @@ impl ToTokens for Component {
|
||||||
let fn_name = self.fn_name();
|
let fn_name = self.fn_name();
|
||||||
|
|
||||||
tokens.append_all(quote! {
|
tokens.append_all(quote! {
|
||||||
dioxus_core::DynamicNode::Component(dioxus_core::VComponent::new(
|
dioxus_core::DynamicNode::Component({
|
||||||
#name #prop_gen_args,
|
use dioxus_core::prelude::Properties;
|
||||||
#builder,
|
(#builder).into_vcomponent(
|
||||||
#fn_name
|
#name #prop_gen_args,
|
||||||
))
|
#fn_name
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@ mod global;
|
||||||
pub use global::*;
|
pub use global::*;
|
||||||
|
|
||||||
mod impls;
|
mod impls;
|
||||||
pub use generational_box::{Storage, SyncStorage, UnsyncStorage};
|
pub use generational_box::{AnyStorage, Owner, Storage, SyncStorage, UnsyncStorage};
|
||||||
|
|
||||||
mod read;
|
mod read;
|
||||||
pub use read::*;
|
pub use read::*;
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
|
use generational_box::AnyStorage;
|
||||||
use generational_box::GenerationalBoxId;
|
use generational_box::GenerationalBoxId;
|
||||||
|
use generational_box::SyncStorage;
|
||||||
use generational_box::UnsyncStorage;
|
use generational_box::UnsyncStorage;
|
||||||
|
use std::any::Any;
|
||||||
|
use std::any::TypeId;
|
||||||
|
use std::cell::RefCell;
|
||||||
use std::mem::MaybeUninit;
|
use std::mem::MaybeUninit;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
use dioxus_core::prelude::*;
|
use dioxus_core::prelude::*;
|
||||||
use dioxus_core::ScopeId;
|
use dioxus_core::ScopeId;
|
||||||
|
@ -13,7 +17,72 @@ use crate::Effect;
|
||||||
use crate::Readable;
|
use crate::Readable;
|
||||||
use crate::Writable;
|
use crate::Writable;
|
||||||
|
|
||||||
fn current_owner<S: Storage<T>, T>() -> Rc<Owner<S>> {
|
/// Run a closure with the given owner.
|
||||||
|
pub fn with_owner<S: AnyStorage, F: FnOnce() -> R, R>(owner: Owner<S>, f: F) -> R {
|
||||||
|
let old_owner = set_owner(Some(owner));
|
||||||
|
let result = f();
|
||||||
|
set_owner(old_owner);
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the owner for the current thread.
|
||||||
|
fn set_owner<S: AnyStorage>(owner: Option<Owner<S>>) -> Option<Owner<S>> {
|
||||||
|
let id = TypeId::of::<S>();
|
||||||
|
if id == TypeId::of::<SyncStorage>() {
|
||||||
|
SYNC_OWNER.with(|cell| {
|
||||||
|
std::mem::replace(
|
||||||
|
&mut *cell.borrow_mut(),
|
||||||
|
owner.map(|owner| {
|
||||||
|
*(Box::new(owner) as Box<dyn Any>)
|
||||||
|
.downcast::<Owner<SyncStorage>>()
|
||||||
|
.unwrap()
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.map(|owner| *(Box::new(owner) as Box<dyn Any>).downcast().unwrap())
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
UNSYNC_OWNER.with(|cell| {
|
||||||
|
std::mem::replace(
|
||||||
|
&mut *cell.borrow_mut(),
|
||||||
|
owner.map(|owner| {
|
||||||
|
*(Box::new(owner) as Box<dyn Any>)
|
||||||
|
.downcast::<Owner<UnsyncStorage>>()
|
||||||
|
.unwrap()
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.map(|owner| *(Box::new(owner) as Box<dyn Any>).downcast().unwrap())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
thread_local! {
|
||||||
|
static SYNC_OWNER: RefCell<Option<Owner<SyncStorage>>> = RefCell::new(None);
|
||||||
|
static UNSYNC_OWNER: RefCell<Option<Owner<UnsyncStorage>>> = RefCell::new(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn current_owner<S: Storage<T>, T>() -> Owner<S> {
|
||||||
|
let id = TypeId::of::<S>();
|
||||||
|
let override_owner = if id == TypeId::of::<SyncStorage>() {
|
||||||
|
SYNC_OWNER.with(|cell| {
|
||||||
|
cell.borrow().clone().map(|owner| {
|
||||||
|
*(Box::new(owner) as Box<dyn Any>)
|
||||||
|
.downcast::<Owner<S>>()
|
||||||
|
.unwrap()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
UNSYNC_OWNER.with(|cell| {
|
||||||
|
cell.borrow().clone().map(|owner| {
|
||||||
|
*(Box::new(owner) as Box<dyn Any>)
|
||||||
|
.downcast::<Owner<S>>()
|
||||||
|
.unwrap()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
};
|
||||||
|
if let Some(owner) = override_owner {
|
||||||
|
return owner;
|
||||||
|
}
|
||||||
|
|
||||||
match Effect::current() {
|
match Effect::current() {
|
||||||
// If we are inside of an effect, we should use the owner of the effect as the owner of the value.
|
// If we are inside of an effect, we should use the owner of the effect as the owner of the value.
|
||||||
Some(effect) => {
|
Some(effect) => {
|
||||||
|
@ -24,18 +93,18 @@ fn current_owner<S: Storage<T>, T>() -> Rc<Owner<S>> {
|
||||||
None => match has_context() {
|
None => match has_context() {
|
||||||
Some(rt) => rt,
|
Some(rt) => rt,
|
||||||
None => {
|
None => {
|
||||||
let owner = Rc::new(S::owner());
|
let owner = S::owner();
|
||||||
provide_context(owner)
|
provide_context(owner)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn owner_in_scope<S: Storage<T>, T>(scope: ScopeId) -> Rc<Owner<S>> {
|
fn owner_in_scope<S: Storage<T>, T>(scope: ScopeId) -> Owner<S> {
|
||||||
match consume_context_from_scope(scope) {
|
match consume_context_from_scope(scope) {
|
||||||
Some(rt) => rt,
|
Some(rt) => rt,
|
||||||
None => {
|
None => {
|
||||||
let owner = Rc::new(S::owner());
|
let owner = S::owner();
|
||||||
scope.provide_context(owner)
|
scope.provide_context(owner)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -128,6 +197,13 @@ impl<T: 'static, S: Storage<T>> CopyValue<T, S> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Take the value out of the CopyValue, invalidating the value in the process.
|
||||||
|
pub fn take(&self) -> T {
|
||||||
|
self.value
|
||||||
|
.take()
|
||||||
|
.expect("value is already dropped or borrowed")
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn invalid() -> Self {
|
pub(crate) fn invalid() -> Self {
|
||||||
let owner = current_owner();
|
let owner = current_owner();
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,6 @@ use crate::{
|
||||||
use std::{
|
use std::{
|
||||||
any::Any,
|
any::Any,
|
||||||
cell::RefCell,
|
cell::RefCell,
|
||||||
mem::MaybeUninit,
|
|
||||||
ops::{Deref, DerefMut},
|
ops::{Deref, DerefMut},
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
|
@ -348,6 +347,11 @@ impl<T: 'static, S: Storage<SignalData<T>>> Signal<T, S> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Take the value out of the signal, invalidating the signal in the process.
|
||||||
|
pub fn take(&self) -> T {
|
||||||
|
self.inner.take().value
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the scope the signal was created in.
|
/// Get the scope the signal was created in.
|
||||||
pub fn origin_scope(&self) -> ScopeId {
|
pub fn origin_scope(&self) -> ScopeId {
|
||||||
self.inner.origin_scope()
|
self.inner.origin_scope()
|
||||||
|
|
Loading…
Reference in a new issue