use crate::CrdtMerge;
use fxhash::{FxHashMap, FxHashSet};
use serde::{Deserialize, Serialize};
use std::hash::Hash;
use uuid::Uuid;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ORSet<T: Hash + Eq + Clone> {
elements: FxHashMap<T, FxHashSet<Uuid>>,
tombstones: FxHashSet<Uuid>,
}
impl<T: Hash + Eq + Clone> Default for ORSet<T> {
fn default() -> Self {
Self {
elements: FxHashMap::default(),
tombstones: FxHashSet::default(),
}
}
}
impl<T: Hash + Eq + Clone> ORSet<T> {
pub fn new() -> Self {
Self::default()
}
pub fn add(&mut self, element: T) -> Uuid {
let tag = Uuid::new_v4();
self.elements.entry(element).or_default().insert(tag);
tag
}
pub fn remove(&mut self, element: &T) {
if let Some(tags) = self.elements.get(element) {
for tag in tags {
self.tombstones.insert(*tag);
}
}
}
pub fn contains(&self, element: &T) -> bool {
if let Some(tags) = self.elements.get(element) {
tags.iter().any(|tag| !self.tombstones.contains(tag))
} else {
false
}
}
pub fn elements(&self) -> Vec<T> {
self.elements
.iter()
.filter(|(_, tags)| tags.iter().any(|tag| !self.tombstones.contains(tag)))
.map(|(elem, _)| elem.clone())
.collect()
}
pub fn len(&self) -> usize {
self.elements
.values()
.filter(|tags| tags.iter().any(|tag| !self.tombstones.contains(tag)))
.count()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
impl<T: Hash + Eq + Clone> CrdtMerge for ORSet<T> {
fn merge(&mut self, other: &Self) {
for (element, other_tags) in &other.elements {
let tags = self.elements.entry(element.clone()).or_default();
for tag in other_tags {
tags.insert(*tag);
}
}
for tag in &other.tombstones {
self.tombstones.insert(*tag);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add_remove() {
let mut os = ORSet::new();
os.add("apple".to_string());
assert!(os.contains(&"apple".to_string()));
os.remove(&"apple".to_string());
assert!(!os.contains(&"apple".to_string()));
}
#[test]
fn test_add_wins() {
let mut a = ORSet::new();
a.add("apple".to_string());
let mut b = a.clone();
b.remove(&"apple".to_string());
a.add("apple".to_string());
a.merge(&b);
assert!(a.contains(&"apple".to_string()));
}
#[test]
fn test_merge() {
let mut a = ORSet::new();
a.add(1);
a.add(2);
let mut b = ORSet::new();
b.add(2);
b.add(3);
a.merge(&b);
let elements = a.elements();
assert!(elements.contains(&1));
assert!(elements.contains(&2));
assert!(elements.contains(&3));
assert_eq!(elements.len(), 3);
}
}