mirror of
https://github.com/loro-dev/loro.git
synced 2025-02-02 02:59:51 +00:00
refactor: make merge trait configurable
This commit is contained in:
parent
3eb415718c
commit
1444029e5e
5 changed files with 72 additions and 46 deletions
|
@ -6,9 +6,6 @@ use smallvec::SmallVec;
|
|||
|
||||
pub type Timestamp = i64;
|
||||
pub type Lamport = u64;
|
||||
const MAX_CHANGE_LENGTH: usize = 256;
|
||||
/// TODO: Should this be configurable?
|
||||
const MAX_MERGABLE_INTERVAL: Timestamp = 60;
|
||||
|
||||
/// Change
|
||||
#[derive(Debug)]
|
||||
|
@ -54,12 +51,17 @@ impl HasLength for Change {
|
|||
}
|
||||
}
|
||||
|
||||
impl Mergable for Change {
|
||||
fn merge(&mut self, other: &Self) {
|
||||
self.ops.merge(&other.ops);
|
||||
pub struct ChangeMergeCfg {
|
||||
max_change_length: usize,
|
||||
max_change_interval: usize,
|
||||
}
|
||||
|
||||
impl Mergable<ChangeMergeCfg> for Change {
|
||||
fn merge(&mut self, other: &Self, cfg: &ChangeMergeCfg) {
|
||||
self.ops.merge(&other.ops, &());
|
||||
}
|
||||
|
||||
fn is_mergable(&self, other: &Self) -> bool {
|
||||
fn is_mergable(&self, other: &Self, cfg: &ChangeMergeCfg) -> bool {
|
||||
if self.freezed {
|
||||
return false;
|
||||
}
|
||||
|
@ -68,11 +70,11 @@ impl Mergable for Change {
|
|||
return false;
|
||||
}
|
||||
|
||||
if self.len() > MAX_CHANGE_LENGTH {
|
||||
if self.len() > cfg.max_change_length {
|
||||
return false;
|
||||
}
|
||||
|
||||
if other.timestamp - self.timestamp > MAX_MERGABLE_INTERVAL {
|
||||
if other.timestamp - self.timestamp > cfg.max_change_interval as i64 {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -60,11 +60,11 @@ impl Sliceable for IdSpan {
|
|||
}
|
||||
|
||||
impl Mergable for IdSpan {
|
||||
fn is_mergable(&self, other: &Self) -> bool {
|
||||
fn is_mergable(&self, other: &Self, _: &()) -> bool {
|
||||
self.client_id == other.client_id && self.to == other.from
|
||||
}
|
||||
|
||||
fn merge(&mut self, other: &Self) {
|
||||
fn merge(&mut self, other: &Self, _: &()) {
|
||||
self.to = other.to;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,10 +2,10 @@ use rle::RleVec;
|
|||
use std::collections::HashMap;
|
||||
use string_cache::{Atom, DefaultAtom, EmptyStaticAtomSet};
|
||||
|
||||
use crate::{change::Change, id::ClientID, Lamport, ID};
|
||||
use crate::{change::Change, id::ClientID, ChangeMergeCfg, Lamport, ID};
|
||||
|
||||
pub struct LogStore {
|
||||
ops: HashMap<ClientID, RleVec<Change>>,
|
||||
ops: HashMap<ClientID, RleVec<Change, ChangeMergeCfg>>,
|
||||
lamport: Lamport,
|
||||
}
|
||||
|
||||
|
|
|
@ -49,7 +49,7 @@ impl Op {
|
|||
}
|
||||
|
||||
impl Mergable for Op {
|
||||
fn is_mergable(&self, other: &Self) -> bool {
|
||||
fn is_mergable(&self, other: &Self, cfg: &()) -> bool {
|
||||
match &self.content {
|
||||
OpContent::Insert { container, content } => match other.content {
|
||||
OpContent::Insert {
|
||||
|
@ -62,20 +62,26 @@ impl Mergable for Op {
|
|||
OpContent::Delete {
|
||||
target: ref other_target,
|
||||
lamport: ref other_lamport,
|
||||
} => lamport + target.len() == *other_lamport && target.is_mergable(other_target),
|
||||
} => {
|
||||
lamport + target.len() == *other_lamport
|
||||
&& target.is_mergable(other_target, cfg)
|
||||
}
|
||||
_ => false,
|
||||
},
|
||||
OpContent::Restore { target, lamport } => match other.content {
|
||||
OpContent::Restore {
|
||||
target: ref other_target,
|
||||
lamport: ref other_lamport,
|
||||
} => lamport + target.len() == *other_lamport && target.is_mergable(other_target),
|
||||
} => {
|
||||
lamport + target.len() == *other_lamport
|
||||
&& target.is_mergable(other_target, cfg)
|
||||
}
|
||||
_ => false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn merge(&mut self, other: &Self) {
|
||||
fn merge(&mut self, other: &Self, cfg: &()) {
|
||||
match &mut self.content {
|
||||
OpContent::Insert { container, content } => match &other.content {
|
||||
OpContent::Insert {
|
||||
|
@ -91,14 +97,14 @@ impl Mergable for Op {
|
|||
OpContent::Delete {
|
||||
target: other_target,
|
||||
..
|
||||
} => target.merge(other_target),
|
||||
} => target.merge(other_target, cfg),
|
||||
_ => unreachable!(),
|
||||
},
|
||||
OpContent::Restore { target, .. } => match &other.content {
|
||||
OpContent::Restore {
|
||||
target: other_target,
|
||||
..
|
||||
} => target.merge(other_target),
|
||||
} => target.merge(other_target, cfg),
|
||||
_ => unreachable!(),
|
||||
},
|
||||
}
|
||||
|
|
|
@ -11,17 +11,18 @@
|
|||
/// - get(index) returns the atom element at the index.
|
||||
/// - slice(from, to) returns a slice of atom elements from the index from to the index to.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RleVec<T> {
|
||||
pub struct RleVec<T, Cfg = ()> {
|
||||
vec: Vec<T>,
|
||||
_len: usize,
|
||||
index: Vec<usize>,
|
||||
cfg: Cfg,
|
||||
}
|
||||
|
||||
pub trait Mergable {
|
||||
fn is_mergable(&self, other: &Self) -> bool
|
||||
pub trait Mergable<Cfg = ()> {
|
||||
fn is_mergable(&self, other: &Self, conf: &Cfg) -> bool
|
||||
where
|
||||
Self: Sized;
|
||||
fn merge(&mut self, other: &Self)
|
||||
fn merge(&mut self, other: &Self, conf: &Cfg)
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
@ -45,12 +46,12 @@ pub trait HasLength {
|
|||
}
|
||||
|
||||
pub struct SearchResult<'a, T> {
|
||||
element: &'a T,
|
||||
merged_index: usize,
|
||||
offset: usize,
|
||||
pub element: &'a T,
|
||||
pub merged_index: usize,
|
||||
pub offset: usize,
|
||||
}
|
||||
|
||||
impl<T: Mergable + HasLength> RleVec<T> {
|
||||
impl<T: Mergable<Cfg> + HasLength, Cfg> RleVec<T, Cfg> {
|
||||
/// push a new element to the end of the array. It may be merged with last element.
|
||||
pub fn push(&mut self, value: T) {
|
||||
self._len += value.len();
|
||||
|
@ -62,8 +63,8 @@ impl<T: Mergable + HasLength> RleVec<T> {
|
|||
}
|
||||
|
||||
let last = self.vec.last_mut().unwrap();
|
||||
if last.is_mergable(&value) {
|
||||
last.merge(&value);
|
||||
if last.is_mergable(&value, &self.cfg) {
|
||||
last.merge(&value, &self.cfg);
|
||||
*self.index.last_mut().unwrap() = self._len;
|
||||
return;
|
||||
}
|
||||
|
@ -75,13 +76,18 @@ impl<T: Mergable + HasLength> RleVec<T> {
|
|||
self.vec.is_empty()
|
||||
}
|
||||
|
||||
/// number of atom elements in the array.
|
||||
pub fn len(&self) -> usize {
|
||||
self._len
|
||||
}
|
||||
|
||||
/// get the element at the given atom index.
|
||||
/// return: (element, merged_index, offset)
|
||||
pub fn get(&self, index: usize) -> SearchResult<'_, T> {
|
||||
pub fn get(&self, index: usize) -> Option<SearchResult<'_, T>> {
|
||||
if index > self.len() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut start = 0;
|
||||
let mut end = self.index.len() - 1;
|
||||
while start < end {
|
||||
|
@ -103,17 +109,17 @@ impl<T: Mergable + HasLength> RleVec<T> {
|
|||
}
|
||||
|
||||
let value = &self.vec[start];
|
||||
SearchResult {
|
||||
Some(SearchResult {
|
||||
element: value,
|
||||
merged_index: start,
|
||||
offset: index - self.index[start],
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// get a slice from `from` to `to` with atom indexes
|
||||
pub fn slice_iter(&self, from: usize, to: usize) -> SliceIterator<'_, T> {
|
||||
let from_result = self.get(from);
|
||||
let to_result = self.get(to);
|
||||
let from_result = self.get(from).unwrap();
|
||||
let to_result = self.get(to).unwrap();
|
||||
SliceIterator {
|
||||
vec: &self.vec,
|
||||
cur_index: from_result.merged_index,
|
||||
|
@ -124,12 +130,24 @@ impl<T: Mergable + HasLength> RleVec<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T> RleVec<T> {
|
||||
impl<T, Conf: Default> RleVec<T, Conf> {
|
||||
pub fn new() -> Self {
|
||||
RleVec {
|
||||
vec: Vec::new(),
|
||||
_len: 0,
|
||||
index: Vec::new(),
|
||||
cfg: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Conf> RleVec<T, Conf> {
|
||||
pub fn new_cfg(cfg: Conf) -> Self {
|
||||
RleVec {
|
||||
vec: Vec::new(),
|
||||
_len: 0,
|
||||
index: Vec::new(),
|
||||
cfg,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -207,12 +225,12 @@ impl<'a, T: HasLength> Iterator for SliceIterator<'a, T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T: Mergable + HasLength + Sliceable + Clone> Mergable for RleVec<T> {
|
||||
fn is_mergable(&self, _: &Self) -> bool {
|
||||
impl<T: Mergable<Cfg> + HasLength + Sliceable + Clone, Cfg> Mergable<Cfg> for RleVec<T, Cfg> {
|
||||
fn is_mergable(&self, _: &Self, _: &Cfg) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn merge(&mut self, other: &Self) {
|
||||
fn merge(&mut self, other: &Self, _: &Cfg) {
|
||||
for item in other.vec.iter() {
|
||||
self.push(item.clone());
|
||||
}
|
||||
|
@ -245,11 +263,11 @@ mod test {
|
|||
}
|
||||
|
||||
impl Mergable for String {
|
||||
fn is_mergable(&self, _: &Self) -> bool {
|
||||
fn is_mergable(&self, _: &Self, _: &()) -> bool {
|
||||
self.len() < 8
|
||||
}
|
||||
|
||||
fn merge(&mut self, other: &Self) {
|
||||
fn merge(&mut self, other: &Self, _: &()) {
|
||||
self.push_str(other);
|
||||
}
|
||||
}
|
||||
|
@ -266,13 +284,13 @@ mod test {
|
|||
vec.push("1234".to_string());
|
||||
vec.push("5678".to_string());
|
||||
vec.push("12345678".to_string());
|
||||
assert_eq!(vec.get(4).element, "12345678");
|
||||
assert_eq!(vec.get(4).merged_index, 0);
|
||||
assert_eq!(vec.get(4).offset, 4);
|
||||
assert_eq!(vec.get(4).unwrap().element, "12345678");
|
||||
assert_eq!(vec.get(4).unwrap().merged_index, 0);
|
||||
assert_eq!(vec.get(4).unwrap().offset, 4);
|
||||
|
||||
assert_eq!(vec.get(8).element, "12345678");
|
||||
assert_eq!(vec.get(8).merged_index, 1);
|
||||
assert_eq!(vec.get(8).offset, 0);
|
||||
assert_eq!(vec.get(8).unwrap().element, "12345678");
|
||||
assert_eq!(vec.get(8).unwrap().merged_index, 1);
|
||||
assert_eq!(vec.get(8).unwrap().offset, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
Loading…
Reference in a new issue