mirror of
https://github.com/loro-dev/loro.git
synced 2024-12-04 05:29:48 +00:00
refactor: extract remote op
This commit is contained in:
parent
ee8df9dd59
commit
6e03c9c04b
15 changed files with 275 additions and 127 deletions
|
@ -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<O = Op> {
|
||||
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<O> Change<O> {
|
||||
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<O> HasId for Change<O> {
|
||||
fn id_start(&self) -> ID {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
||||
impl HasLength for Change {
|
||||
impl<O> HasLamport for Change<O> {
|
||||
fn lamport(&self) -> Lamport {
|
||||
self.lamport
|
||||
}
|
||||
}
|
||||
|
||||
impl<O: Mergable + HasLength + HasIndex> HasLength for Change<O> {
|
||||
fn content_len(&self) -> usize {
|
||||
self.ops.span() as usize
|
||||
self.ops.span().as_()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -112,7 +116,7 @@ impl Mergable<ChangeMergeCfg> 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<ChangeMergeCfg> 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<O: Mergable + HasLength + Sliceable> Sliceable for Change<O> {
|
||||
// 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::<Change>());
|
||||
println!("Op {}", std::mem::size_of::<Op>());
|
||||
println!("InsertContent {}", std::mem::size_of::<InsertContent>());
|
||||
println!("MapSet {}", std::mem::size_of::<MapSet>());
|
||||
println!("ListSlice {}", std::mem::size_of::<ListSlice>());
|
||||
println!("Box {}", std::mem::size_of::<Box<dyn Container>>());
|
||||
println!("InsertValue {}", std::mem::size_of::<InsertValue>());
|
||||
println!("ID {}", std::mem::size_of::<ID>());
|
||||
println!("Vec {}", std::mem::size_of::<Vec<ID>>());
|
||||
println!("IdSpan {}", std::mem::size_of::<IdSpan>());
|
||||
println!("ContainerID {}", std::mem::size_of::<ContainerID>());
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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) {}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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!(
|
||||
|
|
|
@ -228,7 +228,10 @@ impl<'a, T: DagNode + 'a, D: Dag<Node = T>> 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::<i32>()
|
||||
);
|
||||
return None;
|
||||
}
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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<IsoRw<ContainerManager>>,
|
||||
to_self: IsoWeak<IsoRw<LogStore>>,
|
||||
container_to_idx: FxHashMap<ContainerID, u32>,
|
||||
idx_to_container: Vec<ContainerID>,
|
||||
_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<Change>) {
|
||||
pub fn import(&mut self, mut changes: Vec<Change<RemoteOp>>) {
|
||||
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<Change> {
|
||||
pub fn export(&self, remote_vv: &VersionVector) -> Vec<Change<RemoteOp>> {
|
||||
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<RemoteOp>,
|
||||
) -> 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<RemoteOp> {
|
||||
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<RemoteOp>) {
|
||||
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<u32> {
|
||||
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<RemoteOp>) {
|
||||
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::<Change>());
|
||||
println!("Op {}", std::mem::size_of::<Op>());
|
||||
println!("InsertContent {}", std::mem::size_of::<InsertContent>());
|
||||
println!("MapSet {}", std::mem::size_of::<MapSet>());
|
||||
println!("ListSlice {}", std::mem::size_of::<ListSlice>());
|
||||
println!("Box {}", std::mem::size_of::<Box<dyn Container>>());
|
||||
println!("InsertValue {}", std::mem::size_of::<InsertValue>());
|
||||
println!("ID {}", std::mem::size_of::<ID>());
|
||||
println!("Vec {}", std::mem::size_of::<Vec<ID>>());
|
||||
println!("IdSpan {}", std::mem::size_of::<IdSpan>());
|
||||
println!("ContainerID {}", std::mem::size_of::<ContainerID>());
|
||||
}
|
||||
|
|
|
@ -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<ClientID, RleVecWithIndex<Change, ChangeMergeCfg>>,
|
||||
target_span: IdSpan,
|
||||
container: ContainerID,
|
||||
container: ContainerIdx,
|
||||
) -> Self {
|
||||
let rle_changes = changes.get(&target_span.client_id).unwrap();
|
||||
let changes = rle_changes.vec();
|
||||
|
|
|
@ -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<ContainerRefMut<MapContainer>, 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<ContainerRefMut<TextContainer>, 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<Change> {
|
||||
pub fn export(&self, remote_vv: VersionVector) -> Vec<Change<RemoteOp>> {
|
||||
let store = self.log_store.read();
|
||||
store.export(&remote_vv)
|
||||
}
|
||||
|
||||
pub fn import(&mut self, changes: Vec<Change>) {
|
||||
pub fn import(&mut self, changes: Vec<Change<RemoteOp>>) {
|
||||
let mut store = self.log_store.write();
|
||||
store.import(changes)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -129,6 +129,17 @@ impl<A: Array> RleVec<A> {
|
|||
self.vec.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: Array> IntoIterator for RleVec<A> {
|
||||
type Item = A::Item;
|
||||
|
||||
type IntoIter = smallvec::IntoIter<A>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.vec.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: Array> Debug for RleVecWithLen<A>
|
||||
where
|
||||
A::Item: Debug,
|
||||
|
|
Loading…
Reference in a new issue