use std::sync::{Arc, Mutex};
use yrs::encoding::write::Write;
use yrs::error::UpdateError;
use yrs::updates::encoder::{Encode, Encoder, EncoderV1};
use yrs::{IdSet, ReadTxn, TransactionMut, Update};
#[derive(Clone, Default)]
pub(crate) struct RemoteDeletions(Arc<Mutex<Option<IdSet>>>);
impl RemoteDeletions {
pub(crate) fn integrate<'doc>(&self, txn: TransactionMut<'doc>) -> RemoteIntegration<'_, 'doc> {
let pending = ReadTxn::store(&txn)
.pending_ds()
.cloned()
.unwrap_or_default();
*self.0.lock().unwrap() = Some(pending);
RemoteIntegration {
deletions: self,
txn,
}
}
pub(crate) fn residual_update_v1(&self, txn: &TransactionMut) -> Option<Vec<u8>> {
let carried = self.0.lock().unwrap();
let residual = txn.delete_set().diff(carried.as_ref()?);
(!residual.is_empty()).then(|| delete_only_update_v1(&residual))
}
}
pub(crate) struct RemoteIntegration<'a, 'doc> {
deletions: &'a RemoteDeletions,
txn: TransactionMut<'doc>,
}
impl RemoteIntegration<'_, '_> {
pub(crate) fn carry(&mut self, deletions: &IdSet) {
if let Some(carried) = self.deletions.0.lock().unwrap().as_mut() {
carried.merge_with(deletions.clone());
}
}
pub(crate) fn apply_update(&mut self, update: Update) -> Result<(), UpdateError> {
self.txn.apply_update(update)
}
}
impl Drop for RemoteIntegration<'_, '_> {
fn drop(&mut self) {
self.txn.commit();
*self.deletions.0.lock().unwrap() = None;
}
}
pub(crate) fn apply_staged_update(
txn: &mut TransactionMut,
update: Update,
) -> Result<IdSet, UpdateError> {
let mut carried = update.delete_set().clone();
txn.apply_update(update)?;
carried.merge_with(txn.delete_set().clone());
Ok(carried)
}
fn delete_only_update_v1(deletions: &IdSet) -> Vec<u8> {
let mut encoder = EncoderV1::new();
encoder.write_var(0_u32);
deletions.encode(&mut encoder);
encoder.to_vec()
}
#[cfg(test)]
mod tests {
use super::*;
use yrs::types::Attrs;
use yrs::updates::decoder::Decode;
use yrs::{Any, ClientID, Doc, GetString, Options, StateVector, Subscription, Text, Transact};
fn replica(client: u64) -> Doc {
Doc::with_options(Options::with_client_id(ClientID::new(client)))
}
fn edit(doc: &Doc, change: impl FnOnce(&mut TransactionMut, &yrs::TextRef)) -> Vec<u8> {
let text = doc.get_or_insert_text("t");
let mut txn = doc.transact_mut();
change(&mut txn, &text);
txn.encode_update_v1()
}
fn published(
doc: &Doc,
deletions: &RemoteDeletions,
) -> (Subscription, Arc<Mutex<Vec<Vec<u8>>>>) {
let sink = Arc::new(Mutex::new(Vec::new()));
let (residuals, deletions) = (Arc::clone(&sink), deletions.clone());
let subscription = doc
.observe_update_v1(move |txn, _| {
residuals
.lock()
.unwrap()
.extend(deletions.residual_update_v1(txn));
})
.unwrap();
(subscription, sink)
}
fn integrate(doc: &Doc, deletions: &RemoteDeletions, update: &[u8]) {
let update = Update::decode_v1(update).unwrap();
let mut integration = deletions.integrate(doc.transact_mut());
integration.carry(update.delete_set());
integration.apply_update(update).unwrap();
}
fn text(doc: &Doc) -> String {
doc.get_or_insert_text("t").get_string(&doc.transact())
}
#[test]
fn residual_update_deletes_exactly_the_formatting_cleanup() {
let origin = replica(7);
edit(&origin, |txn, text| text.insert(txn, 0, "abcd"));
let state = origin
.transact()
.encode_state_as_update_v1(&StateVector::default());
let (peer, behind) = (replica(8), replica(9));
for doc in [&peer, &behind] {
let state = Update::decode_v1(&state).unwrap();
doc.transact_mut().apply_update(state).unwrap();
}
let bold = || Attrs::from([(Arc::from("b"), Any::Bool(true))]);
let theirs = edit(&origin, |txn, text| text.format(txn, 0, 2, bold()));
let ours = edit(&peer, |txn, text| text.format(txn, 0, 2, bold()));
let deletions = RemoteDeletions::default();
let (_subscription, residuals) = published(&peer, &deletions);
let expected = Arc::new(Mutex::new(IdSet::new()));
let deleted = Arc::clone(&expected);
let _observer = peer
.observe_update_v1(move |txn, _| {
deleted.lock().unwrap().merge_with(txn.delete_set().clone());
})
.unwrap();
assert!(Update::decode_v1(&theirs).unwrap().delete_set().is_empty());
integrate(&peer, &deletions, &theirs);
assert!(deletions.residual_update_v1(&peer.transact_mut()).is_none());
let residuals = residuals.lock().unwrap().clone();
assert_eq!(residuals.len(), 1);
assert_eq!(residuals[0][0], 0, "a residual update carries no structs");
let residual = Update::decode_v1(&residuals[0]).unwrap();
assert!(residual.delete_set() == &*expected.lock().unwrap());
for update in [&ours, &theirs, &residuals[0]] {
let update = Update::decode_v1(update).unwrap();
behind.transact_mut().apply_update(update).unwrap();
}
let encoded = |doc: &Doc| {
doc.transact()
.encode_state_as_update_v1(&StateVector::default())
};
assert_eq!(encoded(&behind), encoded(&peer));
}
#[test]
fn staged_formatting_cleanup_is_republished() {
let origin = replica(7);
let state = edit(&origin, |txn, text| text.insert(txn, 0, "abcd"));
let peer = replica(8);
peer.transact_mut()
.apply_update(Update::decode_v1(&state).unwrap())
.unwrap();
let bold = || Attrs::from([(Arc::from("b"), Any::Bool(true))]);
let theirs = edit(&origin, |txn, text| text.format(txn, 0, 2, bold()));
edit(&peer, |txn, text| text.format(txn, 0, 2, bold()));
let staged = replica(9);
let state = peer
.transact()
.encode_state_as_update_v1(&StateVector::default());
staged
.transact_mut()
.apply_update(Update::decode_v1(&state).unwrap())
.unwrap();
let carried = apply_staged_update(
&mut staged.transact_mut(),
Update::decode_v1(&theirs).unwrap(),
)
.unwrap();
assert!(carried.is_empty());
let diff = staged
.transact()
.encode_state_as_update_v1(&peer.transact().state_vector());
let diff = Update::decode_v1(&diff).unwrap();
let expected = diff.delete_set().clone();
assert!(!expected.is_empty());
let deletions = RemoteDeletions::default();
let (_subscription, residuals) = published(&peer, &deletions);
let mut integration = deletions.integrate(peer.transact_mut());
integration.carry(&carried);
integration.apply_update(diff).unwrap();
drop(integration);
let residuals = residuals.lock().unwrap();
assert_eq!(residuals.len(), 1);
assert_eq!(residuals[0][0], 0);
assert!(Update::decode_v1(&residuals[0]).unwrap().delete_set() == &expected);
}
#[test]
fn a_deletion_that_arrives_before_its_struct_is_not_republished() {
let origin = replica(7);
let insert = edit(&origin, |txn, text| text.insert(txn, 0, "abcd"));
let delete = edit(&origin, |txn, text| text.remove_range(txn, 1, 1));
let peer = replica(8);
let deletions = RemoteDeletions::default();
let (_subscription, residuals) = published(&peer, &deletions);
integrate(&peer, &deletions, &delete);
assert!(ReadTxn::store(&peer.transact()).pending_ds().is_some());
integrate(&peer, &deletions, &insert);
assert_eq!(text(&peer), "acd");
assert!(residuals.lock().unwrap().is_empty());
}
#[test]
fn deletions_held_with_a_pending_struct_are_not_republished() {
let origin = replica(7);
let first = edit(&origin, |txn, text| text.insert(txn, 0, "ab"));
let second = edit(&origin, |txn, text| {
text.insert(txn, 2, "cd");
text.remove_range(txn, 2, 1);
});
let peer = replica(8);
let deletions = RemoteDeletions::default();
let (_subscription, residuals) = published(&peer, &deletions);
integrate(&peer, &deletions, &second);
assert!(ReadTxn::store(&peer.transact()).pending_update().is_some());
integrate(&peer, &deletions, &first);
assert_eq!(text(&peer), "abd");
assert!(residuals.lock().unwrap().is_empty());
}
}