diff --git a/packages/perseus/src/state/rx_collections/mod.rs b/packages/perseus/src/state/rx_collections/mod.rs index 688ecff19b..96ccad4ec3 100644 --- a/packages/perseus/src/state/rx_collections/mod.rs +++ b/packages/perseus/src/state/rx_collections/mod.rs @@ -78,8 +78,17 @@ //! although there is presently no defined reactive container for this.* //! //! **Note:** as a user, you will still have to use `#[rx(nested)]` over any -//! reactive types you use! +//! reactive types you use, even those that are not nested! This is because, +//! without this attribute, the `ReactiveState` derive macro will just wrap +//! the whole field in an `RcSignal` and call it a day, rather than using the +//! fine-grained reactivity enabled by these types. +mod rx_hash_map; +mod rx_hash_map_nested; +mod rx_vec; mod rx_vec_nested; +pub use rx_hash_map::{RxHashMap, RxHashMapRx}; +pub use rx_hash_map_nested::{RxHashMapNested, RxHashMapNestedRx}; +pub use rx_vec::{RxVec, RxVecRx}; pub use rx_vec_nested::{RxVecNested, RxVecNestedRx}; diff --git a/packages/perseus/src/state/rx_collections/rx_hash_map.rs b/packages/perseus/src/state/rx_collections/rx_hash_map.rs new file mode 100644 index 0000000000..604ce053b7 --- /dev/null +++ b/packages/perseus/src/state/rx_collections/rx_hash_map.rs @@ -0,0 +1,110 @@ +use crate::state::{Freeze, MakeRx, MakeUnrx}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use std::collections::HashMap; +use std::hash::Hash; +use std::ops::Deref; +#[cfg(target_arch = "wasm32")] +use sycamore::prelude::Scope; +use sycamore::reactive::{create_rc_signal, RcSignal}; + +/// A reactive version of [`Vec`] that uses nested reactivity on its elements. +/// This requires nothing by `Clone + 'static` of the elements inside the map, +/// and it wraps them in `RcSignal`s to make them reactive. If you want to store +/// nested reactive types inside the map (e.g. `String`s), you should +/// use [`RxVecNested`]. +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct RxHashMap(HashMap) +where + K: Clone + Eq + Hash, + // We get the `Deserialize` derive macro working by tricking Serde by not + // including the actual bounds here + V: Clone + 'static; +/// The reactive version of [`RxHashMap`]. +#[derive(Clone, Debug)] +pub struct RxHashMapRx(HashMap>) +where + K: Clone + Serialize + DeserializeOwned + Eq + Hash, + V: Clone + Serialize + DeserializeOwned + 'static; + +// --- Reactivity implementations --- +impl MakeRx for RxHashMap +where + K: Clone + Serialize + DeserializeOwned + Eq + Hash, + V: Clone + Serialize + DeserializeOwned + 'static, +{ + type Rx = RxHashMapRx; + + fn make_rx(self) -> Self::Rx { + RxHashMapRx( + self.0 + .into_iter() + .map(|(k, v)| (k, create_rc_signal(v))) + .collect(), + ) + } +} +impl MakeUnrx for RxHashMapRx +where + K: Clone + Serialize + DeserializeOwned + Eq + Hash, + V: Clone + Serialize + DeserializeOwned + 'static, +{ + type Unrx = RxHashMap; + + fn make_unrx(self) -> Self::Unrx { + RxHashMap( + self.0 + .into_iter() + .map(|(k, v)| (k, (*v.get_untracked()).clone())) + .collect(), + ) + } + + #[cfg(target_arch = "wasm32")] + fn compute_suspense(&self, cx: Scope) {} +} +// --- Dereferencing --- +impl Deref for RxHashMap +where + K: Clone + Serialize + DeserializeOwned + Eq + Hash, + V: Clone + Serialize + DeserializeOwned + 'static, +{ + type Target = HashMap; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} +impl Deref for RxHashMapRx +where + K: Clone + Serialize + DeserializeOwned + Eq + Hash, + V: Clone + Serialize + DeserializeOwned + 'static, +{ + type Target = HashMap>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} +// --- Conversion implementation --- +impl From> for RxHashMap +where + K: Clone + Serialize + DeserializeOwned + Eq + Hash, + V: Clone + Serialize + DeserializeOwned + 'static, +{ + fn from(value: HashMap) -> Self { + Self(value) + } +} + +// --- Freezing implementation --- +impl Freeze for RxHashMapRx +where + K: Clone + Serialize + DeserializeOwned + Eq + Hash, + V: Clone + Serialize + DeserializeOwned + 'static, +{ + fn freeze(&self) -> String { + let unrx = Self(self.0.clone()).make_unrx(); + // This should never panic, because we're dealing with a vector + serde_json::to_string(&unrx).unwrap() + } +} diff --git a/packages/perseus/src/state/rx_collections/rx_hash_map_nested.rs b/packages/perseus/src/state/rx_collections/rx_hash_map_nested.rs new file mode 100644 index 0000000000..8b1e5065a6 --- /dev/null +++ b/packages/perseus/src/state/rx_collections/rx_hash_map_nested.rs @@ -0,0 +1,116 @@ +use crate::state::{Freeze, MakeRx, MakeUnrx}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use std::collections::HashMap; +use std::hash::Hash; +use std::ops::Deref; +#[cfg(target_arch = "wasm32")] +use sycamore::prelude::Scope; + +/// A reactive version of [`Vec`] that uses nested reactivity on its elements. +/// That means the type inside the vector must implement [`MakeRx`] (usually +/// derived with the `ReactiveState` macro). If you want to store simple types +/// inside the vector, without nested reactivity (e.g. `String`s), you should +/// use [`RxVec`]. +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct RxHashMapNested(HashMap) +where + K: Clone + Eq + Hash, + // We get the `Deserialize` derive macro working by tricking Serde by not + // including the actual bounds here + V: MakeRx + 'static, + V::Rx: MakeUnrx + Freeze + Clone; +/// The reactive version of [`RxHashMap`]. +#[derive(Clone, Debug)] +pub struct RxHashMapNestedRx(HashMap) +where + K: Clone + Serialize + DeserializeOwned + Eq + Hash, + V: MakeRx + Serialize + DeserializeOwned + 'static, + V::Rx: MakeUnrx + Freeze + Clone; + +// --- Reactivity implementations --- +impl MakeRx for RxHashMapNested +where + K: Clone + Serialize + DeserializeOwned + Eq + Hash, + V: MakeRx + Serialize + DeserializeOwned + 'static, + V::Rx: MakeUnrx + Freeze + Clone, +{ + type Rx = RxHashMapNestedRx; + + fn make_rx(self) -> Self::Rx { + RxHashMapNestedRx(self.0.into_iter().map(|(k, v)| (k, v.make_rx())).collect()) + } +} +impl MakeUnrx for RxHashMapNestedRx +where + K: Clone + Serialize + DeserializeOwned + Eq + Hash, + V: MakeRx + Serialize + DeserializeOwned + 'static, + V::Rx: MakeUnrx + Freeze + Clone, +{ + type Unrx = RxHashMapNested; + + fn make_unrx(self) -> Self::Unrx { + RxHashMapNested( + self.0 + .into_iter() + .map(|(k, v)| (k, v.make_unrx())) + .collect(), + ) + } + + #[cfg(target_arch = "wasm32")] + fn compute_suspense(&self, cx: Scope) { + for elem in self.0.values() { + elem.compute_suspense(cx); + } + } +} +// --- Dereferencing --- +impl Deref for RxHashMapNested +where + K: Clone + Serialize + DeserializeOwned + Eq + Hash, + V: MakeRx + Serialize + DeserializeOwned + 'static, + V::Rx: MakeUnrx + Freeze + Clone, +{ + type Target = HashMap; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} +impl Deref for RxHashMapNestedRx +where + K: Clone + Serialize + DeserializeOwned + Eq + Hash, + V: MakeRx + Serialize + DeserializeOwned + 'static, + V::Rx: MakeUnrx + Freeze + Clone, +{ + type Target = HashMap; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} +// --- Conversion implementation --- +impl From> for RxHashMapNested +where + K: Clone + Serialize + DeserializeOwned + Eq + Hash, + V: MakeRx + Serialize + DeserializeOwned + 'static, + V::Rx: MakeUnrx + Freeze + Clone, +{ + fn from(value: HashMap) -> Self { + Self(value) + } +} + +// --- Freezing implementation --- +impl Freeze for RxHashMapNestedRx +where + K: Clone + Serialize + DeserializeOwned + Eq + Hash, + V: MakeRx + Serialize + DeserializeOwned + 'static, + V::Rx: MakeUnrx + Freeze + Clone, +{ + fn freeze(&self) -> String { + let unrx = Self(self.0.clone()).make_unrx(); + // This should never panic, because we're dealing with a vector + serde_json::to_string(&unrx).unwrap() + } +} diff --git a/packages/perseus/src/state/rx_collections/rx_vec.rs b/packages/perseus/src/state/rx_collections/rx_vec.rs new file mode 100644 index 0000000000..953f5169a1 --- /dev/null +++ b/packages/perseus/src/state/rx_collections/rx_vec.rs @@ -0,0 +1,95 @@ +use crate::state::{Freeze, MakeRx, MakeUnrx}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use std::ops::Deref; +#[cfg(target_arch = "wasm32")] +use sycamore::prelude::Scope; +use sycamore::reactive::{create_rc_signal, RcSignal}; + +/// A reactive version of [`Vec`] that uses nested reactivity on its elements. +/// This requires nothing by `Clone + 'static` of the elements inside the +/// vector, and it wraps them in `RcSignal`s to make them reactive. If you want +/// to store nested reactive types inside the vector (e.g. `String`s), you +/// should use [`RxVecNested`]. +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct RxVec(Vec) +where + // We get the `Deserialize` derive macro working by tricking Serde by not + // including the actual bounds here + T: Clone + 'static; +/// The reactive version of [`RxVec`]. +#[derive(Clone, Debug)] +pub struct RxVecRx(Vec>) +where + T: Clone + Serialize + DeserializeOwned + 'static; + +// --- Reactivity implementations --- +impl MakeRx for RxVec +where + T: Clone + Serialize + DeserializeOwned + 'static, +{ + type Rx = RxVecRx; + + fn make_rx(self) -> Self::Rx { + RxVecRx(self.0.into_iter().map(|x| create_rc_signal(x)).collect()) + } +} +impl MakeUnrx for RxVecRx +where + T: Clone + Serialize + DeserializeOwned + 'static, +{ + type Unrx = RxVec; + + fn make_unrx(self) -> Self::Unrx { + RxVec( + self.0 + .into_iter() + .map(|x| (*x.get_untracked()).clone()) + .collect(), + ) + } + + #[cfg(target_arch = "wasm32")] + fn compute_suspense(&self, cx: Scope) {} +} +// --- Dereferencing --- +impl Deref for RxVec +where + T: Clone + Serialize + DeserializeOwned + 'static, +{ + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} +impl Deref for RxVecRx +where + T: Clone + Serialize + DeserializeOwned + 'static, +{ + type Target = Vec>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} +// --- Conversion implementation --- +impl From> for RxVec +where + T: Clone + Serialize + DeserializeOwned + 'static, +{ + fn from(value: Vec) -> Self { + Self(value) + } +} + +// --- Freezing implementation --- +impl Freeze for RxVecRx +where + T: Clone + Serialize + DeserializeOwned + 'static, +{ + fn freeze(&self) -> String { + let unrx = Self(self.0.clone()).make_unrx(); + // This should never panic, because we're dealing with a vector + serde_json::to_string(&unrx).unwrap() + } +}