From 6e03c9c04b312429b3de522fb4c90b22d54a19e3 Mon Sep 17 00:00:00 2001 From: Zixuan Chen Date: Mon, 31 Oct 2022 23:50:00 +0800 Subject: [PATCH] refactor: extract remote op --- crates/loro-core/src/change.rs | 67 +++----- crates/loro-core/src/container.rs | 5 +- crates/loro-core/src/container/manager.rs | 7 +- .../src/container/map/map_container.rs | 9 +- .../src/container/text/text_container.rs | 13 +- .../loro-core/src/container/text/tracker.rs | 2 +- .../src/container/text/tracker/y_span.rs | 20 +-- crates/loro-core/src/dag/iter.rs | 5 +- crates/loro-core/src/id.rs | 1 + crates/loro-core/src/lib.rs | 2 +- crates/loro-core/src/log_store.rs | 148 +++++++++++++----- crates/loro-core/src/log_store/iter.rs | 5 +- crates/loro-core/src/loro.rs | 9 +- crates/loro-core/src/op.rs | 98 +++++++++++- crates/rle/src/rle_vec.rs | 11 ++ 15 files changed, 275 insertions(+), 127 deletions(-) diff --git a/crates/loro-core/src/change.rs b/crates/loro-core/src/change.rs index f789fd50..7aa4f85f 100644 --- a/crates/loro-core/src/change.rs +++ b/crates/loro-core/src/change.rs @@ -8,11 +8,11 @@ use crate::{ dag::DagNode, id::{Counter, ID}, - op::{Op}, - span::{HasId, HasLamport}, - Container, + op::Op, + span::{HasId, HasIdSpan, HasLamport}, }; -use rle::{HasLength, Mergable, RleVec, Sliceable}; +use num::traits::AsPrimitive; +use rle::{HasIndex, HasLength, Mergable, RleVec, Sliceable}; use smallvec::SmallVec; pub type Timestamp = i64; @@ -20,8 +20,8 @@ pub type Lamport = u32; /// A `Change` contains a list of [Op]s. #[derive(Debug, Clone)] -pub struct Change { - pub(crate) ops: RleVec<[Op; 2]>, +pub struct Change { + pub(crate) ops: RleVec<[O; 2]>, pub(crate) deps: SmallVec<[ID; 2]>, /// id of the first op in the change pub(crate) id: ID, @@ -41,9 +41,9 @@ pub struct Change { pub(crate) break_points: SmallVec<[Counter; 2]>, } -impl Change { +impl Change { pub fn new( - ops: RleVec<[Op; 2]>, + ops: RleVec<[O; 2]>, deps: SmallVec<[ID; 2]>, id: ID, lamport: Lamport, @@ -59,19 +59,23 @@ impl Change { break_points: SmallVec::new(), } } +} - pub fn last_id(&self) -> ID { - self.id.inc(self.content_len() as Counter - 1) - } - - pub fn last_lamport(&self) -> Lamport { - self.lamport + self.content_len() as Lamport - 1 +impl HasId for Change { + fn id_start(&self) -> ID { + self.id } } -impl HasLength for Change { +impl HasLamport for Change { + fn lamport(&self) -> Lamport { + self.lamport + } +} + +impl HasLength for Change { fn content_len(&self) -> usize { - self.ops.span() as usize + self.ops.span().as_() } } @@ -112,7 +116,7 @@ impl Mergable for Change { return false; } - if other.deps.is_empty() || !(other.deps.len() == 1 && self.last_id() == other.deps[0]) { + if other.deps.is_empty() || !(other.deps.len() == 1 && self.id_last() == other.deps[0]) { return false; } @@ -130,19 +134,7 @@ impl Mergable for Change { } } -impl HasId for Change { - fn id_start(&self) -> ID { - self.id - } -} - -impl HasLamport for Change { - fn lamport(&self) -> Lamport { - self.lamport - } -} - -impl Sliceable for Change { +impl Sliceable for Change { // TODO: feels slow, need to confirm whether this affects performance fn slice(&self, from: usize, to: usize) -> Self { Self { @@ -165,18 +157,3 @@ impl DagNode for Change { &self.deps } } - -#[test] -fn size_of() { - println!("Change {}", std::mem::size_of::()); - println!("Op {}", std::mem::size_of::()); - println!("InsertContent {}", std::mem::size_of::()); - println!("MapSet {}", std::mem::size_of::()); - println!("ListSlice {}", std::mem::size_of::()); - println!("Box {}", std::mem::size_of::>()); - println!("InsertValue {}", std::mem::size_of::()); - println!("ID {}", std::mem::size_of::()); - println!("Vec {}", std::mem::size_of::>()); - println!("IdSpan {}", std::mem::size_of::()); - println!("ContainerID {}", std::mem::size_of::()); -} diff --git a/crates/loro-core/src/container.rs b/crates/loro-core/src/container.rs index 7553b637..07813a40 100644 --- a/crates/loro-core/src/container.rs +++ b/crates/loro-core/src/container.rs @@ -5,13 +5,12 @@ //! Every [Container] can take a [Snapshot], which contains [crate::LoroValue] that describes the state. //! use crate::{ - op::{Op}, + op::{Op, RemoteOp}, span::IdSpan, version::VersionVector, InternalString, LogStore, LoroValue, ID, }; - use serde::Serialize; use std::{any::Any, fmt::Debug}; @@ -36,7 +35,7 @@ pub trait Container: Debug + Any + Unpin { /// convert an op to export format. for example [ListSlice] should be convert to str before export fn to_export(&self, op: &mut Op); - fn to_import(&mut self, op: &mut Op); + fn to_import(&mut self, op: &mut RemoteOp); } /// [ContainerID] includes the Op's [ID] and the type. So it's impossible to have diff --git a/crates/loro-core/src/container/manager.rs b/crates/loro-core/src/container/manager.rs index 9b1f5bd6..2f4cda0d 100644 --- a/crates/loro-core/src/container/manager.rs +++ b/crates/loro-core/src/container/manager.rs @@ -1,6 +1,4 @@ -use std::{ - ops::{Deref, DerefMut}, -}; +use std::ops::{Deref, DerefMut}; use enum_as_inner::EnumAsInner; use fxhash::FxHashMap; @@ -9,6 +7,7 @@ use owning_ref::{OwningRef, OwningRefMut}; use crate::{ isomorph::{IsoRef, IsoRefMut}, log_store::LogStoreWeakRef, + op::RemoteOp, span::IdSpan, LogStore, LoroError, }; @@ -73,7 +72,7 @@ impl Container for ContainerInstance { } } - fn to_import(&mut self, op: &mut crate::op::Op) { + fn to_import(&mut self, op: &mut RemoteOp) { match self { ContainerInstance::Map(x) => x.to_import(op), ContainerInstance::Text(x) => x.to_import(op), diff --git a/crates/loro-core/src/container/map/map_container.rs b/crates/loro-core/src/container/map/map_container.rs index 0b3b83ef..f1ee61d1 100644 --- a/crates/loro-core/src/container/map/map_container.rs +++ b/crates/loro-core/src/container/map/map_container.rs @@ -4,8 +4,8 @@ use crate::{ container::{Container, ContainerID, ContainerType}, id::Counter, log_store::LogStoreWeakRef, - op::OpContent, op::{InsertContent, Op, RichOp}, + op::{OpContent, RemoteOp}, span::{HasId, IdSpan}, value::{InsertValue, LoroValue}, version::TotalOrderStamp, @@ -44,7 +44,7 @@ impl MapContainer { } pub fn insert(&mut self, key: InternalString, value: InsertValue) { - let self_id = self.id.clone(); + let self_id = &self.id; let m = self.store.upgrade().unwrap(); let mut store = m.write(); let client_id = store.this_client_id; @@ -55,9 +55,10 @@ impl MapContainer { let id = store.next_id_for(client_id); let counter = id.counter; + let container = store.get_container_idx(self_id).unwrap(); store.append_local_ops(&[Op { id, - container: self_id, + container, content: OpContent::Normal { content: InsertContent::Dyn(Box::new(MapSet { key: key.clone(), @@ -159,5 +160,5 @@ impl Container for MapContainer { fn to_export(&self, _op: &mut Op) {} - fn to_import(&mut self, _op: &mut Op) {} + fn to_import(&mut self, _op: &mut RemoteOp) {} } diff --git a/crates/loro-core/src/container/text/text_container.rs b/crates/loro-core/src/container/text/text_container.rs index 83ca4a86..fcf1c016 100644 --- a/crates/loro-core/src/container/text/text_container.rs +++ b/crates/loro-core/src/container/text/text_container.rs @@ -7,9 +7,9 @@ use crate::{ container::{list::list_op::ListOp, Container, ContainerID, ContainerType}, dag::DagUtils, debug_log, - id::{Counter, ID}, + id::{ContainerIdx, Counter, ID}, log_store::LogStoreWeakRef, - op::{InsertContent, Op, OpContent}, + op::{InsertContent, Op, OpContent, RemoteOp}, smstring::SmString, span::{HasIdSpan, IdSpan}, value::LoroValue, @@ -80,7 +80,7 @@ impl TextContainer { pos, }), }, - self.id.clone(), + store.get_or_create_container_idx(&self.id), ); let last_id = op.id_last(); store.append_local_ops(&[op]); @@ -103,7 +103,7 @@ impl TextContainer { OpContent::Normal { content: InsertContent::List(ListOp::Delete { len, pos }), }, - self.id.clone(), + store.get_or_create_container_idx(&self.id), ); let last_id = op.id_last(); @@ -181,6 +181,7 @@ impl Container for TextContainer { // TODO: need a better mechanism to track the head (KEEP IT IN TRACKER?) let path = store.find_path(&head, &latest_head); debug_log!("path={:?}", &path.right); + let self_idx = store.get_container_idx(&self.id).unwrap(); for iter in store.iter_partial(&head, path.right) { // TODO: avoid this clone let change = iter @@ -195,7 +196,7 @@ impl Container for TextContainer { self.tracker.retreat(&iter.retreat); self.tracker.forward(&iter.forward); for op in change.ops.iter() { - if op.container == self.id { + if op.container == self_idx { // TODO: convert op to local self.tracker.apply(op.id, &op.content) } @@ -272,7 +273,7 @@ impl Container for TextContainer { } } - fn to_import(&mut self, op: &mut Op) { + fn to_import(&mut self, op: &mut RemoteOp) { if let Some((slice, _pos)) = op .content .as_normal_mut() diff --git a/crates/loro-core/src/container/text/tracker.rs b/crates/loro-core/src/container/text/tracker.rs index c578c095..11c58cfa 100644 --- a/crates/loro-core/src/container/text/tracker.rs +++ b/crates/loro-core/src/container/text/tracker.rs @@ -4,7 +4,7 @@ use smallvec::SmallVec; use crate::{ container::{list::list_op::ListOp, text::tracker::yata_impl::YataImpl}, debug_log, - id::{Counter, ID}, + id::{ClientID, Counter, ID}, op::OpContent, span::{HasIdSpan, IdSpan}, version::IdSpanVector, 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 8e37ad33..0b217ec5 100644 --- a/crates/loro-core/src/container/text/tracker/y_span.rs +++ b/crates/loro-core/src/container/text/tracker/y_span.rs @@ -204,10 +204,7 @@ pub mod test { slice: Default::default(), })), }, - ContainerID::Normal { - id: ROOT_ID, - container_type: ContainerType::Text, - }, + 5, )); vec.push(Op::new( ID::new(0, 2), @@ -221,10 +218,7 @@ pub mod test { slice: Default::default(), })), }, - ContainerID::Normal { - id: ROOT_ID, - container_type: ContainerType::Text, - }, + 5, )); assert_eq!(vec.merged_len(), 1); let merged = vec.get_merged(0).unwrap(); @@ -248,10 +242,7 @@ pub mod test { slice: Default::default(), })), }, - ContainerID::Normal { - id: ROOT_ID, - container_type: ContainerType::Text, - }, + 5, )); vec.push(Op::new( ID::new(0, 2), @@ -265,10 +256,7 @@ pub mod test { slice: Default::default(), })), }, - ContainerID::Normal { - id: ROOT_ID, - container_type: ContainerType::Text, - }, + 5, )); assert_eq!(vec.merged_len(), 2); assert_eq!( diff --git a/crates/loro-core/src/dag/iter.rs b/crates/loro-core/src/dag/iter.rs index 6453ec27..e9f0217b 100644 --- a/crates/loro-core/src/dag/iter.rs +++ b/crates/loro-core/src/dag/iter.rs @@ -228,7 +228,10 @@ impl<'a, T: DagNode + 'a, D: Dag> Iterator for DagPartialIter<'a, D> { if self.heap.is_empty() { debug_assert_eq!( 0, - self.target.iter().map(|x| x.1.content_len() as i32).sum() + self.target + .iter() + .map(|x| x.1.content_len() as i32) + .sum::() ); return None; } diff --git a/crates/loro-core/src/id.rs b/crates/loro-core/src/id.rs index c99eea8c..21e1aae9 100644 --- a/crates/loro-core/src/id.rs +++ b/crates/loro-core/src/id.rs @@ -6,6 +6,7 @@ use crate::span::{CounterSpan, IdSpan}; pub type ClientID = u64; pub type Counter = i32; +pub(crate) type ContainerIdx = u32; const UNKNOWN: ClientID = 404; #[derive(PartialEq, Eq, Hash, Clone, Copy, Serialize)] diff --git a/crates/loro-core/src/lib.rs b/crates/loro-core/src/lib.rs index a8398b61..4c4bc9a1 100644 --- a/crates/loro-core/src/lib.rs +++ b/crates/loro-core/src/lib.rs @@ -32,7 +32,7 @@ pub use error::LoroError; pub(crate) mod macros; pub(crate) use change::{Lamport, Timestamp}; pub(crate) use id::{ClientID, ID}; -pub(crate) use op::{ContentType, InsertContentTrait, Op, OpType}; +pub(crate) use op::{ContentType, InsertContentTrait, Op, OpType, RemoteOp}; pub(crate) type InternalString = DefaultAtom; diff --git a/crates/loro-core/src/log_store.rs b/crates/loro-core/src/log_store.rs index e0ac7be1..42b8de81 100644 --- a/crates/loro-core/src/log_store.rs +++ b/crates/loro-core/src/log_store.rs @@ -6,7 +6,7 @@ use std::marker::PhantomPinned; use fxhash::{FxHashMap, FxHashSet}; -use rle::{HasLength, RleVecWithIndex, Sliceable}; +use rle::{HasLength, RleVec, RleVecWithIndex, Sliceable}; use smallvec::SmallVec; @@ -18,7 +18,8 @@ use crate::{ debug_log, id::{ClientID, Counter}, isomorph::{Irc, IsoRw, IsoWeak}, - span::{HasIdSpan, IdSpan}, + op::RemoteOp, + span::{HasIdSpan, HasLamportSpan, IdSpan}, Lamport, Op, Timestamp, VersionVector, ID, }; @@ -62,6 +63,8 @@ pub struct LogStore { /// CRDT container manager pub(crate) container: IsoWeak>, to_self: IsoWeak>, + container_to_idx: FxHashMap, + idx_to_container: Vec, _pin: PhantomPinned, } @@ -83,6 +86,8 @@ impl LogStore { container, to_self: x.clone(), vv: Default::default(), + idx_to_container: Default::default(), + container_to_idx: Default::default(), _pin: PhantomPinned, }) }) @@ -95,12 +100,12 @@ impl LogStore { .map(|changes| changes.get(id.counter as usize).unwrap().element) } - pub fn import(&mut self, mut changes: Vec) { + pub fn import(&mut self, mut changes: Vec>) { let self_vv = self.vv(); changes.sort_by_cached_key(|x| x.lamport); for change in changes .into_iter() - .filter(|x| !self_vv.includes_id(x.last_id())) + .filter(|x| !self_vv.includes_id(x.id_last())) { check_import_change_valid(&change); // TODO: cache pending changes @@ -109,16 +114,15 @@ impl LogStore { } } - pub fn export(&self, remote_vv: &VersionVector) -> Vec { + pub fn export(&self, remote_vv: &VersionVector) -> Vec> { let mut ans = Vec::default(); let self_vv = self.vv(); let diff = self_vv.diff(remote_vv); for span in diff.left.iter() { - let mut changes = self.get_changes_slice(span.id_span()); - ans.append(&mut changes); - } - for change in ans.iter_mut() { - self.change_to_export_format(change); + let changes = self.get_changes_slice(span.id_span()); + for change in changes { + ans.push(self.change_to_export_format(change)) + } } ans @@ -142,24 +146,49 @@ impl LogStore { } fn change_to_imported_format( - &self, + &mut self, container_manager: &mut ContainerManager, - change: &mut Change, - ) { - for op in change.ops.vec_mut().iter_mut() { + change: Change, + ) -> Change { + let mut new_ops = RleVec::new(); + for mut op in change.ops.into_iter() { let container = container_manager .get_or_create(&op.container, self.to_self.clone()) .unwrap(); - container.to_import(op); + container.to_import(&mut op); + self.get_or_create_container_idx(&op.container); + new_ops.push(op.convert(self)); + } + + Change { + ops: new_ops, + deps: change.deps, + id: change.id, + lamport: change.lamport, + timestamp: change.timestamp, + break_points: change.break_points, } } - fn change_to_export_format(&self, change: &mut Change) { + fn change_to_export_format(&self, change: Change) -> Change { let upgraded = self.container.upgrade().unwrap(); let container_manager = upgraded.read(); - for op in change.ops.vec_mut().iter_mut() { - let container = container_manager.get(&op.container).unwrap(); - container.to_export(op); + let mut ops = RleVec::new(); + for mut op in change.ops.into_iter() { + let container = container_manager + .get(&self.idx_to_container[op.container as usize]) + .unwrap(); + container.to_export(&mut op); + ops.push(op.convert(self)); + } + + Change { + ops, + deps: change.deps, + id: change.id, + lamport: change.lamport, + timestamp: change.timestamp, + break_points: change.break_points, } } @@ -249,8 +278,8 @@ impl LogStore { debug_log!("CHANGES---------------- site {}", self.this_client_id); } - pub fn apply_remote_change(&mut self, mut change: Change) { - if self.contains(change.last_id()) { + pub fn apply_remote_change(&mut self, mut change: Change) { + if self.contains(change.id_last()) { return; } @@ -264,7 +293,7 @@ impl LogStore { let upgraded = self.container.upgrade().unwrap(); let mut container_manager = upgraded.write(); #[cfg(feature = "slice")] - self.change_to_imported_format(&mut container_manager, &mut change); + let change = self.change_to_imported_format(&mut container_manager, change); let v = self .changes .entry(change.id.client_id) @@ -281,17 +310,20 @@ impl LogStore { for container in set { let container = container_manager - .get_or_create(container, self.to_self.clone()) + .get_or_create( + &self.idx_to_container[*container as usize], + self.to_self.clone(), + ) .unwrap(); container.apply(change.id_span(), self); } drop(container_manager); self.vv.set_end(change.id_end()); - self.update_frontier(&change.deps, &[change.last_id()]); + self.update_frontier(&change.deps, &[change.id_last()]); - if change.last_lamport() > self.latest_lamport { - self.latest_lamport = change.last_lamport(); + if change.lamport_last() > self.latest_lamport { + self.latest_lamport = change.lamport_last(); } if change.timestamp > self.latest_timestamp { @@ -329,7 +361,8 @@ impl LogStore { id_span: IdSpan, container: ContainerID, ) -> iter::OpSpanIter<'_> { - iter::OpSpanIter::new(&self.changes, id_span, container) + let idx = self.get_container_idx(&container).unwrap(); + iter::OpSpanIter::new(&self.changes, id_span, idx) } #[inline(always)] @@ -359,6 +392,25 @@ impl LogStore { .join(", "), ); } + + pub(crate) fn get_container_idx(&self, container: &ContainerID) -> Option { + self.container_to_idx.get(container).copied() + } + + pub(crate) fn get_or_create_container_idx(&mut self, container: &ContainerID) -> u32 { + if let Some(idx) = self.container_to_idx.get(container) { + *idx + } else { + let idx = self.container_to_idx.len() as u32; + self.container_to_idx.insert(container.clone(), idx); + self.idx_to_container.push(container.clone()); + idx + } + } + + pub(crate) fn get_container_id(&self, container: u32) -> &ContainerID { + &self.idx_to_container[container as usize] + } } impl Dag for LogStore { @@ -379,15 +431,39 @@ impl Dag for LogStore { } } -fn check_import_change_valid(change: &Change) { - for op in change.ops.iter() { - if let Some((slice, _)) = op - .content - .as_normal() - .and_then(|x| x.as_list()) - .and_then(|x| x.as_insert()) - { - assert!(slice.as_raw_str().is_some()) +fn check_import_change_valid(change: &Change) { + if cfg!(test) { + for op in change.ops.iter() { + if let Some((slice, _)) = op + .content + .as_normal() + .and_then(|x| x.as_list()) + .and_then(|x| x.as_insert()) + { + assert!(slice.as_raw_str().is_some()) + } } } } + +#[test] +fn size_of() { + use crate::{ + container::{map::MapSet, text::text_content::ListSlice, ContainerID}, + id::ID, + op::{InsertContent, Op}, + span::IdSpan, + Container, InsertValue, + }; + println!("Change {}", std::mem::size_of::()); + println!("Op {}", std::mem::size_of::()); + println!("InsertContent {}", std::mem::size_of::()); + println!("MapSet {}", std::mem::size_of::()); + println!("ListSlice {}", std::mem::size_of::()); + println!("Box {}", std::mem::size_of::>()); + println!("InsertValue {}", std::mem::size_of::()); + println!("ID {}", std::mem::size_of::()); + println!("Vec {}", std::mem::size_of::>()); + println!("IdSpan {}", std::mem::size_of::()); + println!("ContainerID {}", std::mem::size_of::()); +} diff --git a/crates/loro-core/src/log_store/iter.rs b/crates/loro-core/src/log_store/iter.rs index 1a224c86..155bb5e4 100644 --- a/crates/loro-core/src/log_store/iter.rs +++ b/crates/loro-core/src/log_store/iter.rs @@ -4,6 +4,7 @@ use crate::change::Lamport; use crate::container::ContainerID; use crate::id::ClientID; +use crate::id::ContainerIdx; use crate::op::RichOp; use crate::span::HasId; use crate::span::IdSpan; @@ -46,7 +47,7 @@ pub struct OpSpanIter<'a> { changes: &'a [Change], change_index: usize, op_index: usize, - container: ContainerID, + container: ContainerIdx, span: IdSpan, } @@ -54,7 +55,7 @@ impl<'a> OpSpanIter<'a> { pub fn new( changes: &'a FxHashMap>, target_span: IdSpan, - container: ContainerID, + container: ContainerIdx, ) -> Self { let rle_changes = changes.get(&target_span.client_id).unwrap(); let changes = rle_changes.vec(); diff --git a/crates/loro-core/src/loro.rs b/crates/loro-core/src/loro.rs index 8ebaca12..da03b84f 100644 --- a/crates/loro-core/src/loro.rs +++ b/crates/loro-core/src/loro.rs @@ -1,5 +1,3 @@ - - use owning_ref::{OwningRef, OwningRefMut}; use crate::{ @@ -13,6 +11,7 @@ use crate::{ }, id::ClientID, isomorph::{Irc, IsoRw}, + op::RemoteOp, LogStore, LoroError, VersionVector, }; @@ -48,6 +47,7 @@ impl LoroCore { ) -> Result, LoroError> { let mut a = OwningRefMut::new(self.container.write()); let id = ContainerID::new_root(name, ContainerType::Map); + self.log_store.write().get_or_create_container_idx(&id); let ptr = Irc::downgrade(&self.log_store); a.get_or_create(&id, ptr)?; Ok( @@ -63,6 +63,7 @@ impl LoroCore { ) -> Result, LoroError> { let mut a = OwningRefMut::new(self.container.write()); let id = ContainerID::new_root(name, ContainerType::Text); + self.log_store.write().get_or_create_container_idx(&id); let ptr = Irc::downgrade(&self.log_store); a.get_or_create(&id, ptr)?; Ok( @@ -104,12 +105,12 @@ impl LoroCore { Ok(a.map(move |x| x.get(id).unwrap().as_text().unwrap()).into()) } - pub fn export(&self, remote_vv: VersionVector) -> Vec { + pub fn export(&self, remote_vv: VersionVector) -> Vec> { let store = self.log_store.read(); store.export(&remote_vv) } - pub fn import(&mut self, changes: Vec) { + pub fn import(&mut self, changes: Vec>) { let mut store = self.log_store.write(); store.import(changes) } diff --git a/crates/loro-core/src/op.rs b/crates/loro-core/src/op.rs index 11b2da6f..eba6a63b 100644 --- a/crates/loro-core/src/op.rs +++ b/crates/loro-core/src/op.rs @@ -3,6 +3,7 @@ use crate::{ container::ContainerID, id::{Counter, ID}, span::HasId, + LogStore, }; use rle::{HasIndex, HasLength, Mergable, Sliceable}; mod insert_content; @@ -30,6 +31,13 @@ pub enum OpType { /// A Op may have multiple atomic operations, since Op can be merged. #[derive(Debug, Clone)] pub struct Op { + pub(crate) id: ID, + pub(crate) container: u32, + pub(crate) content: OpContent, +} + +#[derive(Debug, Clone)] +pub struct RemoteOp { pub(crate) id: ID, pub(crate) container: ContainerID, pub(crate) content: OpContent, @@ -37,7 +45,7 @@ pub struct Op { impl Op { #[inline] - pub(crate) fn new(id: ID, content: OpContent, container: ContainerID) -> Self { + pub(crate) fn new(id: ID, content: OpContent, container: u32) -> Self { Op { id, content, @@ -46,7 +54,7 @@ impl Op { } #[inline] - pub(crate) fn new_insert_op(id: ID, container: ContainerID, content: InsertContent) -> Self { + pub(crate) fn new_insert_op(id: ID, container: u32, content: InsertContent) -> Self { Op::new(id, OpContent::Normal { content }, container) } @@ -58,8 +66,26 @@ impl Op { } } - pub fn container(&self) -> &ContainerID { - &self.container + pub(crate) fn convert(self, log: &LogStore) -> RemoteOp { + let container = log.get_container_id(self.container).clone(); + RemoteOp { + id: self.id, + container, + content: self.content, + } + } +} + +impl RemoteOp { + pub(crate) fn convert(self, log: &mut LogStore) -> Op { + let id = self.id; + let container = log.get_or_create_container_idx(&self.container); + let content = self.content; + Op { + id, + container, + content, + } } } @@ -119,6 +145,62 @@ impl Sliceable for Op { } } +impl Mergable for RemoteOp { + fn is_mergable(&self, other: &Self, cfg: &()) -> bool { + self.id.is_connected_id(&other.id, self.content_len()) + && self.content.is_mergable(&other.content, cfg) + && self.container == other.container + } + + fn merge(&mut self, other: &Self, cfg: &()) { + match &mut self.content { + OpContent::Normal { content } => match &other.content { + OpContent::Normal { + content: other_content, + } => { + content.merge(other_content, cfg); + } + _ => unreachable!(), + }, + OpContent::Undo { target, .. } => match &other.content { + OpContent::Undo { + target: other_target, + .. + } => target.merge(other_target, cfg), + _ => unreachable!(), + }, + OpContent::Redo { target, .. } => match &other.content { + OpContent::Redo { + target: other_target, + .. + } => target.merge(other_target, cfg), + _ => unreachable!(), + }, + } + } +} + +impl HasLength for RemoteOp { + fn content_len(&self) -> usize { + self.content.content_len() + } +} + +impl Sliceable for RemoteOp { + fn slice(&self, from: usize, to: usize) -> Self { + assert!(to > from); + let content: OpContent = self.content.slice(from, to); + RemoteOp { + id: ID { + client_id: self.id.client_id, + counter: (self.id.counter + from as Counter), + }, + content, + container: self.container.clone(), + } + } +} + impl HasId for Op { fn id_start(&self) -> ID { self.id @@ -138,3 +220,11 @@ impl HasIndex for Op { self.id.counter } } + +impl HasIndex for RemoteOp { + type Int = Counter; + + fn get_start_index(&self) -> Self::Int { + self.id.counter + } +} diff --git a/crates/rle/src/rle_vec.rs b/crates/rle/src/rle_vec.rs index 87bb83d5..c6d9e358 100644 --- a/crates/rle/src/rle_vec.rs +++ b/crates/rle/src/rle_vec.rs @@ -129,6 +129,17 @@ impl RleVec { self.vec.len() } } + +impl IntoIterator for RleVec { + type Item = A::Item; + + type IntoIter = smallvec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.vec.into_iter() + } +} + impl Debug for RleVecWithLen where A::Item: Debug,