Skip to content

Commit

Permalink
feat: added more reactive collections
Browse files Browse the repository at this point in the history
There are now nested and non-nested equivalents for both `Vec` and
`HashMap` (along with `RxResult`).
  • Loading branch information
arctic-hen7 committed Dec 31, 2022
1 parent 307c4fe commit 04e1629
Show file tree
Hide file tree
Showing 4 changed files with 331 additions and 1 deletion.
11 changes: 10 additions & 1 deletion packages/perseus/src/state/rx_collections/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
110 changes: 110 additions & 0 deletions packages/perseus/src/state/rx_collections/rx_hash_map.rs
Original file line number Diff line number Diff line change
@@ -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<K, V>(HashMap<K, V>)
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<K, V>(HashMap<K, RcSignal<V>>)
where
K: Clone + Serialize + DeserializeOwned + Eq + Hash,
V: Clone + Serialize + DeserializeOwned + 'static;

// --- Reactivity implementations ---
impl<K, V> MakeRx for RxHashMap<K, V>
where
K: Clone + Serialize + DeserializeOwned + Eq + Hash,
V: Clone + Serialize + DeserializeOwned + 'static,
{
type Rx = RxHashMapRx<K, V>;

fn make_rx(self) -> Self::Rx {
RxHashMapRx(
self.0
.into_iter()
.map(|(k, v)| (k, create_rc_signal(v)))
.collect(),
)
}
}
impl<K, V> MakeUnrx for RxHashMapRx<K, V>
where
K: Clone + Serialize + DeserializeOwned + Eq + Hash,
V: Clone + Serialize + DeserializeOwned + 'static,
{
type Unrx = RxHashMap<K, V>;

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<K, V> Deref for RxHashMap<K, V>
where
K: Clone + Serialize + DeserializeOwned + Eq + Hash,
V: Clone + Serialize + DeserializeOwned + 'static,
{
type Target = HashMap<K, V>;

fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<K, V> Deref for RxHashMapRx<K, V>
where
K: Clone + Serialize + DeserializeOwned + Eq + Hash,
V: Clone + Serialize + DeserializeOwned + 'static,
{
type Target = HashMap<K, RcSignal<V>>;

fn deref(&self) -> &Self::Target {
&self.0
}
}
// --- Conversion implementation ---
impl<K, V> From<HashMap<K, V>> for RxHashMap<K, V>
where
K: Clone + Serialize + DeserializeOwned + Eq + Hash,
V: Clone + Serialize + DeserializeOwned + 'static,
{
fn from(value: HashMap<K, V>) -> Self {
Self(value)
}
}

// --- Freezing implementation ---
impl<K, V> Freeze for RxHashMapRx<K, V>
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()
}
}
116 changes: 116 additions & 0 deletions packages/perseus/src/state/rx_collections/rx_hash_map_nested.rs
Original file line number Diff line number Diff line change
@@ -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<K, V>(HashMap<K, V>)
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<Unrx = V> + Freeze + Clone;
/// The reactive version of [`RxHashMap`].
#[derive(Clone, Debug)]
pub struct RxHashMapNestedRx<K, V>(HashMap<K, V::Rx>)
where
K: Clone + Serialize + DeserializeOwned + Eq + Hash,
V: MakeRx + Serialize + DeserializeOwned + 'static,
V::Rx: MakeUnrx<Unrx = V> + Freeze + Clone;

// --- Reactivity implementations ---
impl<K, V> MakeRx for RxHashMapNested<K, V>
where
K: Clone + Serialize + DeserializeOwned + Eq + Hash,
V: MakeRx + Serialize + DeserializeOwned + 'static,
V::Rx: MakeUnrx<Unrx = V> + Freeze + Clone,
{
type Rx = RxHashMapNestedRx<K, V>;

fn make_rx(self) -> Self::Rx {
RxHashMapNestedRx(self.0.into_iter().map(|(k, v)| (k, v.make_rx())).collect())
}
}
impl<K, V> MakeUnrx for RxHashMapNestedRx<K, V>
where
K: Clone + Serialize + DeserializeOwned + Eq + Hash,
V: MakeRx + Serialize + DeserializeOwned + 'static,
V::Rx: MakeUnrx<Unrx = V> + Freeze + Clone,
{
type Unrx = RxHashMapNested<K, V>;

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<K, V> Deref for RxHashMapNested<K, V>
where
K: Clone + Serialize + DeserializeOwned + Eq + Hash,
V: MakeRx + Serialize + DeserializeOwned + 'static,
V::Rx: MakeUnrx<Unrx = V> + Freeze + Clone,
{
type Target = HashMap<K, V>;

fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<K, V> Deref for RxHashMapNestedRx<K, V>
where
K: Clone + Serialize + DeserializeOwned + Eq + Hash,
V: MakeRx + Serialize + DeserializeOwned + 'static,
V::Rx: MakeUnrx<Unrx = V> + Freeze + Clone,
{
type Target = HashMap<K, V::Rx>;

fn deref(&self) -> &Self::Target {
&self.0
}
}
// --- Conversion implementation ---
impl<K, V> From<HashMap<K, V>> for RxHashMapNested<K, V>
where
K: Clone + Serialize + DeserializeOwned + Eq + Hash,
V: MakeRx + Serialize + DeserializeOwned + 'static,
V::Rx: MakeUnrx<Unrx = V> + Freeze + Clone,
{
fn from(value: HashMap<K, V>) -> Self {
Self(value)
}
}

// --- Freezing implementation ---
impl<K, V> Freeze for RxHashMapNestedRx<K, V>
where
K: Clone + Serialize + DeserializeOwned + Eq + Hash,
V: MakeRx + Serialize + DeserializeOwned + 'static,
V::Rx: MakeUnrx<Unrx = V> + 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()
}
}
95 changes: 95 additions & 0 deletions packages/perseus/src/state/rx_collections/rx_vec.rs
Original file line number Diff line number Diff line change
@@ -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<T>(Vec<T>)
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<T>(Vec<RcSignal<T>>)
where
T: Clone + Serialize + DeserializeOwned + 'static;

// --- Reactivity implementations ---
impl<T> MakeRx for RxVec<T>
where
T: Clone + Serialize + DeserializeOwned + 'static,
{
type Rx = RxVecRx<T>;

fn make_rx(self) -> Self::Rx {
RxVecRx(self.0.into_iter().map(|x| create_rc_signal(x)).collect())
}
}
impl<T> MakeUnrx for RxVecRx<T>
where
T: Clone + Serialize + DeserializeOwned + 'static,
{
type Unrx = RxVec<T>;

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<T> Deref for RxVec<T>
where
T: Clone + Serialize + DeserializeOwned + 'static,
{
type Target = Vec<T>;

fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T> Deref for RxVecRx<T>
where
T: Clone + Serialize + DeserializeOwned + 'static,
{
type Target = Vec<RcSignal<T>>;

fn deref(&self) -> &Self::Target {
&self.0
}
}
// --- Conversion implementation ---
impl<T> From<Vec<T>> for RxVec<T>
where
T: Clone + Serialize + DeserializeOwned + 'static,
{
fn from(value: Vec<T>) -> Self {
Self(value)
}
}

// --- Freezing implementation ---
impl<T> Freeze for RxVecRx<T>
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()
}
}

0 comments on commit 04e1629

Please sign in to comment.