use super::{
Either, NoExtensionTypes, TypeVariantValue, Value, ValueRef,
mvreg::MvRegValue,
snapshot::{self, AllValues, CollapsedValue, SingleValueError, SingleValueIssue, ToValue},
};
use crate::{
CausalContext, CausalDotStore, DETERMINISTIC_HASHER, Dot, DotFun, DotFunMap, DotMap,
DotStoreJoin, ExtensionType, Identifier, MvReg, OrMap,
dotstores::{DotChange, DotStore, DryJoinOutput},
sentinel::{DummySentinel, KeySentinel, Sentinel, TypeSentinel, ValueSentinel, Visit},
};
pub use position::Position;
use std::{convert::Infallible, fmt};
pub(super) mod position;
#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(::serde::Deserialize, ::serde::Serialize))]
pub struct Uid(Dot);
impl From<Dot> for Uid {
fn from(value: Dot) -> Self {
Self(value)
}
}
impl fmt::Debug for Uid {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("Uid")
.field(&self.0.actor())
.field(&self.0.sequence())
.finish()
}
}
impl Uid {
#[doc(hidden)]
pub fn dot(&self) -> Dot {
self.0
}
}
#[derive(Clone, Default, PartialEq)]
#[cfg_attr(feature = "serde", derive(::serde::Deserialize, ::serde::Serialize))]
pub struct OrArray<C = NoExtensionTypes>(pub(super) DotMap<Uid, PairMap<C>>);
impl<C> fmt::Debug for OrArray<C>
where
C: fmt::Debug + ExtensionType,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "[]{:?}", self.0)
}
}
impl<C> DotStore for OrArray<C>
where
C: ExtensionType,
{
fn dots(&self) -> CausalContext {
self.0.dots()
}
fn add_dots_to(&self, other: &mut CausalContext) {
self.0.add_dots_to(other);
}
fn is_bottom(&self) -> bool {
self.0.is_bottom()
}
fn subset_for_inflation_from(&self, frontier: &CausalContext) -> Self {
Self(DotMap::subset_for_inflation_from(&self.0, frontier))
}
}
impl<C, S> DotStoreJoin<S> for OrArray<C>
where
C: ExtensionType + DotStoreJoin<S> + Default + fmt::Debug + Clone + PartialEq,
S: Visit<Uid>
+ Visit<String>
+ KeySentinel
+ TypeSentinel<C::ValueKind>
+ ValueSentinel<MvRegValue>,
{
fn join(
(m1, cc1): (Self, &CausalContext),
(m2, cc2): (Self, &CausalContext),
on_dot_change: &mut dyn FnMut(DotChange),
sentinel: &mut S,
) -> Result<Self, S::Error>
where
Self: Sized,
S: Sentinel,
{
Ok(Self(DotMap::join(
(m1.0, cc1),
(m2.0, cc2),
on_dot_change,
sentinel,
)?))
}
fn dry_join(
(m1, cc1): (&Self, &CausalContext),
(m2, cc2): (&Self, &CausalContext),
sentinel: &mut S,
) -> Result<DryJoinOutput, S::Error>
where
Self: Sized,
S: Sentinel,
{
DotMap::dry_join((&m1.0, cc1), (&m2.0, cc2), sentinel)
}
}
#[derive(Clone, Default, PartialEq)]
#[cfg_attr(feature = "serde", derive(::serde::Deserialize, ::serde::Serialize))]
pub(super) struct PairMap<Custom> {
#[doc(alias = "first")]
pub(super) value: TypeVariantValue<Custom>,
#[doc(alias = "second")]
pub(super) positions: DotFunMap<DotFun<Position>>,
}
impl<C> fmt::Debug for PairMap<C>
where
C: ExtensionType + fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("")
.field(&format_args!("{:?}", self.value))
.field(&format_args!("pos={:?}", self.positions))
.finish()
}
}
impl<C> DotStore for PairMap<C>
where
C: ExtensionType,
{
fn add_dots_to(&self, other: &mut CausalContext) {
self.value.add_dots_to(other);
self.positions.add_dots_to(other);
}
fn is_bottom(&self) -> bool {
self.value.is_bottom() && self.positions.is_bottom()
}
fn subset_for_inflation_from(&self, frontier: &CausalContext) -> Self {
Self {
value: self.value.subset_for_inflation_from(frontier),
positions: self.positions.subset_for_inflation_from(frontier),
}
}
}
impl<C, S> DotStoreJoin<S> for PairMap<C>
where
C: ExtensionType + DotStoreJoin<S> + fmt::Debug + Clone + PartialEq,
S: Visit<String>
+ Visit<Uid>
+ KeySentinel
+ TypeSentinel<C::ValueKind>
+ ValueSentinel<MvRegValue>,
{
fn join(
ds1: (Self, &CausalContext),
ds2: (Self, &CausalContext),
on_dot_change: &mut dyn FnMut(DotChange),
sentinel: &mut S,
) -> Result<Self, S::Error>
where
Self: Sized,
S: Sentinel,
{
let (m1, cc1) = ds1;
let (m2, cc2) = ds2;
let value = DotStoreJoin::join((m1.value, cc1), (m2.value, cc2), on_dot_change, sentinel)?;
let positions = DotStoreJoin::join(
(m1.positions, cc1),
(m2.positions, cc2),
on_dot_change,
&mut DummySentinel,
)
.expect("DummySentinel is Infallible");
Ok(PairMap { value, positions })
}
fn dry_join(
ds1: (&Self, &CausalContext),
ds2: (&Self, &CausalContext),
sentinel: &mut S,
) -> Result<DryJoinOutput, S::Error>
where
Self: Sized,
S: Sentinel,
{
let (m1, cc1) = ds1;
let (m2, cc2) = ds2;
let value = DotStoreJoin::dry_join((&m1.value, cc1), (&m2.value, cc2), sentinel)?;
let positions = DotStoreJoin::dry_join(
(&m1.positions, cc1),
(&m2.positions, cc2),
&mut DummySentinel,
)
.expect("DummySentinel is Infallible");
Ok(positions.union(value))
}
}
impl<C> OrArray<C> {
pub fn from_entries<I>(iter: I) -> Self
where
I: IntoIterator<Item = (Uid, TypeVariantValue<C>, DotFunMap<DotFun<Position>>)>,
{
let iter = iter
.into_iter()
.map(|(uid, value, positions)| (uid, PairMap { value, positions }));
Self(DotMap::from_iter(iter))
}
#[doc(hidden)]
pub fn insert_raw(
&mut self,
uid: Uid,
pos: impl Iterator<Item = (Dot, Dot, f64)>,
value: TypeVariantValue<C>,
) {
let mut dotfunmap = DotFunMap::<DotFun<Position>>::default();
for (dot1, dot2, ordering_f64) in pos {
let mut dotfun = DotFun::<Position>::default();
dotfun.set(dot2, Position(ordering_f64));
dotfunmap.set(dot1, dotfun);
}
self.0.insert(
uid,
PairMap {
value,
positions: dotfunmap,
},
)
}
pub fn iter_as_is(&self) -> impl Iterator<Item = (&TypeVariantValue<C>, Uid, Position)> {
self.0.iter().map(|(uid, pair)| {
let inner_map = &pair.value;
let positions = &pair.positions;
let p = if let Some(max_root) = positions.keys().max() {
let at_max_root = positions
.get(&max_root)
.expect("this is the max key from just above, so must exist");
let max_dot = at_max_root
.keys()
.max()
.expect("every position set has at least one position (from its creator)");
at_max_root
.get(&max_dot)
.expect("this is the max key from just above, so must exist")
} else {
const MASK: u64 = u64::MAX >> (u64::BITS - f64::MANTISSA_DIGITS + 1);
const SCALE_FACTOR: f64 = Position::UPPER / MASK as f64;
let value = (DETERMINISTIC_HASHER.hash_one(uid) & MASK) as f64;
&Position::from_raw(value * SCALE_FACTOR).expect("within range")
};
(inner_map, *uid, *p)
})
}
pub fn iter_entries(
&self,
) -> impl Iterator<Item = (Uid, &TypeVariantValue<C>, &DotFunMap<DotFun<Position>>)> {
self.0.iter().map(|(uid, pair)| {
let value = &pair.value;
let positions = &pair.positions;
(*uid, value, positions)
})
}
pub fn iter_mut_and_invalidate(
&mut self,
) -> impl ExactSizeIterator<Item = &mut TypeVariantValue<C>> {
self.0
.iter_mut_and_invalidate()
.map(|(_, pair)| &mut pair.value)
}
pub fn retain_values_and_invalidate(
&mut self,
mut f: impl FnMut(&mut TypeVariantValue<C>) -> bool,
) {
self.0.retain_and_invalidate(|_, pair| f(&mut pair.value));
}
pub fn with_list<'ds, F, R, E>(&'ds self, mut map: F) -> Result<Vec<(R, Uid, Position)>, E>
where
F: FnMut(&'ds TypeVariantValue<C>, Uid, Position) -> Result<Option<R>, E>,
{
let mut result: Vec<_> = self
.iter_as_is()
.filter_map(|(inner_map, uid, p)| -> Option<Result<_, E>> {
let v = match (map)(inner_map, uid, p).transpose()? {
Ok(v) => v,
Err(e) => return Some(Err(e)),
};
Some(Ok((v, uid, p)))
})
.collect::<Result<_, E>>()?;
result.sort_unstable_by_key(|&(_, uid, p)| (p, uid));
Ok(result)
}
pub fn get(&self, idx: usize) -> Option<&TypeVariantValue<C>> {
self.get_entry(idx).map(|(_, v)| v)
}
pub fn get_entry(&self, idx: usize) -> Option<(Uid, &TypeVariantValue<C>)> {
if idx >= self.0.len() {
return None;
}
if idx == 0 {
let first = self
.iter_as_is()
.min_by_key(|&(_, _, p)| p)
.expect("0 >= len, so len > 0");
return Some((first.1, first.0));
}
if idx == self.len() - 1 {
let last = self
.iter_as_is()
.max_by_key(|&(_, _, p)| p)
.expect("0 >= len, so len > 0");
return Some((last.1, last.0));
}
let mut result = self
.with_list(|v, u, _| Ok::<_, Infallible>(Some((u, v))))
.expect("E == Infallible");
Some(result.swap_remove(idx).0)
}
pub fn get_by_uid(&self, uid: Uid) -> Option<&TypeVariantValue<C>> {
self.0.get(&uid).map(|pm| &pm.value)
}
pub fn get_by_uid_mut_and_invalidate(&mut self, uid: Uid) -> Option<&mut TypeVariantValue<C>> {
self.0.get_mut_and_invalidate(&uid).map(|pm| &mut pm.value)
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
impl<'doc, C> ToValue for &'doc OrArray<C>
where
C: ExtensionType,
{
type Values = snapshot::OrArray<AllValues<'doc, C::ValueRef<'doc>>>;
type Value = snapshot::OrArray<CollapsedValue<'doc, C::ValueRef<'doc>>>;
type LeafValue = Either<MvRegValue, <C::ValueRef<'doc> as ToValue>::LeafValue>;
fn values(self) -> Self::Values {
let result = self.with_list(|v, _, _| match v.coerce_to_value_ref() {
ValueRef::Map(m) => Ok::<_, Infallible>(Some(AllValues::Map(m.values()))),
ValueRef::Array(a) => Ok(Some(AllValues::Array(a.values()))),
ValueRef::Register(r) => Ok(Some(AllValues::Register(r.values()))),
ValueRef::Custom(c) => Ok(Some(AllValues::Custom(c.values()))),
});
let list = result.unwrap().into_iter().map(|(v, _, _)| v).collect();
snapshot::OrArray { list }
}
fn value(self) -> Result<Self::Value, Box<SingleValueError<Self::LeafValue>>> {
let result = self.with_list(|v, uid, p| match v.coerce_to_value_ref() {
ValueRef::Map(m) => Ok(Some(CollapsedValue::Map(m.value()?))),
ValueRef::Array(a) => Ok(Some(CollapsedValue::Array(a.value()?))),
ValueRef::Custom(c) => Ok(Some(CollapsedValue::Custom(
c.value().map_err(|v| v.map_values(Either::Right))?,
))),
ValueRef::Register(r) => match r.value() {
Ok(v) => Ok(Some(CollapsedValue::Register(v))),
Err(e) if e.issue == SingleValueIssue::Cleared => Ok(None),
Err(mut e) => {
e.path.push(format!("[{uid:?}@{}]", p.0));
Err(e.map_values(Either::Left))
}
},
});
let list = result?.into_iter().map(|(v, _, _)| v).collect();
Ok(snapshot::OrArray { list })
}
}
macro_rules! apply_to_X {
($name:ident, $frag:literal, $field:ident, [$($others:ident),*], $innerType:ty) => {
#[doc = $frag]
pub fn $name<'data, O>(
&'data self,
uid: Uid,
o: O,
p: Position,
cc: &'_ CausalContext,
id: Identifier,
) -> CausalDotStore<Self>
where
O: for<'cc> FnOnce(
&'data $innerType,
&'cc CausalContext,
Identifier
) -> CausalDotStore<$innerType>,
{
let CausalDotStore {
store: ret_map,
context: mut ret_cc,
} = self.apply(
uid,
move |m, cc, id| {
o(&m.$field, cc, id).map_store(Value::from)
},
p,
cc,
id
);
if let Some(inner) = self.0.get(&uid).map(|pm| &pm.value) {
$( inner.$others.add_dots_to(&mut ret_cc); )*
}
CausalDotStore {
store: ret_map,
context: ret_cc,
}
}
};
}
macro_rules! insert_X {
($name:ident, $frag:literal, $field:ident, [$($others:ident),*], $innerType:ty) => {
#[doc = $frag]
pub fn $name<O>(
&self,
uid: Uid,
o: O,
p: Position,
cc: &'_ CausalContext,
id: Identifier,
) -> CausalDotStore<Self>
where
O: for<'cc> FnOnce(&'cc CausalContext, Identifier) -> CausalDotStore<$innerType>,
{
self.insert(
uid,
move |cc, id| {
o(cc, id).map_store(Value::from)
},
p,
cc,
id,
)
}
};
}
impl<C> OrArray<C>
where
C: ExtensionType + fmt::Debug + PartialEq,
{
pub fn create(&self, _cc: &CausalContext, _id: Identifier) -> CausalDotStore<Self> {
CausalDotStore {
store: Self(Default::default()),
context: CausalContext::default(),
}
}
apply_to_X!(
apply_to_map,
"an [`OrMap`]",
map,
[array, reg],
OrMap<String, C>
);
apply_to_X!(apply_to_array, "an [`OrArray`]", array, [map, reg], Self);
apply_to_X!(apply_to_register, "an [`MvReg`]", reg, [map, array], MvReg);
insert_X!(insert_map, "an [`OrMap`]", map, [array, reg], OrMap<String, C>);
insert_X!(insert_array, "an [`OrArray`]", array, [map, reg], Self);
insert_X!(insert_register, "an [`MvReg`]", reg, [map, array], MvReg);
pub fn insert<O>(
&self,
uid: Uid,
o: O,
p: Position,
cc: &'_ CausalContext,
id: Identifier,
) -> CausalDotStore<Self>
where
O: for<'cc> FnOnce(&'cc CausalContext, Identifier) -> CausalDotStore<Value<C>>,
{
let mut cc = cc.clone();
cc.insert_dot(uid.dot());
let mut ret_dot_map = Self::default();
let existing_pair = self.0.get(&uid);
debug_assert_eq!(existing_pair, None);
let CausalDotStore {
store: v,
context: mut ret_cc,
} = o(&cc, id);
ret_cc.insert_dot(uid.dot());
if v.is_bottom() {
return CausalDotStore {
store: Default::default(),
context: ret_cc,
};
}
cc.union(&ret_cc);
let mut dots = cc.next_n_dots_for(2, id);
let position_set_dot = dots.next().expect("should be 2 left");
let position_dot = dots.next().expect("should be 1 left");
ret_cc.insert_dots([position_set_dot, position_dot]);
let mut dot_fun = DotFun::default();
dot_fun.set(position_dot, p);
let mut dot_fun_map = DotFunMap::default();
dot_fun_map.set(position_set_dot, dot_fun);
let pair = PairMap {
value: v.into(),
positions: dot_fun_map,
};
ret_dot_map.0.set(uid, pair);
CausalDotStore {
store: ret_dot_map,
context: ret_cc,
}
}
pub fn apply<'data, O>(
&'data self,
uid: Uid,
o: O,
p: Position,
cc: &'_ CausalContext,
id: Identifier,
) -> CausalDotStore<Self>
where
O: for<'cc> FnOnce(
&'data TypeVariantValue<C>,
&'cc CausalContext,
Identifier,
) -> CausalDotStore<Value<C>>,
{
let mut ret_dot_map = Self::default();
let Some(current) = self.0.get(&uid) else {
panic!("no element in array with uid {uid:?}");
};
let CausalDotStore {
store: v,
context: mut ret_cc,
} = o(¤t.value, cc, id);
if v.is_bottom() {
current.add_dots_to(&mut ret_cc);
return CausalDotStore {
store: Default::default(),
context: ret_cc,
};
}
let mut dot_gen = cc.clone();
dot_gen.union(&ret_cc);
let mut dots = dot_gen.next_n_dots_for(2, id);
let position_set_dot = dots.next().expect("should be 2 left");
let position_dot = dots.next().expect("should be 1 left");
let mut dot_fun = DotFun::default();
dot_fun.set(position_dot, p);
let mut dot_fun_map = DotFunMap::default();
dot_fun_map.set(position_set_dot, dot_fun);
let pair = PairMap {
value: v.into(),
positions: dot_fun_map,
};
ret_dot_map.0.set(uid, pair);
let mut cc = ret_cc;
cc.insert_dots([position_set_dot, position_dot]);
let roots = current.positions.keys();
cc.insert_dots(roots);
CausalDotStore {
store: ret_dot_map,
context: cc,
}
}
pub fn mv(
&self,
uid: Uid,
p: Position,
cc: &CausalContext,
id: Identifier,
) -> CausalDotStore<Self> {
let Some(current) = self.0.get(&uid) else {
panic!("no element in array with uid {uid:?}");
};
let positions = ¤t.positions;
let dots = cc.next_n_dots_for(
u8::try_from(positions.len()).expect("never more than 255 position sets"),
id,
);
let also_dots = dots.clone();
let mut ps = DotFunMap::default();
for (r, d) in positions.keys().zip(dots) {
let mut dot_fun = DotFun::default();
dot_fun.set(d, p);
ps.set(r, dot_fun);
}
let pair = PairMap {
value: Default::default(),
positions: ps,
};
let mut ret_dot_map = Self::default();
ret_dot_map.0.set(uid, pair);
let mut cc = CausalContext::default();
cc.insert_dots(also_dots);
positions.add_dots_to(&mut cc);
CausalDotStore {
store: ret_dot_map,
context: cc,
}
}
pub fn delete(&self, uid: Uid, _cc: &CausalContext, _id: Identifier) -> CausalDotStore<Self> {
let Some(pair_map) = self.0.get(&uid) else {
panic!("no element in array with uid {uid:?}");
};
let mut ret_cc = CausalContext::new();
pair_map.add_dots_to(&mut ret_cc);
CausalDotStore {
store: Self(Default::default()),
context: ret_cc,
}
}
pub fn clear(&self, _cc: &CausalContext, _id: Identifier) -> CausalDotStore<Self> {
let ret_cc = self.dots();
CausalDotStore {
store: Self(Default::default()),
context: ret_cc,
}
}
pub fn insert_idx_register(
&self,
idx: usize,
value: impl Into<MvRegValue>,
cc: &CausalContext,
id: Identifier,
) -> CausalDotStore<Self> {
let uid = cc.next_dot_for(id).into();
let clamped_idx = idx.min(self.len());
let pos = create_position_for_index(self, clamped_idx);
let value = value.into();
self.insert_register(
uid,
|cc, id| MvReg::default().write(value, cc, id),
pos,
cc,
id,
)
}
pub fn insert_idx_with<O>(
&self,
idx: usize,
value_closure: O,
cc: &CausalContext,
id: Identifier,
) -> CausalDotStore<Self>
where
O: for<'cc> FnOnce(&'cc CausalContext, Identifier) -> CausalDotStore<Value<C>>,
{
let uid = cc.next_dot_for(id).into();
let clamped_idx = idx.min(self.len());
let pos = create_position_for_index(self, clamped_idx);
self.insert(uid, value_closure, pos, cc, id)
}
pub fn remove(&self, idx: usize, cc: &CausalContext, id: Identifier) -> CausalDotStore<Self> {
let uid = uid_from_index(self, idx);
self.delete(uid, cc, id)
}
}
fn ids<C>(m: &OrArray<C>) -> Vec<((), Uid, Position)> {
use std::convert::Infallible;
m.with_list(|_, _, _| Ok::<_, Infallible>(Some(())))
.unwrap()
}
fn create_position_for_index<C>(m: &OrArray<C>, idx: usize) -> Position {
if idx == 0 {
let min_p = m.iter_as_is().map(|(_, _, p)| p).min();
return Position::between(None, min_p);
}
if idx == m.len() {
let max_p = m.iter_as_is().map(|(_, _, p)| p).max();
return Position::between(max_p, None);
}
assert!(
idx < m.len(),
"index out of bounds ({idx} when length is {})",
m.len()
);
let ids = ids(m);
let pos_at_index = ids.get(idx).map(|(_, _, p)| *p);
let pos_at_previous_index = if idx == 0 {
None
} else {
Some(
ids.get(idx - 1)
.expect("we check for out-of-bounds above")
.2,
)
};
Position::between(pos_at_previous_index, pos_at_index)
}
fn uid_from_index<C>(m: &OrArray<C>, idx: usize) -> Uid {
ids(m)[idx].1
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
crdts::{
NoExtensionTypes,
test_util::{Ops, join_harness},
},
sentinel::test::{KeyCountingValidator, ValueCountingValidator},
};
use std::collections::BTreeMap;
type OrArray = super::OrArray<NoExtensionTypes>;
#[test]
fn empty() {
let cds = CausalDotStore::<OrArray>::default();
assert!(cds.store.is_bottom());
assert!(cds.store.value().unwrap().is_empty());
assert_eq!(cds.store.values().len(), 0);
}
#[test]
fn created_is_bottom() {
let list = OrArray::default();
let cc = CausalContext::new();
let m = list.create(&cc, Identifier::new(0, 0));
assert!(m.store.is_bottom());
}
#[test]
fn cleared_is_bottom() {
let list = OrArray::default();
let cc = CausalContext::new();
let id = Identifier::new(0, 0);
let m = list.create(&cc, id);
let m = m.store.clear(&m.context, id);
assert!(m.store.is_bottom());
}
#[test]
fn push_get_remove() {
let list = OrArray::default();
let cc = CausalContext::new();
let id = Identifier::new(0, 0);
let uid = cc.next_dot_for(id).into();
let m = list.insert_register(
uid,
|cc, id| MvReg::default().write(MvRegValue::Bool(true), cc, id),
Position::between(None, None),
&cc,
id,
);
assert!(!m.store.is_bottom());
assert_eq!(
m.store.value().unwrap().get(0).cloned(),
Some(CollapsedValue::Register(&MvRegValue::Bool(true)))
);
assert_eq!(m.store.len(), 1);
let m = m.store.delete(uid, &cc, id);
assert!(m.store.is_bottom()); assert_eq!(m.store.value().unwrap().get(0), None);
assert_eq!(m.store.len(), 0);
}
#[test]
#[ignore = "no longer relevant when inserted bottom values are discarded"]
#[should_panic = "asked to insert bottom element Register(MvReg(DotFun { state: {} }))"]
fn push_bottom() {
let list = OrArray::default();
let cc = CausalContext::new();
let id = Identifier::new(0, 0);
let uid = cc.next_dot_for(id).into();
let m = list.insert_register(
uid,
|_, _| MvReg::default().clear(),
Position::between(None, None),
&cc,
id,
);
assert!(!m.store.is_bottom());
assert_eq!(m.store.value().unwrap().get(0).cloned(), None);
assert_eq!(m.store.len(), 1);
let _ = m.store.apply(
uid,
|old, _, _| {
assert!(old.is_bottom());
MvReg::default().clear().map_store(Value::Register)
},
Position::between(None, None),
&cc,
id,
);
let m = m.clone().join(m, &mut DummySentinel).unwrap();
m.store.apply(
uid,
|old, _, _| {
assert!(old.is_bottom());
MvReg::default().clear().map_store(Value::Register)
},
Position::between(None, None),
&cc,
id,
);
}
#[test]
fn delete_overrides_move() {
let list = OrArray::default();
let cc1 = CausalContext::new();
let id1 = Identifier::new(1, 0);
let uid = cc1.next_dot_for(id1).into();
let m = list.insert_register(
uid,
|cc, id| MvReg::default().write(MvRegValue::Bool(true), cc, id),
Position::between(None, None),
&cc1,
id1,
);
let cc1 = m.context;
let cc2 = cc1.clone();
let id2 = Identifier::new(2, 0);
let list = m.store;
let m1 = list.delete(uid, &cc1, id1);
let m2 = list.mv(uid, Position::between(None, None), &cc2, id2);
let mm = m1.join(m2, &mut DummySentinel).unwrap();
assert!(!mm.store.0.has(&uid));
let mm = mm.clone().join(mm, &mut DummySentinel).unwrap();
assert!(!mm.store.0.has(&uid));
}
#[test]
fn outer_remove_vs_inner_mv() {
let id1 = Identifier::new(1, 0);
let id2 = Identifier::new(2, 0);
let mut cc1 = CausalDotStore::<OrArray>::new();
let mut cc2 = CausalDotStore::<OrArray>::new();
let uid: Uid = cc1.context.next_dot_for(id1).into();
let mut inner_uid = None;
cc1.context.extend([uid.dot()]);
let crdt = cc1.store.insert_array(
uid,
|cc, id| {
let mut cc = cc.clone();
let uid: Uid = cc.next_dot_for(id).into();
cc.extend([uid.dot()]);
let mut crdt = OrArray::default().insert_register(
uid,
|cc, id| MvReg::default().write(MvRegValue::Bool(true), cc, id),
Position::between(None, None),
&cc,
id,
);
crdt.context.extend([uid.dot()]);
inner_uid = Some(uid);
crdt
},
Position::between(None, None),
&cc1.context,
id1,
);
cc1 = cc1.join(crdt, &mut DummySentinel).unwrap();
let inner_uid = inner_uid.expect("insert closure is always called");
cc2 = cc1.clone().join(cc2, &mut DummySentinel).unwrap();
let crdt = cc2.store.apply_to_array(
uid,
|old, cc, id| old.mv(inner_uid, Position::between(None, None), cc, id),
Position::between(None, None),
&cc2.context,
id2,
);
eprintln!("mv: {crdt:?}");
cc2 = cc2.join(crdt, &mut DummySentinel).unwrap();
let crdt = cc1.store.clear(&cc1.context, id1);
eprintln!("clear: {crdt:?}");
cc1 = cc1.join(crdt, &mut DummySentinel).unwrap();
cc2 = cc1.clone().join(cc2, &mut DummySentinel).unwrap();
cc1 = cc2.clone().join(cc1, &mut DummySentinel).unwrap();
assert!(
cc1.store.0.keys().any(|k| k == &uid),
"{uid:?} wasn't in cc1 keys:\n{:?}",
cc1.store.0
);
assert!(
cc2.store.0.keys().any(|k| k == &uid),
"{uid:?} wasn't in cc2 keys:\n{:?}",
cc2.store.0
);
eprintln!("{cc1:?}");
cc1.store.apply_to_array(
uid,
|old, cc, id| {
assert!(
old.is_bottom(),
"dot exists due to node 2's update, but value was erased by node 1's clear"
);
old.clone().clear(cc, id)
},
Position::between(None, None),
&cc1.context,
id1,
);
cc2.store.apply_to_array(
uid,
|old, cc, id| {
assert!(
old.is_bottom(),
"dot exists due to node 2's update, but value was erased by node 1's clear"
);
old.clone().clear(cc, id)
},
Position::between(None, None),
&cc2.context,
id2,
);
}
#[test]
fn concurrent_push() {
join_harness(
OrArray::default(),
|cds, _| cds,
|m, cc, id| {
m.insert_register(
cc.next_dot_for(id).into(),
|cc, id| MvReg::default().write(MvRegValue::U64(2), cc, id),
Position::between(None, None),
&cc,
id,
)
},
|m, cc, id| {
m.insert_register(
cc.next_dot_for(id).into(),
|cc, id| MvReg::default().write(MvRegValue::U64(3), cc, id),
Position::between(None, None),
&cc,
id,
)
},
ValueCountingValidator::new(true),
|CausalDotStore { store: m, .. }, sentinel| {
assert!(!m.is_bottom());
let list = m.value().unwrap();
assert_eq!(list.len(), 2);
assert!(
list.iter()
.any(|v| v == &CollapsedValue::Register(&MvRegValue::U64(2)))
);
assert!(
list.iter()
.any(|v| v == &CollapsedValue::Register(&MvRegValue::U64(3)))
);
assert_eq!(sentinel.added, BTreeMap::from([(MvRegValue::U64(3), 1)]));
assert!(sentinel.removed.is_empty());
},
);
}
#[test]
fn concurrent_move_delete() {
let shared_uid = Dot::mint((9, 0).into(), 1).into();
let p = Position::between(None, None);
join_harness(
OrArray::default(),
|cds, id| {
let uid = cds.context.next_dot_for(id).into();
assert_eq!(uid, shared_uid);
cds.store.insert_register(
uid,
|cc, id| MvReg::default().write(MvRegValue::U64(1), cc, id),
p,
&cds.context,
id,
)
},
|m, cc, id| m.mv(shared_uid, p, &cc, id),
|m, cc, id| m.delete(shared_uid, &cc, id),
KeyCountingValidator::default(),
|CausalDotStore { store: m, .. }, sentinel| {
assert_eq!(m.len(), 0);
let list = m.values();
assert_eq!(list.len(), 0);
assert_eq!(list.get(0), None);
assert!(m.is_bottom());
assert_eq!(sentinel.added, 0);
assert_eq!(sentinel.removed, 1);
},
);
}
#[test]
fn concurrent_update() {
let shared_uid = Dot::mint((9, 0).into(), 1).into();
let p = Position::between(None, None);
join_harness(
OrArray::default(),
|cds, id| {
let uid = cds.context.next_dot_for(id).into();
assert_eq!(uid, shared_uid);
cds.store.insert_register(
uid,
|cc, id| MvReg::default().write(MvRegValue::U64(1), cc, id),
p,
&cds.context,
id,
)
},
|m, cc, id| {
m.apply_to_register(
shared_uid,
|old, cc, id| {
assert_eq!(old.value().unwrap(), &MvRegValue::U64(1));
MvReg::default().write(MvRegValue::U64(2), cc, id)
},
p,
&cc,
id,
)
},
|m, cc, id| {
m.apply_to_register(
shared_uid,
|old, cc, id| {
assert_eq!(old.value().unwrap(), &MvRegValue::U64(1));
MvReg::default().write(MvRegValue::U64(3), cc, id)
},
p,
&cc,
id,
)
},
ValueCountingValidator::default(),
|CausalDotStore { store: m, .. }, sentinel| {
assert!(!m.is_bottom());
let list = m.values();
assert_eq!(list.len(), 1);
let AllValues::Register(v) = list.get(0).unwrap() else {
panic!("[0] isn't a register though we only wrote a register")
};
assert!(v.contains(&MvRegValue::U64(2)));
assert!(v.contains(&MvRegValue::U64(3)));
assert_eq!(sentinel.added, BTreeMap::from([(MvRegValue::U64(3), 1)]));
assert!(sentinel.removed.is_empty());
},
);
}
#[test]
fn concurrent_clear() {
let shared_uid = Dot::mint((9, 0).into(), 1).into();
let p = Position::between(None, None);
join_harness(
OrArray::default(),
|CausalDotStore {
store: m,
context: cc,
},
id| {
m.insert_register(
shared_uid,
|cc, id| MvReg::default().write(MvRegValue::Bool(true), cc, id),
p,
&cc,
id,
)
},
|m, cc, id| m.clear(&cc, id),
|m, cc, id| m.clear(&cc, id),
ValueCountingValidator::default(),
|CausalDotStore { store: m, .. }, sentinel| {
assert!(m.is_bottom());
let values = m.values();
assert_eq!(values.len(), 0);
assert!(sentinel.added.is_empty());
assert!(sentinel.removed.is_empty());
},
);
}
#[test]
fn update_vs_remove() {
let shared_uid = Dot::mint((9, 0).into(), 1).into();
let p = Position::between(None, None);
join_harness(
OrArray::default(),
|CausalDotStore {
store: m,
context: cc,
},
id| {
m.insert_register(
shared_uid,
|cc, id| MvReg::default().write(MvRegValue::U64(42), cc, id),
p,
&cc,
id,
)
},
|m, cc, id| {
m.apply_to_register(
shared_uid,
|_old, cc, id| MvReg::default().write(MvRegValue::Bool(true), cc, id),
p,
&cc,
id,
)
},
|m, cc, id| {
m.delete(shared_uid, &cc, id)
},
ValueCountingValidator::default(),
|CausalDotStore { store: m, .. }, sentinel| {
assert!(!m.is_bottom());
let values = m.values();
let AllValues::Register(v) = values.get(0).unwrap() else {
panic!("[0] isn't a register even though we only wrote registers");
};
assert_eq!(v, [MvRegValue::Bool(true)]);
assert!(sentinel.added.is_empty());
assert!(sentinel.removed.is_empty());
},
);
}
#[test]
fn nested_update_vs_remove() {
let shared_uid = Dot::mint((9, 0).into(), 1).into();
let p = Position::between(None, None);
join_harness(
OrArray::default(),
|CausalDotStore {
store: m,
context: cc,
},
id| {
m.insert_map(
shared_uid,
|cc, id| {
OrMap::default().apply_to_register(
|_old, cc, id| MvReg::default().write(MvRegValue::U64(42), cc, id),
"bar".into(),
cc,
id,
)
},
p,
&cc,
id,
)
},
|m, cc, id| {
m.apply_to_map(
shared_uid,
|old, cc, id| {
old.apply_to_register(
|_old, cc, id| MvReg::default().write(MvRegValue::Bool(true), cc, id),
"baz".into(),
cc,
id,
)
},
p,
&cc,
id,
)
},
|m, cc, id| {
m.delete(shared_uid, &cc, id)
},
ValueCountingValidator::default(),
|CausalDotStore { store: m, .. }, sentinel| {
assert!(!m.is_bottom());
let values = m.values();
let AllValues::Map(m) = values.get(0).unwrap() else {
panic!("[0] isn't a map even though we only wrote map");
};
assert_eq!(values.len(), 1);
let AllValues::Register(r) = m
.get(&String::from("baz"))
.expect("baz key isn't preserved")
else {
panic!("baz isn't a register though we only wrote a register ")
};
assert_eq!(m.len(), 1);
assert_eq!(r, [MvRegValue::Bool(true)]);
assert!(sentinel.added.is_empty());
assert!(sentinel.removed.is_empty());
},
);
}
#[test]
fn insert_idx_register_at_head() {
use crate::sentinel::DummySentinel;
let list = OrArray::default();
let cc = CausalContext::new();
let id = Identifier::new(0, 0);
let delta = list.insert_idx_register(0, MvRegValue::U64(10), &cc, id);
let mut m = CausalDotStore {
store: list,
context: cc,
}
.join(delta, &mut DummySentinel)
.unwrap();
assert_eq!(m.store.len(), 1);
assert_eq!(
m.store.value().unwrap().get(0).cloned(),
Some(CollapsedValue::Register(&MvRegValue::U64(10)))
);
let delta = m
.store
.insert_idx_register(0, MvRegValue::U64(20), &m.context, id);
m = m.join(delta, &mut DummySentinel).unwrap();
assert_eq!(m.store.len(), 2);
assert_eq!(
m.store.value().unwrap().get(0).cloned(),
Some(CollapsedValue::Register(&MvRegValue::U64(20)))
);
assert_eq!(
m.store.value().unwrap().get(1).cloned(),
Some(CollapsedValue::Register(&MvRegValue::U64(10)))
);
}
#[test]
fn insert_idx_register_at_middle() {
use crate::sentinel::DummySentinel;
let list = OrArray::default();
let cc = CausalContext::new();
let id = Identifier::new(0, 0);
let delta = list.insert_idx_register(0, MvRegValue::U64(10), &cc, id);
let mut m = CausalDotStore {
store: list,
context: cc,
}
.join(delta, &mut DummySentinel)
.unwrap();
let delta = m
.store
.insert_idx_register(1, MvRegValue::U64(30), &m.context, id);
m = m.join(delta, &mut DummySentinel).unwrap();
let delta = m
.store
.insert_idx_register(1, MvRegValue::U64(20), &m.context, id);
m = m.join(delta, &mut DummySentinel).unwrap();
assert_eq!(m.store.len(), 3);
let values = m.store.value().unwrap();
assert_eq!(
values.get(0).cloned(),
Some(CollapsedValue::Register(&MvRegValue::U64(10)))
);
assert_eq!(
values.get(1).cloned(),
Some(CollapsedValue::Register(&MvRegValue::U64(20)))
);
assert_eq!(
values.get(2).cloned(),
Some(CollapsedValue::Register(&MvRegValue::U64(30)))
);
}
#[test]
fn insert_idx_register_at_tail() {
use crate::sentinel::DummySentinel;
let list = OrArray::default();
let cc = CausalContext::new();
let id = Identifier::new(0, 0);
let delta = list.insert_idx_register(0, MvRegValue::U64(10), &cc, id);
let mut m = CausalDotStore {
store: list,
context: cc,
}
.join(delta, &mut DummySentinel)
.unwrap();
let delta = m
.store
.insert_idx_register(1, MvRegValue::U64(20), &m.context, id);
m = m.join(delta, &mut DummySentinel).unwrap();
let delta = m
.store
.insert_idx_register(2, MvRegValue::U64(30), &m.context, id);
m = m.join(delta, &mut DummySentinel).unwrap();
assert_eq!(m.store.len(), 3);
assert_eq!(
m.store.value().unwrap().get(2).cloned(),
Some(CollapsedValue::Register(&MvRegValue::U64(30)))
);
}
#[test]
fn insert_idx_register_beyond_len_clamps() {
use crate::sentinel::DummySentinel;
let list = OrArray::default();
let cc = CausalContext::new();
let id = Identifier::new(0, 0);
let delta = list.insert_idx_register(0, MvRegValue::U64(10), &cc, id);
let mut m = CausalDotStore {
store: list,
context: cc,
}
.join(delta, &mut DummySentinel)
.unwrap();
let delta = m
.store
.insert_idx_register(100, MvRegValue::U64(20), &m.context, id);
m = m.join(delta, &mut DummySentinel).unwrap();
assert_eq!(m.store.len(), 2);
let values = m.store.value().unwrap();
assert_eq!(
values.get(0).cloned(),
Some(CollapsedValue::Register(&MvRegValue::U64(10)))
);
assert_eq!(
values.get(1).cloned(),
Some(CollapsedValue::Register(&MvRegValue::U64(20)))
);
}
#[test]
fn insert_idx_with_nested_map() {
use crate::sentinel::DummySentinel;
let list = OrArray::default();
let cc = CausalContext::new();
let id = Identifier::new(0, 0);
let delta = list.insert_idx_with(
0,
|cc, id| {
OrMap::<String, NoExtensionTypes>::default()
.apply_to_register(
|reg, cc, id| {
reg.write(MvRegValue::String("test_value".to_string()), cc, id)
},
"test_key".to_string(),
cc,
id,
)
.map_store(Value::Map)
},
&cc,
id,
);
let m = CausalDotStore {
store: list,
context: cc,
}
.join(delta, &mut DummySentinel)
.unwrap();
assert_eq!(m.store.len(), 1);
let value = m.store.get(0).expect("should have element at index 0");
assert!(!value.map.is_bottom(), "map should not be bottom");
assert_eq!(
value
.map
.get(&"test_key".to_string())
.and_then(|v| v.reg.value().ok())
.cloned(),
Some(MvRegValue::String("test_value".to_string()))
);
}
#[test]
fn remove_at_head() {
use crate::sentinel::DummySentinel;
let list = OrArray::default();
let cc = CausalContext::new();
let id = Identifier::new(0, 0);
let delta = list.insert_idx_register(0, MvRegValue::U64(10), &cc, id);
let mut m = CausalDotStore {
store: list,
context: cc,
}
.join(delta, &mut DummySentinel)
.unwrap();
let delta = m
.store
.insert_idx_register(1, MvRegValue::U64(20), &m.context, id);
m = m.join(delta, &mut DummySentinel).unwrap();
let delta = m
.store
.insert_idx_register(2, MvRegValue::U64(30), &m.context, id);
m = m.join(delta, &mut DummySentinel).unwrap();
assert_eq!(m.store.len(), 3);
let delta = m.store.remove(0, &m.context, id);
m = m.join(delta, &mut DummySentinel).unwrap();
assert_eq!(m.store.len(), 2);
let values = m.store.value().unwrap();
assert_eq!(
values.get(0).cloned(),
Some(CollapsedValue::Register(&MvRegValue::U64(20)))
);
assert_eq!(
values.get(1).cloned(),
Some(CollapsedValue::Register(&MvRegValue::U64(30)))
);
}
#[test]
fn remove_at_middle() {
use crate::sentinel::DummySentinel;
let list = OrArray::default();
let cc = CausalContext::new();
let id = Identifier::new(0, 0);
let delta = list.insert_idx_register(0, MvRegValue::U64(10), &cc, id);
let mut m = CausalDotStore {
store: list,
context: cc,
}
.join(delta, &mut DummySentinel)
.unwrap();
let delta = m
.store
.insert_idx_register(1, MvRegValue::U64(20), &m.context, id);
m = m.join(delta, &mut DummySentinel).unwrap();
let delta = m
.store
.insert_idx_register(2, MvRegValue::U64(30), &m.context, id);
m = m.join(delta, &mut DummySentinel).unwrap();
let delta = m.store.remove(1, &m.context, id);
m = m.join(delta, &mut DummySentinel).unwrap();
assert_eq!(m.store.len(), 2);
let values = m.store.value().unwrap();
assert_eq!(
values.get(0).cloned(),
Some(CollapsedValue::Register(&MvRegValue::U64(10)))
);
assert_eq!(
values.get(1).cloned(),
Some(CollapsedValue::Register(&MvRegValue::U64(30)))
);
}
#[test]
fn remove_at_tail() {
use crate::sentinel::DummySentinel;
let list = OrArray::default();
let cc = CausalContext::new();
let id = Identifier::new(0, 0);
let delta = list.insert_idx_register(0, MvRegValue::U64(10), &cc, id);
let mut m = CausalDotStore {
store: list,
context: cc,
}
.join(delta, &mut DummySentinel)
.unwrap();
let delta = m
.store
.insert_idx_register(1, MvRegValue::U64(20), &m.context, id);
m = m.join(delta, &mut DummySentinel).unwrap();
let delta = m
.store
.insert_idx_register(2, MvRegValue::U64(30), &m.context, id);
m = m.join(delta, &mut DummySentinel).unwrap();
let delta = m.store.remove(2, &m.context, id);
m = m.join(delta, &mut DummySentinel).unwrap();
assert_eq!(m.store.len(), 2);
let values = m.store.value().unwrap();
assert_eq!(
values.get(0).cloned(),
Some(CollapsedValue::Register(&MvRegValue::U64(10)))
);
assert_eq!(
values.get(1).cloned(),
Some(CollapsedValue::Register(&MvRegValue::U64(20)))
);
}
#[test]
#[should_panic]
fn remove_out_of_bounds_panics() {
let list = OrArray::default();
let cc = CausalContext::new();
let id = Identifier::new(0, 0);
let _ = list.remove(0, &cc, id);
}
#[quickcheck]
fn order_invariant(ops: Ops<OrArray>, seed: u64) -> quickcheck::TestResult {
ops.check_order_invariance(seed)
}
}