mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2025-04-25 07:07:32 -04:00
add flowy ot
This commit is contained in:
parent
55de7f69a4
commit
b449707021
9 changed files with 880 additions and 0 deletions
|
@ -15,6 +15,7 @@ members = [
|
||||||
"flowy-observable",
|
"flowy-observable",
|
||||||
"flowy-document",
|
"flowy-document",
|
||||||
"flowy-editor",
|
"flowy-editor",
|
||||||
|
"flowy-ot",
|
||||||
]
|
]
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
|
|
13
rust-lib/flowy-ot/Cargo.toml
Normal file
13
rust-lib/flowy-ot/Cargo.toml
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
[package]
|
||||||
|
name = "flowy-ot"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
bytecount = "0.6.0"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
criterion = "0.3"
|
||||||
|
rand = "0.7.3"
|
97
rust-lib/flowy-ot/src/attributes.rs
Normal file
97
rust-lib/flowy-ot/src/attributes.rs
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
use std::collections::{hash_map::RandomState, HashMap};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default, PartialEq)]
|
||||||
|
pub struct Attributes {
|
||||||
|
inner: HashMap<String, String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Attributes {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Attributes {
|
||||||
|
inner: HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_empty_value(&mut self) { self.inner.retain((|_, v| v.is_empty())); }
|
||||||
|
|
||||||
|
pub fn extend(&mut self, other: Attributes) { self.inner.extend(other.inner); }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::convert::From<HashMap<String, String>> for Attributes {
|
||||||
|
fn from(attributes: HashMap<String, String, RandomState>) -> Self {
|
||||||
|
Attributes { inner: attributes }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Deref for Attributes {
|
||||||
|
type Target = HashMap<String, String>;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target { &self.inner }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::DerefMut for Attributes {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.inner }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn compose_attributes(
|
||||||
|
mut a: Attributes,
|
||||||
|
b: Attributes,
|
||||||
|
keep_empty: bool,
|
||||||
|
) -> Option<Attributes> {
|
||||||
|
a.extend(b);
|
||||||
|
let mut result = a;
|
||||||
|
if !keep_empty {
|
||||||
|
result.remove_empty_value()
|
||||||
|
}
|
||||||
|
|
||||||
|
return if result.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(result)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn transform_attributes(a: Attributes, b: Attributes, priority: bool) -> Option<Attributes> {
|
||||||
|
if a.is_empty() {
|
||||||
|
return Some(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !priority {
|
||||||
|
return Some(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = b.iter().fold(Attributes::new(), |mut attributes, (k, v)| {
|
||||||
|
if a.contains_key(k) == false {
|
||||||
|
attributes.insert(k.clone(), v.clone());
|
||||||
|
}
|
||||||
|
attributes
|
||||||
|
});
|
||||||
|
Some(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn invert_attributes(attr: Option<Attributes>, base: Option<Attributes>) -> Attributes {
|
||||||
|
let attr = attr.unwrap_or(Attributes::new());
|
||||||
|
let base = base.unwrap_or(Attributes::new());
|
||||||
|
|
||||||
|
let base_inverted = base
|
||||||
|
.iter()
|
||||||
|
.fold(Attributes::new(), |mut attributes, (k, v)| {
|
||||||
|
if base.get(k) != attr.get(k) && attr.contains_key(k) {
|
||||||
|
attributes.insert(k.clone(), v.clone());
|
||||||
|
}
|
||||||
|
attributes
|
||||||
|
});
|
||||||
|
|
||||||
|
let inverted = attr.iter().fold(base_inverted, |mut attributes, (k, v)| {
|
||||||
|
if base.get(k) != attr.get(k) && !base.contains_key(k) {
|
||||||
|
attributes.insert(k.clone(), "".to_owned());
|
||||||
|
}
|
||||||
|
attributes
|
||||||
|
});
|
||||||
|
|
||||||
|
return inverted;
|
||||||
|
}
|
443
rust-lib/flowy-ot/src/delta.rs
Normal file
443
rust-lib/flowy-ot/src/delta.rs
Normal file
|
@ -0,0 +1,443 @@
|
||||||
|
use crate::{errors::OTError, operation::*};
|
||||||
|
use bytecount::num_chars;
|
||||||
|
use std::{cmp::Ordering, error::Error, fmt, iter::FromIterator};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct Delta {
|
||||||
|
pub ops: Vec<Operation>,
|
||||||
|
pub base_len: usize,
|
||||||
|
pub target_len: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Delta {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
ops: Vec::new(),
|
||||||
|
base_len: 0,
|
||||||
|
target_len: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromIterator<OpType> for Delta {
|
||||||
|
fn from_iter<T: IntoIterator<Item = OpType>>(ops: T) -> Self {
|
||||||
|
let mut operations = Delta::default();
|
||||||
|
for op in ops {
|
||||||
|
operations.add(op);
|
||||||
|
}
|
||||||
|
operations
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Delta {
|
||||||
|
#[inline]
|
||||||
|
pub fn with_capacity(capacity: usize) -> Self {
|
||||||
|
Self {
|
||||||
|
ops: Vec::with_capacity(capacity),
|
||||||
|
base_len: 0,
|
||||||
|
target_len: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add(&mut self, op: OpType) {
|
||||||
|
match op {
|
||||||
|
OpType::Delete(i) => self.delete(i),
|
||||||
|
OpType::Insert(s) => self.insert(&s),
|
||||||
|
OpType::Retain(i) => self.retain(i),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn delete(&mut self, n: u64) {
|
||||||
|
if n == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.base_len += n as usize;
|
||||||
|
|
||||||
|
if let Some(operation) = self.ops.last_mut() {
|
||||||
|
if operation.ty.is_delete() {
|
||||||
|
operation.delete(n);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.ops.push(OperationBuilder::delete(n).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insert(&mut self, s: &str) {
|
||||||
|
if s.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.target_len += num_chars(s.as_bytes());
|
||||||
|
let new_last = match self
|
||||||
|
.ops
|
||||||
|
.iter_mut()
|
||||||
|
.map(|op| &mut op.ty)
|
||||||
|
.collect::<Vec<&mut OpType>>()
|
||||||
|
.as_mut_slice()
|
||||||
|
{
|
||||||
|
[.., OpType::Insert(s_last)] => {
|
||||||
|
*s_last += &s;
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
[.., OpType::Insert(s_pre_last), OpType::Delete(_)] => {
|
||||||
|
*s_pre_last += s;
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
[.., op_last @ OpType::Delete(_)] => {
|
||||||
|
let new_last = op_last.clone();
|
||||||
|
*(*op_last) = OpType::Insert(s.to_owned());
|
||||||
|
new_last
|
||||||
|
},
|
||||||
|
_ => OpType::Insert(s.to_owned()),
|
||||||
|
};
|
||||||
|
self.ops.push(OperationBuilder::new(new_last).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn retain(&mut self, n: u64) {
|
||||||
|
if n == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.base_len += n as usize;
|
||||||
|
self.target_len += n as usize;
|
||||||
|
|
||||||
|
if let Some(operation) = self.ops.last_mut() {
|
||||||
|
if operation.ty.is_retain() {
|
||||||
|
operation.retain(n);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.ops.push(OperationBuilder::retain(n).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Merges the operation with `other` into one operation while preserving
|
||||||
|
/// the changes of both. Or, in other words, for each input string S and a
|
||||||
|
/// pair of consecutive operations A and B.
|
||||||
|
/// `apply(apply(S, A), B) = apply(S, compose(A, B))`
|
||||||
|
/// must hold.
|
||||||
|
///
|
||||||
|
/// # Error
|
||||||
|
///
|
||||||
|
/// Returns an `OTError` if the operations are not composable due to length
|
||||||
|
/// conflicts.
|
||||||
|
pub fn compose(&self, other: &Self) -> Result<Self, OTError> {
|
||||||
|
if self.target_len != other.base_len {
|
||||||
|
return Err(OTError);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut new_delta = Delta::default();
|
||||||
|
let mut ops1 = self.ops.iter().cloned();
|
||||||
|
let mut ops2 = other.ops.iter().cloned();
|
||||||
|
|
||||||
|
let mut maybe_op1 = ops1.next();
|
||||||
|
let mut maybe_op2 = ops2.next();
|
||||||
|
loop {
|
||||||
|
match (
|
||||||
|
&maybe_op1.as_ref().map(|o| &o.ty),
|
||||||
|
&maybe_op2.as_ref().map(|o| &o.ty),
|
||||||
|
) {
|
||||||
|
(None, None) => break,
|
||||||
|
(Some(OpType::Delete(i)), _) => {
|
||||||
|
new_delta.delete(*i);
|
||||||
|
maybe_op1 = ops1.next();
|
||||||
|
},
|
||||||
|
(_, Some(OpType::Insert(s))) => {
|
||||||
|
new_delta.insert(s);
|
||||||
|
maybe_op2 = ops2.next();
|
||||||
|
},
|
||||||
|
(None, _) | (_, None) => {
|
||||||
|
return Err(OTError);
|
||||||
|
},
|
||||||
|
(Some(OpType::Retain(i)), Some(OpType::Retain(j))) => match i.cmp(&j) {
|
||||||
|
Ordering::Less => {
|
||||||
|
new_delta.retain(*i);
|
||||||
|
maybe_op2 = Some(OperationBuilder::retain(*j - *i).build());
|
||||||
|
maybe_op1 = ops1.next();
|
||||||
|
},
|
||||||
|
std::cmp::Ordering::Equal => {
|
||||||
|
new_delta.retain(*i);
|
||||||
|
maybe_op1 = ops1.next();
|
||||||
|
maybe_op2 = ops2.next();
|
||||||
|
},
|
||||||
|
std::cmp::Ordering::Greater => {
|
||||||
|
new_delta.retain(*j);
|
||||||
|
maybe_op1 = Some(OperationBuilder::retain(*i - *j).build());
|
||||||
|
maybe_op2 = ops2.next();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
(Some(OpType::Insert(s)), Some(OpType::Delete(j))) => {
|
||||||
|
match (num_chars(s.as_bytes()) as u64).cmp(j) {
|
||||||
|
Ordering::Less => {
|
||||||
|
maybe_op2 = Some(
|
||||||
|
OperationBuilder::delete(*j - num_chars(s.as_bytes()) as u64)
|
||||||
|
.build(),
|
||||||
|
);
|
||||||
|
maybe_op1 = ops1.next();
|
||||||
|
},
|
||||||
|
Ordering::Equal => {
|
||||||
|
maybe_op1 = ops1.next();
|
||||||
|
maybe_op2 = ops2.next();
|
||||||
|
},
|
||||||
|
Ordering::Greater => {
|
||||||
|
maybe_op1 = Some(
|
||||||
|
OperationBuilder::insert(s.chars().skip(*j as usize).collect())
|
||||||
|
.build(),
|
||||||
|
);
|
||||||
|
maybe_op2 = ops2.next();
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(Some(OpType::Insert(s)), Some(OpType::Retain(j))) => {
|
||||||
|
match (num_chars(s.as_bytes()) as u64).cmp(j) {
|
||||||
|
Ordering::Less => {
|
||||||
|
new_delta.insert(s);
|
||||||
|
maybe_op2 = Some(
|
||||||
|
OperationBuilder::retain(*j - num_chars(s.as_bytes()) as u64)
|
||||||
|
.build(),
|
||||||
|
);
|
||||||
|
maybe_op1 = ops1.next();
|
||||||
|
},
|
||||||
|
Ordering::Equal => {
|
||||||
|
new_delta.insert(s);
|
||||||
|
maybe_op1 = ops1.next();
|
||||||
|
maybe_op2 = ops2.next();
|
||||||
|
},
|
||||||
|
Ordering::Greater => {
|
||||||
|
let chars = &mut s.chars();
|
||||||
|
new_delta.insert(&chars.take(*j as usize).collect::<String>());
|
||||||
|
maybe_op1 = Some(OperationBuilder::insert(chars.collect()).build());
|
||||||
|
maybe_op2 = ops2.next();
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(Some(OpType::Retain(i)), Some(OpType::Delete(j))) => match i.cmp(&j) {
|
||||||
|
Ordering::Less => {
|
||||||
|
new_delta.delete(*i);
|
||||||
|
maybe_op2 = Some(OperationBuilder::delete(*j - *i).build());
|
||||||
|
maybe_op1 = ops1.next();
|
||||||
|
},
|
||||||
|
Ordering::Equal => {
|
||||||
|
new_delta.delete(*j);
|
||||||
|
maybe_op2 = ops2.next();
|
||||||
|
maybe_op1 = ops1.next();
|
||||||
|
},
|
||||||
|
Ordering::Greater => {
|
||||||
|
new_delta.delete(*j);
|
||||||
|
maybe_op1 = Some(OperationBuilder::retain(*i - *j).build());
|
||||||
|
maybe_op2 = ops2.next();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Ok(new_delta)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Transforms two operations A and B that happened concurrently and
|
||||||
|
/// produces two operations A' and B' (in an array) such that
|
||||||
|
/// `apply(apply(S, A), B') = apply(apply(S, B), A')`.
|
||||||
|
/// This function is the heart of OT.
|
||||||
|
///
|
||||||
|
/// # Error
|
||||||
|
///
|
||||||
|
/// Returns an `OTError` if the operations cannot be transformed due to
|
||||||
|
/// length conflicts.
|
||||||
|
pub fn transform(&self, other: &Self) -> Result<(Self, Self), OTError> {
|
||||||
|
if self.base_len != other.base_len {
|
||||||
|
return Err(OTError);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut a_prime = Delta::default();
|
||||||
|
let mut b_prime = Delta::default();
|
||||||
|
|
||||||
|
let mut ops1 = self.ops.iter().cloned();
|
||||||
|
let mut ops2 = other.ops.iter().cloned();
|
||||||
|
|
||||||
|
let mut maybe_op1 = ops1.next();
|
||||||
|
let mut maybe_op2 = ops2.next();
|
||||||
|
loop {
|
||||||
|
match (
|
||||||
|
&maybe_op1.as_ref().map(|o| &o.ty),
|
||||||
|
&maybe_op2.as_ref().map(|o| &o.ty),
|
||||||
|
) {
|
||||||
|
(None, None) => break,
|
||||||
|
(Some(OpType::Insert(s)), _) => {
|
||||||
|
a_prime.insert(s);
|
||||||
|
b_prime.retain(num_chars(s.as_bytes()) as _);
|
||||||
|
maybe_op1 = ops1.next();
|
||||||
|
},
|
||||||
|
(_, Some(OpType::Insert(s))) => {
|
||||||
|
a_prime.retain(num_chars(s.as_bytes()) as _);
|
||||||
|
b_prime.insert(s);
|
||||||
|
maybe_op2 = ops2.next();
|
||||||
|
},
|
||||||
|
(None, _) => {
|
||||||
|
return Err(OTError);
|
||||||
|
},
|
||||||
|
(_, None) => {
|
||||||
|
return Err(OTError);
|
||||||
|
},
|
||||||
|
(Some(OpType::Retain(i)), Some(OpType::Retain(j))) => {
|
||||||
|
match i.cmp(&j) {
|
||||||
|
Ordering::Less => {
|
||||||
|
a_prime.retain(*i);
|
||||||
|
b_prime.retain(*i);
|
||||||
|
maybe_op2 = Some(OperationBuilder::retain(*j - *i).build());
|
||||||
|
maybe_op1 = ops1.next();
|
||||||
|
},
|
||||||
|
Ordering::Equal => {
|
||||||
|
a_prime.retain(*i);
|
||||||
|
b_prime.retain(*i);
|
||||||
|
maybe_op1 = ops1.next();
|
||||||
|
maybe_op2 = ops2.next();
|
||||||
|
},
|
||||||
|
Ordering::Greater => {
|
||||||
|
a_prime.retain(*j);
|
||||||
|
b_prime.retain(*j);
|
||||||
|
maybe_op1 = Some(OperationBuilder::retain(*i - *j).build());
|
||||||
|
maybe_op2 = ops2.next();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
(Some(OpType::Delete(i)), Some(OpType::Delete(j))) => match i.cmp(&j) {
|
||||||
|
Ordering::Less => {
|
||||||
|
maybe_op2 = Some(OperationBuilder::delete(*j - *i).build());
|
||||||
|
maybe_op1 = ops1.next();
|
||||||
|
},
|
||||||
|
Ordering::Equal => {
|
||||||
|
maybe_op1 = ops1.next();
|
||||||
|
maybe_op2 = ops2.next();
|
||||||
|
},
|
||||||
|
Ordering::Greater => {
|
||||||
|
maybe_op1 = Some(OperationBuilder::delete(*i - *j).build());
|
||||||
|
maybe_op2 = ops2.next();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
(Some(OpType::Delete(i)), Some(OpType::Retain(j))) => {
|
||||||
|
match i.cmp(&j) {
|
||||||
|
Ordering::Less => {
|
||||||
|
a_prime.delete(*i);
|
||||||
|
maybe_op2 = Some(OperationBuilder::retain(*j - *i).build());
|
||||||
|
maybe_op1 = ops1.next();
|
||||||
|
},
|
||||||
|
Ordering::Equal => {
|
||||||
|
a_prime.delete(*i);
|
||||||
|
maybe_op1 = ops1.next();
|
||||||
|
maybe_op2 = ops2.next();
|
||||||
|
},
|
||||||
|
Ordering::Greater => {
|
||||||
|
a_prime.delete(*j);
|
||||||
|
maybe_op1 = Some(OperationBuilder::delete(*i - *j).build());
|
||||||
|
maybe_op2 = ops2.next();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
(Some(OpType::Retain(i)), Some(OpType::Delete(j))) => {
|
||||||
|
match i.cmp(&j) {
|
||||||
|
Ordering::Less => {
|
||||||
|
b_prime.delete(*i);
|
||||||
|
maybe_op2 = Some(OperationBuilder::delete(*j - *i).build());
|
||||||
|
maybe_op1 = ops1.next();
|
||||||
|
},
|
||||||
|
Ordering::Equal => {
|
||||||
|
b_prime.delete(*i);
|
||||||
|
maybe_op1 = ops1.next();
|
||||||
|
maybe_op2 = ops2.next();
|
||||||
|
},
|
||||||
|
Ordering::Greater => {
|
||||||
|
b_prime.delete(*j);
|
||||||
|
maybe_op1 = Some(OperationBuilder::retain(*i - *j).build());
|
||||||
|
maybe_op2 = ops2.next();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((a_prime, b_prime))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Applies an operation to a string, returning a new string.
|
||||||
|
///
|
||||||
|
/// # Error
|
||||||
|
///
|
||||||
|
/// Returns an error if the operation cannot be applied due to length
|
||||||
|
/// conflicts.
|
||||||
|
pub fn apply(&self, s: &str) -> Result<String, OTError> {
|
||||||
|
if num_chars(s.as_bytes()) != self.base_len {
|
||||||
|
return Err(OTError);
|
||||||
|
}
|
||||||
|
let mut new_s = String::new();
|
||||||
|
let chars = &mut s.chars();
|
||||||
|
for op in &self.ops {
|
||||||
|
match &op.ty {
|
||||||
|
OpType::Retain(retain) => {
|
||||||
|
for c in chars.take(*retain as usize) {
|
||||||
|
new_s.push(c);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
OpType::Delete(delete) => {
|
||||||
|
for _ in 0..*delete {
|
||||||
|
chars.next();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
OpType::Insert(insert) => {
|
||||||
|
new_s += insert;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(new_s)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Computes the inverse of an operation. The inverse of an operation is the
|
||||||
|
/// operation that reverts the effects of the operation
|
||||||
|
pub fn invert(&self, s: &str) -> Self {
|
||||||
|
let mut inverse = Delta::default();
|
||||||
|
let chars = &mut s.chars();
|
||||||
|
for op in &self.ops {
|
||||||
|
match &op.ty {
|
||||||
|
OpType::Retain(retain) => {
|
||||||
|
inverse.retain(*retain);
|
||||||
|
for _ in 0..*retain {
|
||||||
|
chars.next();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
OpType::Insert(insert) => {
|
||||||
|
inverse.delete(num_chars(insert.as_bytes()) as u64);
|
||||||
|
},
|
||||||
|
OpType::Delete(delete) => {
|
||||||
|
inverse.insert(&chars.take(*delete as usize).collect::<String>());
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inverse
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if this operation has no effect.
|
||||||
|
#[inline]
|
||||||
|
pub fn is_noop(&self) -> bool {
|
||||||
|
match self
|
||||||
|
.ops
|
||||||
|
.iter()
|
||||||
|
.map(|op| &op.ty)
|
||||||
|
.collect::<Vec<&OpType>>()
|
||||||
|
.as_slice()
|
||||||
|
{
|
||||||
|
[] => true,
|
||||||
|
[OpType::Retain(_)] => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the length of a string these operations can be applied to
|
||||||
|
#[inline]
|
||||||
|
pub fn base_len(&self) -> usize { self.base_len }
|
||||||
|
|
||||||
|
/// Returns the length of the resulting string after the operations have
|
||||||
|
/// been applied.
|
||||||
|
#[inline]
|
||||||
|
pub fn target_len(&self) -> usize { self.target_len }
|
||||||
|
|
||||||
|
/// Returns the wrapped sequence of operations.
|
||||||
|
#[inline]
|
||||||
|
pub fn ops(&self) -> &[Operation] { &self.ops }
|
||||||
|
}
|
12
rust-lib/flowy-ot/src/errors.rs
Normal file
12
rust-lib/flowy-ot/src/errors.rs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
use std::{error::Error, fmt};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct OTError;
|
||||||
|
|
||||||
|
impl fmt::Display for OTError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "incompatible lengths") }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error for OTError {
|
||||||
|
fn source(&self) -> Option<&(dyn Error + 'static)> { None }
|
||||||
|
}
|
4
rust-lib/flowy-ot/src/lib.rs
Normal file
4
rust-lib/flowy-ot/src/lib.rs
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
mod attributes;
|
||||||
|
pub mod delta;
|
||||||
|
pub mod errors;
|
||||||
|
pub mod operation;
|
103
rust-lib/flowy-ot/src/operation.rs
Normal file
103
rust-lib/flowy-ot/src/operation.rs
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
use crate::attributes::Attributes;
|
||||||
|
use std::{
|
||||||
|
cmp::Ordering,
|
||||||
|
collections::{hash_map::RandomState, HashMap},
|
||||||
|
ops::Deref,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct Operation {
|
||||||
|
pub ty: OpType,
|
||||||
|
pub attrs: Attributes,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Operation {
|
||||||
|
pub fn delete(&mut self, n: u64) { self.ty.delete(n); }
|
||||||
|
|
||||||
|
pub fn retain(&mut self, n: u64) { self.ty.retain(n); }
|
||||||
|
|
||||||
|
pub fn is_plain(&self) -> bool { self.attrs.is_empty() }
|
||||||
|
|
||||||
|
pub fn is_noop(&self) -> bool {
|
||||||
|
match self.ty {
|
||||||
|
OpType::Retain(_) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub enum OpType {
|
||||||
|
Delete(u64),
|
||||||
|
Retain(u64),
|
||||||
|
Insert(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OpType {
|
||||||
|
pub fn is_delete(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
OpType::Delete(_) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_retain(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
OpType::Retain(_) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_insert(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
OpType::Insert(_) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn delete(&mut self, n: u64) {
|
||||||
|
debug_assert_eq!(self.is_delete(), true);
|
||||||
|
if let OpType::Delete(n_last) = self {
|
||||||
|
*n_last += n;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn retain(&mut self, n: u64) {
|
||||||
|
debug_assert_eq!(self.is_retain(), true);
|
||||||
|
if let OpType::Retain(i_last) = self {
|
||||||
|
*i_last += n;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct OperationBuilder {
|
||||||
|
ty: OpType,
|
||||||
|
attrs: Attributes,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OperationBuilder {
|
||||||
|
pub fn new(ty: OpType) -> OperationBuilder {
|
||||||
|
OperationBuilder {
|
||||||
|
ty,
|
||||||
|
attrs: Attributes::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn retain(n: u64) -> OperationBuilder { OperationBuilder::new(OpType::Retain(n)) }
|
||||||
|
|
||||||
|
pub fn delete(n: u64) -> OperationBuilder { OperationBuilder::new(OpType::Delete(n)) }
|
||||||
|
|
||||||
|
pub fn insert(s: String) -> OperationBuilder { OperationBuilder::new(OpType::Insert(s)) }
|
||||||
|
|
||||||
|
pub fn with_attrs(mut self, attrs: Attributes) -> OperationBuilder {
|
||||||
|
self.attrs = attrs;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build(self) -> Operation {
|
||||||
|
Operation {
|
||||||
|
ty: self.ty,
|
||||||
|
attrs: self.attrs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
46
rust-lib/flowy-ot/tests/helper/mod.rs
Normal file
46
rust-lib/flowy-ot/tests/helper/mod.rs
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
use flowy_ot::delta::Delta;
|
||||||
|
use rand::{prelude::*, Rng as WrappedRng};
|
||||||
|
|
||||||
|
pub struct Rng(StdRng);
|
||||||
|
|
||||||
|
impl Default for Rng {
|
||||||
|
fn default() -> Self { Rng(StdRng::from_rng(thread_rng()).unwrap()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Rng {
|
||||||
|
pub fn from_seed(seed: [u8; 32]) -> Self { Rng(StdRng::from_seed(seed)) }
|
||||||
|
|
||||||
|
pub fn gen_string(&mut self, len: usize) -> String {
|
||||||
|
(0..len).map(|_| self.0.gen::<char>()).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn gen_delta(&mut self, s: &str) -> Delta {
|
||||||
|
let mut op = Delta::default();
|
||||||
|
loop {
|
||||||
|
let left = s.chars().count() - op.base_len();
|
||||||
|
if left == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let i = if left == 1 {
|
||||||
|
1
|
||||||
|
} else {
|
||||||
|
1 + self.0.gen_range(0, std::cmp::min(left - 1, 20))
|
||||||
|
};
|
||||||
|
match self.0.gen_range(0.0, 1.0) {
|
||||||
|
f if f < 0.2 => {
|
||||||
|
op.insert(&self.gen_string(i));
|
||||||
|
},
|
||||||
|
f if f < 0.4 => {
|
||||||
|
op.delete(i as u64);
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
op.retain(i as u64);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if self.0.gen_range(0.0, 1.0) < 0.3 {
|
||||||
|
op.insert(&("1".to_owned() + &self.gen_string(10)));
|
||||||
|
}
|
||||||
|
op
|
||||||
|
}
|
||||||
|
}
|
161
rust-lib/flowy-ot/tests/op_test.rs
Normal file
161
rust-lib/flowy-ot/tests/op_test.rs
Normal file
|
@ -0,0 +1,161 @@
|
||||||
|
mod helper;
|
||||||
|
|
||||||
|
use crate::helper::Rng;
|
||||||
|
use bytecount::num_chars;
|
||||||
|
use flowy_ot::{
|
||||||
|
delta::Delta,
|
||||||
|
operation::{OpType, OperationBuilder},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn lengths() {
|
||||||
|
let mut delta = Delta::default();
|
||||||
|
assert_eq!(delta.base_len, 0);
|
||||||
|
assert_eq!(delta.target_len, 0);
|
||||||
|
delta.retain(5);
|
||||||
|
assert_eq!(delta.base_len, 5);
|
||||||
|
assert_eq!(delta.target_len, 5);
|
||||||
|
delta.insert("abc");
|
||||||
|
assert_eq!(delta.base_len, 5);
|
||||||
|
assert_eq!(delta.target_len, 8);
|
||||||
|
delta.retain(2);
|
||||||
|
assert_eq!(delta.base_len, 7);
|
||||||
|
assert_eq!(delta.target_len, 10);
|
||||||
|
delta.delete(2);
|
||||||
|
assert_eq!(delta.base_len, 9);
|
||||||
|
assert_eq!(delta.target_len, 10);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn sequence() {
|
||||||
|
let mut delta = Delta::default();
|
||||||
|
delta.retain(5);
|
||||||
|
delta.retain(0);
|
||||||
|
delta.insert("lorem");
|
||||||
|
delta.insert("");
|
||||||
|
delta.delete(3);
|
||||||
|
delta.delete(0);
|
||||||
|
assert_eq!(delta.ops.len(), 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn apply() {
|
||||||
|
for _ in 0..1000 {
|
||||||
|
let mut rng = Rng::default();
|
||||||
|
let s = rng.gen_string(50);
|
||||||
|
let delta = rng.gen_delta(&s);
|
||||||
|
assert_eq!(num_chars(s.as_bytes()), delta.base_len);
|
||||||
|
assert_eq!(delta.apply(&s).unwrap().chars().count(), delta.target_len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn invert() {
|
||||||
|
for _ in 0..1000 {
|
||||||
|
let mut rng = Rng::default();
|
||||||
|
let s = rng.gen_string(50);
|
||||||
|
let delta_a = rng.gen_delta(&s);
|
||||||
|
let delta_b = delta_a.invert(&s);
|
||||||
|
assert_eq!(delta_a.base_len, delta_b.target_len);
|
||||||
|
assert_eq!(delta_a.target_len, delta_b.base_len);
|
||||||
|
assert_eq!(delta_b.apply(&delta_a.apply(&s).unwrap()).unwrap(), s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn empty_ops() {
|
||||||
|
let mut delta = Delta::default();
|
||||||
|
delta.retain(0);
|
||||||
|
delta.insert("");
|
||||||
|
delta.delete(0);
|
||||||
|
assert_eq!(delta.ops.len(), 0);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn eq() {
|
||||||
|
let mut delta_a = Delta::default();
|
||||||
|
delta_a.delete(1);
|
||||||
|
delta_a.insert("lo");
|
||||||
|
delta_a.retain(2);
|
||||||
|
delta_a.retain(3);
|
||||||
|
let mut delta_b = Delta::default();
|
||||||
|
delta_b.delete(1);
|
||||||
|
delta_b.insert("l");
|
||||||
|
delta_b.insert("o");
|
||||||
|
delta_b.retain(5);
|
||||||
|
assert_eq!(delta_a, delta_b);
|
||||||
|
delta_a.delete(1);
|
||||||
|
delta_b.retain(1);
|
||||||
|
assert_ne!(delta_a, delta_b);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn ops_merging() {
|
||||||
|
let mut delta = Delta::default();
|
||||||
|
assert_eq!(delta.ops.len(), 0);
|
||||||
|
delta.retain(2);
|
||||||
|
assert_eq!(delta.ops.len(), 1);
|
||||||
|
assert_eq!(delta.ops.last(), Some(&OperationBuilder::retain(2).build()));
|
||||||
|
delta.retain(3);
|
||||||
|
assert_eq!(delta.ops.len(), 1);
|
||||||
|
assert_eq!(delta.ops.last(), Some(&OperationBuilder::retain(5).build()));
|
||||||
|
delta.insert("abc");
|
||||||
|
assert_eq!(delta.ops.len(), 2);
|
||||||
|
assert_eq!(
|
||||||
|
delta.ops.last(),
|
||||||
|
Some(&OperationBuilder::insert("abc".to_owned()).build())
|
||||||
|
);
|
||||||
|
delta.insert("xyz");
|
||||||
|
assert_eq!(delta.ops.len(), 2);
|
||||||
|
assert_eq!(
|
||||||
|
delta.ops.last(),
|
||||||
|
Some(&OperationBuilder::insert("abcxyz".to_owned()).build())
|
||||||
|
);
|
||||||
|
delta.delete(1);
|
||||||
|
assert_eq!(delta.ops.len(), 3);
|
||||||
|
assert_eq!(delta.ops.last(), Some(&OperationBuilder::delete(1).build()));
|
||||||
|
delta.delete(1);
|
||||||
|
assert_eq!(delta.ops.len(), 3);
|
||||||
|
assert_eq!(delta.ops.last(), Some(&OperationBuilder::delete(2).build()));
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn is_noop() {
|
||||||
|
let mut delta = Delta::default();
|
||||||
|
assert!(delta.is_noop());
|
||||||
|
delta.retain(5);
|
||||||
|
assert!(delta.is_noop());
|
||||||
|
delta.retain(3);
|
||||||
|
assert!(delta.is_noop());
|
||||||
|
delta.insert("lorem");
|
||||||
|
assert!(!delta.is_noop());
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn compose() {
|
||||||
|
for _ in 0..1000 {
|
||||||
|
let mut rng = Rng::default();
|
||||||
|
let s = rng.gen_string(20);
|
||||||
|
let a = rng.gen_delta(&s);
|
||||||
|
let after_a = a.apply(&s).unwrap();
|
||||||
|
assert_eq!(a.target_len, num_chars(after_a.as_bytes()));
|
||||||
|
|
||||||
|
let b = rng.gen_delta(&after_a);
|
||||||
|
let after_b = b.apply(&after_a).unwrap();
|
||||||
|
assert_eq!(b.target_len, num_chars(after_b.as_bytes()));
|
||||||
|
|
||||||
|
let ab = a.compose(&b).unwrap();
|
||||||
|
assert_eq!(ab.target_len, b.target_len);
|
||||||
|
let after_ab = ab.apply(&s).unwrap();
|
||||||
|
assert_eq!(after_b, after_ab);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn transform() {
|
||||||
|
for _ in 0..1000 {
|
||||||
|
let mut rng = Rng::default();
|
||||||
|
let s = rng.gen_string(20);
|
||||||
|
let a = rng.gen_delta(&s);
|
||||||
|
let b = rng.gen_delta(&s);
|
||||||
|
let (a_prime, b_prime) = a.transform(&b).unwrap();
|
||||||
|
let ab_prime = a.compose(&b_prime).unwrap();
|
||||||
|
let ba_prime = b.compose(&a_prime).unwrap();
|
||||||
|
let after_ab_prime = ab_prime.apply(&s).unwrap();
|
||||||
|
let after_ba_prime = ba_prime.apply(&s).unwrap();
|
||||||
|
assert_eq!(ab_prime, ba_prime);
|
||||||
|
assert_eq!(after_ab_prime, after_ba_prime);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue