refactor: extract remote op

This commit is contained in:
Zixuan Chen 2022-10-31 23:50:00 +08:00
parent ee8df9dd59
commit 6e03c9c04b
15 changed files with 275 additions and 127 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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