diff --git a/packages/signals/Cargo.toml b/packages/signals/Cargo.toml index 8dc9da3af..e13da5dc0 100644 --- a/packages/signals/Cargo.toml +++ b/packages/signals/Cargo.toml @@ -25,7 +25,7 @@ futures-util = { workspace = true } flume = { version = "0.11.0", default-features = false, features = ["async"] } [dev-dependencies] -dioxus = { workspace = true } +dioxus = { workspace = true, features = ["desktop"]} # dioxus-desktop = { workspace = true } tokio = { version = "1", features = ["full"] } tracing-subscriber = "0.3.17" diff --git a/packages/signals/examples/map_signal.rs b/packages/signals/examples/map_signal.rs new file mode 100644 index 000000000..a48886445 --- /dev/null +++ b/packages/signals/examples/map_signal.rs @@ -0,0 +1,51 @@ +#![allow(non_snake_case)] + +use dioxus::prelude::*; +use dioxus_signals::Signal; + +fn main() { + launch(app); +} + +fn app() -> Element { + let mut vec = use_signal(|| vec![0]); + + rsx! { + button { + onclick: move |_| { + let mut write = vec.write(); + let len = write.len() as i32; + write.push(len); + }, + "Create" + } + + button { + onclick: move |_| { + vec.write().pop(); + }, + "Destroy" + } + + for i in 0..vec.len() { + Child { count: vec.map(move |v| &v[i]) } + } + } +} + +#[component] +fn Child(count: MappedSignal>>) -> Element { + use_memo({ + to_owned![count]; + move || { + let value = count.read(); + print!("Child value: {value}"); + } + }); + + rsx! { + div { + "Child: {count}" + } + } +} diff --git a/packages/signals/src/global/signal.rs b/packages/signals/src/global/signal.rs index 99ef1d5e6..e00a41fde 100644 --- a/packages/signals/src/global/signal.rs +++ b/packages/signals/src/global/signal.rs @@ -2,11 +2,11 @@ use crate::read::Readable; use crate::write::Writable; use crate::Write; use dioxus_core::prelude::{IntoAttributeValue, ScopeId}; -use generational_box::{AnyStorage, GenerationalRef, UnsyncStorage}; -use std::{cell::Ref, mem::MaybeUninit, ops::Deref}; +use generational_box::{AnyStorage, UnsyncStorage}; +use std::{mem::MaybeUninit, ops::Deref}; use super::get_global_context; -use crate::{MappedSignal, Signal}; +use crate::Signal; /// A signal that can be accessed from anywhere in the application and created in a static pub struct GlobalSignal { @@ -60,15 +60,6 @@ impl GlobalSignal { self.signal().with_mut(f) } - /// Map the signal to a new type. - pub fn map( - &self, - f: impl Fn(&T) -> &O + 'static, - ) -> MappedSignal>> { - // MappedSignal::new(self.signal(), f) - todo!() - } - /// Get the generational id of the signal. pub fn id(&self) -> generational_box::GenerationalBoxId { self.signal().id() diff --git a/packages/signals/src/impls.rs b/packages/signals/src/impls.rs index 7f5f2b8f6..0fd843e50 100644 --- a/packages/signals/src/impls.rs +++ b/packages/signals/src/impls.rs @@ -2,7 +2,7 @@ use crate::copy_value::CopyValue; use crate::read::Readable; use crate::signal::Signal; use crate::write::Writable; -use crate::{GlobalMemo, GlobalSignal, ReadOnlySignal, SignalData}; +use crate::{GlobalMemo, GlobalSignal, MappedSignal, ReadOnlySignal, SignalData}; use generational_box::Storage; use std::{ @@ -10,7 +10,7 @@ use std::{ ops::{Add, Div, Mul, Sub}, }; -macro_rules! read_impls { +macro_rules! default_impl { ($ty:ident $(: $extra_bounds:path)? $(, $bound_ty:ident : $bound:path, $vec_bound_ty:ident : $vec_bound:path)?) => { $( impl Default for $ty { @@ -20,7 +20,11 @@ macro_rules! read_impls { } } )? + } +} +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 { @@ -113,6 +117,7 @@ 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 { @@ -124,6 +129,7 @@ impl> Clone for CopyValue { 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 { @@ -139,6 +145,11 @@ read_impls!( S: Storage>, S: Storage>> ); +default_impl!( + ReadOnlySignal, + S: Storage>, + S: Storage>> +); impl>> Clone for ReadOnlySignal { fn clone(&self) -> Self { @@ -149,5 +160,8 @@ impl>> Clone for ReadOnlySignal { impl>> Copy for ReadOnlySignal {} read_impls!(GlobalSignal); +default_impl!(GlobalSignal); read_impls!(GlobalMemo: PartialEq); + +read_impls!(MappedSignal, S: Readable, S: Readable); diff --git a/packages/signals/src/map.rs b/packages/signals/src/map.rs index b23d82ef8..152e9eb17 100644 --- a/packages/signals/src/map.rs +++ b/packages/signals/src/map.rs @@ -1,107 +1,101 @@ +use std::{ops::Deref, rc::Rc}; + use crate::read::Readable; -use crate::CopyValue; -use crate::Signal; -use crate::SignalData; -use dioxus_core::ScopeId; -use generational_box::AnyStorage; -use generational_box::Storage; -use generational_box::UnsyncStorage; -use std::fmt::Debug; -use std::fmt::Display; +use dioxus_core::prelude::*; /// A read only signal that has been mapped to a new type. -pub struct MappedSignal { - mapping: CopyValue S::Ref>>, +pub struct MappedSignal { + readable: R, + mapping: Rc &O + 'static>, } -impl MappedSignal<()> { - /// Create a new mapped signal. - pub fn new(readable: R, mapping: impl Fn(&T) -> &U + 'static) -> MappedSignal - where - S: Storage>, - U: ?Sized, - { - todo!() - // MappedSignal { - // mapping: CopyValue::new(Box::new(move || S::map(signal.read(), &mapping))), - // } +impl Clone for MappedSignal { + fn clone(&self) -> Self { + MappedSignal { + readable: self.readable.clone(), + mapping: self.mapping.clone(), + } } } -// impl MappedSignal { -// /// Get the current value of the signal. This will subscribe the current scope to the signal. -// pub fn read(&self) -> U { -// (self.mapping.read())() -// } +impl MappedSignal +where + O: ?Sized, + R: Readable + 'static, +{ + /// Create a new mapped signal. + pub(crate) fn new(readable: R, mapping: impl Fn(&R::Target) -> &O + 'static) -> Self { + MappedSignal { + readable, + mapping: Rc::new(mapping), + } + } +} -// /// Run a closure with a reference to the signal's value. -// pub fn with(&self, f: impl FnOnce(U) -> O) -> O { -// f(self.read()) -// } -// } +impl Readable for MappedSignal +where + O: ?Sized, + R: Readable, +{ + type Target = O; + type Ref = R::Ref; -// impl MappedSignal { -// /// Get the current value of the signal. This will subscribe the current scope to the signal. -// pub fn value(&self) -> U { -// self.read().clone() -// } -// } + fn map_ref &U>( + ref_: Self::Ref, + f: F, + ) -> Self::Ref { + R::map_ref(ref_, f) + } -// impl PartialEq for MappedSignal { -// fn eq(&self, other: &Self) -> bool { -// self.mapping == other.mapping -// } -// } + fn try_map_ref Option<&U>>( + ref_: Self::Ref, + f: F, + ) -> Option> { + R::try_map_ref(ref_, f) + } -// impl std::clone::Clone for MappedSignal { -// fn clone(&self) -> Self { -// *self -// } -// } + fn try_read(&self) -> Result, generational_box::BorrowError> { + self.readable + .try_read() + .map(|ref_| R::map_ref(ref_, |r| (self.mapping)(r))) + } -// impl Copy for MappedSignal {} + fn peek(&self) -> Self::Ref { + R::map_ref(self.readable.peek(), |r| (self.mapping)(r)) + } +} -// impl Display for MappedSignal { -// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { -// self.with(|v| Display::fmt(&v, f)) -// } -// } +impl IntoAttributeValue for MappedSignal +where + O: Clone + IntoAttributeValue + ?Sized, + R: Readable + 'static, +{ + fn into_value(self) -> dioxus_core::AttributeValue { + self.with(|f| f.clone().into_value()) + } +} -// impl Debug for MappedSignal { -// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { -// self.with(|v| Debug::fmt(&v, f)) -// } -// } +impl PartialEq for MappedSignal +where + O: ?Sized, + R: PartialEq + Readable + 'static, +{ + fn eq(&self, other: &Self) -> bool { + self.readable == other.readable && std::ptr::eq(&self.mapping, &other.mapping) + } +} -// impl std::ops::Deref for MappedSignal { -// type Target = dyn Fn() -> T; +/// Allow calling a signal with signal() syntax +/// +/// Currently only limited to copy types, though could probably specialize for string/arc/rc +impl Deref for MappedSignal +where + O: Clone, + R: Readable + 'static, +{ + type Target = dyn Fn() -> O; -// 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 || 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 -// } -// } + fn deref(&self) -> &Self::Target { + Readable::deref_impl(self) + } +} diff --git a/packages/signals/src/read.rs b/packages/signals/src/read.rs index c54095769..12a4bdfa8 100644 --- a/packages/signals/src/read.rs +++ b/packages/signals/src/read.rs @@ -3,13 +3,15 @@ use std::{ ops::{Deref, Index}, }; +use crate::MappedSignal; + /// A trait for states that can be read from like [`crate::Signal`], [`crate::GlobalSignal`], or [`crate::ReadOnlySignal`]. You may choose to accept this trait as a parameter instead of the concrete type to allow for more flexibility in your API. For example, instead of creating two functions, one that accepts a [`crate::Signal`] and one that accepts a [`crate::GlobalSignal`], you can create one function that accepts a [`Readable`] type. pub trait Readable { /// The target type of the reference. type Target: ?Sized + 'static; /// The type of the reference. - type Ref: Deref; + type Ref: Deref + 'static; /// Map the reference to a new type. fn map_ref &U>(ref_: Self::Ref, f: F) @@ -24,6 +26,14 @@ pub trait Readable { /// Try to get the current value of the state. If this is a signal, this will subscribe the current scope to the signal. If the value has been dropped, this will panic. fn try_read(&self) -> Result, generational_box::BorrowError>; + /// Map the readable type to a new type. + fn map(self, f: impl Fn(&Self::Target) -> &O + 'static) -> MappedSignal + where + Self: Sized + 'static, + { + MappedSignal::new(self, f) + } + /// Get the current value of the state. If this is a signal, this will subscribe the current scope to the signal. If the value has been dropped, this will panic. #[track_caller] fn read(&self) -> Self::Ref { @@ -132,26 +142,24 @@ pub trait ReadableVecExt: Readable> { /// Get an iterator over the values of the inner vector. #[track_caller] - fn iter(&self) -> ReadableValueIterator<'_, T, Self> + fn iter(&self) -> ReadableValueIterator<'_, Self> where Self: Sized, { ReadableValueIterator { index: 0, value: self, - phantom: std::marker::PhantomData, } } } /// An iterator over the values of a `Readable>`. -pub struct ReadableValueIterator<'a, T, R> { +pub struct ReadableValueIterator<'a, R> { index: usize, value: &'a R, - phantom: std::marker::PhantomData, } -impl<'a, T: 'static, R: Readable>> Iterator for ReadableValueIterator<'a, T, R> { +impl<'a, T: 'static, R: Readable>> Iterator for ReadableValueIterator<'a, R> { type Item = R::Ref; fn next(&mut self) -> Option { diff --git a/packages/signals/src/signal.rs b/packages/signals/src/signal.rs index 36ebefa91..14233307c 100644 --- a/packages/signals/src/signal.rs +++ b/packages/signals/src/signal.rs @@ -1,6 +1,6 @@ use crate::{ - read::Readable, write::Writable, CopyValue, GlobalMemo, GlobalSignal, MappedSignal, - ReactiveContext, ReadOnlySignal, + read::Readable, write::Writable, CopyValue, GlobalMemo, GlobalSignal, ReactiveContext, + ReadOnlySignal, }; use dioxus_core::{ prelude::{flush_sync, spawn, IntoAttributeValue}, @@ -187,12 +187,6 @@ impl>> Signal { } } - /// Map the signal to a new type. - pub fn map(self, f: impl Fn(&T) -> &O + 'static) -> MappedSignal> { - // MappedSignal::new(self, f) - todo!() - } - /// Get the generational id of the signal. pub fn id(&self) -> generational_box::GenerationalBoxId { self.inner.id() diff --git a/packages/signals/src/write.rs b/packages/signals/src/write.rs index 8866eac96..4cba48c62 100644 --- a/packages/signals/src/write.rs +++ b/packages/signals/src/write.rs @@ -6,7 +6,7 @@ use crate::read::Readable; /// A trait for states that can be read from like [`crate::Signal`], or [`crate::GlobalSignal`]. You may choose to accept this trait as a parameter instead of the concrete type to allow for more flexibility in your API. For example, instead of creating two functions, one that accepts a [`crate::Signal`] and one that accepts a [`crate::GlobalSignal`], you can create one function that accepts a [`Writable`] type. pub trait Writable: Readable { /// The type of the reference. - type Mut: DerefMut; + type Mut: DerefMut + 'static; /// Map the reference to a new type. fn map_mut &mut U>( @@ -68,7 +68,7 @@ pub trait Writable: Readable { where Self::Target: Default, { - self.with_mut(|v| std::mem::take(v)) + self.with_mut(std::mem::take) } /// Replace the value in the Signal, returning the old value. @@ -184,26 +184,24 @@ pub trait WritableVecExt: Writable> { /// Gets an iterator over the values of the vector. #[track_caller] - fn iter_mut(&self) -> WritableValueIterator + fn iter_mut(&self) -> WritableValueIterator where Self: Sized + Clone, { WritableValueIterator { index: 0, value: self.clone(), - phantom: std::marker::PhantomData, } } } /// An iterator over the values of a `Writable>`. -pub struct WritableValueIterator { +pub struct WritableValueIterator { index: usize, value: R, - phantom: std::marker::PhantomData, } -impl>> Iterator for WritableValueIterator { +impl>> Iterator for WritableValueIterator { type Item = R::Mut; fn next(&mut self) -> Option {