feat: init content map

This commit is contained in:
Zixuan Chen 2022-09-09 23:31:49 +08:00
parent 3b271b5ede
commit bd1b0a2215
13 changed files with 303 additions and 62 deletions

View file

@ -1,7 +1,5 @@
{
"cSpell.words": [
"smstring"
],
"cSpell.words": ["smstring", "yspan"],
"rust-analyzer.runnableEnv": {
"RUST_BACKTRACE": "full"
}

View file

@ -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() {

View file

@ -4,11 +4,13 @@ use enum_as_inner::EnumAsInner;
use crate::id::ID;
pub(super) type TextPointer = Range<usize>;
#[derive(Debug, EnumAsInner)]
pub(super) enum TextOpContent {
Insert {
id: ID,
text: Range<usize>,
text: TextPointer,
pos: usize,
},
Delete {

View file

@ -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::<TextOpContent>(&**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(),
}
}

View file

@ -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<YSpan, YSpanTreeTrait>);
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<CursorWithId<'_>>, Option<CursorWithId<'_>>) {
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<YSpan, YSpanTreeTrait>;

View file

@ -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(),
}
}
}

View file

@ -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<YSpan, 10>;
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::<YSpan>(&**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::<YSpan>(
&**x.into_inner().insert_content()
)
.unwrap()
.len)
.map(|x| rle::HasLength::len(
&crate::op::utils::downcast_ref::<YSpan>(&**x.into_inner().insert_content())
.unwrap()
.text
))
.collect::<Vec<usize>>(),
vec![2, 2]
)

View file

@ -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<T>> 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<T>> 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,
));
}
}
}

View file

@ -5,9 +5,10 @@ use crate::{Rle, RleTreeTrait};
use super::{node::LeafNode, tree_trait::Position};
pub struct UnsafeCursor<'tree, 'bump, T: Rle, A: RleTreeTrait<T>> {
pub(crate) leaf: NonNull<LeafNode<'bump, T, A>>,
pub(crate) index: usize,
pub(crate) pos: Position,
pub leaf: NonNull<LeafNode<'bump, T, A>>,
pub index: usize,
pub offset: usize,
pub pos: Position,
_phantom: PhantomData<&'tree usize>,
}
@ -18,6 +19,7 @@ impl<'tree, 'bump, T: Rle, A: RleTreeTrait<T>> 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<T>> Copy for SafeCursor<'tree,
impl<'tree, 'bump: 'tree, T: Rle, A: RleTreeTrait<T>> UnsafeCursor<'tree, 'bump, T, A> {
#[inline]
pub(crate) fn new(leaf: NonNull<LeafNode<'bump, T, A>>, index: usize, pos: Position) -> Self {
pub(crate) fn new(
leaf: NonNull<LeafNode<'bump, T, A>>,
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<T>> UnsafeCursor<'tree, 'bump,
pub unsafe fn next(&self) -> Option<Self> {
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<Self> {
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<T>> SafeCursor<'tree, 'bump, T
pub fn index(&self) -> usize {
self.0.index
}
}
impl<'tree, 'bump: 'tree, T: Rle, A: RleTreeTrait<T>> SafeCursor<'tree, 'bump, T, A> {
#[inline]
pub(crate) fn new(leaf: NonNull<LeafNode<'bump, T, A>>, 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<LeafNode<'bump, T, A>>,
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<T>> SafeCursorMut<'tree, 'bump
impl<'tree, 'bump: 'tree, T: Rle, A: RleTreeTrait<T>> SafeCursorMut<'tree, 'bump, T, A> {
#[inline]
pub(crate) fn new(leaf: NonNull<LeafNode<'bump, T, A>>, index: usize, pos: Position) -> Self {
Self(UnsafeCursor::new(leaf, index, pos))
pub(crate) fn new(
leaf: NonNull<LeafNode<'bump, T, A>>,
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<T>> SafeCursorMut<'tree, 'bump
}
}
}
#[inline]
pub fn unwrap(self) -> UnsafeCursor<'tree, 'bump, T, A> {
self.0
}
#[inline]
pub fn next(&self) -> Option<Self> {
unsafe { self.0.next().map(|x| Self(x)) }
}
#[inline]
pub fn prev(&self) -> Option<Self> {
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<T>> AsMut<T>

View file

@ -70,6 +70,7 @@ impl<'rf, 'bump, T: Rle, A: RleTreeTrait<T>> Iterator for Iter<'rf, 'bump, T, A>
return Some(SafeCursor::new(
node.into(),
self.child_index - 1,
0,
Position::Start,
));
}

View file

@ -51,13 +51,13 @@ impl<'bump, T: Rle, A: RleTreeTrait<T>> 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<F>(

View file

@ -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,
}

View file

@ -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