소스 검색

convert T into signals automatically

Evan Almloff 1 년 전
부모
커밋
50e3216d8b

+ 12 - 0
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<i32>) -> Element {
+    rsx! {
+        h1 { "{count}" }
+        button { onclick: move |_| count += 1, "Up high!" }
+        button { onclick: move |_| count -= 1, "Down low!" }
     }
 }

+ 255 - 58
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<TokenStream, Error> {
     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<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> {
             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<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,
+    }
+}

+ 5 - 5
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<F: ComponentFunction<P, M>, 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<M>,
@@ -40,7 +40,7 @@ impl<F: ComponentFunction<P, M> + 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<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
     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>() {
-            Some(other) => (self.memo)(&self.props, other),
+            Some(other) => (self.memo)(&mut self.props, other),
             None => false,
         }
     }

+ 2 - 2
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

+ 1 - 1
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
     }
 }

+ 1 - 1
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
     }
 }

+ 12 - 3
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<M: 'static>(
+        self,
+        render_fn: impl ComponentFunction<Self, M>,
+        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
     }
 }

+ 43 - 19
packages/generational-box/src/lib.rs

@@ -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()
         }
     }
+
+    /// 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> {}
@@ -211,6 +220,9 @@ pub trait Storage<Data = ()>: 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<Data>;
 }
 
 /// 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<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.
     fn owner() -> Owner<Self> {
-        Owner {
+        Owner(Arc::new(Mutex::new(OwnerInner {
             owned: Default::default(),
-            phantom: PhantomData,
-        }
+        })))
     }
 }
 
@@ -324,7 +335,7 @@ impl<S> MemoryLocation<S> {
     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<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.
-pub struct Owner<S: AnyStorage + 'static = UnsyncStorage> {
-    owned: Arc<Mutex<Vec<MemoryLocation<S>>>>,
-    phantom: PhantomData<S>,
+pub struct Owner<S: AnyStorage + 'static = UnsyncStorage>(Arc<Mutex<OwnerInner<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> {
@@ -391,7 +423,7 @@ impl<S: AnyStorage> Owner<S> {
             #[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<S: AnyStorage> Owner<S> {
             created_at: std::panic::Location::caller(),
             _marker: PhantomData,
         };
-        self.owned.lock().push(location);
+        self.0.lock().owned.push(location);
         generational_box
     }
 }
-
-impl<S: AnyStorage> Drop for Owner<S> {
-    fn drop(&mut self) {
-        for location in self.owned.lock().iter() {
-            S::recycle(location)
-        }
-    }
-}

+ 8 - 1
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<T: Sync + Send + 'static> Storage<T> for SyncStorage {
     fn set(&self, value: T) {
         *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))
+    }
 }

+ 8 - 1
packages/generational-box/src/unsync.rs

@@ -75,6 +75,13 @@ impl<T: 'static> Storage<T> for UnsyncStorage {
     fn set(&self, value: T) {
         *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! {
@@ -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()
     }
 

+ 7 - 5
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
+                )
+            })
         })
     }
 }

+ 1 - 1
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::*;

+ 81 - 5
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<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() {
         // 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<S: Storage<T>, T>() -> Rc<Owner<S>> {
         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<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) {
         Some(rt) => rt,
         None => {
-            let owner = Rc::new(S::owner());
+            let owner = S::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 {
         let owner = current_owner();
 

+ 5 - 1
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<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.
     pub fn origin_scope(&self) -> ScopeId {
         self.inner.origin_scope()