fix: origin left should points to non-deleted

This commit is contained in:
Zixuan Chen 2022-10-13 17:27:25 +08:00
parent 71aca40c7b
commit 7c032b6321
8 changed files with 279 additions and 466 deletions

1
.gitignore vendored
View file

@ -1,2 +1,3 @@
/target
*.log
flamegraph.svg

View file

@ -18,7 +18,7 @@ crev:
cargo crev crate check
fuzz:
cargo fuzz run yata -- -max_total_time=60 -max_len=10000 -jobs=2
cargo fuzz run yata -- -max_total_time=300 -max_len=8000 -jobs=2
flame:
cargo flamegraph --example test --features=fuzzing --root

View file

@ -23,7 +23,7 @@ struct CursorWithId<'tree> {
impl ContentMap {
#[inline]
pub(super) fn get_yspan_at_pos(&mut self, id: ID, pos: usize, len: usize) -> YSpan {
pub(super) fn get_yspan_at_pos(&self, id: ID, pos: usize, len: usize) -> YSpan {
let (left, right) = self.get_sibling_at_dumb(pos);
YSpan {
origin_left: left.as_ref().map(|x| x.id),
@ -39,48 +39,44 @@ impl ContentMap {
pos: usize,
) -> (Option<CursorWithId<'_>>, Option<CursorWithId<'_>>) {
if let Some(cursor) = self.get(pos) {
let cursor: SafeCursor<'_, YSpan, YSpanTreeTrait> =
let mut cursor: SafeCursor<'_, YSpan, YSpanTreeTrait> =
// SAFETY: we only change the lifetime of the cursor; the returned lifetime is kinda wrong in this situation
// because Bumpalo's lifetime is static due to the self-referential structure limitation; Maybe there is a better way?
unsafe { std::mem::transmute(cursor) };
let (mut prev, mut next) = match cursor.pos() {
Position::Start => {
let id = cursor.as_ref().id;
(
None,
Some(CursorWithId {
id,
cursor: cursor.unwrap(),
}),
)
}
let mut prev = match cursor.pos() {
Position::Start => None,
Position::Middle => {
let id = cursor.as_ref().id;
let offset = cursor.offset();
let mut prev_offset_cursor = cursor.unwrap();
prev_offset_cursor.offset -= 1;
(
Some(CursorWithId {
id: id.inc(offset as i32 - 1),
cursor: prev_offset_cursor,
}),
Some(CursorWithId {
id: id.inc(offset as i32),
cursor: cursor.unwrap(),
}),
)
if cursor.as_ref().can_be_origin() {
return (
Some(CursorWithId {
id: id.inc(offset as i32 - 1),
cursor: prev_offset_cursor,
}),
Some(CursorWithId {
id: id.inc(offset as i32),
cursor: cursor.unwrap(),
}),
);
} else {
None
}
}
Position::End => {
let mut prev_offset_cursor = cursor.unwrap();
prev_offset_cursor.offset -= 1;
prev_offset_cursor.pos = Position::Middle;
(
if cursor.as_ref().can_be_origin() {
let mut prev_offset_cursor = cursor.unwrap();
prev_offset_cursor.offset -= 1;
prev_offset_cursor.pos = Position::Middle;
Some(CursorWithId {
id: cursor.as_ref().last_id(),
cursor: prev_offset_cursor,
}),
None,
)
})
} else {
None
}
}
_ => {
unreachable!()
@ -89,31 +85,50 @@ impl ContentMap {
if prev.is_none() {
let mut prev_cursor = cursor.prev_elem();
if let Some(prev_inner) = prev_cursor {
let cursor = prev_inner;
let offset = cursor.as_ref().content_len() - 1;
let mut cursor = cursor.unwrap();
cursor.offset = offset;
cursor.pos = Position::Middle;
prev = Some(CursorWithId {
id: prev_inner.as_ref().last_id(),
cursor,
});
while let Some(prev_inner) = prev_cursor {
cursor = prev_inner;
if prev_inner.as_ref().status.is_activated() {
let cursor = prev_inner;
let offset = cursor.as_ref().content_len() - 1;
let mut cursor = cursor.unwrap();
cursor.offset = offset;
cursor.pos = Position::Middle;
prev = Some(CursorWithId {
id: prev_inner.as_ref().last_id(),
cursor,
});
break;
}
prev_cursor = prev_inner.prev_elem();
}
}
if next.is_none() {
let mut next_cursor = cursor.next_elem_start();
let next = if prev.is_some() {
let next_cursor = cursor.next_elem_start();
if let Some(next_inner) = next_cursor {
let mut cursor = next_inner.unwrap();
cursor.offset = 0;
cursor.pos = Position::Start;
next = Some(CursorWithId {
Some(CursorWithId {
id: next_inner.as_ref().id,
cursor,
});
})
} else {
None
}
}
} else {
// if prev is none, next should be the first element in the tree
let mut prev = cursor.prev_elem();
while let Some(prev_inner) = prev {
cursor = prev_inner;
prev = prev_inner.prev_elem();
}
Some(CursorWithId {
id: cursor.as_ref().id,
cursor: cursor.unwrap(),
})
};
(prev, next)
} else {
@ -265,3 +280,98 @@ pub(super) fn change_status(
cursor.update_cache_recursively();
}
}
#[cfg(test)]
mod test_get_yspan_at_pos {
use crate::{
container::text::tracker::y_span::{Status, YSpan},
id::ID,
};
use super::ContentMap;
fn insert(map: &mut ContentMap, id: ID, pos: usize, len: usize) {
map.insert(
pos,
YSpan {
id,
len,
status: Default::default(),
origin_left: None,
origin_right: None,
},
);
}
fn delete(map: &mut ContentMap, pos: usize, len: usize) {
map.0.update_range(
pos,
Some(pos + len),
&mut |v| v.status.delete_times = 1,
&mut |_, _| {},
)
}
fn insert_deleted(map: &mut ContentMap, id: ID, pos: usize, len: usize) {
map.insert(
pos,
YSpan {
id,
len,
status: Status {
delete_times: 1,
unapplied: false,
undo_times: 0,
},
origin_left: None,
origin_right: None,
},
);
}
fn assert_at_pos(
map: &ContentMap,
pos: usize,
origin_left: Option<ID>,
origin_right: Option<ID>,
) {
let ans = map.get_yspan_at_pos(ID::new(111, 11), pos, 1);
assert_eq!(ans.origin_left, origin_left);
assert_eq!(ans.origin_right, origin_right);
}
#[test]
fn simple() {
let mut map = ContentMap::default();
insert(&mut map, ID::new(0, 0), 0, 10);
assert_at_pos(&map, 0, None, Some(ID::new(0, 0)));
assert_at_pos(&map, 10, Some(ID::new(0, 9)), None);
assert_at_pos(&map, 3, Some(ID::new(0, 2)), Some(ID::new(0, 3)));
}
#[test]
fn complicated() {
let mut map = ContentMap::default();
insert(&mut map, ID::new(0, 0), 0, 20);
delete(&mut map, 10, 10);
insert(&mut map, ID::new(1, 0), 10, 10);
insert(&mut map, ID::new(2, 0), 20, 10);
insert(&mut map, ID::new(3, 0), 30, 10);
// dbg!(&map);
assert_at_pos(&map, 10, Some(ID::new(0, 9)), Some(ID::new(0, 10)));
assert_at_pos(&map, 11, Some(ID::new(1, 0)), Some(ID::new(1, 1)));
assert_at_pos(&map, 20, Some(ID::new(1, 9)), Some(ID::new(2, 0)));
assert_at_pos(&map, 21, Some(ID::new(2, 0)), Some(ID::new(2, 1)));
delete(&mut map, 20, 1);
assert_at_pos(&map, 20, Some(ID::new(1, 9)), Some(ID::new(2, 0)));
assert_at_pos(&map, 21, Some(ID::new(2, 1)), Some(ID::new(2, 2)));
delete(&mut map, 0, 10);
assert_at_pos(&map, 0, None, Some(ID::new(0, 0)));
assert_at_pos(&map, 29, Some(ID::new(3, 9)), None);
delete(&mut map, 0, 28);
assert_at_pos(&map, 1, Some(ID::new(3, 9)), None);
}
}

View file

@ -39,7 +39,6 @@ impl Marker {
Marker::Insert { ptr, len: _ } => {
// SAFETY: tree data is always valid
let node = unsafe { ptr.as_ref() };
debug_assert!(!node.is_deleted());
let position = node.children().iter().position(|x| x.contain_id(id))?;
let child = &node.children()[position];
let start_counter = child.id.counter;
@ -64,7 +63,6 @@ impl Marker {
Marker::Insert { ptr, len: _ } => {
// SAFETY: tree data is always valid
let node = unsafe { ptr.as_ref() };
debug_assert!(!node.is_deleted());
node.children()
.iter()
.enumerate()
@ -108,7 +106,6 @@ impl Marker {
match self {
Marker::Insert { ptr, len: _ } => {
let node = ptr.as_ref();
debug_assert!(!node.is_deleted());
let position = node.children().iter().position(|x| x.contain_id(id))?;
let child = &node.children()[position];
let start_counter = child.id.counter;

View file

@ -5,9 +5,9 @@ use rle::{rle_tree::tree_trait::CumulateTreeTrait, HasLength, Mergable, Sliceabl
#[derive(Debug, Clone, PartialEq, Eq, Default, Hash)]
pub struct Status {
unapplied: bool,
delete_times: usize,
undo_times: usize,
pub unapplied: bool,
pub delete_times: usize,
pub undo_times: usize,
}
impl Status {
@ -61,7 +61,7 @@ pub enum StatusChange {
UndoDelete,
}
pub(super) type YSpanTreeTrait = CumulateTreeTrait<YSpan, 10>;
pub(super) type YSpanTreeTrait = CumulateTreeTrait<YSpan, 4>;
impl YSpan {
/// this is the last id of the span, which is **included** by self

View file

@ -303,429 +303,72 @@ pub mod fuzz {
#[test]
fn issue_1() {
crdt_list::test::test_with_actions::<YataImpl>(
3,
5,
100,
vec![
NewOp {
client_id: 2,
pos: 2,
Delete {
client_id: 252,
pos: 252,
len: 252,
},
Delete {
client_id: 0,
pos: 0,
len: 2,
client_id: 39,
pos: 252,
len: 178,
},
Delete {
client_id: 1,
pos: 0,
len: 0,
client_id: 252,
pos: 252,
len: 252,
},
Delete {
client_id: 0,
pos: 0,
len: 0,
client_id: 252,
pos: 252,
len: 252,
},
Delete {
client_id: 0,
pos: 2,
len: 3,
},
NewOp {
client_id: 0,
pos: 1,
},
NewOp {
client_id: 0,
pos: 1,
},
NewOp {
client_id: 1,
pos: 2,
client_id: 145,
pos: 145,
len: 252,
},
Delete {
client_id: 0,
pos: 0,
len: 4,
},
Sync { from: 0, to: 1 },
Delete {
client_id: 1,
pos: 0,
len: 0,
},
NewOp {
client_id: 0,
pos: 3,
},
NewOp {
client_id: 1,
pos: 0,
client_id: 252,
pos: 178,
len: 178,
},
Delete {
client_id: 0,
pos: 0,
len: 2,
},
NewOp {
client_id: 0,
pos: 4,
client_id: 252,
pos: 252,
len: 178,
},
Delete {
client_id: 0,
pos: 0,
len: 1,
},
Sync { from: 0, to: 0 },
NewOp {
client_id: 0,
pos: 1,
client_id: 252,
pos: 252,
len: 252,
},
NewOp {
client_id: 0,
pos: 1,
},
NewOp {
client_id: 0,
pos: 0,
client_id: 145,
pos: 252,
},
Delete {
client_id: 0,
pos: 1,
len: 1,
},
NewOp {
client_id: 0,
pos: 1,
},
NewOp {
client_id: 0,
pos: 1,
},
NewOp {
client_id: 0,
pos: 1,
},
Delete {
client_id: 0,
pos: 0,
len: 0,
},
Delete {
client_id: 1,
pos: 0,
len: 1,
client_id: 252,
pos: 178,
len: 252,
},
Sync { from: 0, to: 0 },
Delete {
client_id: 0,
pos: 3,
len: 2,
client_id: 252,
pos: 178,
len: 178,
},
Delete {
client_id: 0,
pos: 0,
len: 0,
},
Delete {
client_id: 0,
pos: 0,
len: 0,
},
Delete {
client_id: 2,
pos: 1,
len: 1,
},
Sync { from: 0, to: 0 },
Sync { from: 0, to: 0 },
Sync { from: 0, to: 0 },
Sync { from: 0, to: 0 },
Sync { from: 0, to: 0 },
Delete {
client_id: 0,
pos: 0,
len: 0,
},
Delete {
client_id: 0,
pos: 0,
len: 0,
},
Delete {
client_id: 2,
pos: 0,
len: 0,
},
Delete {
client_id: 0,
pos: 2,
len: 1,
},
Delete {
client_id: 0,
pos: 0,
len: 0,
},
Delete {
client_id: 0,
pos: 0,
len: 3,
},
Delete {
client_id: 0,
pos: 0,
len: 0,
},
Delete {
client_id: 2,
pos: 1,
len: 2,
},
Delete {
client_id: 2,
pos: 0,
len: 2,
},
Delete {
client_id: 0,
pos: 0,
len: 0,
},
Delete {
client_id: 0,
pos: 0,
len: 0,
},
Delete {
client_id: 0,
pos: 2,
len: 0,
},
Delete {
client_id: 0,
pos: 0,
len: 0,
},
Delete {
client_id: 1,
pos: 0,
len: 1,
client_id: 252,
pos: 252,
len: 252,
},
NewOp {
client_id: 0,
pos: 1,
},
NewOp {
client_id: 0,
pos: 1,
},
NewOp {
client_id: 1,
pos: 0,
},
Sync { from: 0, to: 1 },
Delete {
client_id: 2,
pos: 4,
len: 4,
},
Delete {
client_id: 0,
pos: 0,
len: 0,
},
Delete {
client_id: 0,
pos: 1,
len: 1,
},
NewOp {
client_id: 0,
pos: 1,
},
NewOp {
client_id: 0,
pos: 1,
},
NewOp {
client_id: 0,
pos: 1,
},
Delete {
client_id: 0,
pos: 0,
len: 2,
},
Delete {
client_id: 0,
pos: 0,
len: 4,
},
Sync { from: 0, to: 1 },
Delete {
client_id: 1,
pos: 0,
len: 0,
},
NewOp {
client_id: 0,
pos: 3,
},
NewOp {
client_id: 1,
pos: 0,
},
Delete {
client_id: 0,
pos: 0,
len: 2,
},
NewOp {
client_id: 0,
pos: 1,
},
NewOp {
client_id: 0,
pos: 1,
},
NewOp {
client_id: 0,
pos: 2,
},
NewOp {
client_id: 0,
pos: 2,
},
NewOp {
client_id: 0,
pos: 1,
},
Delete {
client_id: 0,
pos: 4,
len: 3,
},
NewOp {
client_id: 2,
pos: 3,
},
NewOp {
client_id: 1,
pos: 3,
},
NewOp {
client_id: 0,
pos: 1,
},
Delete {
client_id: 2,
pos: 0,
len: 0,
},
Delete {
client_id: 0,
pos: 0,
len: 0,
},
Delete {
client_id: 0,
pos: 0,
len: 0,
},
Delete {
client_id: 0,
pos: 0,
len: 0,
},
NewOp {
client_id: 0,
pos: 4,
},
Delete {
client_id: 0,
pos: 2,
len: 0,
},
Sync { from: 2, to: 2 },
Delete {
client_id: 2,
pos: 0,
len: 3,
},
Delete {
client_id: 0,
pos: 4,
len: 1,
},
Delete {
client_id: 0,
pos: 0,
len: 0,
},
Delete {
client_id: 0,
pos: 0,
len: 0,
},
Sync { from: 0, to: 0 },
Delete {
client_id: 0,
pos: 0,
len: 3,
},
Delete {
client_id: 0,
pos: 0,
len: 1,
},
Delete {
client_id: 1,
pos: 0,
len: 4,
},
NewOp {
client_id: 2,
pos: 0,
},
Delete {
client_id: 1,
pos: 0,
len: 0,
},
Delete {
client_id: 0,
pos: 1,
len: 1,
},
NewOp {
client_id: 0,
pos: 1,
},
NewOp {
client_id: 0,
pos: 1,
},
NewOp {
client_id: 2,
pos: 1,
},
NewOp {
client_id: 1,
pos: 4,
},
Delete {
client_id: 0,
pos: 1,
len: 4,
},
Delete {
client_id: 0,
pos: 4,
len: 3,
},
NewOp {
client_id: 2,
pos: 0,
client_id: 252,
pos: 252,
},
],
)
@ -733,9 +376,74 @@ pub mod fuzz {
#[test]
fn normalize() {
let mut actions = vec![];
let mut actions = vec![
Delete {
client_id: 252,
pos: 252,
len: 252,
},
Delete {
client_id: 39,
pos: 252,
len: 178,
},
Delete {
client_id: 252,
pos: 252,
len: 252,
},
Delete {
client_id: 252,
pos: 252,
len: 252,
},
Delete {
client_id: 145,
pos: 145,
len: 252,
},
Delete {
client_id: 252,
pos: 178,
len: 178,
},
Delete {
client_id: 252,
pos: 252,
len: 178,
},
Delete {
client_id: 252,
pos: 252,
len: 252,
},
NewOp {
client_id: 145,
pos: 252,
},
Delete {
client_id: 252,
pos: 178,
len: 252,
},
Sync { from: 0, to: 0 },
Delete {
client_id: 252,
pos: 178,
len: 178,
},
Delete {
client_id: 252,
pos: 252,
len: 252,
},
NewOp {
client_id: 252,
pos: 252,
},
];
crdt_list::test::normalize_actions(&mut actions, 3, 5);
crdt_list::test::normalize_actions(&mut actions, 5, 100);
dbg!(actions);
}
}

View file

@ -204,10 +204,6 @@ impl<'tree, T: Rle, A: RleTreeTrait<T>> UnsafeCursor<'tree, T, A> {
while shift > 0 {
let diff = leaf.children[self.index].content_len() - self.offset;
#[cfg(test)]
{
leaf.check();
}
match shift.cmp(&diff) {
std::cmp::Ordering::Less => {
self.offset += shift;

View file

@ -140,13 +140,13 @@ impl<T: Rle, const MAX_CHILD: usize> RleTreeTrait<T> for CumulateTreeTrait<T, MA
last_cache = match child {
Node::Internal(x) => {
if index <= x.cache {
return FindPosResult::new(i, index, get_pos(index, child.len()));
return FindPosResult::new(i, index, Position::get_pos(index, child.len()));
}
x.cache
}
Node::Leaf(x) => {
if index <= x.cache {
return FindPosResult::new(i, index, get_pos(index, child.len()));
return FindPosResult::new(i, index, Position::get_pos(index, child.len()));
}
x.cache
}
@ -169,7 +169,7 @@ impl<T: Rle, const MAX_CHILD: usize> RleTreeTrait<T> for CumulateTreeTrait<T, MA
for (i, child) in node.children().iter().enumerate() {
if index < HasLength::len(&**child) {
return FindPosResult::new(i, index, get_pos(index, child.len()));
return FindPosResult::new(i, index, Position::get_pos(index, child.len()));
}
index -= HasLength::len(&**child);
@ -199,14 +199,15 @@ impl<T: Rle, const MAX_CHILD: usize> RleTreeTrait<T> for CumulateTreeTrait<T, MA
}
}
#[inline]
fn get_pos(index: usize, len: usize) -> Position {
if index == 0 {
Position::Start
} else if index == len {
Position::End
} else {
Position::Middle
impl Position {
pub fn get_pos(index: usize, len: usize) -> Position {
if index == 0 {
Position::Start
} else if index == len {
Position::End
} else {
Position::Middle
}
}
}