From 58f7efafea0a82a974ab1aa035e5941eb636559a Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 19 Mar 2024 21:48:53 -0500 Subject: [PATCH] Make EventHandler copy (#2112) * implement Copy for EventHandler * implement from closure for event handler and remove special on prefix * fix props implementation of EventHandler --- Cargo.lock | 1 + packages/autofmt/src/component.rs | 13 +- packages/core-macro/src/props/mod.rs | 229 ++++++---- packages/core/Cargo.toml | 1 + packages/core/src/events.rs | 105 +++-- packages/core/src/generational_box.rs | 102 +++++ packages/core/src/lib.rs | 14 +- packages/generational-box/src/lib.rs | 8 +- packages/generational-box/src/references.rs | 6 +- packages/router/src/components/link.rs | 4 +- packages/rsx/src/component.rs | 15 +- packages/signals/src/copy_value.rs | 110 +---- packages/signals/src/global/memo.rs | 9 +- packages/signals/src/global/signal.rs | 9 +- packages/signals/src/impls.rs | 445 +++++++++++++++----- packages/signals/src/map.rs | 4 +- packages/signals/src/memo.rs | 11 + packages/signals/src/read_only_signal.rs | 18 + packages/signals/src/signal.rs | 15 + 19 files changed, 755 insertions(+), 364 deletions(-) create mode 100644 packages/core/src/generational_box.rs diff --git a/Cargo.lock b/Cargo.lock index 32855b311..3e67c75d2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2112,6 +2112,7 @@ dependencies = [ "dioxus-ssr", "futures-channel", "futures-util", + "generational-box", "longest-increasing-subsequence", "pretty_assertions", "rand 0.8.5", diff --git a/packages/autofmt/src/component.rs b/packages/autofmt/src/component.rs index 26cfb9b42..7115a078a 100644 --- a/packages/autofmt/src/component.rs +++ b/packages/autofmt/src/component.rs @@ -185,17 +185,6 @@ impl Writer<'_> { ContentField::Shorthand(e) => { write!(self.out, "{}", e.to_token_stream())?; } - ContentField::OnHandlerRaw(exp) => { - let out = unparse_expr(exp); - let mut lines = out.split('\n').peekable(); - let first = lines.next().unwrap(); - write!(self.out, "{name}: {first}")?; - for line in lines { - self.out.new_line()?; - self.out.indented_tab()?; - write!(self.out, "{line}")?; - } - } } if field_iter.peek().is_some() || manual_props.is_some() { @@ -227,7 +216,7 @@ impl Writer<'_> { .map(|field| match &field.content { ContentField::Formatted(s) => ifmt_to_string(s).len() , ContentField::Shorthand(e) => e.to_token_stream().to_string().len(), - ContentField::OnHandlerRaw(exp) | ContentField::ManExpr(exp) => { + ContentField::ManExpr(exp) => { let formatted = unparse_expr(exp); let len = if formatted.contains('\n') { 10000 diff --git a/packages/core-macro/src/props/mod.rs b/packages/core-macro/src/props/mod.rs index fd438096f..525a28f0c 100644 --- a/packages/core-macro/src/props/mod.rs +++ b/packages/core-macro/src/props/mod.rs @@ -517,11 +517,11 @@ 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, }; + use super::{child_owned_type, looks_like_event_handler_type, looks_like_signal_type}; #[derive(Debug)] pub struct StructInfo<'a> { @@ -579,24 +579,76 @@ mod struct_info { generics } - fn has_signal_fields(&self) -> bool { - self.fields.iter().any(|f| looks_like_signal_type(f.ty)) + /// Checks if the props have any fields that should be owned by the child. For example, when converting T to `ReadOnlySignal`, the new signal should be owned by the child + fn has_child_owned_fields(&self) -> bool { + self.fields.iter().any(|f| child_owned_type(f.ty)) } fn memoize_impl(&self) -> Result { // First check if there are any ReadOnlySignal fields, if there are not, we can just use the partialEq impl - let has_signal_fields = self.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(); - 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(); + let move_signal_fields = quote! { + 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; + )* + }; + + let event_handlers_fields: Vec<_> = self + .included_fields() + .filter(|f| looks_like_event_handler_type(f.ty)) + .map(|f| { + let name = f.name; + quote!(#name) + }) + .collect(); + + let move_event_handlers = quote! { + #( + // Update the event handlers + self.#event_handlers_fields.__set(new.#event_handlers_fields.__take()); + )* + }; + + if !signal_fields.is_empty() { Ok(quote! { // First check if the fields are equal let exactly_equal = self == new; @@ -618,46 +670,18 @@ mod struct_info { 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; - )* + #move_signal_fields + #move_event_handlers true }) } else { Ok(quote! { - self == new + let equal = self == new; + if equal { + #move_event_handlers + } + equal }) } } @@ -758,7 +782,7 @@ Finally, call `.build()` to create the instance of `{name}`. let ty = f.ty; quote!(#name: #ty) }) - .chain(self.has_signal_fields().then(|| quote!(owner: Owner))); + .chain(self.has_child_owned_fields().then(|| quote!(owner: Owner))); let global_fields_value = self .extend_fields() .map(|f| { @@ -766,7 +790,7 @@ Finally, call `.build()` to create the instance of `{name}`. quote!(#name: Vec::new()) }) .chain( - self.has_signal_fields() + self.has_child_owned_fields() .then(|| quote!(owner: Owner::default())), ); @@ -1051,7 +1075,7 @@ 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 looks_like_signal_type(arg_type) { + let (arg_type, arg_expr) = if child_owned_type(arg_type) { let marker_ident = syn::Ident::new("__Marker", proc_macro2::Span::call_site()); marker = Some(marker_ident.clone()); ( @@ -1091,7 +1115,10 @@ Finally, call `.build()` to create the instance of `{name}`. let name = f.name; quote!(#name: self.#name) }) - .chain(self.has_signal_fields().then(|| quote!(owner: self.owner))); + .chain( + self.has_child_owned_fields() + .then(|| quote!(owner: self.owner)), + ); Ok(quote! { #[allow(dead_code, non_camel_case_types, missing_docs)] @@ -1333,7 +1360,7 @@ Finally, call `.build()` to create the instance of `{name}`. quote!() }; - if self.has_signal_fields() { + if self.has_child_owned_fields() { let name = Ident::new(&format!("{}WithOwner", name), name.span()); let original_name = &self.name; let vis = &self.vis; @@ -1530,46 +1557,70 @@ Finally, call `.build()` to create the instance of `{name}`. } } +/// A helper function for paring types with a single generic argument. +fn extract_base_type_without_single_generic(ty: &Type) -> Option { + let Type::Path(ty) = ty else { + return None; + }; + if ty.qself.is_some() { + return None; + } + + 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 None; + } + _ => {} + } + segment.arguments = syn::PathArguments::None; + path_segments_without_generics.push(segment); + } + + // If there is more than the type and the single generic argument, it doesn't look like the type we want + if generic_arg_count > 2 { + return None; + } + + let path_without_generics = syn::Path { + leading_colon: None, + segments: Punctuated::from_iter(path_segments_without_generics), + }; + + Some(path_without_generics) +} + +/// Check if a type should be owned by the child component after conversion +fn child_owned_type(ty: &Type) -> bool { + looks_like_signal_type(ty) || looks_like_event_handler_type(ty) +} + 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), - }; - + match extract_base_type_without_single_generic(ty) { + Some(path_without_generics) => { path_without_generics == parse_quote!(dioxus_core::prelude::ReadOnlySignal) || path_without_generics == parse_quote!(prelude::ReadOnlySignal) || path_without_generics == parse_quote!(ReadOnlySignal) } - _ => false, + None => false, + } +} + +fn looks_like_event_handler_type(ty: &Type) -> bool { + match extract_base_type_without_single_generic(ty) { + Some(path_without_generics) => { + path_without_generics == parse_quote!(dioxus_core::prelude::EventHandler) + || path_without_generics == parse_quote!(prelude::EventHandler) + || path_without_generics == parse_quote!(EventHandler) + } + None => false, } } diff --git a/packages/core/Cargo.toml b/packages/core/Cargo.toml index 5ea994f78..98b6ed0a4 100644 --- a/packages/core/Cargo.toml +++ b/packages/core/Cargo.toml @@ -21,6 +21,7 @@ futures-channel = { workspace = true } tracing = { workspace = true } serde = { version = "1", features = ["derive"], optional = true } tracing-subscriber = "0.3.18" +generational-box = { workspace = true } [dev-dependencies] tokio = { workspace = true, features = ["full"] } diff --git a/packages/core/src/events.rs b/packages/core/src/events.rs index 7581d0bb6..5be005f46 100644 --- a/packages/core/src/events.rs +++ b/packages/core/src/events.rs @@ -1,8 +1,7 @@ -use crate::{global_context::current_scope_id, Runtime, ScopeId}; -use std::{ - cell::{Cell, RefCell}, - rc::Rc, -}; +use generational_box::{GenerationalBox, UnsyncStorage}; + +use crate::{generational_box::current_owner, global_context::current_scope_id, Runtime, ScopeId}; +use std::{cell::Cell, rc::Rc}; /// A wrapper around some generic data that handles the event's state /// @@ -165,41 +164,45 @@ impl std::fmt::Debug for Event { /// ``` pub struct EventHandler { pub(crate) origin: ScopeId, - pub(super) callback: Rc>>>, + pub(super) callback: GenerationalBox>>, } +impl Default for EventHandler { + fn default() -> Self { + EventHandler::new(|_| {}) + } +} + +impl From for EventHandler { + fn from(f: F) -> Self { + EventHandler::new(f) + } +} + +impl Copy for EventHandler {} + impl Clone for EventHandler { fn clone(&self) -> Self { - Self { - origin: self.origin, - callback: self.callback.clone(), - } + *self } } -impl PartialEq for EventHandler { - fn eq(&self, other: &Self) -> bool { - Rc::ptr_eq(&self.callback, &other.callback) - } -} - -impl Default for EventHandler { - fn default() -> Self { - Self { - origin: ScopeId::ROOT, - callback: Default::default(), - } +impl PartialEq for EventHandler { + fn eq(&self, _: &Self) -> bool { + true } } type ExternalListenerCallback = Box; -impl EventHandler { +impl EventHandler { /// Create a new [`EventHandler`] from an [`FnMut`] + #[track_caller] pub fn new(mut f: impl FnMut(T) + 'static) -> EventHandler { - let callback = Rc::new(RefCell::new(Some(Box::new(move |event: T| { + let owner = current_owner::(); + let callback = owner.insert(Some(Box::new(move |event: T| { f(event); - }) as Box))); + }) as Box)); EventHandler { callback, origin: current_scope_id().expect("to be in a dioxus runtime"), @@ -210,7 +213,7 @@ impl EventHandler { /// /// This borrows the event using a RefCell. Recursively calling a listener will cause a panic. pub fn call(&self, event: T) { - if let Some(callback) = self.callback.borrow_mut().as_mut() { + if let Some(callback) = self.callback.write().as_mut() { Runtime::with(|rt| rt.scope_stack.borrow_mut().push(self.origin)); callback(event); Runtime::with(|rt| rt.scope_stack.borrow_mut().pop()); @@ -221,6 +224,54 @@ impl EventHandler { /// /// This will force any future calls to "call" to not doing anything pub fn release(&self) { - self.callback.replace(None); + self.callback.set(None); + } + + #[doc(hidden)] + /// This should only be used by the `rsx!` macro. + pub fn __set(&mut self, value: impl FnMut(T) + 'static) { + self.callback.set(Some(Box::new(value))); + } + + #[doc(hidden)] + /// This should only be used by the `rsx!` macro. + pub fn __take(&self) -> ExternalListenerCallback { + self.callback + .manually_drop() + .expect("Signal has already been dropped") + .expect("EventHandler was manually dropped") + } +} + +impl std::ops::Deref for EventHandler { + type Target = dyn Fn(T) + 'static; + + fn deref(&self) -> &Self::Target { + // https://github.com/dtolnay/case-studies/tree/master/callable-types + + // First we create a closure that captures something with the Same in memory layout as Self (MaybeUninit). + let uninit_callable = std::mem::MaybeUninit::::uninit(); + // Then move that value into the closure. We assume that the closure now has a in memory layout of Self. + let uninit_closure = move |t| Self::call(unsafe { &*uninit_callable.as_ptr() }, t); + + // Check that the size of the closure is the same as the size of Self in case the compiler changed the layout of the closure. + let size_of_closure = std::mem::size_of_val(&uninit_closure); + assert_eq!(size_of_closure, std::mem::size_of::()); + + // Then cast the lifetime of the closure to the lifetime of &self. + fn cast_lifetime<'a, T>(_a: &T, b: &'a T) -> &'a T { + b + } + let reference_to_closure = cast_lifetime( + { + // The real closure that we will never use. + &uninit_closure + }, + // We transmute self into a reference to the closure. This is safe because we know that the closure has the same memory layout as Self so &Closure == &Self. + unsafe { std::mem::transmute(self) }, + ); + + // Cast the closure to a trait object. + reference_to_closure as &_ } } diff --git a/packages/core/src/generational_box.rs b/packages/core/src/generational_box.rs new file mode 100644 index 000000000..2d7f67280 --- /dev/null +++ b/packages/core/src/generational_box.rs @@ -0,0 +1,102 @@ +//! Integration with the generational-box crate for copy state management. +//! +//! Each scope in dioxus has a single [Owner] + +use std::{ + any::{Any, TypeId}, + cell::RefCell, +}; + +use generational_box::{AnyStorage, Owner, SyncStorage, UnsyncStorage}; + +use crate::{innerlude::current_scope_id, ScopeId}; + +/// Run a closure with the given owner. +/// +/// This will override the default owner for the current component. +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>> = const { RefCell::new(None) }; + static UNSYNC_OWNER: RefCell>> = const { RefCell::new(None) }; +} + +/// Returns the current owner. This owner will be used to drop any `Copy` state that is created by the `generational-box` crate. +/// +/// If an owner has been set with `with_owner`, that owner will be returned. Otherwise, the owner from the current scope will be returned. +pub fn current_owner() -> Owner { + let id = TypeId::of::(); + let override_owner = if id == TypeId::of::() { + SYNC_OWNER.with(|cell| { + let owner = cell.borrow(); + + owner.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; + } + + // Otherwise get the owner from the current scope + current_scope_id().expect("in a virtual dom").owner() +} + +impl ScopeId { + /// Get the owner for the current scope. + pub fn owner(self) -> Owner { + match self.has_context() { + Some(rt) => rt, + None => { + let owner = S::owner(); + self.provide_context(owner) + } + } + } +} diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index 987949374..eb4b5b6ff 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -10,6 +10,7 @@ mod dirty_scope; mod error_boundary; mod events; mod fragment; +mod generational_box; mod global_context; mod mutations; mod nodes; @@ -29,6 +30,7 @@ pub(crate) mod innerlude { pub use crate::error_boundary::*; pub use crate::events::*; pub use crate::fragment::*; + pub use crate::generational_box::*; pub use crate::global_context::*; pub use crate::mutations::*; pub use crate::nodes::*; @@ -88,13 +90,13 @@ pub use crate::innerlude::{ /// This includes types like [`Element`], and [`Component`]. pub mod prelude { pub use crate::innerlude::{ - consume_context, consume_context_from_scope, current_scope_id, fc_to_builder, generation, - has_context, needs_update, needs_update_any, parent_scope, provide_context, - provide_root_context, remove_future, schedule_update, schedule_update_any, spawn, - spawn_forever, spawn_isomorphic, suspend, try_consume_context, use_after_render, + consume_context, consume_context_from_scope, current_owner, current_scope_id, + fc_to_builder, generation, has_context, needs_update, needs_update_any, parent_scope, + provide_context, provide_root_context, remove_future, schedule_update, schedule_update_any, + spawn, spawn_forever, spawn_isomorphic, suspend, try_consume_context, use_after_render, use_before_render, use_drop, use_error_boundary, use_hook, use_hook_with_cleanup, - wait_for_next_render, AnyValue, Attribute, Component, ComponentFunction, Element, - ErrorBoundary, Event, EventHandler, Fragment, HasAttributes, IntoAttributeValue, + wait_for_next_render, with_owner, AnyValue, Attribute, Component, ComponentFunction, + Element, ErrorBoundary, Event, EventHandler, Fragment, HasAttributes, IntoAttributeValue, IntoDynNode, OptionStringFromMarker, Properties, Runtime, RuntimeGuard, ScopeId, ScopeState, SuperFrom, SuperInto, Task, Template, TemplateAttribute, TemplateNode, Throw, VNode, VNodeInner, VirtualDom, diff --git a/packages/generational-box/src/lib.rs b/packages/generational-box/src/lib.rs index 95e5ef056..c94ebb12c 100644 --- a/packages/generational-box/src/lib.rs +++ b/packages/generational-box/src/lib.rs @@ -51,7 +51,7 @@ pub struct GenerationalBox { _marker: PhantomData, } -impl Debug for GenerationalBox { +impl Debug for GenerationalBox { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { #[cfg(any(debug_assertions, feature = "check_generation"))] f.write_fmt(format_args!( @@ -65,7 +65,7 @@ impl Debug for GenerationalBox { } } -impl> GenerationalBox { +impl> GenerationalBox { #[inline(always)] pub(crate) fn validate(&self) -> bool { #[cfg(any(debug_assertions, feature = "check_generation"))] @@ -196,7 +196,7 @@ impl> GenerationalBox { } } -impl Copy for GenerationalBox {} +impl Copy for GenerationalBox {} impl Clone for GenerationalBox { fn clone(&self) -> Self { @@ -362,7 +362,7 @@ impl MemoryLocation { } } - fn replace_with_caller( + fn replace_with_caller( &mut self, value: T, #[cfg(any(debug_assertions, feature = "debug_ownership"))] diff --git a/packages/generational-box/src/references.rs b/packages/generational-box/src/references.rs index 36f8504e4..236e1cc82 100644 --- a/packages/generational-box/src/references.rs +++ b/packages/generational-box/src/references.rs @@ -68,7 +68,7 @@ pub struct GenerationalRefMut { pub(crate) borrow: GenerationalRefMutBorrowInfo, } -impl> GenerationalRefMut { +impl> GenerationalRefMut { pub(crate) fn new( inner: R, #[cfg(any(debug_assertions, feature = "debug_borrows"))] @@ -82,7 +82,7 @@ impl> GenerationalRefMut { } } -impl> Deref for GenerationalRefMut { +impl> Deref for GenerationalRefMut { type Target = T; fn deref(&self) -> &Self::Target { @@ -90,7 +90,7 @@ impl> Deref for GenerationalRefMut< } } -impl> DerefMut for GenerationalRefMut { +impl> DerefMut for GenerationalRefMut { fn deref_mut(&mut self) -> &mut Self::Target { self.inner.deref_mut() } diff --git a/packages/router/src/components/link.rs b/packages/router/src/components/link.rs index 1d0be0979..e8f69f7ff 100644 --- a/packages/router/src/components/link.rs +++ b/packages/router/src/components/link.rs @@ -268,13 +268,13 @@ pub fn Link(props: LinkProps) -> Element { router.push_any(router.resolve_into_routable(to.clone())); } - if let Some(handler) = onclick.clone() { + if let Some(handler) = onclick { handler.call(event); } }; let onmounted = move |event| { - if let Some(handler) = props.onmounted.clone() { + if let Some(handler) = props.onmounted { handler.call(event); } }; diff --git a/packages/rsx/src/component.rs b/packages/rsx/src/component.rs index 5a3c07547..a54bf5aa3 100644 --- a/packages/rsx/src/component.rs +++ b/packages/rsx/src/component.rs @@ -213,15 +213,10 @@ pub enum ContentField { Shorthand(Ident), ManExpr(Expr), Formatted(IfmtInput), - OnHandlerRaw(Expr), } impl ContentField { - fn new_from_name(name: &Ident, input: ParseStream) -> Result { - if name.to_string().starts_with("on") { - return Ok(ContentField::OnHandlerRaw(input.parse()?)); - } - + fn new(input: ParseStream) -> Result { if input.peek(LitStr) { let forked = input.fork(); let t: LitStr = forked.parse()?; @@ -243,17 +238,11 @@ impl ContentField { impl ToTokens for ContentField { fn to_tokens(&self, tokens: &mut TokenStream2) { match self { - ContentField::Shorthand(i) if i.to_string().starts_with("on") => { - tokens.append_all(quote! { EventHandler::new(#i) }) - } ContentField::Shorthand(i) => tokens.append_all(quote! { #i }), ContentField::ManExpr(e) => e.to_tokens(tokens), ContentField::Formatted(s) => tokens.append_all(quote! { #s }), - ContentField::OnHandlerRaw(e) => tokens.append_all(quote! { - EventHandler::new(#e) - }), } } } @@ -270,7 +259,7 @@ impl Parse for ComponentField { }); }; - let content = ContentField::new_from_name(&name, input)?; + let content = ContentField::new(input)?; if input.peek(LitStr) || input.peek(Ident) { missing_trailing_comma!(content.span()); diff --git a/packages/signals/src/copy_value.rs b/packages/signals/src/copy_value.rs index 20aafa21e..b8fd5d859 100644 --- a/packages/signals/src/copy_value.rs +++ b/packages/signals/src/copy_value.rs @@ -1,106 +1,18 @@ -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::ops::Deref; use dioxus_core::prelude::*; use dioxus_core::ScopeId; -use generational_box::{GenerationalBox, Owner, Storage}; +use generational_box::{GenerationalBox, Storage}; +use crate::read_impls; +use crate::Readable; use crate::ReadableRef; use crate::Writable; use crate::WritableRef; -use crate::{ReactiveContext, Readable}; - -/// 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>> = const { RefCell::new(None) }; - static UNSYNC_OWNER: RefCell>> = const { RefCell::new(None) }; -} - -fn current_owner, T>() -> Owner { - let id = TypeId::of::(); - let override_owner = if id == TypeId::of::() { - SYNC_OWNER.with(|cell| { - let owner = cell.borrow(); - - owner.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; - } - - // Otherwise get the owner from the current reactive context. - match ReactiveContext::current() { - Some(current_reactive_context) => owner_in_scope(current_reactive_context.origin_scope()), - None => owner_in_scope(current_scope_id().expect("in a virtual dom")), - } -} - -fn owner_in_scope, T>(scope: ScopeId) -> Owner { - match scope.has_context() { - Some(rt) => rt, - None => { - let owner = S::owner(); - scope.provide_context(owner) - } - } -} +use crate::{default_impl, write_impls}; /// CopyValue is a wrapper around a value to make the value mutable and Copy. /// @@ -181,7 +93,7 @@ impl> CopyValue { /// Create a new CopyValue. The value will be stored in the given scope. When the specified scope is dropped, the value will be dropped. #[track_caller] pub fn new_maybe_sync_in_scope(value: T, scope: ScopeId) -> Self { - let owner = owner_in_scope(scope); + let owner = scope.owner(); Self { value: owner.insert(value), @@ -270,3 +182,15 @@ impl> Deref for CopyValue { Readable::deref_impl(self) } } + +impl> Clone for CopyValue { + fn clone(&self) -> Self { + *self + } +} + +impl> Copy for CopyValue {} + +read_impls!(CopyValue>); +default_impl!(CopyValue>); +write_impls!(CopyValue>); diff --git a/packages/signals/src/global/memo.rs b/packages/signals/src/global/memo.rs index 67578a45e..d7ff0b265 100644 --- a/packages/signals/src/global/memo.rs +++ b/packages/signals/src/global/memo.rs @@ -1,3 +1,4 @@ +use crate::read_impls; use crate::{read::Readable, Memo, ReadableRef}; use dioxus_core::prelude::ScopeId; use generational_box::UnsyncStorage; @@ -68,12 +69,6 @@ impl Readable for GlobalMemo { } } -impl PartialEq for GlobalMemo { - fn eq(&self, other: &Self) -> bool { - std::ptr::eq(self, other) - } -} - /// Allow calling a signal with memo() syntax /// /// Currently only limited to copy types, though could probably specialize for string/arc/rc @@ -84,3 +79,5 @@ impl Deref for GlobalMemo { Readable::deref_impl(self) } } + +read_impls!(GlobalMemo where T: PartialEq); diff --git a/packages/signals/src/global/signal.rs b/packages/signals/src/global/signal.rs index 45623e1eb..4c8f0fbc2 100644 --- a/packages/signals/src/global/signal.rs +++ b/packages/signals/src/global/signal.rs @@ -6,6 +6,7 @@ use generational_box::UnsyncStorage; use std::ops::Deref; use super::get_global_context; +use crate::read_impls; use crate::Signal; /// A signal that can be accessed from anywhere in the application and created in a static @@ -118,12 +119,6 @@ impl Writable for GlobalSignal { } } -impl PartialEq for GlobalSignal { - fn eq(&self, other: &Self) -> bool { - std::ptr::eq(self, other) - } -} - /// Allow calling a signal with signal() syntax /// /// Currently only limited to copy types, though could probably specialize for string/arc/rc @@ -134,3 +129,5 @@ impl Deref for GlobalSignal { Readable::deref_impl(self) } } + +read_impls!(GlobalSignal); diff --git a/packages/signals/src/impls.rs b/packages/signals/src/impls.rs index 806a1fe1a..2242b44d6 100644 --- a/packages/signals/src/impls.rs +++ b/packages/signals/src/impls.rs @@ -1,46 +1,265 @@ -use crate::copy_value::CopyValue; -use crate::memo::Memo; -use crate::read::Readable; -use crate::signal::Signal; -use crate::write::Writable; -use crate::{GlobalMemo, GlobalSignal, MappedSignal, ReadOnlySignal, SignalData}; -use generational_box::{AnyStorage, Storage}; - -use std::{ - fmt::{Debug, Display}, - ops::{Add, Div, Mul, Sub}, -}; - +/// This macro is used to generate a `impl Default` block for any type with the function new_maybe_sync that takes a generic `T` +/// +/// # Example +/// ```rust +/// use generational_box::*; +/// use dioxus::prelude::*; +/// +/// struct MyCopyValue> { +/// value: CopyValue, +/// } +/// +/// impl> MyCopyValue { +/// fn new_maybe_sync(value: T) -> Self { +/// Self { value: CopyValue::new_maybe_sync(value) } +/// } +/// } +/// +/// impl> Readable for MyCopyValue { +/// type Target = T; +/// type Storage = S; +/// +/// fn try_read_unchecked( +/// &self, +/// ) -> Result, generational_box::BorrowError> { +/// self.value.try_read_unchecked() +/// } +/// +/// fn peek_unchecked(&self) -> ReadableRef<'static, Self> { +/// self.value.read_unchecked() +/// } +/// } +/// +/// default_impl!(MyCopyValue>); +/// ``` +#[macro_export] macro_rules! default_impl { - ($ty:ident $(: $extra_bounds:path)? $(, $bound_ty:ident : $bound:path, $vec_bound_ty:ident : $vec_bound:path)?) => { + ( + $ty:ident + // Accept generics + < T $(, $gen:ident $(: $gen_bound:path)?)* $(,)?> + // Accept extra bounds $( - impl Default for $ty { - #[track_caller] - fn default() -> Self { - Self::new_maybe_sync(Default::default()) - } - } + where + $( + $extra_bound_ty:ident: $extra_bound:path + ),+ )? + ) => { + impl Default for $ty + $( + where + $( + $extra_bound_ty: $extra_bound + ),+ + )? + { + #[track_caller] + fn default() -> Self { + Self::new_maybe_sync(Default::default()) + } + } } } +/// This macro is used to generate `impl Display`, `impl Debug`, `impl PartialEq`, and `impl Eq` blocks for any Readable type that takes a generic `T` +/// +/// # Example +/// ```rust +/// use generational_box::*; +/// use dioxus::prelude::*; +/// +/// struct MyCopyValue> { +/// value: CopyValue, +/// } +/// +/// impl> Readable for MyCopyValue { +/// type Target = T; +/// type Storage = S; +/// +/// fn try_read_unchecked( +/// &self, +/// ) -> Result, generational_box::BorrowError> { +/// self.value.try_read_unchecked() +/// } +/// +/// fn peek_unchecked(&self) -> ReadableRef<'static, Self> { +/// self.value.read_unchecked() +/// } +/// } +/// +/// read_impls!(MyCopyValue>); +/// ``` +#[macro_export] macro_rules! read_impls { - ($ty:ident $(: $extra_bounds:path)? $(, $bound_ty:ident : $bound:path, $vec_bound_ty:ident : $vec_bound:path)?) => { - impl Display for $ty { - #[track_caller] - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.with(|v| Display::fmt(v, f)) - } + ( + $ty:ident + // Accept generics + < T $(, $gen:ident $(: $gen_bound:path)?)* $(,)?> + // Accept extra bounds + $( + where + $( + $extra_bound_ty:ident: $extra_bound:path + ),+ + )? + ) => { + $crate::fmt_impls!{ + $ty< + T + $( + , $gen + $(: $gen_bound)? + )* + > + $( + where + $($extra_bound_ty: $extra_bound),* + )? } - - impl Debug for $ty { - #[track_caller] - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.with(|v| Debug::fmt(v, f)) - } + $crate::eq_impls!{ + $ty< + T + $( + , $gen + $(: $gen_bound)? + )* + > + $( + where + $($extra_bound_ty: $extra_bound),* + )? } + }; +} - impl PartialEq for $ty { +/// This macro is used to generate `impl Display`, and `impl Debug` blocks for any Readable type that takes a generic `T` +/// +/// # Example +/// ```rust +/// use generational_box::*; +/// use dioxus::prelude::*; +/// +/// struct MyCopyValue> { +/// value: CopyValue, +/// } +/// +/// impl> Readable for MyCopyValue { +/// type Target = T; +/// type Storage = S; +/// +/// fn try_read_unchecked( +/// &self, +/// ) -> Result, generational_box::BorrowError> { +/// self.value.try_read_unchecked() +/// } +/// +/// fn peek_unchecked(&self) -> ReadableRef<'static, Self> { +/// self.value.read_unchecked() +/// } +/// } +/// +/// fmt_impls!(MyCopyValue>); +/// ``` +#[macro_export] +macro_rules! fmt_impls { + ( + $ty:ident + // Accept generics + < T $(, $gen:ident $(: $gen_bound:path)?)* $(,)?> + // Accept extra bounds + $( + where + $( + $extra_bound_ty:ident: $extra_bound:path + ),+ + )? + ) => { + impl< + T: std::fmt::Display + 'static + $(, $gen $(: $gen_bound)?)* + > std::fmt::Display for $ty + $( + where + $($extra_bound_ty: $extra_bound,)* + )? + { + #[track_caller] + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.with(|v| std::fmt::Display::fmt(v, f)) + } + } + + impl< + T: std::fmt::Debug + 'static + $(, $gen $(: $gen_bound)?)* + > std::fmt::Debug for $ty + $( + where + $($extra_bound_ty: $extra_bound,)* + )? + { + #[track_caller] + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.with(|v| std::fmt::Debug::fmt(v, f)) + } + } +}; + } + +/// This macro is used to generate `impl PartialEq` blocks for any Readable type that takes a generic `T` +/// +/// # Example +/// ```rust +/// use generational_box::*; +/// use dioxus::prelude::*; +/// +/// struct MyCopyValue> { +/// value: CopyValue, +/// } +/// +/// impl> Readable for MyCopyValue { +/// type Target = T; +/// type Storage = S; +/// +/// fn try_read_unchecked( +/// &self, +/// ) -> Result, generational_box::BorrowError> { +/// self.value.try_read_unchecked() +/// } +/// +/// fn peek_unchecked(&self) -> ReadableRef<'static, Self> { +/// self.value.read_unchecked() +/// } +/// } +/// +/// eq_impls!(MyCopyValue>); +/// ``` +#[macro_export] +macro_rules! eq_impls { + ( + $ty:ident + // Accept generics + < T $(, $gen:ident $(: $gen_bound:path)?)* $(,)?> + // Accept extra bounds + $( + where + $( + $extra_bound_ty:ident: $extra_bound:path + ),+ + )? + ) => { + impl< + T: PartialEq + 'static + $(, $gen $(: $gen_bound)?)* + > PartialEq for $ty + $( + where + $($extra_bound_ty: $extra_bound,)* + )? + { #[track_caller] fn eq(&self, other: &T) -> bool { self.with(|v| *v == *other) @@ -49,9 +268,63 @@ macro_rules! read_impls { }; } +/// This macro is used to generate `impl Add`, `impl AddAssign`, `impl Sub`, `impl SubAssign`, `impl Mul`, `impl MulAssign`, `impl Div`, and `impl DivAssign` blocks for any Writable type that takes a generic `T` +/// +/// # Example +/// ```rust, ignore +/// use generational_box::*; +/// use dioxus::prelude::*; +/// +/// struct MyCopyValue> { +/// value: CopyValue, +/// } +/// +/// impl> Readable for MyCopyValue { +/// type Target = T; +/// type Storage = S; +/// +/// fn try_read_unchecked( +/// &self, +/// ) -> Result, generational_box::BorrowError> { +/// self.value.try_read_unchecked() +/// } +/// +/// fn peek_unchecked(&self) -> ReadableRef<'static, Self> { +/// self.value.read_unchecked() +/// } +/// } +/// +/// impl> Writable for MyCopyValue { +/// fn try_write_unchecked( +/// &self, +/// ) -> Result, generational_box::BorrowMutError> { +/// self.value.try_write_unchecked() +/// +/// } +/// +/// //... +/// } +/// +/// write_impls!(MyCopyValue>); +/// ``` +#[macro_export] macro_rules! write_impls { - ($ty:ident, $bound:path, $vec_bound:path) => { - impl + Copy + 'static, S: $bound> std::ops::Add for $ty { + ( + $ty:ident + // Accept generics + < T $(, $gen:ident $(: $gen_bound:path)?)* $(,)?> + // Accept extra bounds + $( + where + $( + $extra_bound_ty:ident: $extra_bound:path + ),+ + )?) => { + impl + Copy + 'static + $(, $gen $(: $gen_bound)?)* + > std::ops::Add + for $ty + { type Output = T; #[track_caller] @@ -60,21 +333,33 @@ macro_rules! write_impls { } } - impl + Copy + 'static, S: $bound> std::ops::AddAssign for $ty { + impl + Copy + 'static + $(, $gen $(: $gen_bound)?)* + > std::ops::AddAssign + for $ty + { #[track_caller] fn add_assign(&mut self, rhs: T) { self.with_mut(|v| *v = *v + rhs) } } - impl + Copy + 'static, S: $bound> std::ops::SubAssign for $ty { + impl + Copy + 'static + $(, $gen $(: $gen_bound)?)* + > std::ops::SubAssign + for $ty + { #[track_caller] fn sub_assign(&mut self, rhs: T) { self.with_mut(|v| *v = *v - rhs) } } - impl + Copy + 'static, S: $bound> std::ops::Sub for $ty { + impl + Copy + 'static + $(, $gen $(: $gen_bound)?)* + > std::ops::Sub + for $ty + { type Output = T; #[track_caller] @@ -83,14 +368,22 @@ macro_rules! write_impls { } } - impl + Copy + 'static, S: $bound> std::ops::MulAssign for $ty { + impl + Copy + 'static + $(, $gen $(: $gen_bound)?)* + > std::ops::MulAssign + for $ty + { #[track_caller] fn mul_assign(&mut self, rhs: T) { self.with_mut(|v| *v = *v * rhs) } } - impl + Copy + 'static, S: $bound> std::ops::Mul for $ty { + impl + Copy + 'static + $(, $gen $(: $gen_bound)?)* + > std::ops::Mul + for $ty + { type Output = T; #[track_caller] @@ -99,14 +392,22 @@ macro_rules! write_impls { } } - impl + Copy + 'static, S: $bound> std::ops::DivAssign for $ty { + impl + Copy + 'static + $(, $gen $(: $gen_bound)?)* + > std::ops::DivAssign + for $ty + { #[track_caller] fn div_assign(&mut self, rhs: T) { self.with_mut(|v| *v = *v / rhs) } } - impl + Copy + 'static, S: $bound> std::ops::Div for $ty { + impl + Copy + 'static + $(, $gen $(: $gen_bound)?)* + > std::ops::Div + for $ty + { type Output = T; #[track_caller] @@ -116,63 +417,3 @@ macro_rules! write_impls { } }; } - -read_impls!(CopyValue, S: Storage, S: Storage>); -default_impl!(CopyValue, S: Storage, S: Storage>); -write_impls!(CopyValue, Storage, Storage>); - -impl> Clone for CopyValue { - fn clone(&self) -> Self { - *self - } -} - -impl> Copy for CopyValue {} - -read_impls!(Signal, S: Storage>, S: Storage>>); -default_impl!(Signal, S: Storage>, S: Storage>>); -write_impls!(Signal, Storage>, Storage>>); - -impl>> Clone for Signal { - fn clone(&self) -> Self { - *self - } -} - -impl>> Copy for Signal {} - -read_impls!( - ReadOnlySignal, - S: Storage>, - S: Storage>> -); -default_impl!( - ReadOnlySignal, - S: Storage>, - S: Storage>> -); - -impl>> Clone for ReadOnlySignal { - fn clone(&self) -> Self { - *self - } -} - -impl>> Copy for ReadOnlySignal {} - -read_impls!(Memo: PartialEq); - -impl Clone for Memo { - fn clone(&self) -> Self { - *self - } -} - -impl Copy for Memo {} - -read_impls!(GlobalSignal); -default_impl!(GlobalSignal); - -read_impls!(GlobalMemo: PartialEq); - -read_impls!(MappedSignal, S: AnyStorage, S: AnyStorage); diff --git a/packages/signals/src/map.rs b/packages/signals/src/map.rs index e3c548d6a..490594156 100644 --- a/packages/signals/src/map.rs +++ b/packages/signals/src/map.rs @@ -1,6 +1,6 @@ use std::{ops::Deref, rc::Rc}; -use crate::{read::Readable, ReadableRef}; +use crate::{read::Readable, read_impls, ReadableRef}; use dioxus_core::prelude::*; use generational_box::{AnyStorage, UnsyncStorage}; @@ -88,3 +88,5 @@ where Readable::deref_impl(self) } } + +read_impls!(MappedSignal); diff --git a/packages/signals/src/memo.rs b/packages/signals/src/memo.rs index cc850510a..fe0509f8d 100644 --- a/packages/signals/src/memo.rs +++ b/packages/signals/src/memo.rs @@ -1,3 +1,4 @@ +use crate::read_impls; use crate::write::Writable; use crate::{read::Readable, ReactiveContext, ReadableRef, Signal}; use crate::{CopyValue, ReadOnlySignal}; @@ -191,3 +192,13 @@ where Readable::deref_impl(self) } } + +read_impls!(Memo where T: PartialEq); + +impl Clone for Memo { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for Memo {} diff --git a/packages/signals/src/read_only_signal.rs b/packages/signals/src/read_only_signal.rs index 0ebfe37d1..bb736382b 100644 --- a/packages/signals/src/read_only_signal.rs +++ b/packages/signals/src/read_only_signal.rs @@ -2,6 +2,7 @@ use crate::{read::Readable, ReadableRef, Signal, SignalData}; use dioxus_core::IntoDynNode; use std::ops::Deref; +use crate::{default_impl, read_impls}; use dioxus_core::{prelude::IntoAttributeValue, ScopeId}; use generational_box::{Storage, UnsyncStorage}; @@ -128,3 +129,20 @@ impl> + 'static> Deref for ReadOnlySignal where + S: Storage> +); +default_impl!( + ReadOnlySignal where + S: Storage> +); + +impl>> Clone for ReadOnlySignal { + fn clone(&self) -> Self { + *self + } +} + +impl>> Copy for ReadOnlySignal {} diff --git a/packages/signals/src/signal.rs b/packages/signals/src/signal.rs index e826bc1cd..ee7f184d2 100644 --- a/packages/signals/src/signal.rs +++ b/packages/signals/src/signal.rs @@ -1,3 +1,4 @@ +use crate::{default_impl, fmt_impls, write_impls}; use crate::{ read::Readable, write::Writable, CopyValue, GlobalMemo, GlobalSignal, ReactiveContext, ReadableRef, @@ -382,6 +383,8 @@ impl>> PartialEq for Signal { } } +impl>> Eq for Signal {} + /// Allow calling a signal with signal() syntax /// /// Currently only limited to copy types, though could probably specialize for string/arc/rc @@ -488,3 +491,15 @@ impl>> Drop for SignalSubscriberDrop self.signal.update_subscribers(); } } + +fmt_impls!(Signal>>); +default_impl!(Signal>>); +write_impls!(Signal>>); + +impl>> Clone for Signal { + fn clone(&self) -> Self { + *self + } +} + +impl>> Copy for Signal {}