compare delta between flutter and rust

This commit is contained in:
appflowy 2021-09-15 16:35:40 +08:00
parent 0edd442562
commit 87d3f4fc0d
20 changed files with 117 additions and 75 deletions

View File

@ -2,6 +2,7 @@ import 'dart:convert';
import 'package:flowy_editor/flowy_editor.dart';
import 'package:dartz/dartz.dart';
// ignore: implementation_imports
import 'package:flowy_editor/src/model/quill_delta.dart';
import 'package:flowy_log/flowy_log.dart';
import 'package:flowy_sdk/protobuf/flowy-document/doc.pb.dart';
@ -18,16 +19,29 @@ class FlowyDoc implements EditorChangesetSender {
String get id => doc.id;
@override
void sendDelta(Delta delta) {
final json = jsonEncode(delta.toJson());
void sendDelta(Delta changeset, Delta delta) async {
final json = jsonEncode(changeset.toJson());
Log.debug("Send json: $json");
iDocImpl.applyChangeset(json: json);
final result = await iDocImpl.applyChangeset(json: json);
result.fold((rustDoc) {
// final json = utf8.decode(doc.data);
final rustDelta = Delta.fromJson(jsonDecode(utf8.decode(rustDoc.data)));
if (delta != rustDelta) {
Log.error("Receive : $rustDelta");
Log.error("Expected : $delta");
} else {
Log.info("Receive : $rustDelta");
Log.info("Expected : $delta");
}
}, (r) => null);
}
}
abstract class IDoc {
Future<Either<Doc, WorkspaceError>> readDoc();
Future<Either<Unit, WorkspaceError>> saveDoc({String? json});
Future<Either<Unit, WorkspaceError>> applyChangeset({String? json});
Future<Either<Doc, WorkspaceError>> applyChangeset({String? json});
Future<Either<Unit, WorkspaceError>> closeDoc();
}

View File

@ -33,7 +33,7 @@ class IDocImpl extends IDoc {
}
@override
Future<Either<Unit, WorkspaceError>> applyChangeset({String? json}) {
Future<Either<Doc, WorkspaceError>> applyChangeset({String? json}) {
return repo.applyChangeset(data: _encodeText(json));
}
}

View File

@ -25,7 +25,7 @@ class DocRepository {
return WorkspaceEventSaveViewData(request).send();
}
Future<Either<Unit, WorkspaceError>> applyChangeset(
Future<Either<Doc, WorkspaceError>> applyChangeset(
{required Uint8List data}) {
final request = ApplyChangesetRequest.create()
..viewId = docId

View File

@ -15,7 +15,7 @@ import 'node/node.dart';
import 'package:flowy_log/flowy_log.dart';
abstract class EditorChangesetSender {
void sendDelta(Delta delta);
void sendDelta(Delta changeset, Delta delta);
}
/// The rich text document
@ -182,8 +182,11 @@ class Document {
}
try {
sender?.sendDelta(delta);
final changeset = delta;
_delta = _delta.compose(delta);
sender?.sendDelta(changeset, _delta);
} catch (e) {
throw '_delta compose failed';
}

View File

@ -275,14 +275,14 @@ class WorkspaceEventApplyChangeset {
ApplyChangesetRequest request;
WorkspaceEventApplyChangeset(this.request);
Future<Either<Unit, WorkspaceError>> send() {
Future<Either<Doc, WorkspaceError>> send() {
final request = FFIRequest.create()
..event = WorkspaceEvent.ApplyChangeset.toString()
..payload = requestToBytes(this.request);
return Dispatch.asyncRequest(request)
.then((bytesResult) => bytesResult.fold(
(bytes) => left(unit),
(okBytes) => left(Doc.fromBuffer(okBytes)),
(errBytes) => right(WorkspaceError.fromBuffer(errBytes)),
));
}

View File

@ -9,6 +9,7 @@ use crate::{
};
use diesel::SqliteConnection;
use flowy_database::ConnectionPool;
use flowy_ot::client::Document;
use std::sync::Arc;
use tokio::sync::RwLock;
@ -54,15 +55,25 @@ impl FlowyDocument {
Ok(())
}
pub async fn apply_changeset(&self, params: ApplyChangesetParams) -> Result<(), DocError> {
pub async fn apply_changeset(&self, params: ApplyChangesetParams) -> Result<Doc, DocError> {
let _ = self
.cache
.mut_doc(&params.id, |doc| {
log::debug!("Document:{} applying delta", &params.id);
let _ = doc.apply_changeset(params.data.clone())?;
Ok(())
})
.await?;
Ok(())
let doc_str = match self.cache.read_doc(&params.id).await? {
None => "".to_owned(),
Some(doc_json) => doc_json,
};
let doc = Doc {
id: params.id,
data: doc_str.as_bytes().to_vec(),
};
Ok(doc)
}
}

View File

@ -11,7 +11,7 @@ use tokio::sync::RwLock;
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct DocId(pub(crate) String);
pub struct DocInfo {
pub struct OpenDocument {
document: Document,
}
@ -23,7 +23,7 @@ where
}
pub(crate) struct DocCache {
inner: DashMap<DocId, RwLock<DocInfo>>,
inner: DashMap<DocId, RwLock<OpenDocument>>,
}
impl DocCache {
@ -37,7 +37,7 @@ impl DocCache {
let doc_id = id.into();
let delta = data.try_into()?;
let document = Document::from_delta(delta);
let doc_info = DocInfo { document };
let doc_info = OpenDocument { document };
self.inner.insert(doc_id, RwLock::new(doc_info));
Ok(())
}
@ -49,9 +49,7 @@ impl DocCache {
{
let doc_id = id.into();
match self.inner.get(&doc_id) {
None => Err(ErrorBuilder::new(ErrorCode::DocNotfound)
.msg("Doc is close or you should call open first")
.build()),
None => Err(doc_not_found()),
Some(doc_info) => {
let mut write_guard = doc_info.write().await;
f(&mut write_guard.document)
@ -59,6 +57,21 @@ impl DocCache {
}
}
pub(crate) async fn read_doc<T>(&self, id: T) -> Result<Option<String>, DocError>
where
T: Into<DocId>,
{
let doc_id = id.into();
match self.inner.get(&doc_id) {
None => Err(doc_not_found()),
Some(doc_info) => {
let mut write_guard = doc_info.read().await;
let doc = &(*write_guard).document;
Ok(Some(doc.to_json()))
},
}
}
pub(crate) fn close<T>(&self, id: T) -> Result<(), DocError>
where
T: Into<DocId>,
@ -68,3 +81,9 @@ impl DocCache {
Ok(())
}
}
fn doc_not_found() -> DocError {
ErrorBuilder::new(ErrorCode::DocNotfound)
.msg("Doc is close or you should call open first")
.build()
}

View File

@ -51,14 +51,16 @@ impl Document {
pub fn to_json(&self) -> String { self.delta.to_json() }
pub fn to_string(&self) -> String { self.delta.apply("").unwrap() }
pub fn apply_changeset<T>(&mut self, changeset: T) -> Result<(), OTError>
where
T: TryInto<Delta, Error = OTError>,
{
let new_delta: Delta = changeset.try_into()?;
log::debug!("Apply delta: {}", new_delta);
self.add_delta(&new_delta);
log::info!("Current delta: {:?}", self.to_json());
log::debug!("Current delta: {}", self.to_json());
Ok(())
}
@ -68,7 +70,7 @@ impl Document {
let text = data.into_string()?;
let delta = self.view.insert(&self.delta, &text, interval)?;
log::debug!("👉 receive change: {}", delta);
log::trace!("👉 receive change: {}", delta);
self.add_delta(&delta)?;
Ok(delta)
}
@ -78,7 +80,7 @@ impl Document {
debug_assert_eq!(interval.is_empty(), false);
let delete = self.view.delete(&self.delta, interval)?;
if !delete.is_empty() {
log::debug!("👉 receive change: {}", delete);
log::trace!("👉 receive change: {}", delete);
let _ = self.add_delta(&delete)?;
}
Ok(delete)
@ -86,10 +88,10 @@ impl Document {
pub fn format(&mut self, interval: Interval, attribute: Attribute) -> Result<(), OTError> {
let _ = validate_interval(&self.delta, &interval)?;
log::debug!("format with {} at {}", attribute, interval);
log::trace!("format with {} at {}", attribute, interval);
let format_delta = self.view.format(&self.delta, attribute.clone(), interval).unwrap();
log::debug!("👉 receive change: {}", format_delta);
log::trace!("👉 receive change: {}", format_delta);
self.add_delta(&format_delta)?;
Ok(())
}
@ -100,7 +102,7 @@ impl Document {
let text = data.into_string()?;
if !text.is_empty() {
delta = self.view.insert(&self.delta, &text, interval)?;
log::debug!("👉 receive change: {}", delta);
log::trace!("👉 receive change: {}", delta);
self.add_delta(&delta)?;
}
@ -144,8 +146,6 @@ impl Document {
}
}
pub fn to_string(&self) -> String { self.delta.apply("").unwrap() }
pub fn data(&self) -> &Delta { &self.delta }
pub fn set_data(&mut self, data: Delta) { self.delta = data; }
@ -160,21 +160,21 @@ impl Document {
let now = chrono::Utc::now().timestamp_millis() as usize;
if now - self.last_edit_time < RECORD_THRESHOLD {
if let Some(last_delta) = self.history.undo() {
log::debug!("compose previous change");
log::debug!("current = {}", undo_delta);
log::debug!("previous = {}", last_delta);
log::trace!("compose previous change");
log::trace!("current = {}", undo_delta);
log::trace!("previous = {}", last_delta);
undo_delta = undo_delta.compose(&last_delta)?;
}
} else {
self.last_edit_time = now;
}
log::debug!("👉 receive change undo: {}", undo_delta);
log::trace!("👉 receive change undo: {}", undo_delta);
if !undo_delta.is_empty() {
self.history.record(undo_delta);
}
log::debug!("document delta: {}", &composed_delta);
log::trace!("document delta: {}", &composed_delta);
self.delta = composed_delta;
Ok(())
}
@ -183,7 +183,7 @@ impl Document {
// c = a.compose(b)
// d = b.invert(a)
// a = c.compose(d)
log::debug!("👉invert change {}", change);
log::trace!("👉invert change {}", change);
let new_delta = self.delta.compose(change)?;
let inverted_delta = change.invert(&self.delta);
Ok((new_delta, inverted_delta))

View File

@ -25,7 +25,7 @@ impl View {
let mut new_delta = None;
for ext in &self.insert_exts {
if let Some(delta) = ext.apply(delta, interval.size(), text, interval.start) {
log::debug!("[{}]: applied, delta: {}", ext.ext_name(), delta);
log::trace!("[{}]: applied, delta: {}", ext.ext_name(), delta);
new_delta = Some(delta);
break;
}
@ -41,7 +41,7 @@ impl View {
let mut new_delta = None;
for ext in &self.delete_exts {
if let Some(delta) = ext.apply(delta, interval) {
log::debug!("[{}]: applied, delta: {}", ext.ext_name(), delta);
log::trace!("[{}]: applied, delta: {}", ext.ext_name(), delta);
new_delta = Some(delta);
break;
}
@ -57,7 +57,7 @@ impl View {
let mut new_delta = None;
for ext in &self.format_exts {
if let Some(delta) = ext.apply(delta, interval, &attribute) {
log::debug!("[{}]: applied, delta: {}", ext.ext_name(), delta);
log::trace!("[{}]: applied, delta: {}", ext.ext_name(), delta);
new_delta = Some(delta);
break;
}

View File

@ -252,7 +252,7 @@ impl Delta {
},
(Some(Operation::Retain(retain)), Some(Operation::Retain(o_retain))) => {
let composed_attrs = compose_operation(&next_op1, &next_op2);
log::debug!("[retain:{} - retain:{}]: {:?}", retain.n, o_retain.n, composed_attrs);
log::trace!("[retain:{} - retain:{}]: {:?}", retain.n, o_retain.n, composed_attrs);
match retain.cmp(&o_retain) {
Ordering::Less => {
new_delta.retain(retain.n, composed_attrs);
@ -299,7 +299,7 @@ impl Delta {
let mut composed_attrs = compose_operation(&next_op1, &next_op2);
composed_attrs.remove_empty();
log::debug!("compose: [{} - {}], composed_attrs: {}", insert, o_retain, composed_attrs);
log::trace!("compose: [{} - {}], composed_attrs: {}", insert, o_retain, composed_attrs);
match (insert.num_chars()).cmp(o_retain) {
Ordering::Less => {
new_delta.insert(&insert.s, composed_attrs.clone());
@ -533,9 +533,9 @@ impl Delta {
if other.is_empty() {
return inverted;
}
log::debug!("🌜Calculate invert delta");
log::debug!("current: {}", self);
log::debug!("other: {}", other);
log::trace!("🌜Calculate invert delta");
log::trace!("current: {}", self);
log::trace!("other: {}", other);
let mut index = 0;
for op in &self.ops {
let len: usize = op.len() as usize;
@ -548,20 +548,20 @@ impl Delta {
match op.has_attribute() {
true => invert_from_other(&mut inverted, other, op, index, index + len),
false => {
log::debug!("invert retain: {} by retain {} {}", op, len, op.get_attributes());
log::trace!("invert retain: {} by retain {} {}", op, len, op.get_attributes());
inverted.retain(len as usize, op.get_attributes())
},
}
index += len;
},
Operation::Insert(_) => {
log::debug!("invert insert: {} by delete {}", op, len);
log::trace!("invert insert: {} by delete {}", op, len);
inverted.delete(len as usize);
},
}
}
log::debug!("🌛invert result: {}", inverted);
log::trace!("🌛invert result: {}", inverted);
inverted
}
@ -581,22 +581,22 @@ impl Delta {
}
fn invert_from_other(base: &mut Delta, other: &Delta, operation: &Operation, start: usize, end: usize) {
log::debug!("invert op: {} [{}:{}]", operation, start, end);
log::trace!("invert op: {} [{}:{}]", operation, start, end);
let other_ops = DeltaIter::from_interval(other, Interval::new(start, end)).ops();
other_ops.into_iter().for_each(|other_op| match operation {
Operation::Delete(n) => {
log::debug!("invert delete: {} by add {}", n, other_op);
log::trace!("invert delete: {} by add {}", n, other_op);
base.add(other_op);
},
Operation::Retain(retain) => {
log::debug!(
log::trace!(
"invert attributes: {:?}, {:?}",
operation.get_attributes(),
other_op.get_attributes()
);
let inverted_attrs = invert_attributes(operation.get_attributes(), other_op.get_attributes());
log::debug!("invert attributes result: {:?}", inverted_attrs);
log::debug!("invert retain: {} by retain len: {}, {}", retain, other_op.len(), inverted_attrs);
log::trace!("invert attributes result: {:?}", inverted_attrs);
log::trace!("invert retain: {} by retain len: {}, {}", retain, other_op.len(), inverted_attrs);
base.retain(other_op.len(), inverted_attrs);
},
Operation::Insert(_) => {

View File

@ -154,13 +154,13 @@ impl<'a> Iterator for AttributesIter<'a> {
match next_op.unwrap() {
Operation::Delete(_n) => {},
Operation::Retain(retain) => {
log::debug!("extend retain attributes with {} ", &retain.attributes);
log::trace!("extend retain attributes with {} ", &retain.attributes);
attributes.extend(retain.attributes.clone());
length = retain.n;
},
Operation::Insert(insert) => {
log::debug!("extend insert attributes with {} ", &insert.attributes);
log::trace!("extend insert attributes with {} ", &insert.attributes);
attributes.extend(insert.attributes.clone());
length = insert.num_chars();
},

View File

@ -161,7 +161,7 @@ impl fmt::Display for Retain {
impl Retain {
pub fn merge_or_new(&mut self, n: usize, attributes: Attributes) -> Option<Operation> {
log::debug!("merge_retain_or_new_op: len: {:?}, l: {} - r: {}", n, self.attributes, attributes);
log::trace!("merge_retain_or_new_op: len: {:?}, l: {} - r: {}", n, self.attributes, attributes);
if self.attributes == attributes {
self.n += n;

View File

@ -490,11 +490,11 @@ fn delta_transform_test() {
let (a_prime, b_prime) = a.transform(&b).unwrap();
assert_eq!(
r#"[{"insert":"123","attributes":{"bold":"true"}},{"retain":3}]"#,
r#"[{"insert":"123","attributes":{"bold":true}},{"retain":3}]"#,
serde_json::to_string(&a_prime).unwrap()
);
assert_eq!(
r#"[{"retain":3,"attributes":{"bold":"true"}},{"insert":"456"}]"#,
r#"[{"retain":3,"attributes":{"bold":true}},{"insert":"456"}]"#,
serde_json::to_string(&b_prime).unwrap()
);
}

View File

@ -82,10 +82,10 @@ fn delta_deserialize_test() {
#[test]
fn delta_deserialize_null_test() {
let json = r#"[
{"retain":2,"attributes":{"italic":null}}
{"retain":7,"attributes":{"bold":null}}
]"#;
let delta = Delta::from_json(json).unwrap();
eprintln!("{}", delta);
println!("{}", delta);
}
#[test]

View File

@ -40,6 +40,7 @@ fn crate_log_filter(level: Option<String>) -> String {
filters.push(format!("flowy_user={}", level));
filters.push(format!("flowy_document={}", level));
filters.push(format!("flowy_observable={}", level));
filters.push(format!("flowy_ot={}", level));
filters.push(format!("info"));
filters.join(",")
}

View File

@ -1,16 +1,12 @@
use flowy_ot::core::Delta;
#[derive(Debug)]
pub struct DeltaData(pub Delta);
pub struct DeltaData(pub Vec<u8>);
impl DeltaData {
pub fn parse(data: Vec<u8>) -> Result<DeltaData, String> {
let delta = Delta::from_bytes(data).map_err(|e| format!("{:?}", e))?;
// let _ = Delta::from_bytes(data.clone()).map_err(|e| format!("{:?}", e))?;
Ok(Self(delta))
Ok(Self(data))
}
}
impl AsRef<Delta> for DeltaData {
fn as_ref(&self) -> &Delta { &self.0 }
}

View File

@ -130,8 +130,7 @@ impl TryInto<SaveDocParams> for SaveViewDataRequest {
// Opti: Vec<u8> -> Delta -> Vec<u8>
let data = DeltaData::parse(self.data)
.map_err(|e| ErrorBuilder::new(ErrorCode::ViewDataInvalid).msg(e).build())?
.0
.into_bytes();
.0;
Ok(SaveDocParams { id: view_id, data })
}
@ -157,8 +156,7 @@ impl TryInto<ApplyChangesetParams> for ApplyChangesetRequest {
// Opti: Vec<u8> -> Delta -> Vec<u8>
let data = DeltaData::parse(self.data)
.map_err(|e| ErrorBuilder::new(ErrorCode::ViewDataInvalid).msg(e).build())?
.0
.into_bytes();
.0;
Ok(ApplyChangesetParams { id: view_id, data })
}

View File

@ -52,6 +52,6 @@ pub enum WorkspaceEvent {
#[event(input = "SaveViewDataRequest")]
SaveViewData = 206,
#[event(input = "ApplyChangesetRequest")]
#[event(input = "ApplyChangesetRequest", output = "Doc")]
ApplyChangeset = 207,
}

View File

@ -69,10 +69,10 @@ pub(crate) async fn update_view_data_handler(
pub(crate) async fn apply_changeset_handler(
data: Data<ApplyChangesetRequest>,
controller: Unit<Arc<ViewController>>,
) -> Result<(), WorkspaceError> {
) -> DataResult<Doc, WorkspaceError> {
let params: ApplyChangesetParams = data.into_inner().try_into()?;
let _ = controller.apply_changeset(params).await?;
Ok(())
let doc = controller.apply_changeset(params).await?;
data_result(doc)
}
#[tracing::instrument(skip(data, controller), err)]

View File

@ -129,9 +129,9 @@ impl ViewController {
Ok(())
}
pub(crate) async fn apply_changeset(&self, params: ApplyChangesetParams) -> Result<(), WorkspaceError> {
let _ = self.document.apply_changeset(params).await?;
Ok(())
pub(crate) async fn apply_changeset(&self, params: ApplyChangesetParams) -> Result<Doc, WorkspaceError> {
let doc = self.document.apply_changeset(params).await?;
Ok(doc)
}
}