From 50e3216d8b2059e665e7e7d3b8f2e67d440db79f Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 29 Jan 2024 13:36:39 -0600 Subject: [PATCH] convert T into signals automatically --- examples/signals.rs | 12 + packages/core-macro/src/props/mod.rs | 313 +++++++++++++++++++----- packages/core/src/any_props.rs | 10 +- packages/core/src/diff/component.rs | 4 +- packages/core/src/error_boundary.rs | 2 +- packages/core/src/fragment.rs | 2 +- packages/core/src/properties.rs | 15 +- packages/generational-box/src/lib.rs | 62 +++-- packages/generational-box/src/sync.rs | 9 +- packages/generational-box/src/unsync.rs | 9 +- packages/rsx/src/component.rs | 12 +- packages/signals/src/lib.rs | 2 +- packages/signals/src/rt.rs | 86 ++++++- packages/signals/src/signal.rs | 6 +- 14 files changed, 441 insertions(+), 103 deletions(-) diff --git a/examples/signals.rs b/examples/signals.rs index b7de86146..da7880e20 100644 --- a/examples/signals.rs +++ b/examples/signals.rs @@ -57,5 +57,17 @@ fn app() -> Element { } else { "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) -> Element { + rsx! { + h1 { "{count}" } + button { onclick: move |_| count += 1, "Up high!" } + button { onclick: move |_| count -= 1, "Down low!" } } } diff --git a/packages/core-macro/src/props/mod.rs b/packages/core-macro/src/props/mod.rs index a96a72545..7fe01bbcf 100644 --- a/packages/core-macro/src/props/mod.rs +++ b/packages/core-macro/src/props/mod.rs @@ -8,10 +8,12 @@ use proc_macro2::TokenStream; -use syn::parse::Error; +use syn::punctuated::Punctuated; use syn::spanned::Spanned; +use syn::{parse::Error, PathArguments}; use quote::quote; +use syn::{parse_quote, Type}; pub fn impl_my_derive(ast: &syn::DeriveInput) -> Result { let data = match &ast.data { @@ -515,6 +517,7 @@ mod struct_info { use syn::{Expr, Ident}; use super::field_info::{FieldBuilderAttr, FieldInfo}; + use super::looks_like_signal_type; use super::util::{ 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, @@ -576,6 +579,89 @@ mod struct_info { generics } + fn has_signal_fields(&self) -> bool { + self.fields.iter().any(|f| looks_like_signal_type(f.ty)) + } + + fn memoize_impl(&self) -> Result { + // 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 NonPartialEq for &&T { + fn compare(&self, other: &Self) -> bool { + false + } + } + + trait CanPartialEq: PartialEq { + fn compare(&self, other: &Self) -> bool; + } + + impl 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 { let StructInfo { ref vis, @@ -584,12 +670,6 @@ mod struct_info { .. } = *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 (impl_generics, ty_generics, where_clause) = 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()); } - let can_memoize = match are_there_generics { - true => quote! { false }, - false => quote! { self == other }, - }; + let memoize = self.memoize_impl()?; - let extend_fields = self.extend_fields().map(|f| { - let name = f.name; - let ty = f.ty; - quote!(#name: #ty) - }); - let extend_fields_value = self.extend_fields().map(|f| { - let name = f.name; - quote!(#name: Vec::new()) - }); + let global_fields = self + .extend_fields() + .map(|f| { + let name = f.name; + let ty = f.ty; + quote!(#name: #ty) + }) + .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! { 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)] #vis fn builder() -> #builder_name #generics_with_empty { #builder_name { - #(#extend_fields_value,)* + #(#global_fields_value,)* fields: #empties_tuple, _phantom: ::core::default::Default::default(), } @@ -701,7 +787,7 @@ Finally, call `.build()` to create the instance of `{name}`. #builder_type_doc #[allow(dead_code, non_camel_case_types, non_snake_case)] #vis struct #builder_name #b_generics { - #(#extend_fields,)* + #(#global_fields,)* fields: #all_fields_param, _phantom: (#( #phantom_generics ),*), } @@ -713,11 +799,10 @@ Finally, call `.build()` to create the instance of `{name}`. fn builder() -> Self::Builder { #name::builder() } - fn memoize(&self, other: &Self) -> bool { - #can_memoize + fn memoize(&mut self, new: &Self) -> bool { + #memoize } } - }) } @@ -966,22 +1051,29 @@ Finally, call `.build()` to create the instance of `{name}`. let arg_type = field_type; // If the field is auto_into, we need to add a generic parameter to the builder for specialization let mut marker = None; - let (arg_type, arg_expr) = - if field.builder_attr.auto_into || field.builder_attr.strip_option { - let marker_ident = syn::Ident::new("__Marker", proc_macro2::Span::call_site()); - marker = Some(marker_ident.clone()); - ( - quote!(impl dioxus_core::prelude::SuperInto<#arg_type, #marker_ident>), - quote!(dioxus_core::prelude::SuperInto::super_into(#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 (arg_type, arg_expr) = if looks_like_signal_type(arg_type) { + let marker_ident = syn::Ident::new("__Marker", proc_macro2::Span::call_site()); + marker = Some(marker_ident.clone()); + ( + 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!(with_owner(self.owner.clone(), move || dioxus_core::prelude::SuperInto::super_into(#field_name))), + ) + } else if field.builder_attr.auto_into || field.builder_attr.strip_option { + let marker_ident = syn::Ident::new("__Marker", proc_macro2::Span::call_site()); + marker = Some(marker_ident.clone()); + ( + quote!(impl dioxus_core::prelude::SuperInto<#arg_type, #marker_ident>), + quote!(dioxus_core::prelude::SuperInto::super_into(#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( &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 forward_extended_fields = self.extend_fields().map(|f| { - let name = f.name; - quote!(#name: self.#name) - }); + let forward_fields = self + .extend_fields() + .map(|f| { + let name = f.name; + quote!(#name: self.#name) + }) + .chain(self.has_signal_fields().then(|| quote!(owner: self.owner))); Ok(quote! { #[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 ( #(#descructuring,)* ) = self.fields; #builder_name { - #(#forward_extended_fields,)* + #(#forward_fields,)* fields: ( #(#reconstructing,)* ), _phantom: self._phantom, } @@ -1132,7 +1227,7 @@ Finally, call `.build()` to create the instance of `{name}`. note = #early_build_error_message )] 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 // correct is roughly impossible. let doc = - format!("Finalise the builder and create its [`{name}`] instance"); + format!("Finalize the builder and create its [`{name}`] instance"); quote!(#[doc = #doc]) } } } else { quote!() }; - 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 ),* + + if self.has_signal_fields() { + let name = Ident::new(&format!("{}WithOwner", name), name.span()); + let original_name = &self.name; + quote! { + #[doc(hidden)] + #[allow(dead_code, non_camel_case_types, missing_docs)] + #[derive(Clone)] + 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( + 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, + } +} diff --git a/packages/core/src/any_props.rs b/packages/core/src/any_props.rs index e5cf42694..b737a549a 100644 --- a/packages/core/src/any_props.rs +++ b/packages/core/src/any_props.rs @@ -8,7 +8,7 @@ pub(crate) trait AnyProps: 'static { /// Render the component with the internal props. fn render(&self) -> RenderReturn; /// 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`. fn props(&self) -> &dyn Any; /// 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. pub(crate) struct VProps, P, M> { render_fn: F, - memo: fn(&P, &P) -> bool, + memo: fn(&mut P, &P) -> bool, props: P, name: &'static str, phantom: std::marker::PhantomData, @@ -40,7 +40,7 @@ impl + Clone, P: Clone + 'static, M: 'static> VProps< /// Create a [`VProps`] object. pub fn new( render_fn: F, - memo: fn(&P, &P) -> bool, + memo: fn(&mut P, &P) -> bool, props: P, name: &'static str, ) -> VProps { @@ -57,9 +57,9 @@ impl + Clone, P: Clone + 'static, M: 'static> VProps< impl + Clone, P: Clone + 'static, M: 'static> AnyProps for VProps { - fn memoize(&self, other: &dyn Any) -> bool { + fn memoize(&mut self, other: &dyn Any) -> bool { match other.downcast_ref::

() { - Some(other) => (self.memo)(&self.props, other), + Some(other) => (self.memo)(&mut self.props, other), None => false, } } diff --git a/packages/core/src/diff/component.rs b/packages/core/src/diff/component.rs index a468b8ae1..6d4ab005e 100644 --- a/packages/core/src/diff/component.rs +++ b/packages/core/src/diff/component.rs @@ -1,4 +1,4 @@ -use std::ops::Deref; +use std::ops::{Deref, DerefMut}; use crate::{ any_props::AnyProps, @@ -72,7 +72,7 @@ impl VNode { // copy out the box for both 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(); // If the props are static, then we try to memoize by setting the new with the old diff --git a/packages/core/src/error_boundary.rs b/packages/core/src/error_boundary.rs index bd9855ab1..7fa1d2591 100644 --- a/packages/core/src/error_boundary.rs +++ b/packages/core/src/error_boundary.rs @@ -330,7 +330,7 @@ impl Properties for ErrorBoundaryProps { fn builder() -> Self::Builder { ErrorBoundaryProps::builder() } - fn memoize(&self, _: &Self) -> bool { + fn memoize(&mut self, _: &Self) -> bool { false } } diff --git a/packages/core/src/fragment.rs b/packages/core/src/fragment.rs index 22e98b692..b41dabe81 100644 --- a/packages/core/src/fragment.rs +++ b/packages/core/src/fragment.rs @@ -90,7 +90,7 @@ impl Properties for FragmentProps { fn builder() -> Self::Builder { FragmentBuilder(None) } - fn memoize(&self, _other: &Self) -> bool { + fn memoize(&mut self, _other: &Self) -> bool { false } } diff --git a/packages/core/src/properties.rs b/packages/core/src/properties.rs index f76ec7f8a..8c2b2bcee 100644 --- a/packages/core/src/properties.rs +++ b/packages/core/src/properties.rs @@ -32,7 +32,16 @@ pub trait Properties: Clone + Sized + 'static { fn builder() -> Self::Builder; /// 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( + self, + render_fn: impl ComponentFunction, + component_name: &'static str, + ) -> VComponent { + VComponent::new(render_fn, self, component_name) + } } impl Properties for () { @@ -40,7 +49,7 @@ impl Properties for () { fn builder() -> Self::Builder { EmptyBuilder {} } - fn memoize(&self, _other: &Self) -> bool { + fn memoize(&mut self, _other: &Self) -> bool { true } } @@ -65,7 +74,7 @@ where fn builder() -> Self::Builder { todo!() } - fn memoize(&self, _other: &Self) -> bool { + fn memoize(&mut self, _other: &Self) -> bool { true } } diff --git a/packages/generational-box/src/lib.rs b/packages/generational-box/src/lib.rs index 4f7ae6dfa..052f95a37 100644 --- a/packages/generational-box/src/lib.rs +++ b/packages/generational-box/src/lib.rs @@ -185,6 +185,15 @@ impl> GenerationalBox { 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 { + if self.validate() { + Storage::take(&self.raw.0.data) + } else { + None + } + } } impl Copy for GenerationalBox {} @@ -211,6 +220,9 @@ pub trait Storage: AnyStorage + 'static { /// Set the value 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; } /// 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. fn data_ptr(&self) -> *const (); - /// Take the value out of the storage. This will return true if the value was taken. - fn take(&self) -> bool; + /// Drop the value from the storage. This will return true if the value was taken. + fn manually_drop(&self) -> bool; /// Recycle a memory location. This will drop the memory location and return it to the runtime. fn recycle(location: &MemoryLocation); @@ -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. fn owner() -> Owner { - Owner { + Owner(Arc::new(Mutex::new(OwnerInner { owned: Default::default(), - phantom: PhantomData, - } + }))) } } @@ -324,7 +335,7 @@ impl MemoryLocation { where S: AnyStorage, { - let old = self.0.data.take(); + let old = self.0.data.manually_drop(); #[cfg(any(debug_assertions, feature = "check_generation"))] if old { let new_generation = self.0.generation.load(std::sync::atomic::Ordering::Relaxed) + 1; @@ -355,10 +366,31 @@ impl MemoryLocation { } } +struct OwnerInner { + owned: Vec>, +} + +impl Drop for OwnerInner { + 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. -pub struct Owner { - owned: Arc>>>, - phantom: PhantomData, +pub struct Owner(Arc>>); + +impl Default for Owner { + fn default() -> Self { + S::owner() + } +} + +impl Clone for Owner { + fn clone(&self) -> Self { + Self(self.0.clone()) + } } impl Owner { @@ -391,7 +423,7 @@ impl Owner { #[cfg(any(debug_assertions, feature = "debug_borrows"))] caller, ); - self.owned.lock().push(location); + self.0.lock().owned.push(location); key } @@ -409,15 +441,7 @@ impl Owner { created_at: std::panic::Location::caller(), _marker: PhantomData, }; - self.owned.lock().push(location); + self.0.lock().owned.push(location); generational_box } } - -impl Drop for Owner { - fn drop(&mut self) { - for location in self.owned.lock().iter() { - S::recycle(location) - } - } -} diff --git a/packages/generational-box/src/sync.rs b/packages/generational-box/src/sync.rs index 9e6532485..5241fb22e 100644 --- a/packages/generational-box/src/sync.rs +++ b/packages/generational-box/src/sync.rs @@ -72,7 +72,7 @@ impl AnyStorage for SyncStorage { self.0.data_ptr() as *const () } - fn take(&self) -> bool { + fn manually_drop(&self) -> bool { self.0.write().take().is_some() } @@ -162,4 +162,11 @@ impl Storage for SyncStorage { fn set(&self, value: T) { *self.0.write() = Some(Box::new(value)); } + + fn take(&'static self) -> Option { + self.0 + .write() + .take() + .and_then(|any| any.downcast().ok().map(|boxed| *boxed)) + } } diff --git a/packages/generational-box/src/unsync.rs b/packages/generational-box/src/unsync.rs index c01417656..8cdf4444b 100644 --- a/packages/generational-box/src/unsync.rs +++ b/packages/generational-box/src/unsync.rs @@ -75,6 +75,13 @@ impl Storage for UnsyncStorage { fn set(&self, value: T) { *self.0.borrow_mut() = Some(Box::new(value)); } + + fn take(&'static self) -> Option { + self.0 + .borrow_mut() + .take() + .map(|any| *any.downcast().unwrap()) + } } thread_local! { @@ -128,7 +135,7 @@ impl AnyStorage for UnsyncStorage { self.0.as_ptr() as *const () } - fn take(&self) -> bool { + fn manually_drop(&self) -> bool { self.0.borrow_mut().take().is_some() } diff --git a/packages/rsx/src/component.rs b/packages/rsx/src/component.rs index 09251bfc0..ac2a26c1e 100644 --- a/packages/rsx/src/component.rs +++ b/packages/rsx/src/component.rs @@ -101,11 +101,13 @@ impl ToTokens for Component { let fn_name = self.fn_name(); tokens.append_all(quote! { - dioxus_core::DynamicNode::Component(dioxus_core::VComponent::new( - #name #prop_gen_args, - #builder, - #fn_name - )) + dioxus_core::DynamicNode::Component({ + use dioxus_core::prelude::Properties; + (#builder).into_vcomponent( + #name #prop_gen_args, + #fn_name + ) + }) }) } } diff --git a/packages/signals/src/lib.rs b/packages/signals/src/lib.rs index d5f65ea4a..5f13e17fe 100644 --- a/packages/signals/src/lib.rs +++ b/packages/signals/src/lib.rs @@ -32,7 +32,7 @@ mod global; pub use global::*; mod impls; -pub use generational_box::{Storage, SyncStorage, UnsyncStorage}; +pub use generational_box::{AnyStorage, Owner, Storage, SyncStorage, UnsyncStorage}; mod read; pub use read::*; diff --git a/packages/signals/src/rt.rs b/packages/signals/src/rt.rs index c36fcf6e7..7090816b0 100644 --- a/packages/signals/src/rt.rs +++ b/packages/signals/src/rt.rs @@ -1,8 +1,12 @@ +use generational_box::AnyStorage; use generational_box::GenerationalBoxId; +use generational_box::SyncStorage; use generational_box::UnsyncStorage; +use std::any::Any; +use std::any::TypeId; +use std::cell::RefCell; use std::mem::MaybeUninit; use std::ops::Deref; -use std::rc::Rc; use dioxus_core::prelude::*; use dioxus_core::ScopeId; @@ -13,7 +17,72 @@ use crate::Effect; use crate::Readable; use crate::Writable; -fn current_owner, T>() -> Rc> { +/// Run a closure with the given owner. +pub fn with_owner R, R>(owner: Owner, 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(owner: Option>) -> Option> { + let id = TypeId::of::(); + if id == TypeId::of::() { + SYNC_OWNER.with(|cell| { + std::mem::replace( + &mut *cell.borrow_mut(), + owner.map(|owner| { + *(Box::new(owner) as Box) + .downcast::>() + .unwrap() + }), + ) + .map(|owner| *(Box::new(owner) as Box).downcast().unwrap()) + }) + } else { + UNSYNC_OWNER.with(|cell| { + std::mem::replace( + &mut *cell.borrow_mut(), + owner.map(|owner| { + *(Box::new(owner) as Box) + .downcast::>() + .unwrap() + }), + ) + .map(|owner| *(Box::new(owner) as Box).downcast().unwrap()) + }) + } +} + +thread_local! { + static SYNC_OWNER: RefCell>> = RefCell::new(None); + static UNSYNC_OWNER: RefCell>> = RefCell::new(None); +} + +fn current_owner, T>() -> Owner { + let id = TypeId::of::(); + let override_owner = if id == TypeId::of::() { + SYNC_OWNER.with(|cell| { + cell.borrow().clone().map(|owner| { + *(Box::new(owner) as Box) + .downcast::>() + .unwrap() + }) + }) + } else { + UNSYNC_OWNER.with(|cell| { + cell.borrow().clone().map(|owner| { + *(Box::new(owner) as Box) + .downcast::>() + .unwrap() + }) + }) + }; + if let Some(owner) = override_owner { + return owner; + } + match Effect::current() { // If we are inside of an effect, we should use the owner of the effect as the owner of the value. Some(effect) => { @@ -24,18 +93,18 @@ fn current_owner, T>() -> Rc> { None => match has_context() { Some(rt) => rt, None => { - let owner = Rc::new(S::owner()); + let owner = S::owner(); provide_context(owner) } }, } } -fn owner_in_scope, T>(scope: ScopeId) -> Rc> { +fn owner_in_scope, T>(scope: ScopeId) -> Owner { match consume_context_from_scope(scope) { Some(rt) => rt, None => { - let owner = Rc::new(S::owner()); + let owner = S::owner(); scope.provide_context(owner) } } @@ -128,6 +197,13 @@ impl> CopyValue { } } + /// 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 { let owner = current_owner(); diff --git a/packages/signals/src/signal.rs b/packages/signals/src/signal.rs index 1fdef1a1f..6b55359ff 100644 --- a/packages/signals/src/signal.rs +++ b/packages/signals/src/signal.rs @@ -5,7 +5,6 @@ use crate::{ use std::{ any::Any, cell::RefCell, - mem::MaybeUninit, ops::{Deref, DerefMut}, rc::Rc, sync::Arc, @@ -348,6 +347,11 @@ impl>> Signal { } } + /// 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. pub fn origin_scope(&self) -> ScopeId { self.inner.origin_scope()