use super::causal::{Dot, DotContext, VClock};
use super::delta::DeltaCrdt;
use super::replica::{generate_replica_id, ReplicaId};
use super::Merge;
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
use std::hash::Hash;
use std::marker::PhantomData;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(bound = "T: Serialize + serde::de::DeserializeOwned + Hash + Eq")]
pub struct ORSetDelta<T> {
pub entries: HashMap<T, HashSet<Dot>>,
pub context: DotContext,
}
pub trait SetBias: Default + Clone + Send + 'static {
fn resolve(
local_has_dots: bool,
remote_has_dots: bool,
local_removed: bool,
remote_removed: bool,
) -> bool;
}
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
pub struct AddWins;
impl SetBias for AddWins {
fn resolve(
local_has_dots: bool,
remote_has_dots: bool,
_local_removed: bool,
_remote_removed: bool,
) -> bool {
local_has_dots || remote_has_dots
}
}
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
pub struct RemoveWins;
impl SetBias for RemoveWins {
fn resolve(
local_has_dots: bool,
remote_has_dots: bool,
local_removed: bool,
remote_removed: bool,
) -> bool {
if local_removed || remote_removed {
false
} else {
local_has_dots || remote_has_dots
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(bound = "T: Serialize + serde::de::DeserializeOwned + Hash + Eq")]
pub struct ORSet<T, B: SetBias = AddWins> {
entries: HashMap<T, HashSet<Dot>>,
context: DotContext,
replica_id: ReplicaId,
#[serde(skip)]
_bias: PhantomData<B>,
}
impl<T: Hash + Eq + Clone, B: SetBias> ORSet<T, B> {
pub fn new(replica_id: ReplicaId) -> Self {
Self {
entries: HashMap::new(),
context: DotContext::new(),
replica_id,
_bias: PhantomData,
}
}
pub fn new_random() -> Self {
Self::new(generate_replica_id())
}
pub fn add(&mut self, value: T) {
let dot = self.context.next(self.replica_id);
self.entries.entry(value).or_default().insert(dot);
}
pub fn insert(&mut self, value: T) {
self.add(value);
}
pub fn remove(&mut self, value: &T) {
self.entries.remove(value);
}
pub fn contains(&self, value: &T) -> bool {
self.entries
.get(value)
.map_or(false, |dots| !dots.is_empty())
}
pub fn len(&self) -> usize {
self.entries
.values()
.filter(|dots| !dots.is_empty())
.count()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn iter(&self) -> impl Iterator<Item = &T> {
self.entries
.iter()
.filter(|(_, dots)| !dots.is_empty())
.map(|(v, _)| v)
}
pub fn replica_id(&self) -> ReplicaId {
self.replica_id
}
}
impl<T: Hash + Eq, B: SetBias> PartialEq for ORSet<T, B> {
fn eq(&self, other: &Self) -> bool {
self.entries == other.entries && self.context == other.context
}
}
impl<T: Hash + Eq + Clone, B: SetBias> Merge for ORSet<T, B> {
fn merge(&mut self, other: &Self) {
let all_keys: HashSet<T> = self
.entries
.keys()
.chain(other.entries.keys())
.cloned()
.collect();
for value in all_keys {
let my_dots_before: HashSet<Dot> = self
.entries
.get(&value)
.cloned()
.unwrap_or_default();
let other_dots: HashSet<Dot> = other
.entries
.get(&value)
.cloned()
.unwrap_or_default();
let my_removed = my_dots_before.is_empty()
&& other_dots.iter().any(|dot| self.context.has_seen(dot));
let other_removed = other_dots.is_empty()
&& my_dots_before.iter().any(|dot| other.context.has_seen(dot));
let mut combined_dots: HashSet<Dot> = HashSet::new();
for dot in &my_dots_before {
if !other.context.has_seen(dot) || other_dots.contains(dot) {
combined_dots.insert(*dot);
}
}
for dot in &other_dots {
if !self.context.has_seen(dot) || my_dots_before.contains(dot) {
combined_dots.insert(*dot);
}
}
let my_has_dots = !my_dots_before.is_empty();
let other_has_dots = !other_dots.is_empty();
let keep = B::resolve(my_has_dots, other_has_dots, my_removed, other_removed);
let my_dots = self.entries.entry(value).or_default();
if keep {
*my_dots = combined_dots;
} else {
my_dots.clear();
}
}
self.context.merge(&other.context);
self.entries.retain(|_, dots| !dots.is_empty());
}
}
impl<T: Hash + Eq + Clone + Serialize + DeserializeOwned + Send + 'static, B: SetBias> DeltaCrdt
for ORSet<T, B>
{
type Delta = ORSetDelta<T>;
fn delta_since(&self, since: &VClock) -> Option<Self::Delta> {
let current = self.version();
if since.dominates(¤t) {
return None;
}
let mut entries: HashMap<T, HashSet<Dot>> = HashMap::new();
let mut context = DotContext::new();
for (value, dots) in &self.entries {
let fresh: HashSet<Dot> =
dots.iter().filter(|d| d.counter > since.get(d.replica)).copied().collect();
if !fresh.is_empty() {
for d in &fresh {
context.add(*d);
}
entries.insert(value.clone(), fresh);
}
}
Some(ORSetDelta { entries, context })
}
fn apply_delta(&mut self, delta: &Self::Delta) {
let temp: ORSet<T, B> = ORSet {
entries: delta.entries.clone(),
context: delta.context.clone(),
replica_id: 0, _bias: PhantomData,
};
self.merge(&temp);
}
fn version(&self) -> VClock {
self.context.version()
}
}
impl<T: Hash + Eq + Clone, B: SetBias> Default for ORSet<T, B> {
fn default() -> Self {
Self::new_random()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_orset_add_contains() {
let mut set: ORSet<String> = ORSet::new(1);
set.add("alice".to_string());
assert!(set.contains(&"alice".to_string()));
assert!(!set.contains(&"bob".to_string()));
}
#[test]
fn test_orset_remove() {
let mut set: ORSet<String> = ORSet::new(1);
set.add("alice".to_string());
set.remove(&"alice".to_string());
assert!(!set.contains(&"alice".to_string()));
}
#[test]
fn test_orset_add_wins() {
let mut a: ORSet<String> = ORSet::new(1);
let mut b: ORSet<String> = ORSet::new(2);
a.add("item".to_string());
b.merge(&a);
a.remove(&"item".to_string());
b.add("item".to_string());
a.merge(&b);
assert!(a.contains(&"item".to_string()));
}
#[test]
fn test_orset_merge_commutative() {
let mut a: ORSet<String> = ORSet::new(1);
let mut b: ORSet<String> = ORSet::new(2);
a.add("x".to_string());
b.add("y".to_string());
let mut a1 = a.clone();
let mut b1 = b.clone();
a1.merge(&b);
b1.merge(&a);
assert_eq!(a1.len(), b1.len());
}
}