From ffc49530f6d8001ac5e019eacee16059e0477f2c Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Fri, 8 Mar 2024 11:17:28 -0600 Subject: [PATCH] implement readable and callable for all structs in the hooks crate --- Cargo.lock | 1 + packages/hooks/Cargo.toml | 1 + packages/hooks/src/use_callback.rs | 40 ++- packages/hooks/src/use_future.rs | 37 ++- packages/hooks/src/use_memo.rs | 5 +- packages/hooks/src/use_resource.rs | 58 +++- packages/hooks/src/use_shared_state.rs | 376 ----------------------- packages/signals/src/copy_value.rs | 28 +- packages/signals/src/global/memo.rs | 40 +-- packages/signals/src/global/signal.rs | 43 +-- packages/signals/src/memo.rs | 9 + packages/signals/src/read_only_signal.rs | 10 + packages/signals/src/signal.rs | 10 + 13 files changed, 167 insertions(+), 491 deletions(-) delete mode 100644 packages/hooks/src/use_shared_state.rs diff --git a/Cargo.lock b/Cargo.lock index d47df79c6..db729b453 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2492,6 +2492,7 @@ dependencies = [ "dioxus-signals", "futures-channel", "futures-util", + "generational-box", "slab", "thiserror", "tokio", diff --git a/packages/hooks/Cargo.toml b/packages/hooks/Cargo.toml index 734b9b76e..c12eba9eb 100644 --- a/packages/hooks/Cargo.toml +++ b/packages/hooks/Cargo.toml @@ -22,6 +22,7 @@ thiserror = { workspace = true } slab = { workspace = true } dioxus-debug-cell = "0.1.1" futures-util = { workspace = true} +generational-box.workspace = true [dev-dependencies] futures-util = { workspace = true, default-features = false } diff --git a/packages/hooks/src/use_callback.rs b/packages/hooks/src/use_callback.rs index 61e64fa55..fa38a246a 100644 --- a/packages/hooks/src/use_callback.rs +++ b/packages/hooks/src/use_callback.rs @@ -49,7 +49,43 @@ impl Copy for UseCallback {} impl UseCallback { /// Call the callback - pub fn call(&mut self) -> O { - self.inner.with_mut(|f| f()) + pub fn call(&self) -> O { + (self.inner.write_unchecked())() + } +} + +// This makes UseCallback callable like a normal function +impl std::ops::Deref for UseCallback { + type Target = dyn Fn() -> O; + + fn deref(&self) -> &Self::Target { + use std::mem::MaybeUninit; + + // 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 = 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 || Self::call(unsafe { &*uninit_callable.as_ptr() }); + + // 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/hooks/src/use_future.rs b/packages/hooks/src/use_future.rs index ae35770bc..589c2ccd9 100644 --- a/packages/hooks/src/use_future.rs +++ b/packages/hooks/src/use_future.rs @@ -4,6 +4,7 @@ use dioxus_core::{prelude::*, Task}; use dioxus_signals::*; use dioxus_signals::{Readable, Writable}; use std::future::Future; +use std::ops::Deref; /// A hook that allows you to spawn a future. /// This future will **not** run on the server @@ -39,7 +40,7 @@ where { let mut state = use_signal(|| UseFutureState::Pending); - let mut callback = use_callback(move || { + let callback = use_callback(move || { let fut = future(); spawn(async move { state.set(UseFutureState::Pending); @@ -147,3 +148,37 @@ impl UseFuture { self.state.into() } } + +impl From for ReadOnlySignal { + fn from(val: UseFuture) -> Self { + val.state.into() + } +} + +impl Readable for UseFuture { + type Target = UseFutureState; + type Storage = UnsyncStorage; + + #[track_caller] + fn try_read_unchecked( + &self, + ) -> Result, generational_box::BorrowError> { + self.state.try_read_unchecked() + } + + #[track_caller] + fn peek_unchecked(&self) -> ReadableRef<'static, Self> { + self.state.peek_unchecked() + } +} + +/// Allow calling a signal with signal() syntax +/// +/// Currently only limited to copy types, though could probably specialize for string/arc/rc +impl Deref for UseFuture { + type Target = dyn Fn() -> UseFutureState; + + fn deref(&self) -> &Self::Target { + Readable::deref_impl(self) + } +} diff --git a/packages/hooks/src/use_memo.rs b/packages/hooks/src/use_memo.rs index 36a65f83a..2f423e07a 100644 --- a/packages/hooks/src/use_memo.rs +++ b/packages/hooks/src/use_memo.rs @@ -25,8 +25,9 @@ use futures_util::StreamExt; /// ``` #[track_caller] pub fn use_memo(f: impl FnMut() -> R + 'static) -> Memo { - let mut callback = use_callback(f); - use_hook(|| Signal::memo(move || callback.call())) + let callback = use_callback(f); + #[allow(clippy::redundant_closure)] + use_hook(|| Signal::memo(move || callback())) } /// Creates a new unsync Selector with some local dependencies. The selector will be run immediately and whenever any signal it reads or any dependencies it tracks changes diff --git a/packages/hooks/src/use_resource.rs b/packages/hooks/src/use_resource.rs index 4fa39c222..42c4a14a0 100644 --- a/packages/hooks/src/use_resource.rs +++ b/packages/hooks/src/use_resource.rs @@ -1,12 +1,14 @@ #![allow(missing_docs)] use crate::{use_callback, use_signal, UseCallback}; +use dioxus_core::prelude::*; use dioxus_core::{ prelude::{spawn, use_hook}, Task, }; use dioxus_signals::*; use futures_util::{future, pin_mut, FutureExt, StreamExt}; +use std::ops::Deref; use std::{cell::Cell, future::Future, rc::Rc}; /// A memo that resolve to a value asynchronously. @@ -49,7 +51,7 @@ where (rc, Rc::new(Cell::new(Some(changed)))) }); - let mut cb = use_callback(move || { + let cb = use_callback(move || { // Create the user's task #[allow(clippy::redundant_closure)] let fut = rc.run_in(|| future()); @@ -70,7 +72,7 @@ where }) }); - let mut task = use_hook(|| Signal::new(cb.call())); + let mut task = use_hook(|| Signal::new(cb())); use_hook(|| { let mut changed = changed.take().unwrap(); @@ -83,7 +85,7 @@ where task.write().cancel(); // Start a new task - task.set(cb.call()); + task.set(cb()); } }) }); @@ -181,10 +183,54 @@ impl Resource { } } -impl std::ops::Deref for Resource { - type Target = Signal>; +impl From> for ReadOnlySignal> { + fn from(val: Resource) -> Self { + val.value.into() + } +} +impl Readable for Resource { + type Target = Option; + type Storage = UnsyncStorage; + + #[track_caller] + fn try_read_unchecked( + &self, + ) -> Result, generational_box::BorrowError> { + self.value.try_read_unchecked() + } + + #[track_caller] + fn peek_unchecked(&self) -> ReadableRef<'static, Self> { + self.value.peek_unchecked() + } +} + +impl IntoAttributeValue for Resource +where + T: Clone + IntoAttributeValue, +{ + fn into_value(self) -> dioxus_core::AttributeValue { + self.with(|f| f.clone().into_value()) + } +} + +impl IntoDynNode for Resource +where + T: Clone + IntoDynNode, +{ + fn into_dyn_node(self) -> dioxus_core::DynamicNode { + self().into_dyn_node() + } +} + +/// Allow calling a signal with signal() syntax +/// +/// Currently only limited to copy types, though could probably specialize for string/arc/rc +impl Deref for Resource { + type Target = dyn Fn() -> Option; + fn deref(&self) -> &Self::Target { - &self.value + Readable::deref_impl(self) } } diff --git a/packages/hooks/src/use_shared_state.rs b/packages/hooks/src/use_shared_state.rs deleted file mode 100644 index d00f7b533..000000000 --- a/packages/hooks/src/use_shared_state.rs +++ /dev/null @@ -1,376 +0,0 @@ -use self::error::{UseSharedStateError, UseSharedStateResult}; -use dioxus_core::ScopeId; -use std::{collections::HashSet, rc::Rc, sync::Arc}; - -#[cfg(debug_assertions)] -pub use dioxus_debug_cell::{ - error::{BorrowError, BorrowMutError}, - Ref, RefCell, RefMut, -}; - -#[cfg(not(debug_assertions))] -pub use std::cell::{BorrowError, BorrowMutError, Ref, RefCell, RefMut}; - -#[macro_export] -macro_rules! debug_location { - () => {{ - #[cfg(debug_assertions)] - { - std::panic::Location::caller() - } - #[cfg(not(debug_assertions))] - { - () - } - }}; -} - -pub mod error { - #[cfg(debug_assertions)] - fn locations_display(locations: &[&'static std::panic::Location<'static>]) -> String { - locations - .iter() - .map(|location| format!(" - {location}")) - .collect::>() - .join("\n") - } - #[derive(thiserror::Error, Debug)] - pub enum UseSharedStateError { - #[cfg_attr( - debug_assertions, - error( - "[{0}] {1} is already borrowed at, so it cannot be borrowed mutably. Previous borrows:\n[{2}]\n\n", - .source.attempted_at, - .type_name, - locations_display(&.source.already_borrowed_at) - ) - )] - #[cfg_attr( - not(debug_assertions), - error("{type_name} is already borrowed, so it cannot be borrowed mutably. (More detail available in debug mode)") - )] - AlreadyBorrowed { - source: super::BorrowMutError, - type_name: &'static str, - }, - #[cfg_attr( - debug_assertions, - error( - "[{0}] {1} is already borrowed mutably at [{2}], so it cannot be borrowed anymore.", - .source.attempted_at, - .type_name, - locations_display(&.source.already_borrowed_at) - ) - )] - #[cfg_attr( - not(debug_assertions), - error("{type_name} is already borrowed mutably, so it cannot be borrowed anymore. (More detail available in debug mode)") - )] - AlreadyBorrowedMutably { - source: super::BorrowError, - type_name: &'static str, - }, - } - - pub type UseSharedStateResult = Result; -} - -type ProvidedState = Rc>>; - -// Tracks all the subscribers to a shared State -pub(crate) struct ProvidedStateInner { - value: T, - notify_any: Arc, - consumers: HashSet, - gen: usize, -} - -impl ProvidedStateInner { - pub(crate) fn notify_consumers(&mut self) { - self.gen += 1; - for consumer in self.consumers.iter() { - (self.notify_any)(*consumer); - } - } -} - -/// This hook provides some relatively light ergonomics around shared state. -/// -/// It is not a substitute for a proper state management system, but it is capable enough to provide use_state - type -/// ergonomics in a pinch, with zero cost. -/// -/// # Example -/// -/// ```rust -/// # use dioxus::prelude::*; -/// # -/// # fn app() -> Element { -/// # rsx! { -/// # Parent{} -/// # } -/// # } -/// -/// #[derive(Clone, Copy)] -/// enum Theme { -/// Light, -/// Dark, -/// } -/// -/// // Provider -/// fn Parent<'a>(cx: Scope<'a>) -> Element { -/// use_shared_state_provider(|| Theme::Dark); -/// let theme = use_shared_state::().unwrap(); -/// -/// rsx! { -/// button{ -/// onclick: move |_| { -/// let current_theme = *theme.read(); -/// *theme.write() = match current_theme { -/// Theme::Dark => Theme::Light, -/// Theme::Light => Theme::Dark, -/// }; -/// }, -/// "Change theme" -/// } -/// Child{} -/// } -/// } -/// -/// // Consumer -/// fn Child<'a>(cx: Scope<'a>) -> Element { -/// let theme = use_shared_state::().unwrap(); -/// let current_theme = *theme.read(); -/// -/// rsx! { -/// match current_theme { -/// Theme::Dark => { -/// "Dark mode" -/// } -/// Theme::Light => { -/// "Light mode" -/// } -/// } -/// } -/// } -/// ``` -/// -/// # How it works -/// -/// Any time a component calls `write`, every consumer of the state will be notified - excluding the provider. -/// -/// Right now, there is not a distinction between read-only and write-only, so every consumer will be notified. -#[must_use] -pub fn use_shared_state() -> Option<&UseSharedState> { - let state_owner: &mut Option> = &mut *cx.use_hook(move || { - let scope_id = cx.scope_id(); - let root = cx.consume_context::>()?; - - root.borrow_mut().consumers.insert(scope_id); - - let state = UseSharedState::new(root); - let owner = UseSharedStateOwner { state, scope_id }; - Some(owner) - }); - state_owner.as_mut().map(|s| { - s.state.gen = s.state.inner.borrow().gen; - &s.state - }) -} - -/// This wrapper detects when the hook is dropped and will unsubscribe when the component is unmounted -struct UseSharedStateOwner { - state: UseSharedState, - scope_id: ScopeId, -} - -impl Drop for UseSharedStateOwner { - fn drop(&mut self) { - // we need to unsubscribe when our component is unmounted - let mut root = self.state.inner.borrow_mut(); - root.consumers.remove(&self.scope_id); - } -} - -/// State that is shared between components through the context system -pub struct UseSharedState { - pub(crate) inner: Rc>>, - gen: usize, -} - -impl UseSharedState { - fn new(inner: Rc>>) -> Self { - let gen = inner.borrow().gen; - Self { inner, gen } - } - - /// Notify all consumers of the state that it has changed. (This is called automatically when you call "write") - pub fn notify_consumers(&self) { - self.inner.borrow_mut().notify_consumers(); - } - - /// Try reading the shared state - #[cfg_attr(debug_assertions, track_caller)] - #[cfg_attr(debug_assertions, inline(never))] - pub fn try_read(&self) -> UseSharedStateResult> { - match self.inner.try_borrow() { - Ok(value) => Ok(Ref::map(value, |inner| &inner.value)), - Err(source) => Err(UseSharedStateError::AlreadyBorrowedMutably { - source, - type_name: std::any::type_name::(), - }), - } - } - - /// Read the shared value - #[cfg_attr(debug_assertions, track_caller)] - #[cfg_attr(debug_assertions, inline(never))] - pub fn read(&self) -> Ref<'_, T> { - match self.try_read() { - Ok(value) => value, - Err(message) => panic!( - "Reading the shared state failed: {}\n({:?})", - message, message - ), - } - } - - /// Try writing the shared state - #[cfg_attr(debug_assertions, track_caller)] - #[cfg_attr(debug_assertions, inline(never))] - pub fn try_write(&self) -> UseSharedStateResult> { - match self.inner.try_borrow_mut() { - Ok(mut value) => { - value.notify_consumers(); - Ok(RefMut::map(value, |inner| &mut inner.value)) - } - Err(source) => Err(UseSharedStateError::AlreadyBorrowed { - source, - type_name: std::any::type_name::(), - }), - } - } - - /// Calling "write" will force the component to re-render - /// - /// - // TODO: We prevent unncessary notifications only in the hook, but we should figure out some more global lock - #[cfg_attr(debug_assertions, track_caller)] - #[cfg_attr(debug_assertions, inline(never))] - pub fn write(&self) -> RefMut<'_, T> { - match self.try_write() { - Ok(value) => value, - Err(message) => panic!( - "Writing to shared state failed: {}\n({:?})", - message, message - ), - } - } - - /// Tries writing the value without forcing a re-render - #[cfg_attr(debug_assertions, track_caller)] - #[cfg_attr(debug_assertions, inline(never))] - pub fn try_write_silent(&self) -> UseSharedStateResult> { - match self.inner.try_borrow_mut() { - Ok(value) => Ok(RefMut::map(value, |inner| &mut inner.value)), - Err(source) => Err(UseSharedStateError::AlreadyBorrowed { - source, - type_name: std::any::type_name::(), - }), - } - } - - /// Writes the value without forcing a re-render - #[cfg_attr(debug_assertions, track_caller)] - #[cfg_attr(debug_assertions, inline(never))] - pub fn write_silent(&self) -> RefMut<'_, T> { - match self.try_write_silent() { - Ok(value) => value, - Err(message) => panic!( - "Writing to shared state silently failed: {}\n({:?})", - message, message - ), - } - } - - /// Take a reference to the inner value temporarily and produce a new value - #[cfg_attr(debug_assertions, track_caller)] - #[cfg_attr(debug_assertions, inline(never))] - pub fn with(&self, immutable_callback: impl FnOnce(&T) -> O) -> O { - immutable_callback(&*self.read()) - } - - /// Take a mutable reference to the inner value temporarily and produce a new value - #[cfg_attr(debug_assertions, track_caller)] - #[cfg_attr(debug_assertions, inline(never))] - pub fn with_mut(&self, mutable_callback: impl FnOnce(&mut T) -> O) -> O { - mutable_callback(&mut *self.write()) - } -} - -impl Clone for UseSharedState { - fn clone(&self) -> Self { - Self { - inner: self.inner.clone(), - gen: self.gen, - } - } -} - -impl PartialEq for UseSharedState { - fn eq(&self, other: &Self) -> bool { - self.gen == other.gen - } -} - -/// Provide some state for components down the hierarchy to consume without having to drill props. See [`use_shared_state`] to consume the state -/// -/// -/// # Example -/// -/// ```rust -/// # use dioxus::prelude::*; -/// # -/// # fn app() -> Element { -/// # rsx! { -/// # Parent{} -/// # } -/// # } -/// -/// #[derive(Clone, Copy)] -/// enum Theme { -/// Light, -/// Dark, -/// } -/// -/// // Provider -/// fn Parent<'a>(cx: Scope<'a>) -> Element { -/// use_shared_state_provider(|| Theme::Dark); -/// let theme = use_shared_state::().unwrap(); -/// -/// rsx! { -/// button{ -/// onclick: move |_| { -/// let current_theme = *theme.read(); -/// *theme.write() = match current_theme { -/// Theme::Dark => Theme::Light, -/// Theme::Light => Theme::Dark, -/// }; -/// }, -/// "Change theme" -/// } -/// // Children components that consume the state... -/// } -/// } -/// ``` -pub fn use_shared_state_provider(f: impl FnOnce() -> T) { - cx.use_hook(|| { - let state: ProvidedState = Rc::new(RefCell::new(ProvidedStateInner { - value: f(), - notify_any: cx.schedule_update_any(), - consumers: HashSet::new(), - gen: 0, - })); - - cx.provide_context(state); - }); -} diff --git a/packages/signals/src/copy_value.rs b/packages/signals/src/copy_value.rs index 97ee1ffbc..20aafa21e 100644 --- a/packages/signals/src/copy_value.rs +++ b/packages/signals/src/copy_value.rs @@ -5,7 +5,6 @@ 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 dioxus_core::prelude::*; @@ -268,31 +267,6 @@ impl> Deref for CopyValue { type Target = dyn Fn() -> T; 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 = 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 || *Self::read(unsafe { &*uninit_callable.as_ptr() }); - - // 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 &Self::Target + Readable::deref_impl(self) } } diff --git a/packages/signals/src/global/memo.rs b/packages/signals/src/global/memo.rs index 67e9544fc..67578a45e 100644 --- a/packages/signals/src/global/memo.rs +++ b/packages/signals/src/global/memo.rs @@ -1,7 +1,7 @@ use crate::{read::Readable, Memo, ReadableRef}; -use dioxus_core::prelude::{IntoAttributeValue, ScopeId}; +use dioxus_core::prelude::ScopeId; use generational_box::UnsyncStorage; -use std::{mem::MaybeUninit, ops::Deref}; +use std::ops::Deref; use crate::Signal; @@ -68,15 +68,6 @@ impl Readable for GlobalMemo { } } -impl IntoAttributeValue for GlobalMemo -where - T: Clone + IntoAttributeValue, -{ - fn into_value(self) -> dioxus_core::AttributeValue { - self.memo().into_value() - } -} - impl PartialEq for GlobalMemo { fn eq(&self, other: &Self) -> bool { std::ptr::eq(self, other) @@ -90,31 +81,6 @@ impl Deref for GlobalMemo { type Target = dyn Fn() -> T; 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 = 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 || Self::read(unsafe { &*uninit_callable.as_ptr() }).clone(); - - // 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 &Self::Target + Readable::deref_impl(self) } } diff --git a/packages/signals/src/global/signal.rs b/packages/signals/src/global/signal.rs index 7bdab6e0b..45623e1eb 100644 --- a/packages/signals/src/global/signal.rs +++ b/packages/signals/src/global/signal.rs @@ -1,9 +1,9 @@ use crate::write::Writable; use crate::{read::Readable, ReadableRef}; use crate::{WritableRef, Write}; -use dioxus_core::prelude::{IntoAttributeValue, ScopeId}; +use dioxus_core::prelude::ScopeId; use generational_box::UnsyncStorage; -use std::{mem::MaybeUninit, ops::Deref}; +use std::ops::Deref; use super::get_global_context; use crate::Signal; @@ -118,15 +118,6 @@ impl Writable for GlobalSignal { } } -impl IntoAttributeValue for GlobalSignal -where - T: Clone + IntoAttributeValue, -{ - fn into_value(self) -> dioxus_core::AttributeValue { - self.signal().into_value() - } -} - impl PartialEq for GlobalSignal { fn eq(&self, other: &Self) -> bool { std::ptr::eq(self, other) @@ -140,34 +131,6 @@ impl Deref for GlobalSignal { type Target = dyn Fn() -> T; 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 = 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 || { - as Readable>::read(unsafe { &*uninit_callable.as_ptr() }).clone() - }; - - // 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 &Self::Target + Readable::deref_impl(self) } } diff --git a/packages/signals/src/memo.rs b/packages/signals/src/memo.rs index 86d6405e1..786b813fe 100644 --- a/packages/signals/src/memo.rs +++ b/packages/signals/src/memo.rs @@ -204,6 +204,15 @@ where } } +impl IntoDynNode for Memo +where + T: Clone + IntoDynNode + PartialEq, +{ + fn into_dyn_node(self) -> dioxus_core::DynamicNode { + self().into_dyn_node() + } +} + impl PartialEq for Memo { fn eq(&self, other: &Self) -> bool { self.inner == other.inner diff --git a/packages/signals/src/read_only_signal.rs b/packages/signals/src/read_only_signal.rs index 618049eb1..0ebfe37d1 100644 --- a/packages/signals/src/read_only_signal.rs +++ b/packages/signals/src/read_only_signal.rs @@ -1,4 +1,5 @@ use crate::{read::Readable, ReadableRef, Signal, SignalData}; +use dioxus_core::IntoDynNode; use std::ops::Deref; use dioxus_core::{prelude::IntoAttributeValue, ScopeId}; @@ -105,6 +106,15 @@ where } } +impl IntoDynNode for ReadOnlySignal +where + T: Clone + IntoDynNode, +{ + fn into_dyn_node(self) -> dioxus_core::DynamicNode { + self().into_dyn_node() + } +} + impl>> PartialEq for ReadOnlySignal { fn eq(&self, other: &Self) -> bool { self.inner == other.inner diff --git a/packages/signals/src/signal.rs b/packages/signals/src/signal.rs index 1c530af29..b81f30d2e 100644 --- a/packages/signals/src/signal.rs +++ b/packages/signals/src/signal.rs @@ -3,6 +3,7 @@ use crate::{ ReadableRef, }; use crate::{Memo, WritableRef}; +use dioxus_core::IntoDynNode; use dioxus_core::{prelude::IntoAttributeValue, ScopeId}; use generational_box::{AnyStorage, Storage, SyncStorage, UnsyncStorage}; use std::{ @@ -245,6 +246,15 @@ where } } +impl IntoDynNode for Signal +where + T: Clone + IntoDynNode, +{ + fn into_dyn_node(self) -> dioxus_core::DynamicNode { + self().into_dyn_node() + } +} + impl>> PartialEq for Signal { fn eq(&self, other: &Self) -> bool { self.inner == other.inner