diff --git a/.vscode/settings.json b/.vscode/settings.json index be139fdc..edeca986 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,7 +1,5 @@ { - "cSpell.words": [ - "smstring" - ], + "cSpell.words": ["smstring", "yspan"], "rust-analyzer.runnableEnv": { "RUST_BACKTRACE": "full" } diff --git a/crates/loro-core/src/container/map/tests.rs b/crates/loro-core/src/container/map/tests.rs index 422e5ccb..9b63f070 100644 --- a/crates/loro-core/src/container/map/tests.rs +++ b/crates/loro-core/src/container/map/tests.rs @@ -2,21 +2,13 @@ use std::collections::HashMap; - use fxhash::FxHashMap; use proptest::prelude::*; use proptest::proptest; use crate::value::proptest::gen_insert_value; -use crate::{ - container::{Container}, - fx_map, - value::InsertValue, - LoroCore, LoroValue, -}; - - +use crate::{container::Container, fx_map, value::InsertValue, LoroCore, LoroValue}; #[test] fn basic() { diff --git a/crates/loro-core/src/container/text/text_content.rs b/crates/loro-core/src/container/text/text_content.rs index 2e220581..44a3697f 100644 --- a/crates/loro-core/src/container/text/text_content.rs +++ b/crates/loro-core/src/container/text/text_content.rs @@ -4,11 +4,13 @@ use enum_as_inner::EnumAsInner; use crate::id::ID; +pub(super) type TextPointer = Range; + #[derive(Debug, EnumAsInner)] pub(super) enum TextOpContent { Insert { id: ID, - text: Range, + text: TextPointer, pos: usize, }, Delete { diff --git a/crates/loro-core/src/container/text/tracker.rs b/crates/loro-core/src/container/text/tracker.rs index 9a905472..cfdc9b88 100644 --- a/crates/loro-core/src/container/text/tracker.rs +++ b/crates/loro-core/src/container/text/tracker.rs @@ -1,12 +1,19 @@ -use crate::{op::Op, span::IdSpan, VersionVector}; +use crate::{ + op::{utils::downcast_ref, Op}, + span::IdSpan, + VersionVector, +}; -use self::cursor_map::CursorMap; +use self::{content_map::ContentMap, cursor_map::CursorMap}; + +use super::text_content::TextOpContent; mod content_map; mod cursor_map; mod y_span; struct Tracker { + content: ContentMap, index: CursorMap, } @@ -14,7 +21,23 @@ impl Tracker { fn turn_on(&mut self, _id: IdSpan) {} fn turn_off(&mut self, _id: IdSpan) {} fn checkout(&mut self, _vv: VersionVector) {} - fn apply(&mut self, _content: &Op) {} + + fn apply(&mut self, op: &Op) { + match &op.content { + crate::op::OpContent::Normal { content } => { + if let Some(textContent) = downcast_ref::(&**content) { + match textContent { + TextOpContent::Insert { id, text, pos } => { + let yspan = self.content.new_yspan_at_pos(*id, *pos, text.clone()); + } + TextOpContent::Delete { id, pos, len } => todo!(), + } + } + } + crate::op::OpContent::Undo { .. } => todo!(), + crate::op::OpContent::Redo { .. } => todo!(), + } + } } #[cfg(test)] @@ -23,6 +46,7 @@ mod test { fn create_tracker() -> Tracker { Tracker { + content: Default::default(), index: Default::default(), } } diff --git a/crates/loro-core/src/container/text/tracker/content_map.rs b/crates/loro-core/src/container/text/tracker/content_map.rs index 0ff7e07f..88fda08f 100644 --- a/crates/loro-core/src/container/text/tracker/content_map.rs +++ b/crates/loro-core/src/container/text/tracker/content_map.rs @@ -1,13 +1,141 @@ use std::ops::{Deref, DerefMut}; -use rle::{rle_tree::SafeCursorMut, RleTree}; +use moveit::new::of; +use rle::{ + rle_tree::{Position, SafeCursor, SafeCursorMut, UnsafeCursor}, + HasLength, RleTree, +}; + +use crate::{container::text::text_content::TextPointer, id::ID}; use super::y_span::{StatusChange, YSpan, YSpanTreeTrait}; #[repr(transparent)] -#[derive(Debug)] +#[derive(Debug, Default)] pub(super) struct ContentMap(RleTree); +struct CursorWithId<'tree> { + id: ID, + cursor: UnsafeCursor<'tree, 'static, YSpan, YSpanTreeTrait>, +} + +impl ContentMap { + #[inline] + pub fn new_yspan_at_pos(&mut self, id: ID, pos: usize, text: TextPointer) -> YSpan { + let (left, right) = self.get_sibling_at(pos); + YSpan { + origin_left: left.map(|x| x.id).unwrap_or_else(ID::null), + origin_right: right.map(|x| x.id).unwrap_or_else(ID::null), + id, + text, + status: Default::default(), + } + } + + fn get_sibling_at(&self, pos: usize) -> (Option>, Option>) { + self.with_tree(|tree| { + if let Some(cursor) = tree.get(pos) { + let cursor: SafeCursor<'_, 'static, YSpan, YSpanTreeTrait> = + unsafe { std::mem::transmute(cursor) }; + let (mut prev, mut next) = match cursor.pos() { + Position::Start => { + if cursor.as_ref().can_be_origin() { + let id = cursor.as_ref().id; + ( + None, + Some(CursorWithId { + id, + cursor: cursor.unwrap(), + }), + ) + } else { + (None, None) + } + } + Position::Middle => { + if cursor.as_ref().can_be_origin() { + 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(), + }), + ) + } else { + (None, None) + } + } + Position::End => { + if cursor.as_ref().can_be_origin() { + let mut prev_offset_cursor = cursor.unwrap(); + prev_offset_cursor.offset -= 1; + ( + Some(CursorWithId { + id: cursor.as_ref().last_id(), + cursor: prev_offset_cursor, + }), + None, + ) + } else { + (None, None) + } + } + _ => { + unreachable!() + } + }; + + if prev.is_none() { + let mut prev_cursor = cursor.prev(); + while let Some(prev_inner) = prev_cursor { + if prev_inner.as_ref().status.is_activated() { + let cursor = prev_inner; + let offset = cursor.as_ref().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(); + } + } + + if next.is_none() { + let mut next_cursor = cursor.next(); + while let Some(next_inner) = next_cursor { + if next_inner.as_ref().status.is_activated() { + let mut cursor = next_inner.unwrap(); + cursor.offset = 0; + cursor.pos = Position::Start; + next = Some(CursorWithId { + id: next_inner.as_ref().id, + cursor, + }); + break; + } + next_cursor = next_inner.next(); + } + } + + (prev, next) + } else { + (None, None) + } + }) + } +} + impl Deref for ContentMap { type Target = RleTree; diff --git a/crates/loro-core/src/container/text/tracker/cursor_map.rs b/crates/loro-core/src/container/text/tracker/cursor_map.rs index aebf8969..fc47564e 100644 --- a/crates/loro-core/src/container/text/tracker/cursor_map.rs +++ b/crates/loro-core/src/container/text/tracker/cursor_map.rs @@ -8,6 +8,7 @@ use crate::span::IdSpan; use super::y_span::{YSpan, YSpanTreeTrait}; +#[non_exhaustive] #[derive(Debug, Clone, EnumAsInner)] pub(super) enum Marker { Insert { @@ -15,6 +16,7 @@ pub(super) enum Marker { len: usize, }, Delete(IdSpan), + // TODO: REDO, UNDO } impl Sliceable for Marker { @@ -31,7 +33,10 @@ impl Sliceable for Marker { impl HasLength for Marker { fn len(&self) -> usize { - todo!() + match self { + Marker::Insert { ptr, len } => *len, + Marker::Delete(span) => span.len(), + } } } diff --git a/crates/loro-core/src/container/text/tracker/y_span.rs b/crates/loro-core/src/container/text/tracker/y_span.rs index ceb79bbe..cc25e5c1 100644 --- a/crates/loro-core/src/container/text/tracker/y_span.rs +++ b/crates/loro-core/src/container/text/tracker/y_span.rs @@ -1,4 +1,6 @@ -use crate::{id::Counter, ContentType, InsertContent, ID}; +use crate::{ + container::text::text_content::TextPointer, id::Counter, ContentType, InsertContent, ID, +}; use rle::{rle_tree::tree_trait::CumulateTreeTrait, HasLength, Mergable, Sliceable}; #[derive(Debug, Clone, PartialEq, Eq, Default)] @@ -36,7 +38,7 @@ pub(super) struct YSpan { pub origin_left: ID, pub origin_right: ID, pub id: ID, - pub len: usize, + pub text: TextPointer, pub status: Status, } @@ -52,6 +54,20 @@ pub(super) enum StatusChange { pub(super) type YSpanTreeTrait = CumulateTreeTrait; +impl YSpan { + #[inline] + pub fn last_id(&self) -> ID { + self.id + .inc(std::iter::ExactSizeIterator::len(&self.text) as i32 - 1) + } + + #[inline] + pub fn can_be_origin(&self) -> bool { + debug_assert!(rle::HasLength::len(&self.text) > 0); + self.status.is_activated() + } +} + impl Mergable for YSpan { fn is_mergable(&self, other: &Self, _: &()) -> bool { other.id.client_id == self.id.client_id @@ -60,11 +76,12 @@ impl Mergable for YSpan { && self.id.counter + self.len() as Counter - 1 == other.origin_left.counter && self.origin_right == other.origin_right && self.status == other.status + && self.text.is_mergable(&other.text, &()) } fn merge(&mut self, other: &Self, _: &()) { self.origin_right = other.origin_right; - self.len += other.len; + self.text.merge(&other.text, &()); } } @@ -75,7 +92,7 @@ impl Sliceable for YSpan { origin_left: self.origin_left, origin_right: self.origin_right, id: self.id, - len: to - from, + text: self.text.slice(from, to), status: self.status.clone(), } } else { @@ -89,7 +106,7 @@ impl Sliceable for YSpan { client_id: self.id.client_id, counter: self.id.counter + from as Counter, }, - len: to - from, + text: self.text.slice(from, to), status: self.status.clone(), } } @@ -103,9 +120,10 @@ impl InsertContent for YSpan { } impl HasLength for YSpan { + #[inline] fn len(&self) -> usize { if self.status.is_activated() { - self.len + rle::HasLength::len(&self.text) } else { 0 } @@ -119,7 +137,7 @@ mod test { id::ROOT_ID, ContentType, Op, OpContent, ID, }; - use rle::RleVec; + use rle::{HasLength, RleVec}; use super::YSpan; @@ -133,7 +151,7 @@ mod test { origin_left: ID::new(0, 0), origin_right: ID::null(), id: ID::new(0, 1), - len: 1, + text: 0..1, status: Default::default(), }), }, @@ -149,7 +167,7 @@ mod test { origin_left: ID::new(0, 1), origin_right: ID::null(), id: ID::new(0, 2), - len: 1, + text: 1..2, status: Default::default(), }), }, @@ -163,7 +181,7 @@ mod test { assert_eq!(merged.insert_content().id(), ContentType::Text); let text_content = crate::op::utils::downcast_ref::(&**merged.insert_content()).unwrap(); - assert_eq!(text_content.len, 2); + assert_eq!(text_content.len(), 2); } #[test] @@ -176,7 +194,7 @@ mod test { origin_left: ID::new(0, 0), origin_right: ID::null(), id: ID::new(0, 1), - len: 4, + text: 2..6, status: Default::default(), }), }, @@ -192,7 +210,7 @@ mod test { origin_left: ID::new(0, 0), origin_right: ID::new(0, 1), id: ID::new(0, 5), - len: 4, + text: 3..7, status: Default::default(), }), }, @@ -204,11 +222,11 @@ mod test { assert_eq!(vec.merged_len(), 2); assert_eq!( vec.slice_iter(2, 6) - .map(|x| crate::op::utils::downcast_ref::( - &**x.into_inner().insert_content() - ) - .unwrap() - .len) + .map(|x| rle::HasLength::len( + &crate::op::utils::downcast_ref::(&**x.into_inner().insert_content()) + .unwrap() + .text + )) .collect::>(), vec![2, 2] ) diff --git a/crates/rle/src/rle_tree.rs b/crates/rle/src/rle_tree.rs index 554171d9..c62fce97 100644 --- a/crates/rle/src/rle_tree.rs +++ b/crates/rle/src/rle_tree.rs @@ -2,9 +2,10 @@ use self::node::{InternalNode, LeafNode, Node}; use crate::Rle; pub(self) use bumpalo::collections::vec::Vec as BumpVec; use bumpalo::Bump; -pub use cursor::{SafeCursor, SafeCursorMut}; +pub use cursor::{SafeCursor, SafeCursorMut, UnsafeCursor}; use ouroboros::self_referencing; use std::marker::{PhantomData, PhantomPinned}; +pub use tree_trait::Position; use tree_trait::RleTreeTrait; mod cursor; @@ -92,7 +93,12 @@ impl<'bump, T: Rle, A: RleTreeTrait> RleTreeRaw<'bump, T, A> { return None; } - return Some(SafeCursor::new(leaf.into(), result.child_index, result.pos)); + return Some(SafeCursor::new( + leaf.into(), + result.child_index, + result.offset, + result.pos, + )); } } } @@ -119,7 +125,12 @@ impl<'bump, T: Rle, A: RleTreeTrait> RleTreeRaw<'bump, T, A> { return None; } - return Some(SafeCursor::new(leaf.into(), result.child_index, result.pos)); + return Some(SafeCursor::new( + leaf.into(), + result.child_index, + result.offset, + result.pos, + )); } } } diff --git a/crates/rle/src/rle_tree/cursor.rs b/crates/rle/src/rle_tree/cursor.rs index 33135d78..968a76c8 100644 --- a/crates/rle/src/rle_tree/cursor.rs +++ b/crates/rle/src/rle_tree/cursor.rs @@ -5,9 +5,10 @@ use crate::{Rle, RleTreeTrait}; use super::{node::LeafNode, tree_trait::Position}; pub struct UnsafeCursor<'tree, 'bump, T: Rle, A: RleTreeTrait> { - pub(crate) leaf: NonNull>, - pub(crate) index: usize, - pub(crate) pos: Position, + pub leaf: NonNull>, + pub index: usize, + pub offset: usize, + pub pos: Position, _phantom: PhantomData<&'tree usize>, } @@ -18,6 +19,7 @@ impl<'tree, 'bump, T: Rle, A: RleTreeTrait> Clone for UnsafeCursor<'tree, 'bu leaf: self.leaf, index: self.index, pos: self.pos, + offset: self.offset, _phantom: Default::default(), } } @@ -45,11 +47,17 @@ impl<'tree, 'bump: 'tree, T: Rle, A: RleTreeTrait> Copy for SafeCursor<'tree, impl<'tree, 'bump: 'tree, T: Rle, A: RleTreeTrait> UnsafeCursor<'tree, 'bump, T, A> { #[inline] - pub(crate) fn new(leaf: NonNull>, index: usize, pos: Position) -> Self { + pub(crate) fn new( + leaf: NonNull>, + index: usize, + offset: usize, + pos: Position, + ) -> Self { Self { leaf, index, pos, + offset, _phantom: PhantomData, } } @@ -81,20 +89,20 @@ impl<'tree, 'bump: 'tree, T: Rle, A: RleTreeTrait> UnsafeCursor<'tree, 'bump, pub unsafe fn next(&self) -> Option { let leaf = self.leaf.as_ref(); if leaf.children.len() > self.index + 1 { - return Some(Self::new(self.leaf, self.index + 1, self.pos)); + return Some(Self::new(self.leaf, self.index + 1, 0, Position::Start)); } - leaf.next.map(|next| Self::new(next, 0, self.pos)) + leaf.next.map(|next| Self::new(next, 0, 0, Position::Start)) } pub unsafe fn prev(&self) -> Option { let leaf = self.leaf.as_ref(); if self.index > 0 { - return Some(Self::new(self.leaf, self.index - 1, self.pos)); + return Some(Self::new(self.leaf, self.index - 1, 0, Position::Start)); } leaf.prev - .map(|prev| Self::new(prev, prev.as_ref().children.len() - 1, self.pos)) + .map(|prev| Self::new(prev, prev.as_ref().children.len() - 1, 0, Position::Start)) } } @@ -130,12 +138,30 @@ impl<'tree, 'bump: 'tree, T: Rle, A: RleTreeTrait> SafeCursor<'tree, 'bump, T pub fn index(&self) -> usize { self.0.index } -} -impl<'tree, 'bump: 'tree, T: Rle, A: RleTreeTrait> SafeCursor<'tree, 'bump, T, A> { #[inline] - pub(crate) fn new(leaf: NonNull>, index: usize, pos: Position) -> Self { - Self(UnsafeCursor::new(leaf, index, pos)) + pub fn pos(&self) -> Position { + self.0.pos + } + + #[inline] + pub fn offset(&self) -> usize { + self.0.offset + } + + #[inline] + pub(crate) fn new( + leaf: NonNull>, + index: usize, + offset: usize, + pos: Position, + ) -> Self { + Self(UnsafeCursor::new(leaf, index, offset, pos)) + } + + #[inline] + pub fn unwrap(self) -> UnsafeCursor<'tree, 'bump, T, A> { + self.0 } } @@ -167,12 +193,17 @@ impl<'tree, 'bump: 'tree, T: Rle, A: RleTreeTrait> SafeCursorMut<'tree, 'bump impl<'tree, 'bump: 'tree, T: Rle, A: RleTreeTrait> SafeCursorMut<'tree, 'bump, T, A> { #[inline] - pub(crate) fn new(leaf: NonNull>, index: usize, pos: Position) -> Self { - Self(UnsafeCursor::new(leaf, index, pos)) + pub(crate) fn new( + leaf: NonNull>, + index: usize, + offset: usize, + pos: Position, + ) -> Self { + Self(UnsafeCursor::new(leaf, index, offset, pos)) } #[inline] - fn as_mut_(&mut self) -> &'tree mut T { + fn as_tree_mut(&mut self) -> &'tree mut T { unsafe { self.0.as_mut() } } @@ -189,6 +220,36 @@ impl<'tree, 'bump: 'tree, T: Rle, A: RleTreeTrait> SafeCursorMut<'tree, 'bump } } } + + #[inline] + pub fn unwrap(self) -> UnsafeCursor<'tree, 'bump, T, A> { + self.0 + } + + #[inline] + pub fn next(&self) -> Option { + unsafe { self.0.next().map(|x| Self(x)) } + } + + #[inline] + pub fn prev(&self) -> Option { + unsafe { self.0.prev().map(|x| Self(x)) } + } + + #[inline] + pub fn index(&self) -> usize { + self.0.index + } + + #[inline] + pub fn pos(&self) -> Position { + self.0.pos + } + + #[inline] + pub fn offset(&self) -> usize { + self.0.offset + } } impl<'tree, 'bump: 'tree, T: Rle, A: RleTreeTrait> AsMut diff --git a/crates/rle/src/rle_tree/iter.rs b/crates/rle/src/rle_tree/iter.rs index 0ae834df..408185b4 100644 --- a/crates/rle/src/rle_tree/iter.rs +++ b/crates/rle/src/rle_tree/iter.rs @@ -70,6 +70,7 @@ impl<'rf, 'bump, T: Rle, A: RleTreeTrait> Iterator for Iter<'rf, 'bump, T, A> return Some(SafeCursor::new( node.into(), self.child_index - 1, + 0, Position::Start, )); } diff --git a/crates/rle/src/rle_tree/node/leaf_impl.rs b/crates/rle/src/rle_tree/node/leaf_impl.rs index 7c4c7a3f..5b2ea9e5 100644 --- a/crates/rle/src/rle_tree/node/leaf_impl.rs +++ b/crates/rle/src/rle_tree/node/leaf_impl.rs @@ -51,13 +51,13 @@ impl<'bump, T: Rle, A: RleTreeTrait> LeafNode<'bump, T, A> { #[inline] pub fn get_cursor<'tree>(&'tree self, pos: A::Int) -> SafeCursor<'tree, 'bump, T, A> { let result = A::find_pos_leaf(self, pos); - SafeCursor::new(self.into(), result.child_index, result.pos) + SafeCursor::new(self.into(), result.child_index, result.offset, result.pos) } #[inline] pub fn get_cursor_mut<'b>(&'b mut self, pos: A::Int) -> SafeCursorMut<'b, 'bump, T, A> { let result = A::find_pos_leaf(self, pos); - SafeCursorMut::new(self.into(), result.child_index, result.pos) + SafeCursorMut::new(self.into(), result.child_index, result.offset, result.pos) } pub fn push_child( diff --git a/crates/rle/src/rle_tree/tree_trait.rs b/crates/rle/src/rle_tree/tree_trait.rs index cf92a3ab..ab043497 100644 --- a/crates/rle/src/rle_tree/tree_trait.rs +++ b/crates/rle/src/rle_tree/tree_trait.rs @@ -12,10 +12,11 @@ use super::node::{InternalNode, LeafNode, Node}; /// - Or it is before/after a node. #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum Position { + Before, Start, Middle, + // can after and end be merged together? End, - Before, After, } diff --git a/justfile b/justfile index 9674d29d..f139dc39 100644 --- a/justfile +++ b/justfile @@ -4,13 +4,13 @@ build: test *FLAGS: RUST_BACKTRACE=full cargo nextest run {{FLAGS}} -# test without proptest -test-fast *FLAGS: - RUSTFLAGS='--cfg no_proptest' cargo nextest run {{FLAGS}} +# test with proptest +test-prop *FLAGS: + RUST_BACKTRACE=full RUSTFLAGS='--cfg proptest' cargo nextest run {{FLAGS}} # test with slower proptest -test-slow *FLAGS: - RUSTFLAGS='--cfg slow_proptest' cargo nextest run {{FLAGS}} +test-slowprop *FLAGS: + RUST_BACKTRACE=full RUSTFLAGS='--cfg slow_proptest' cargo nextest run {{FLAGS}} check-unsafe: env RUSTFLAGS="-Funsafe-code --cap-lints=warn" cargo check