mirror of
https://github.com/loro-dev/loro.git
synced 2025-02-02 11:06:14 +00:00
fix: add 2 site tests & fix update cursor bug
This commit is contained in:
parent
0cd640340a
commit
b099b4507c
17 changed files with 393 additions and 347 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -819,6 +819,7 @@ dependencies = [
|
|||
"ouroboros",
|
||||
"proptest",
|
||||
"rand",
|
||||
"smallvec",
|
||||
"smartstring",
|
||||
"static_assertions",
|
||||
]
|
||||
|
|
1
crates/loro-core/fuzz/Cargo.lock
generated
1
crates/loro-core/fuzz/Cargo.lock
generated
|
@ -558,6 +558,7 @@ dependencies = [
|
|||
"fxhash",
|
||||
"num",
|
||||
"ouroboros",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
@ -35,3 +35,9 @@ name = "single_client_text"
|
|||
path = "fuzz_targets/single_client_text.rs"
|
||||
test = false
|
||||
doc = false
|
||||
|
||||
[[bin]]
|
||||
name = "two_client_text"
|
||||
path = "fuzz_targets/two_client_text.rs"
|
||||
test = false
|
||||
doc = false
|
||||
|
|
5
crates/loro-core/fuzz/fuzz_targets/two_client_text.rs
Normal file
5
crates/loro-core/fuzz/fuzz_targets/two_client_text.rs
Normal file
|
@ -0,0 +1,5 @@
|
|||
#![no_main]
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use loro_core::fuzz::{test_multi_sites, Action};
|
||||
|
||||
fuzz_target!(|actions: Vec<Action>| { test_multi_sites(2, actions) });
|
|
@ -92,8 +92,8 @@ impl ContainerManager {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get(&self, id: ContainerID) -> Option<&ContainerInstance> {
|
||||
self.containers.get(&id)
|
||||
pub fn get(&self, id: &ContainerID) -> Option<&ContainerInstance> {
|
||||
self.containers.get(id)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
|
|
@ -54,6 +54,10 @@ impl TextContainer {
|
|||
}
|
||||
|
||||
pub fn insert(&mut self, pos: usize, text: &str) -> Option<ID> {
|
||||
if text.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let id = if let Ok(mut store) = self.log_store.upgrade().unwrap().write() {
|
||||
let id = store.next_id();
|
||||
// let slice = ListSlice::from_range(self.raw_str.alloc(text));
|
||||
|
@ -79,6 +83,10 @@ impl TextContainer {
|
|||
}
|
||||
|
||||
pub fn delete(&mut self, pos: usize, len: usize) -> Option<ID> {
|
||||
if len == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let id = if let Ok(mut store) = self.log_store.upgrade().unwrap().write() {
|
||||
let id = store.next_id();
|
||||
let op = Op::new(
|
||||
|
@ -159,10 +167,10 @@ impl Container for TextContainer {
|
|||
// stage 2
|
||||
let path = store.find_path(&latest_head, &self.head);
|
||||
self.tracker.retreat(&path.left);
|
||||
// println!(
|
||||
// "Iterate path: {:?} from {:?} => {:?}",
|
||||
// path.left, &self.head, &latest_head
|
||||
// );
|
||||
println!(
|
||||
"Iterate path: {:?} from {:?} => {:?}",
|
||||
path.left, &self.head, &latest_head
|
||||
);
|
||||
for effect in self.tracker.iter_effects(path.left) {
|
||||
// println!("{:?}", &effect);
|
||||
match effect {
|
||||
|
@ -175,13 +183,14 @@ impl Container for TextContainer {
|
|||
|
||||
self.head.push(new_op_id);
|
||||
self.vv.set_last(new_op_id);
|
||||
// println!("------------------------------------------------------------------------");
|
||||
println!("------------------------------------------------------------------------");
|
||||
}
|
||||
|
||||
fn checkout_version(&mut self, _vv: &crate::VersionVector) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
// TODO: maybe we need to let this return Cow
|
||||
fn get_value(&mut self) -> &LoroValue {
|
||||
let mut ans_str = SmString::new();
|
||||
for v in self.state.iter() {
|
||||
|
|
|
@ -133,8 +133,8 @@ impl Tracker {
|
|||
}
|
||||
|
||||
pub fn forward(&mut self, spans: &IdSpanVector) {
|
||||
let mut to_set_as_applied = Vec::with_capacity(spans.len());
|
||||
let mut to_delete = Vec::with_capacity(spans.len());
|
||||
let mut cursors = Vec::with_capacity(spans.len());
|
||||
let mut args = Vec::with_capacity(spans.len());
|
||||
for span in spans.iter() {
|
||||
self.head_vv.set_end(ID::new(*span.0, span.1.end));
|
||||
let IdSpanQueryResult { inserts, deletes } = self
|
||||
|
@ -142,37 +142,38 @@ impl Tracker {
|
|||
.get_cursors_at_id_span(IdSpan::new(*span.0, span.1.start, span.1.end));
|
||||
for (_, delete) in deletes {
|
||||
for deleted_span in delete.iter() {
|
||||
to_delete.append(
|
||||
&mut self
|
||||
.id_to_cursor
|
||||
.get_cursors_at_id_span(*deleted_span)
|
||||
.inserts
|
||||
.iter()
|
||||
.map(|x| x.1)
|
||||
.collect(),
|
||||
);
|
||||
for span in self
|
||||
.id_to_cursor
|
||||
.get_cursors_at_id_span(*deleted_span)
|
||||
.inserts
|
||||
.iter()
|
||||
.map(|x| x.1)
|
||||
{
|
||||
cursors.push(span);
|
||||
args.push(StatusChange::Delete);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: maybe we can skip this collect
|
||||
to_set_as_applied.append(&mut inserts.iter().map(|x| x.1).collect());
|
||||
for span in inserts.iter().map(|x| x.1) {
|
||||
cursors.push(span);
|
||||
args.push(StatusChange::SetAsCurrent);
|
||||
}
|
||||
}
|
||||
|
||||
self.content.update_at_cursors_twice(
|
||||
&[&to_set_as_applied, &to_delete],
|
||||
&mut |v| {
|
||||
v.status.apply(StatusChange::SetAsCurrent);
|
||||
},
|
||||
&mut |v: &mut YSpan| {
|
||||
v.status.apply(StatusChange::Delete);
|
||||
self.content.update_at_cursors_with_args(
|
||||
&cursors,
|
||||
&args,
|
||||
&mut |v: &mut YSpan, arg| {
|
||||
v.status.apply(*arg);
|
||||
},
|
||||
&mut make_notify(&mut self.id_to_cursor),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn retreat(&mut self, spans: &IdSpanVector) {
|
||||
let mut to_set_as_future = Vec::with_capacity(spans.len());
|
||||
let mut to_undo_delete = Vec::with_capacity(spans.len());
|
||||
let mut cursors = Vec::with_capacity(spans.len());
|
||||
let mut args = Vec::with_capacity(spans.len());
|
||||
for span in spans.iter() {
|
||||
self.head_vv.set_end(ID::new(*span.0, span.1.start));
|
||||
let IdSpanQueryResult { inserts, deletes } = self
|
||||
|
@ -180,29 +181,30 @@ impl Tracker {
|
|||
.get_cursors_at_id_span(IdSpan::new(*span.0, span.1.start, span.1.end));
|
||||
for (_, delete) in deletes {
|
||||
for deleted_span in delete.iter() {
|
||||
to_undo_delete.append(
|
||||
&mut self
|
||||
.id_to_cursor
|
||||
.get_cursors_at_id_span(*deleted_span)
|
||||
.inserts
|
||||
.iter()
|
||||
.map(|x| x.1)
|
||||
.collect(),
|
||||
);
|
||||
for span in self
|
||||
.id_to_cursor
|
||||
.get_cursors_at_id_span(*deleted_span)
|
||||
.inserts
|
||||
.iter()
|
||||
.map(|x| x.1)
|
||||
{
|
||||
cursors.push(span);
|
||||
args.push(StatusChange::UndoDelete);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: maybe we can skip this collect
|
||||
to_set_as_future.append(&mut inserts.iter().map(|x| x.1).collect());
|
||||
for span in inserts.iter().map(|x| x.1) {
|
||||
cursors.push(span);
|
||||
args.push(StatusChange::SetAsFuture);
|
||||
}
|
||||
}
|
||||
|
||||
self.content.update_at_cursors_twice(
|
||||
&[&to_set_as_future, &to_undo_delete],
|
||||
&mut |v| {
|
||||
v.status.apply(StatusChange::SetAsFuture);
|
||||
},
|
||||
&mut |v: &mut YSpan| {
|
||||
v.status.apply(StatusChange::UndoDelete);
|
||||
self.content.update_at_cursors_with_args(
|
||||
&cursors,
|
||||
&args,
|
||||
&mut |v: &mut YSpan, arg| {
|
||||
v.status.apply(*arg);
|
||||
},
|
||||
&mut make_notify(&mut self.id_to_cursor),
|
||||
)
|
||||
|
|
|
@ -275,8 +275,8 @@ mod test {
|
|||
use crate::{
|
||||
container::{ContainerID, ContainerType},
|
||||
id::ROOT_ID,
|
||||
op::InsertContent,
|
||||
ContentType, Op, OpContent, ID,
|
||||
op::{InsertContent, OpContent},
|
||||
ContentType, Op, ID,
|
||||
};
|
||||
use rle::{HasLength, RleVec};
|
||||
|
||||
|
|
|
@ -3,53 +3,90 @@ use enum_as_inner::EnumAsInner;
|
|||
use tabled::{TableIteratorExt, Tabled};
|
||||
|
||||
use crate::{
|
||||
array_mut_ref,
|
||||
container::{text::text_container::TextContainer, Container},
|
||||
LoroCore,
|
||||
};
|
||||
|
||||
#[derive(Arbitrary, EnumAsInner, Clone, PartialEq, Eq, Debug)]
|
||||
pub enum Action {
|
||||
Ins { content: String, pos: usize },
|
||||
Del { pos: usize, len: usize },
|
||||
Ins {
|
||||
content: String,
|
||||
pos: usize,
|
||||
site: u8,
|
||||
},
|
||||
Del {
|
||||
pos: usize,
|
||||
len: usize,
|
||||
site: u8,
|
||||
},
|
||||
Sync {
|
||||
from: u8,
|
||||
to: u8,
|
||||
},
|
||||
SyncAll,
|
||||
}
|
||||
|
||||
impl Tabled for Action {
|
||||
const LENGTH: usize = 4;
|
||||
const LENGTH: usize = 5;
|
||||
|
||||
fn fields(&self) -> Vec<std::borrow::Cow<'_, str>> {
|
||||
match self {
|
||||
Action::Ins { content, pos } => vec![
|
||||
Action::Ins { content, pos, site } => vec![
|
||||
"ins".into(),
|
||||
pos.to_string().into(),
|
||||
content.len().to_string().into(),
|
||||
content.into(),
|
||||
site.to_string().into(),
|
||||
],
|
||||
Action::Del { pos, len } => vec![
|
||||
Action::Del { pos, len, site } => vec![
|
||||
"del".into(),
|
||||
pos.to_string().into(),
|
||||
len.to_string().into(),
|
||||
"".into(),
|
||||
site.to_string().into(),
|
||||
],
|
||||
Action::Sync { from, to } => vec![
|
||||
"sync".into(),
|
||||
"".into(),
|
||||
"".into(),
|
||||
"".into(),
|
||||
format!("{} {}", from, to).into(),
|
||||
],
|
||||
Action::SyncAll => vec![
|
||||
"sync all".into(),
|
||||
"".into(),
|
||||
"".into(),
|
||||
"".into(),
|
||||
"".into(),
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
fn headers() -> Vec<std::borrow::Cow<'static, str>> {
|
||||
vec!["type".into(), "pos".into(), "len".into(), "content".into()]
|
||||
vec![
|
||||
"type".into(),
|
||||
"pos".into(),
|
||||
"len".into(),
|
||||
"content".into(),
|
||||
"site".into(),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
trait Actionable {
|
||||
fn apply_action(&mut self, action: &Action);
|
||||
fn preprocess(&self, action: &mut Action);
|
||||
fn preprocess(&mut self, action: &mut Action);
|
||||
}
|
||||
|
||||
impl Action {
|
||||
pub fn preprocess(&mut self, max_len: usize) {
|
||||
pub fn preprocess(&mut self, max_len: usize, max_users: u8) {
|
||||
match self {
|
||||
Action::Ins { pos, .. } => {
|
||||
Action::Ins { pos, site, .. } => {
|
||||
*pos %= max_len + 1;
|
||||
*site %= max_users;
|
||||
}
|
||||
Action::Del { pos, len, .. } => {
|
||||
Action::Del { pos, len, site } => {
|
||||
if max_len == 0 {
|
||||
*pos = 0;
|
||||
*len = 0;
|
||||
|
@ -57,7 +94,13 @@ impl Action {
|
|||
*pos %= max_len;
|
||||
*len = (*len).min(max_len - (*pos));
|
||||
}
|
||||
*site %= max_users;
|
||||
}
|
||||
Action::Sync { from, to } => {
|
||||
*from %= max_users;
|
||||
*to %= max_users;
|
||||
}
|
||||
Action::SyncAll => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -65,28 +108,29 @@ impl Action {
|
|||
impl Actionable for String {
|
||||
fn apply_action(&mut self, action: &Action) {
|
||||
match action {
|
||||
Action::Ins { content, pos } => {
|
||||
Action::Ins { content, pos, .. } => {
|
||||
self.insert_str(*pos, content);
|
||||
}
|
||||
&Action::Del { pos, len } => {
|
||||
if self.len() == 0 {
|
||||
&Action::Del { pos, len, .. } => {
|
||||
if self.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
self.drain(pos..pos + len);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn preprocess(&self, action: &mut Action) {
|
||||
action.preprocess(self.len());
|
||||
fn preprocess(&mut self, action: &mut Action) {
|
||||
action.preprocess(self.len(), 1);
|
||||
match action {
|
||||
Action::Ins { pos, .. } => {
|
||||
while !self.is_char_boundary(*pos) {
|
||||
*pos = (*pos + 1) % (self.len() + 1)
|
||||
}
|
||||
}
|
||||
Action::Del { pos, len } => {
|
||||
Action::Del { pos, len, .. } => {
|
||||
if self.is_empty() {
|
||||
*len = 0;
|
||||
*pos = 0;
|
||||
|
@ -102,6 +146,7 @@ impl Actionable for String {
|
|||
*len += 1;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -109,30 +154,115 @@ impl Actionable for String {
|
|||
impl Actionable for TextContainer {
|
||||
fn apply_action(&mut self, action: &Action) {
|
||||
match action {
|
||||
Action::Ins { content, pos } => {
|
||||
Action::Ins { content, pos, .. } => {
|
||||
self.insert(*pos, content);
|
||||
}
|
||||
&Action::Del { pos, len } => {
|
||||
&Action::Del { pos, len, .. } => {
|
||||
if self.text_len() == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
self.delete(pos, len);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn preprocess(&self, _action: &mut Action) {
|
||||
fn preprocess(&mut self, _action: &mut Action) {
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
|
||||
impl Actionable for Vec<LoroCore> {
|
||||
fn apply_action(&mut self, action: &Action) {
|
||||
match action {
|
||||
Action::Ins { content, pos, site } => {
|
||||
self[*site as usize]
|
||||
.get_or_create_text_container_mut("text".into())
|
||||
.insert(*pos, content);
|
||||
}
|
||||
Action::Del { pos, len, site } => {
|
||||
self[*site as usize]
|
||||
.get_or_create_text_container_mut("text".into())
|
||||
.delete(*pos, *len);
|
||||
}
|
||||
Action::Sync { from, to } => {
|
||||
let to_vv = self[*to as usize].vv();
|
||||
let from_exported = self[*from as usize].export(to_vv);
|
||||
self[*to as usize].import(from_exported);
|
||||
}
|
||||
Action::SyncAll => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn preprocess(&mut self, action: &mut Action) {
|
||||
match action {
|
||||
Action::Ins { content, pos, site } => {
|
||||
*site %= self.len() as u8;
|
||||
let mut text = self[*site as usize].get_or_create_text_container_mut("text".into());
|
||||
let value = text.get_value().as_string().unwrap();
|
||||
*pos %= value.len() + 1;
|
||||
while !value.is_char_boundary(*pos) {
|
||||
*pos = (*pos + 1) % (value.len() + 1)
|
||||
}
|
||||
}
|
||||
Action::Del { pos, len, site } => {
|
||||
*site %= self.len() as u8;
|
||||
let mut text = self[*site as usize].get_or_create_text_container_mut("text".into());
|
||||
if text.text_len() == 0 {
|
||||
*len = 0;
|
||||
*pos = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
let str = text.get_value().as_string().unwrap();
|
||||
*pos %= str.len() + 1;
|
||||
while !str.is_char_boundary(*pos) {
|
||||
*pos = (*pos + 1) % str.len();
|
||||
}
|
||||
|
||||
*len = (*len).min(str.len() - (*pos));
|
||||
while !str.is_char_boundary(*pos + *len) {
|
||||
*len += 1;
|
||||
}
|
||||
}
|
||||
Action::Sync { from, to } => {
|
||||
*from %= self.len() as u8;
|
||||
*to %= self.len() as u8;
|
||||
}
|
||||
Action::SyncAll => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_eq(site_a: &mut LoroCore, site_b: &mut LoroCore) {
|
||||
let mut a = site_a.get_or_create_text_container_mut("text".into());
|
||||
let mut b = site_b.get_or_create_text_container_mut("text".into());
|
||||
let value_a = a.get_value();
|
||||
let value_b = b.get_value();
|
||||
assert_eq!(value_a.as_string().unwrap(), value_b.as_string().unwrap());
|
||||
}
|
||||
|
||||
fn check_synced(sites: &mut [LoroCore]) {
|
||||
for i in 0..sites.len() - 1 {
|
||||
for j in i + 1..sites.len() {
|
||||
let (a, b) = array_mut_ref!(sites, [i, j]);
|
||||
b.import(a.export(b.vv()));
|
||||
a.import(b.export(a.vv()));
|
||||
check_eq(a, b)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn test_single_client(mut actions: Vec<Action>) {
|
||||
let mut store = LoroCore::new(Default::default(), Some(1));
|
||||
let mut text_container = store.get_text_container("haha".into());
|
||||
let mut text_container = store.get_or_create_text_container_mut("haha".into());
|
||||
let mut ground_truth = String::new();
|
||||
let mut applied = Vec::new();
|
||||
for action in actions.iter_mut() {
|
||||
for action in actions
|
||||
.iter_mut()
|
||||
.filter(|x| x.as_del().is_some() || x.as_ins().is_some())
|
||||
{
|
||||
ground_truth.preprocess(action);
|
||||
applied.push(action.clone());
|
||||
// println!("{}", (&applied).table());
|
||||
|
@ -147,6 +277,25 @@ pub fn test_single_client(mut actions: Vec<Action>) {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn test_multi_sites(site_num: u8, mut actions: Vec<Action>) {
|
||||
let mut sites = Vec::new();
|
||||
for i in 0..site_num {
|
||||
sites.push(LoroCore::new(Default::default(), Some(i as u64)));
|
||||
}
|
||||
|
||||
let mut applied = Vec::new();
|
||||
for action in actions.iter_mut() {
|
||||
sites.preprocess(action);
|
||||
applied.push(action.clone());
|
||||
println!("{}", (&applied).table());
|
||||
sites.apply_action(action);
|
||||
}
|
||||
|
||||
println!("SYNC");
|
||||
// println!("{}", actions.table());
|
||||
check_synced(&mut sites);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use ctor::ctor;
|
||||
|
@ -156,240 +305,26 @@ mod test {
|
|||
|
||||
#[test]
|
||||
fn test() {
|
||||
test_single_client(vec! [
|
||||
Ins {
|
||||
content: "\u{16}\u{16}\u{16}\u{16}\u{16}#####BBBBSSSSSSSSS".into(),
|
||||
pos: 60797853338129363,
|
||||
},
|
||||
Ins {
|
||||
content: "\u{13}T0\u{18}5\u{13}".into(),
|
||||
pos: 1369375761697341439,
|
||||
},
|
||||
Ins {
|
||||
content: "\0\0\0SS".into(),
|
||||
pos: 280733345338323,
|
||||
},
|
||||
Ins {
|
||||
content: "**".into(),
|
||||
pos: 5444570,
|
||||
},
|
||||
Ins {
|
||||
content: "\u{13}".into(),
|
||||
pos: 5692550972993381338,
|
||||
},
|
||||
Ins {
|
||||
content: "OOOOOOOOOOOOOOBBBBBBBBBBBBBBBBB#\0\0####".into(),
|
||||
pos: 138028458976,
|
||||
},
|
||||
Ins {
|
||||
content: "".into(),
|
||||
pos: 267263250998051,
|
||||
},
|
||||
Ins {
|
||||
content: "".into(),
|
||||
pos: 4774378554966147091,
|
||||
},
|
||||
Ins {
|
||||
content: "BBBBB#\0\0######## \0\0\0######".into(),
|
||||
pos: 3038287259207217298,
|
||||
},
|
||||
Ins {
|
||||
content: "".into(),
|
||||
pos: 16645325113485036074,
|
||||
},
|
||||
Ins {
|
||||
content: "".into(),
|
||||
pos: 23362835702677503,
|
||||
},
|
||||
Ins {
|
||||
content: "S".into(),
|
||||
pos: 280733345338323,
|
||||
},
|
||||
Ins {
|
||||
content: "*UUU".into(),
|
||||
pos: 2761092332,
|
||||
},
|
||||
Ins {
|
||||
content: "\u{5ec}".into(),
|
||||
pos: 15332975680940594378,
|
||||
},
|
||||
Ins {
|
||||
content: "".into(),
|
||||
pos: 3038287259199214554,
|
||||
},
|
||||
Ins {
|
||||
content: "PPPPPPPPPPPPP\u{13}".into(),
|
||||
pos: 6004374254117322995,
|
||||
},
|
||||
Ins {
|
||||
content: "SSSSSS".into(),
|
||||
pos: 48379484722131,
|
||||
},
|
||||
Ins {
|
||||
content: ",\0\0\0UUUU".into(),
|
||||
pos: 2761092332,
|
||||
},
|
||||
Ins {
|
||||
content: "\u{5ec}".into(),
|
||||
pos: 15332975680940594378,
|
||||
},
|
||||
Ins {
|
||||
content: "".into(),
|
||||
pos: 3038287259199214554,
|
||||
},
|
||||
Ins {
|
||||
content: "".into(),
|
||||
pos: 5787213827046133840,
|
||||
},
|
||||
Ins {
|
||||
content: "PPPPPPPPPPPPPPPP*****".into(),
|
||||
pos: 2762368,
|
||||
},
|
||||
Ins {
|
||||
content: "".into(),
|
||||
pos: 0,
|
||||
},
|
||||
Ins {
|
||||
content: "".into(),
|
||||
pos: 0,
|
||||
},
|
||||
Ins {
|
||||
content: "".into(),
|
||||
pos: 3038287259199220266,
|
||||
},
|
||||
Ins {
|
||||
content: "*\0\u{13}EEEEEEEEEEEEEEEEEEEEEEEE".into(),
|
||||
pos: 4179340455027348442,
|
||||
},
|
||||
Ins {
|
||||
content: "\0UUUU".into(),
|
||||
pos: 2761092332,
|
||||
},
|
||||
Ins {
|
||||
content: "\u{5ec}".into(),
|
||||
pos: 15332976539934053578,
|
||||
},
|
||||
Ins {
|
||||
content: "ڨ\0\0\0*******************".into(),
|
||||
pos: 3038287259199220352,
|
||||
},
|
||||
Ins {
|
||||
content: "*&*****".into(),
|
||||
pos: 6004234345560396434,
|
||||
},
|
||||
Ins {
|
||||
content: "".into(),
|
||||
pos: 3038287259199220307,
|
||||
},
|
||||
Ins {
|
||||
content: "******".into(),
|
||||
pos: 3038287259889816210,
|
||||
},
|
||||
Ins {
|
||||
content: "*****".into(),
|
||||
pos: 11350616413819538,
|
||||
},
|
||||
Ins {
|
||||
content: "".into(),
|
||||
pos: 6004234345560363859,
|
||||
},
|
||||
Ins {
|
||||
content: "S".into(),
|
||||
pos: 60797853338129363,
|
||||
},
|
||||
Ins {
|
||||
content: "\u{13}T3\u{18}5\u{13}".into(),
|
||||
pos: 1369375761697341439,
|
||||
},
|
||||
Ins {
|
||||
content: "\0\0\0SS".into(),
|
||||
pos: 280733345338323,
|
||||
},
|
||||
Ins {
|
||||
content: "*UUU".into(),
|
||||
pos: 2761092332,
|
||||
},
|
||||
Ins {
|
||||
content: "\u{5ec}".into(),
|
||||
pos: 15332975680940594378,
|
||||
},
|
||||
Ins {
|
||||
content: "".into(),
|
||||
pos: 3038287259199214554,
|
||||
},
|
||||
Ins {
|
||||
content: "".into(),
|
||||
pos: 5787213827046133840,
|
||||
},
|
||||
Ins {
|
||||
content: "".into(),
|
||||
pos: 5787213827046133840,
|
||||
},
|
||||
Ins {
|
||||
content: "PPPP*****".into(),
|
||||
pos: 2762368,
|
||||
},
|
||||
Ins {
|
||||
content: "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0******".into(),
|
||||
pos: 3038287259199220266,
|
||||
},
|
||||
Ins {
|
||||
content: "EEEEEEEEEEEEEEEEEEEEEEE".into(),
|
||||
pos: 4179340455027348442,
|
||||
},
|
||||
Ins {
|
||||
content: "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0 ,\0\0\0UUUU".into(),
|
||||
pos: 2761092332,
|
||||
},
|
||||
Ins {
|
||||
content: "\u{5ec}".into(),
|
||||
pos: 14483766535198004426,
|
||||
},
|
||||
Ins {
|
||||
content: "".into(),
|
||||
pos: 3038240898625886739,
|
||||
},
|
||||
Ins {
|
||||
content: "*************".into(),
|
||||
pos: 3038287259199220352,
|
||||
},
|
||||
Ins {
|
||||
content: "*&*****".into(),
|
||||
pos: 6004234345560396434,
|
||||
},
|
||||
Ins {
|
||||
content: "S*********\0*******".into(),
|
||||
pos: 3038287259889816210,
|
||||
},
|
||||
Ins {
|
||||
content: "*****".into(),
|
||||
pos: 11350616413819538,
|
||||
},
|
||||
Ins {
|
||||
content: "SSSSSSSSSSSSS".into(),
|
||||
pos: 60797853338129363,
|
||||
},
|
||||
Ins {
|
||||
content: "\u{13}T4\u{18}5\u{13}".into(),
|
||||
pos: 1369375761697341439,
|
||||
},
|
||||
Ins {
|
||||
content: "\0\0\0SS".into(),
|
||||
pos: 3834029289772372947,
|
||||
},
|
||||
Ins {
|
||||
content: "55555555555555555555555555555555555555555555555555555555555555555555555555555555555".into(),
|
||||
pos: 280603991029045,
|
||||
},
|
||||
Ins {
|
||||
content: "".into(),
|
||||
pos: 356815350314,
|
||||
},
|
||||
Ins {
|
||||
content: "\u{13}\0\u{13}".into(),
|
||||
pos: 1369095330717705178,
|
||||
},
|
||||
])
|
||||
test_single_client(vec![])
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_two() {
|
||||
test_multi_sites(
|
||||
2,
|
||||
vec![
|
||||
Ins {
|
||||
content: "\0\u{1}\u{1}".into(),
|
||||
pos: 2170205186765623551,
|
||||
site: 0,
|
||||
},
|
||||
Del {
|
||||
pos: 108084720300767744,
|
||||
len: 485,
|
||||
site: 0,
|
||||
},
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
#[ctor]
|
||||
|
|
|
@ -103,6 +103,7 @@ impl LogStore {
|
|||
.into_iter()
|
||||
.filter(|x| !self_vv.includes_id(x.last_id()))
|
||||
{
|
||||
println!("APPLY {:?} to {}", change, self.this_client_id);
|
||||
check_import_change_valid(&change);
|
||||
// TODO: cache pending changes
|
||||
assert!(change.deps.iter().all(|x| self_vv.includes_id(*x)));
|
||||
|
@ -145,7 +146,7 @@ impl LogStore {
|
|||
fn change_to_export_format(&self, change: &mut Change) {
|
||||
let container_manager = self.container.read().unwrap();
|
||||
for op in change.ops.vec_mut().iter_mut() {
|
||||
let container = container_manager.get(op.container.clone()).unwrap();
|
||||
let container = container_manager.get(&op.container).unwrap();
|
||||
container.to_export(op);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ use std::{
|
|||
sync::{Arc, RwLock, RwLockWriteGuard},
|
||||
};
|
||||
|
||||
use owning_ref::OwningRefMut;
|
||||
use owning_ref::{OwningRef, OwningRefMut};
|
||||
|
||||
use crate::{
|
||||
change::Change,
|
||||
|
@ -74,7 +74,7 @@ impl LoroCore {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn get_text_container(
|
||||
pub fn get_or_create_text_container_mut(
|
||||
&mut self,
|
||||
name: InternalString,
|
||||
) -> OwningRefMut<RwLockWriteGuard<ContainerManager>, Box<TextContainer>> {
|
||||
|
@ -89,6 +89,19 @@ impl LoroCore {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn get_text_container(
|
||||
&self,
|
||||
name: InternalString,
|
||||
) -> OwningRef<RwLockWriteGuard<ContainerManager>, Box<TextContainer>> {
|
||||
let a = OwningRef::new(self.container.write().unwrap());
|
||||
a.map(|x| {
|
||||
x.get(&ContainerID::new_root(name, ContainerType::Text))
|
||||
.unwrap()
|
||||
.as_text()
|
||||
.unwrap()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn export(&self, remote_vv: VersionVector) -> Vec<Change> {
|
||||
let store = self.log_store.read().unwrap();
|
||||
store.export(&remote_vv)
|
||||
|
|
|
@ -5,7 +5,7 @@ use loro_core::LoroCore;
|
|||
#[test]
|
||||
fn test() {
|
||||
let mut store = LoroCore::new(Default::default(), Some(10));
|
||||
let mut text_container = store.get_text_container("haha".into());
|
||||
let mut text_container = store.get_or_create_text_container_mut("haha".into());
|
||||
text_container.insert(0, "012");
|
||||
text_container.insert(1, "34");
|
||||
text_container.insert(1, "56");
|
||||
|
@ -17,7 +17,7 @@ fn test() {
|
|||
let mut store_b = LoroCore::new(Default::default(), Some(11));
|
||||
let exported = store.export(Default::default());
|
||||
store_b.import(exported);
|
||||
let mut text_container = store_b.get_text_container("haha".into());
|
||||
let mut text_container = store_b.get_or_create_text_container_mut("haha".into());
|
||||
text_container.check();
|
||||
let value = text_container.get_value();
|
||||
let value = value.as_string().unwrap();
|
||||
|
@ -31,7 +31,7 @@ fn test() {
|
|||
drop(text_container);
|
||||
|
||||
store.import(store_b.export(store.vv()));
|
||||
let mut text_container = store.get_text_container("haha".into());
|
||||
let mut text_container = store.get_or_create_text_container_mut("haha".into());
|
||||
let value = text_container.get_value();
|
||||
let value = value.as_string().unwrap();
|
||||
assert_eq!(value.as_str(), "63417892");
|
||||
|
@ -43,7 +43,7 @@ fn test() {
|
|||
drop(text_container);
|
||||
|
||||
store_b.import(store.export(Default::default()));
|
||||
let mut text_container = store_b.get_text_container("haha".into());
|
||||
let mut text_container = store_b.get_or_create_text_container_mut("haha".into());
|
||||
text_container.check();
|
||||
let value = text_container.get_value();
|
||||
let value = value.as_string().unwrap();
|
||||
|
|
|
@ -6,6 +6,7 @@ edition = "2021"
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
smallvec = "1.8.0"
|
||||
bumpalo = { version = "3.10.0", features = ["collections", "boxed"] }
|
||||
num = "0.4.0"
|
||||
enum-as-inner = "0.5.1"
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use std::{fmt::Debug, ops::Range};
|
||||
|
||||
use num::{cast, Integer, NumCast};
|
||||
use smallvec::{Array, SmallVec};
|
||||
|
||||
pub trait Mergable<Cfg = ()> {
|
||||
fn is_mergable(&self, _other: &Self, _conf: &Cfg) -> bool
|
||||
|
@ -98,3 +99,21 @@ impl<T: HasLength> HasLength for &T {
|
|||
(*self).len()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: HasLength + Sliceable, A: Array<Item = T>> Sliceable for SmallVec<A> {
|
||||
fn slice(&self, from: usize, to: usize) -> Self {
|
||||
let mut index = 0;
|
||||
let mut ans = smallvec::smallvec![];
|
||||
for item in self.iter() {
|
||||
if index < to && from < index + item.content_len() {
|
||||
let start = if index < from { from - index } else { 0 };
|
||||
let len = item.content_len().min(to - index);
|
||||
ans.push(item.slice(start, from + len));
|
||||
}
|
||||
|
||||
index += item.content_len();
|
||||
}
|
||||
|
||||
ans
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ pub use cursor::{SafeCursor, SafeCursorMut, UnsafeCursor};
|
|||
use fxhash::FxHashMap;
|
||||
use num::FromPrimitive;
|
||||
use ouroboros::self_referencing;
|
||||
use smallvec::SmallVec;
|
||||
pub use tree_trait::Position;
|
||||
use tree_trait::RleTreeTrait;
|
||||
|
||||
|
@ -280,51 +281,52 @@ impl<T: Rle, A: RleTreeTrait<T>> RleTree<T, A> {
|
|||
self.update_with_gathered_map(updates_map, notify);
|
||||
}
|
||||
|
||||
pub fn update_at_cursors_twice<U, V, F>(
|
||||
// TODO: perf, use smallvec
|
||||
pub fn update_at_cursors_with_args<U, F, Arg>(
|
||||
&mut self,
|
||||
cursor_groups: &[&[UnsafeCursor<T, A>]; 2],
|
||||
update_fn_u: &mut U,
|
||||
update_fn_v: &mut V,
|
||||
cursor_groups: &[UnsafeCursor<T, A>],
|
||||
args: &[Arg],
|
||||
update_fn: &mut U,
|
||||
notify: &mut F,
|
||||
) where
|
||||
U: FnMut(&mut T),
|
||||
V: FnMut(&mut T),
|
||||
U: FnMut(&mut T, &Arg),
|
||||
F: FnMut(&T, *mut LeafNode<T, A>),
|
||||
{
|
||||
let mut updates_map: HashMap<NonNull<_>, Vec<(usize, Vec<T>)>, _> = FxHashMap::default();
|
||||
for (i, cursors) in cursor_groups.iter().enumerate() {
|
||||
for cursor in cursors.iter() {
|
||||
// SAFETY: we has the exclusive reference to the tree and the cursor is valid
|
||||
let updates = unsafe {
|
||||
if i == 0 {
|
||||
cursor.leaf.as_ref().pure_update(
|
||||
cursor.index,
|
||||
cursor.offset,
|
||||
cursor.len,
|
||||
update_fn_u,
|
||||
)
|
||||
} else {
|
||||
cursor.leaf.as_ref().pure_update(
|
||||
cursor.index,
|
||||
cursor.offset,
|
||||
cursor.len,
|
||||
update_fn_v,
|
||||
)
|
||||
}
|
||||
};
|
||||
let mut cursor_map: HashMap<(NonNull<_>, usize), Vec<(&UnsafeCursor<T, A>, &Arg)>, _> =
|
||||
FxHashMap::default();
|
||||
for (i, arg) in args.iter().enumerate() {
|
||||
let cursor = &cursor_groups[i];
|
||||
cursor_map
|
||||
.entry((cursor.leaf, cursor.index))
|
||||
.or_default()
|
||||
.push((cursor, arg));
|
||||
}
|
||||
|
||||
if let Some(update) = updates {
|
||||
updates_map
|
||||
.entry(cursor.leaf)
|
||||
.or_default()
|
||||
.push((cursor.index, update));
|
||||
}
|
||||
let mut updates_map: HashMap<NonNull<_>, Vec<(usize, Vec<T>)>, _> = FxHashMap::default();
|
||||
for ((mut leaf, index), args) in cursor_map.iter() {
|
||||
// SAFETY: we has the exclusive reference to the tree and the cursor is valid
|
||||
let leaf = unsafe { leaf.as_mut() };
|
||||
let input_args = args.iter().map(|x| x.1).collect::<Vec<_>>();
|
||||
let updates = leaf.pure_updates_at_same_index(
|
||||
*index,
|
||||
&args.iter().map(|x| x.0.offset).collect::<Vec<_>>(),
|
||||
&args.iter().map(|x| x.0.len).collect::<Vec<_>>(),
|
||||
&input_args,
|
||||
update_fn,
|
||||
);
|
||||
|
||||
if let Some(update) = updates {
|
||||
updates_map
|
||||
.entry(leaf.into())
|
||||
.or_default()
|
||||
.push((*index, update.into_iter().collect()));
|
||||
}
|
||||
}
|
||||
|
||||
self.update_with_gathered_map(updates_map, notify);
|
||||
}
|
||||
|
||||
// TODO: perf, use smallvec
|
||||
fn update_with_gathered_map<F, M>(
|
||||
&mut self,
|
||||
iter: HashMap<NonNull<LeafNode<T, A>>, Vec<(usize, Vec<T>)>, M>,
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
use crate::rle_tree::{
|
||||
cursor::SafeCursorMut,
|
||||
tree_trait::{FindPosResult, Position},
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use crate::{
|
||||
rle_tree::{
|
||||
cursor::SafeCursorMut,
|
||||
tree_trait::{FindPosResult, Position},
|
||||
},
|
||||
Sliceable,
|
||||
};
|
||||
use std::fmt::{Debug, Error, Formatter};
|
||||
|
||||
|
@ -407,6 +412,42 @@ impl<'bump, T: Rle, A: RleTreeTrait<T>> LeafNode<'bump, T, A> {
|
|||
Some(ans)
|
||||
}
|
||||
|
||||
pub(crate) fn pure_updates_at_same_index<U, Arg>(
|
||||
&self,
|
||||
child_index: usize,
|
||||
offsets: &[usize],
|
||||
lens: &[usize],
|
||||
args: &[&Arg],
|
||||
update_fn: &mut U,
|
||||
) -> Option<SmallVec<[T; 2]>>
|
||||
where
|
||||
U: FnMut(&mut T, &Arg),
|
||||
{
|
||||
if offsets.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut ans = SmallVec::new();
|
||||
ans.push(self.children[child_index].clone());
|
||||
for i in 0..offsets.len() {
|
||||
let offset = offsets[i];
|
||||
let len = lens[i];
|
||||
let arg = &args[i];
|
||||
// TODO: can be optimized if needed
|
||||
let mut target_spans = ans.slice(offset, offset + len);
|
||||
for span in target_spans.iter_mut() {
|
||||
update_fn(span, arg);
|
||||
}
|
||||
|
||||
let mut end = ans.slice(offset + len, ans.len());
|
||||
ans = ans.slice(0, offset);
|
||||
ans.append(&mut target_spans);
|
||||
ans.append(&mut end);
|
||||
}
|
||||
|
||||
Some(ans)
|
||||
}
|
||||
|
||||
pub(crate) fn apply_updates<F>(
|
||||
&mut self,
|
||||
mut updates: Vec<(usize, Vec<T>)>,
|
||||
|
|
|
@ -121,6 +121,16 @@ impl<T: Mergable<Cfg> + HasLength, Cfg> RleVec<T, Cfg> {
|
|||
|
||||
/// get a slice from `from` to `to` with atom indexes
|
||||
pub fn slice_iter(&self, from: usize, to: usize) -> SliceIterator<'_, T> {
|
||||
if from == to {
|
||||
return SliceIterator {
|
||||
vec: &self.vec,
|
||||
cur_index: 0,
|
||||
cur_offset: 0,
|
||||
end_index: Some(0),
|
||||
end_offset: Some(0),
|
||||
};
|
||||
}
|
||||
|
||||
let from_result = self.get(from).unwrap();
|
||||
let to_result = self.get(to);
|
||||
if let Some(to_result) = to_result {
|
||||
|
|
Loading…
Reference in a new issue