mirror of
https://github.com/loro-dev/loro.git
synced 2025-02-06 12:25:03 +00:00
feat: list init
This commit is contained in:
parent
da8b2668e7
commit
29c4d2011e
12 changed files with 293 additions and 162 deletions
|
@ -17,6 +17,7 @@ pin-project = "1.0.10"
|
|||
serde = {version = "1.0.140", features = ["derive"]}
|
||||
thiserror = "1.0.31"
|
||||
im = "15.1.0"
|
||||
enum-as-inner = "0.5.1"
|
||||
|
||||
[dev-dependencies]
|
||||
proptest = "1.0.0"
|
||||
|
|
|
@ -20,6 +20,7 @@ use std::{
|
|||
mod container_content;
|
||||
mod manager;
|
||||
|
||||
pub mod list;
|
||||
pub mod map;
|
||||
pub mod text;
|
||||
pub use container_content::*;
|
||||
|
|
0
crates/loro-core/src/container/list/list_container.rs
Normal file
0
crates/loro-core/src/container/list/list_container.rs
Normal file
28
crates/loro-core/src/container/list/list_content.rs
Normal file
28
crates/loro-core/src/container/list/list_content.rs
Normal file
|
@ -0,0 +1,28 @@
|
|||
use rle::{HasLength, Mergable, Sliceable};
|
||||
|
||||
use crate::{id::ID, value::InsertValue, ContentType, InsertContent, InternalString};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct ListInsertContent {
|
||||
pub(crate) key: u32,
|
||||
pub(crate) value: InsertValue,
|
||||
}
|
||||
|
||||
impl Mergable for ListInsertContent {}
|
||||
impl Sliceable for ListInsertContent {
|
||||
fn slice(&self, from: usize, to: usize) -> Self {
|
||||
assert!(from == 0 && to == 1);
|
||||
self.clone()
|
||||
}
|
||||
}
|
||||
impl HasLength for ListInsertContent {
|
||||
fn len(&self) -> usize {
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
impl InsertContent for ListInsertContent {
|
||||
fn id(&self) -> ContentType {
|
||||
ContentType::Map
|
||||
}
|
||||
}
|
2
crates/loro-core/src/container/list/mod.rs
Normal file
2
crates/loro-core/src/container/list/mod.rs
Normal file
|
@ -0,0 +1,2 @@
|
|||
mod list_container;
|
||||
mod list_content;
|
|
@ -1,3 +1,5 @@
|
|||
mod text_container;
|
||||
mod y_span;
|
||||
pub use y_span::*;
|
||||
mod text_content;
|
||||
pub use text_content::*;
|
||||
mod tracker;
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
use std::{pin::Pin, ptr::NonNull, rc::Weak};
|
||||
|
||||
use fxhash::FxHashMap;
|
||||
use rle::RleVec;
|
||||
use serde::Serialize;
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use crate::{
|
||||
change::Change,
|
||||
container::{Container, ContainerID, ContainerType},
|
||||
id::{Counter, ID},
|
||||
op::{utils::downcast_ref, Op},
|
||||
op::{OpContent, OpProxy},
|
||||
span::IdSpan,
|
||||
value::{InsertValue, LoroValue},
|
||||
version::TotalOrderStamp,
|
||||
ClientID, InternalString, Lamport, LogStore, OpType,
|
||||
};
|
||||
|
||||
use super::y_span::YSpan;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct DagNode {
|
||||
id: IdSpan,
|
||||
deps: SmallVec<[ID; 2]>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct TextContainer {
|
||||
id: ContainerID,
|
||||
sub_dag: FxHashMap<ClientID, RleVec<DagNode, ()>>,
|
||||
log_store: NonNull<LogStore>,
|
||||
}
|
||||
|
||||
impl TextContainer {
|
||||
pub fn insert(&mut self, pos: usize, text: &str) -> ID {
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub fn delete(&mut self, pos: usize, len: usize) {}
|
||||
}
|
||||
|
||||
impl Container for TextContainer {
|
||||
fn id(&self) -> &ContainerID {
|
||||
&self.id
|
||||
}
|
||||
|
||||
fn type_(&self) -> ContainerType {
|
||||
ContainerType::Text
|
||||
}
|
||||
|
||||
fn apply(&mut self, op: &OpProxy) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn checkout_version(&mut self, vv: &crate::VersionVector) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn get_value(&mut self) -> &LoroValue {
|
||||
todo!()
|
||||
}
|
||||
}
|
|
@ -1,162 +1,19 @@
|
|||
use crate::{id::Counter, ContentType, InsertContent, ID};
|
||||
use rle::{HasLength, Mergable, Sliceable};
|
||||
use std::ops::Range;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TextContent {
|
||||
origin_left: ID,
|
||||
origin_right: ID,
|
||||
id: ID,
|
||||
text: String,
|
||||
}
|
||||
|
||||
impl Mergable for TextContent {
|
||||
fn is_mergable(&self, other: &Self, _: &()) -> bool {
|
||||
other.id.client_id == self.id.client_id
|
||||
&& self.id.counter + self.len() as Counter == other.id.counter
|
||||
&& self.id.client_id == other.origin_left.client_id
|
||||
&& self.id.counter + self.len() as Counter - 1 == other.origin_left.counter
|
||||
&& self.origin_right == other.origin_right
|
||||
}
|
||||
|
||||
fn merge(&mut self, other: &Self, _: &()) {
|
||||
self.text.push_str(&other.text);
|
||||
}
|
||||
}
|
||||
|
||||
impl Sliceable for TextContent {
|
||||
fn slice(&self, from: usize, to: usize) -> Self {
|
||||
if from == 0 {
|
||||
TextContent {
|
||||
origin_left: self.origin_left,
|
||||
origin_right: self.origin_right,
|
||||
id: self.id,
|
||||
text: self.text[..to].to_owned(),
|
||||
}
|
||||
} else {
|
||||
TextContent {
|
||||
origin_left: ID {
|
||||
client_id: self.id.client_id,
|
||||
counter: self.id.counter + from as Counter - 1,
|
||||
},
|
||||
origin_right: self.origin_right,
|
||||
id: ID {
|
||||
client_id: self.id.client_id,
|
||||
counter: self.id.counter + from as Counter,
|
||||
},
|
||||
text: self.text[from..to].to_owned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl InsertContent for TextContent {
|
||||
fn id(&self) -> ContentType {
|
||||
ContentType::Text
|
||||
}
|
||||
}
|
||||
|
||||
impl HasLength for TextContent {
|
||||
fn len(&self) -> usize {
|
||||
self.text.len()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::{
|
||||
container::{ContainerID, ContainerType},
|
||||
id::ROOT_ID,
|
||||
ContentType, Op, OpContent, ID,
|
||||
};
|
||||
use rle::RleVec;
|
||||
|
||||
use super::TextContent;
|
||||
|
||||
#[test]
|
||||
fn test_merge() {
|
||||
let mut vec: RleVec<Op> = RleVec::new();
|
||||
vec.push(Op::new(
|
||||
ID::new(0, 1),
|
||||
OpContent::Normal {
|
||||
content: Box::new(TextContent {
|
||||
origin_left: ID::new(0, 0),
|
||||
origin_right: ID::null(),
|
||||
id: ID::new(0, 1),
|
||||
text: "a".to_owned(),
|
||||
}),
|
||||
},
|
||||
ContainerID::Normal {
|
||||
id: ROOT_ID,
|
||||
container_type: ContainerType::Text,
|
||||
},
|
||||
));
|
||||
vec.push(Op::new(
|
||||
ID::new(0, 2),
|
||||
OpContent::Normal {
|
||||
content: Box::new(TextContent {
|
||||
origin_left: ID::new(0, 1),
|
||||
origin_right: ID::null(),
|
||||
id: ID::new(0, 2),
|
||||
text: "b".to_owned(),
|
||||
}),
|
||||
},
|
||||
ContainerID::Normal {
|
||||
id: ROOT_ID,
|
||||
container_type: ContainerType::Text,
|
||||
},
|
||||
));
|
||||
assert_eq!(vec.merged_len(), 1);
|
||||
let merged = vec.get_merged(0).unwrap();
|
||||
assert_eq!(merged.insert_content().id(), ContentType::Text);
|
||||
let text_content =
|
||||
crate::op::utils::downcast_ref::<TextContent>(&**merged.insert_content()).unwrap();
|
||||
assert_eq!(text_content.text, "ab");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn slice() {
|
||||
let mut vec: RleVec<Op> = RleVec::new();
|
||||
vec.push(Op::new(
|
||||
ID::new(0, 1),
|
||||
OpContent::Normal {
|
||||
content: Box::new(TextContent {
|
||||
origin_left: ID::new(0, 0),
|
||||
origin_right: ID::null(),
|
||||
id: ID::new(0, 1),
|
||||
text: "1234".to_owned(),
|
||||
}),
|
||||
},
|
||||
ContainerID::Normal {
|
||||
id: ROOT_ID,
|
||||
container_type: ContainerType::Text,
|
||||
},
|
||||
));
|
||||
vec.push(Op::new(
|
||||
ID::new(0, 2),
|
||||
OpContent::Normal {
|
||||
content: Box::new(TextContent {
|
||||
origin_left: ID::new(0, 0),
|
||||
origin_right: ID::new(0, 1),
|
||||
id: ID::new(0, 5),
|
||||
text: "5678".to_owned(),
|
||||
}),
|
||||
},
|
||||
ContainerID::Normal {
|
||||
id: ROOT_ID,
|
||||
container_type: ContainerType::Text,
|
||||
},
|
||||
));
|
||||
assert_eq!(vec.merged_len(), 2);
|
||||
assert_eq!(
|
||||
vec.slice_iter(2, 6)
|
||||
.map(|x| crate::op::utils::downcast_ref::<TextContent>(
|
||||
&**x.into_inner().insert_content()
|
||||
)
|
||||
.unwrap()
|
||||
.text
|
||||
.clone())
|
||||
.collect::<Vec<String>>(),
|
||||
vec!["34", "56"]
|
||||
)
|
||||
}
|
||||
use enum_as_inner::EnumAsInner;
|
||||
|
||||
use crate::id::ID;
|
||||
|
||||
#[derive(Debug, EnumAsInner)]
|
||||
pub(super) enum TextContent {
|
||||
Insert {
|
||||
id: ID,
|
||||
text: Range<usize>,
|
||||
pos: usize,
|
||||
},
|
||||
Delete {
|
||||
id: ID,
|
||||
pos: usize,
|
||||
len: usize,
|
||||
},
|
||||
}
|
||||
|
|
12
crates/loro-core/src/container/text/tracker.rs
Normal file
12
crates/loro-core/src/container/text/tracker.rs
Normal file
|
@ -0,0 +1,12 @@
|
|||
use crate::{span::IdSpan, VersionVector};
|
||||
|
||||
use super::text_content::TextContent;
|
||||
|
||||
struct Tracker {}
|
||||
|
||||
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: TextContent) {}
|
||||
}
|
162
crates/loro-core/src/container/text/y_span.rs
Normal file
162
crates/loro-core/src/container/text/y_span.rs
Normal file
|
@ -0,0 +1,162 @@
|
|||
use crate::{id::Counter, ContentType, InsertContent, ID};
|
||||
use rle::{HasLength, Mergable, Sliceable};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct YSpan {
|
||||
origin_left: ID,
|
||||
origin_right: ID,
|
||||
id: ID,
|
||||
text: String,
|
||||
}
|
||||
|
||||
impl Mergable for YSpan {
|
||||
fn is_mergable(&self, other: &Self, _: &()) -> bool {
|
||||
other.id.client_id == self.id.client_id
|
||||
&& self.id.counter + self.len() as Counter == other.id.counter
|
||||
&& self.id.client_id == other.origin_left.client_id
|
||||
&& self.id.counter + self.len() as Counter - 1 == other.origin_left.counter
|
||||
&& self.origin_right == other.origin_right
|
||||
}
|
||||
|
||||
fn merge(&mut self, other: &Self, _: &()) {
|
||||
self.text.push_str(&other.text);
|
||||
}
|
||||
}
|
||||
|
||||
impl Sliceable for YSpan {
|
||||
fn slice(&self, from: usize, to: usize) -> Self {
|
||||
if from == 0 {
|
||||
YSpan {
|
||||
origin_left: self.origin_left,
|
||||
origin_right: self.origin_right,
|
||||
id: self.id,
|
||||
text: self.text[..to].to_owned(),
|
||||
}
|
||||
} else {
|
||||
YSpan {
|
||||
origin_left: ID {
|
||||
client_id: self.id.client_id,
|
||||
counter: self.id.counter + from as Counter - 1,
|
||||
},
|
||||
origin_right: self.origin_right,
|
||||
id: ID {
|
||||
client_id: self.id.client_id,
|
||||
counter: self.id.counter + from as Counter,
|
||||
},
|
||||
text: self.text[from..to].to_owned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl InsertContent for YSpan {
|
||||
fn id(&self) -> ContentType {
|
||||
ContentType::Text
|
||||
}
|
||||
}
|
||||
|
||||
impl HasLength for YSpan {
|
||||
fn len(&self) -> usize {
|
||||
self.text.len()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::{
|
||||
container::{ContainerID, ContainerType},
|
||||
id::ROOT_ID,
|
||||
ContentType, Op, OpContent, ID,
|
||||
};
|
||||
use rle::RleVec;
|
||||
|
||||
use super::YSpan;
|
||||
|
||||
#[test]
|
||||
fn test_merge() {
|
||||
let mut vec: RleVec<Op> = RleVec::new();
|
||||
vec.push(Op::new(
|
||||
ID::new(0, 1),
|
||||
OpContent::Normal {
|
||||
content: Box::new(YSpan {
|
||||
origin_left: ID::new(0, 0),
|
||||
origin_right: ID::null(),
|
||||
id: ID::new(0, 1),
|
||||
text: "a".to_owned(),
|
||||
}),
|
||||
},
|
||||
ContainerID::Normal {
|
||||
id: ROOT_ID,
|
||||
container_type: ContainerType::Text,
|
||||
},
|
||||
));
|
||||
vec.push(Op::new(
|
||||
ID::new(0, 2),
|
||||
OpContent::Normal {
|
||||
content: Box::new(YSpan {
|
||||
origin_left: ID::new(0, 1),
|
||||
origin_right: ID::null(),
|
||||
id: ID::new(0, 2),
|
||||
text: "b".to_owned(),
|
||||
}),
|
||||
},
|
||||
ContainerID::Normal {
|
||||
id: ROOT_ID,
|
||||
container_type: ContainerType::Text,
|
||||
},
|
||||
));
|
||||
assert_eq!(vec.merged_len(), 1);
|
||||
let merged = vec.get_merged(0).unwrap();
|
||||
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.text, "ab");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn slice() {
|
||||
let mut vec: RleVec<Op> = RleVec::new();
|
||||
vec.push(Op::new(
|
||||
ID::new(0, 1),
|
||||
OpContent::Normal {
|
||||
content: Box::new(YSpan {
|
||||
origin_left: ID::new(0, 0),
|
||||
origin_right: ID::null(),
|
||||
id: ID::new(0, 1),
|
||||
text: "1234".to_owned(),
|
||||
}),
|
||||
},
|
||||
ContainerID::Normal {
|
||||
id: ROOT_ID,
|
||||
container_type: ContainerType::Text,
|
||||
},
|
||||
));
|
||||
vec.push(Op::new(
|
||||
ID::new(0, 2),
|
||||
OpContent::Normal {
|
||||
content: Box::new(YSpan {
|
||||
origin_left: ID::new(0, 0),
|
||||
origin_right: ID::new(0, 1),
|
||||
id: ID::new(0, 5),
|
||||
text: "5678".to_owned(),
|
||||
}),
|
||||
},
|
||||
ContainerID::Normal {
|
||||
id: ROOT_ID,
|
||||
container_type: ContainerType::Text,
|
||||
},
|
||||
));
|
||||
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()
|
||||
.text
|
||||
.clone())
|
||||
.collect::<Vec<String>>(),
|
||||
vec!["34", "56"]
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
use std::{
|
||||
borrow::{Borrow, BorrowMut},
|
||||
cell::{Ref, RefCell, RefMut},
|
||||
ops::{Range},
|
||||
ops::Range,
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
|
|
|
@ -28,6 +28,9 @@ pub struct InternalNode<'a, T: Rle, A: RleTreeTrait<T>> {
|
|||
_a: PhantomData<A>,
|
||||
}
|
||||
|
||||
// TODO: remove bump field
|
||||
// TODO: remove parent field?
|
||||
// TODO: only one child?
|
||||
pub struct LeafNode<'a, T: Rle, A: RleTreeTrait<T>> {
|
||||
bump: &'a Bump,
|
||||
parent: NonNull<InternalNode<'a, T, A>>,
|
||||
|
|
Loading…
Reference in a new issue