fix: add 2 site tests & fix update cursor bug

This commit is contained in:
Zixuan Chen 2022-10-21 17:57:46 +08:00
parent 0cd640340a
commit b099b4507c
17 changed files with 393 additions and 347 deletions

1
Cargo.lock generated
View file

@ -819,6 +819,7 @@ dependencies = [
"ouroboros",
"proptest",
"rand",
"smallvec",
"smartstring",
"static_assertions",
]

View file

@ -558,6 +558,7 @@ dependencies = [
"fxhash",
"num",
"ouroboros",
"smallvec",
]
[[package]]

View file

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

View 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) });

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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