use crate::{
effect::RenderEffect,
signal::ArcRwSignal,
traits::{Track, Update},
};
use or_poisoned::OrPoisoned;
use rustc_hash::FxHashMap;
use std::{
hash::Hash,
sync::{Arc, RwLock},
};
#[derive(Clone)]
pub struct Selector<T>
where
T: PartialEq + Eq + Clone + Hash + 'static,
{
subs: Arc<RwLock<FxHashMap<T, ArcRwSignal<bool>>>>,
v: Arc<RwLock<Option<T>>>,
#[allow(clippy::type_complexity)]
f: Arc<dyn Fn(&T, &T) -> bool + Send + Sync>,
#[allow(dead_code)]
effect: Arc<RenderEffect<T>>,
}
impl<T> Selector<T>
where
T: PartialEq + Send + Sync + Eq + Clone + Hash + 'static,
{
pub fn new(source: impl Fn() -> T + Send + Sync + Clone + 'static) -> Self {
Self::new_with_fn(source, PartialEq::eq)
}
pub fn new_with_fn(
source: impl Fn() -> T + Clone + Send + Sync + 'static,
f: impl Fn(&T, &T) -> bool + Send + Sync + Clone + 'static,
) -> Self {
let subs: Arc<RwLock<FxHashMap<T, ArcRwSignal<bool>>>> =
Default::default();
let v: Arc<RwLock<Option<T>>> = Default::default();
let f = Arc::new(f) as Arc<dyn Fn(&T, &T) -> bool + Send + Sync>;
let effect = Arc::new(RenderEffect::new_isomorphic({
let subs = Arc::clone(&subs);
let f = Arc::clone(&f);
let v = Arc::clone(&v);
move |prev: Option<T>| {
let next_value = source();
*v.write().or_poisoned() = Some(next_value.clone());
if prev.as_ref() != Some(&next_value) {
for (key, signal) in &*subs.read().or_poisoned() {
if f(key, &next_value)
|| (prev.is_some()
&& f(key, prev.as_ref().unwrap()))
{
signal.update(|n| *n = true);
}
}
}
next_value
}
}));
Selector { subs, v, f, effect }
}
pub fn selected(&self, key: &T) -> bool {
let read = {
let sub = self.subs.read().or_poisoned().get(key).cloned();
sub.unwrap_or_else(|| {
self.subs
.write()
.or_poisoned()
.entry(key.clone())
.or_insert_with(|| ArcRwSignal::new(false))
.clone()
})
};
read.track();
(self.f)(key, self.v.read().or_poisoned().as_ref().unwrap())
}
pub fn remove(&self, key: &T) {
let mut subs = self.subs.write().or_poisoned();
subs.remove(key);
}
pub fn clear(&self) {
let mut subs = self.subs.write().or_poisoned();
subs.clear();
}
}