use std::{hash::Hash, marker::PhantomData, sync::Arc};
use dashmap::DashSet;
use uuid::Uuid;
use crate::{
cell::{Cell, CellImmutable, CellMutable, WeakCell},
signal::Signal,
subscription::SubscriptionGuard,
traits::{CellValue, Gettable, Mutable, Watchable},
};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SetDiff<T> {
Insert(T),
Remove(T),
}
struct CellSetInner<T>
where
T: Hash + Eq + CellValue,
{
data: DashSet<T>,
membership_cells: dashmap::DashMap<T, WeakCell<bool, CellMutable>>,
diffs_cell: Cell<Option<SetDiff<T>>, CellMutable>,
len_cell: Cell<usize, CellMutable>,
owned: dashmap::DashMap<Uuid, SubscriptionGuard>,
}
pub struct CellSet<T, M = CellMutable>
where
T: Hash + Eq + CellValue,
{
inner: Arc<CellSetInner<T>>,
_marker: PhantomData<M>,
}
impl<T> CellSet<T, CellMutable>
where
T: Hash + Eq + CellValue,
{
#[track_caller]
pub fn new() -> Self {
Self {
inner: Arc::new(CellSetInner {
data: DashSet::new(),
membership_cells: dashmap::DashMap::new(),
diffs_cell: Cell::new(None),
len_cell: Cell::new(0),
owned: dashmap::DashMap::new(),
}),
_marker: PhantomData,
}
}
pub fn insert(&self, value: T) -> bool {
let is_new = self.inner.data.insert(value.clone());
if is_new {
self.inner
.diffs_cell
.set(Some(SetDiff::Insert(value.clone())));
self.inner.len_cell.set(self.inner.data.len());
if let Some(weak) = self.inner.membership_cells.get(&value)
&& let Some(cell) = weak.upgrade()
{
cell.set(true);
}
}
is_new
}
pub fn remove(&self, value: &T) -> bool {
let was_present = self.inner.data.remove(value).is_some();
if was_present {
self.inner
.diffs_cell
.set(Some(SetDiff::Remove(value.clone())));
self.inner.len_cell.set(self.inner.data.len());
if let Some(weak) = self.inner.membership_cells.get(value)
&& let Some(cell) = weak.upgrade()
{
cell.set(false);
}
}
was_present
}
pub fn lock(self) -> CellSet<T, CellImmutable> {
CellSet {
inner: self.inner,
_marker: PhantomData,
}
}
}
impl<T> Default for CellSet<T, CellMutable>
where
T: Hash + Eq + CellValue,
{
fn default() -> Self {
Self::new()
}
}
impl<T, M> CellSet<T, M>
where
T: Hash + Eq + CellValue,
{
#[track_caller]
pub fn contains(&self, value: &T) -> Cell<bool, CellImmutable> {
if let Some(weak) = self.inner.membership_cells.get(value)
&& let Some(cell) = weak.upgrade()
{
return cell.lock();
}
let is_member = self.inner.data.contains(value);
let cell = Cell::new(is_member);
let weak = cell.downgrade();
self.inner.membership_cells.insert(value.clone(), weak);
cell.lock()
}
#[track_caller]
pub fn values(&self) -> Cell<Vec<T>, CellImmutable> {
let initial: Vec<T> = self.inner.data.iter().map(|r| r.clone()).collect();
let cell = Cell::new(initial);
let cell_clone = cell.clone();
let first = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(true));
let guard = self.inner.diffs_cell.subscribe(move |signal| {
if first.swap(false, std::sync::atomic::Ordering::SeqCst) {
return;
}
if let Signal::Value(arc_opt) = signal
&& let Some(diff) = arc_opt.as_ref()
{
let mut values = cell_clone.get();
match diff {
SetDiff::Insert(value) => {
values.push(value.clone());
}
SetDiff::Remove(value) => {
values.retain(|v| v != value);
}
}
cell_clone.set(values);
}
});
self.inner.owned.insert(Uuid::new_v4(), guard);
cell.lock()
}
pub fn len(&self) -> Cell<usize, CellImmutable> {
self.inner.len_cell.clone().lock()
}
pub fn is_empty(&self) -> bool {
self.inner.data.is_empty()
}
pub fn diffs(&self) -> Cell<Option<SetDiff<T>>, CellImmutable> {
self.inner.diffs_cell.clone().lock()
}
pub fn contains_value(&self, value: &T) -> bool {
self.inner.data.contains(value)
}
}
impl<T, M> Clone for CellSet<T, M>
where
T: Hash + Eq + CellValue,
{
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
_marker: PhantomData,
}
}
}
#[cfg(test)]
mod tests {
use std::sync::atomic::{AtomicUsize, Ordering};
use super::*;
use crate::traits::{Gettable, Watchable};
#[test]
fn test_cellset_basic() {
let set = CellSet::<String>::new();
assert!(set.is_empty());
assert!(!set.contains_value(&"a".to_string()));
assert!(set.insert("a".to_string())); assert!(!set.insert("a".to_string())); assert!(set.contains_value(&"a".to_string()));
assert!(!set.is_empty());
assert!(set.insert("b".to_string()));
assert!(set.contains_value(&"b".to_string()));
assert!(set.remove(&"a".to_string())); assert!(!set.remove(&"a".to_string())); assert!(!set.contains_value(&"a".to_string()));
}
#[test]
fn test_cellset_membership_observation() {
let set = CellSet::<String>::new();
let is_member = set.contains(&"a".to_string());
assert!(!is_member.get());
let count = Arc::new(AtomicUsize::new(0));
let c = count.clone();
let _guard = is_member.subscribe(move |_| {
c.fetch_add(1, Ordering::SeqCst);
});
assert_eq!(count.load(Ordering::SeqCst), 1);
set.insert("a".to_string());
assert!(is_member.get());
assert_eq!(count.load(Ordering::SeqCst), 2);
set.insert("a".to_string());
assert_eq!(count.load(Ordering::SeqCst), 2);
set.remove(&"a".to_string());
assert!(!is_member.get());
assert_eq!(count.load(Ordering::SeqCst), 3);
}
#[test]
fn test_cellset_values_observation() {
let set = CellSet::<String>::new();
let values = set.values();
assert_eq!(values.get(), Vec::<String>::new());
set.insert("a".to_string());
assert_eq!(values.get().len(), 1);
set.insert("b".to_string());
assert_eq!(values.get().len(), 2);
set.remove(&"a".to_string());
assert_eq!(values.get().len(), 1);
}
#[test]
fn test_cellset_diffs() {
let set = CellSet::<String>::new();
let diffs = set.diffs();
assert_eq!(diffs.get(), None);
set.insert("a".to_string());
assert_eq!(diffs.get(), Some(SetDiff::Insert("a".to_string())));
set.remove(&"a".to_string());
assert_eq!(diffs.get(), Some(SetDiff::Remove("a".to_string())));
}
#[test]
fn test_cellset_len() {
let set = CellSet::<String>::new();
let len = set.len();
assert_eq!(len.get(), 0);
set.insert("a".to_string());
assert_eq!(len.get(), 1);
set.insert("b".to_string());
assert_eq!(len.get(), 2);
set.remove(&"a".to_string());
assert_eq!(len.get(), 1);
}
#[test]
fn test_cellset_lock() {
let set = CellSet::<String>::new();
set.insert("a".to_string());
let locked = set.lock();
assert!(locked.contains(&"a".to_string()).get());
assert_eq!(locked.values().get().len(), 1);
}
#[test]
fn test_cellset_same_cell_returned() {
let set = CellSet::<String>::new();
let cell1 = set.contains(&"a".to_string());
let cell2 = set.contains(&"a".to_string());
set.insert("a".to_string());
assert!(cell1.get());
assert!(cell2.get());
}
}