feat: create doc from snapshot (#136)

This commit is contained in:
Zixuan Chen 2023-10-31 19:02:52 +08:00 committed by GitHub
parent c9cf106338
commit e1ab03f30f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 57 additions and 21 deletions

View file

@ -677,7 +677,7 @@ pub fn decode_oplog_v2(oplog: &mut OpLog, input: &[u8]) -> Result<(), LoroError>
// convert change into inner format
let mut ops = RleVec::new();
for op in change.ops {
let mut lamport = change.lamport;
let lamport = change.lamport;
let content = op.content;
let op = converter.convert_single_op(
&op.container,
@ -686,7 +686,6 @@ pub fn decode_oplog_v2(oplog: &mut OpLog, input: &[u8]) -> Result<(), LoroError>
lamport,
content,
);
lamport += op.atom_len() as Lamport;
ops.push(op);
}

View file

@ -83,6 +83,20 @@ impl LoroDoc {
doc
}
pub fn from_snapshot(bytes: &[u8]) -> LoroResult<Self> {
let doc = Self::new();
let (input, mode) = parse_encode_header(bytes)?;
match mode {
EncodeMode::Snapshot => {
decode_app_snapshot(&doc, input, true)?;
Ok(doc)
}
_ => Err(LoroError::DecodeError(
"Invalid encode mode".to_string().into(),
)),
}
}
/// Is the document empty? (no ops)
#[inline(always)]
pub fn can_reset_with_snapshot(&self) -> bool {
@ -340,21 +354,7 @@ impl LoroDoc {
bytes: &[u8],
origin: string_cache::Atom<string_cache::EmptyStaticAtomSet>,
) -> Result<(), LoroError> {
if bytes.len() <= 6 {
return Err(LoroError::DecodeError("Invalid bytes".into()));
}
let (magic_bytes, input) = bytes.split_at(4);
let magic_bytes: [u8; 4] = magic_bytes.try_into().unwrap();
if magic_bytes != MAGIC_BYTES {
return Err(LoroError::DecodeError("Invalid header bytes".into()));
}
let (version, input) = input.split_at(1);
if version != [ENCODE_SCHEMA_VERSION] {
return Err(LoroError::DecodeError("Invalid version".into()));
}
let mode: EncodeMode = input[0].try_into()?;
let (input, mode) = parse_encode_header(bytes)?;
match mode {
EncodeMode::Updates | EncodeMode::RleUpdates | EncodeMode::CompressedRleUpdates => {
// TODO: need to throw error if state is in transaction
@ -386,10 +386,10 @@ impl LoroDoc {
}
EncodeMode::Snapshot => {
if self.can_reset_with_snapshot() {
decode_app_snapshot(self, &input[1..], !self.detached)?;
decode_app_snapshot(self, input, !self.detached)?;
} else {
let app = LoroDoc::new();
decode_app_snapshot(&app, &input[1..], false)?;
decode_app_snapshot(&app, input, false)?;
let oplog = self.oplog.lock().unwrap();
// TODO: PERF: the ser and de can be optimized out
let updates = app.export_from(oplog.vv());
@ -629,6 +629,23 @@ impl LoroDoc {
}
}
fn parse_encode_header(bytes: &[u8]) -> Result<(&[u8], EncodeMode), LoroError> {
if bytes.len() <= 6 {
return Err(LoroError::DecodeError("Invalid import data".into()));
}
let (magic_bytes, input) = bytes.split_at(4);
let magic_bytes: [u8; 4] = magic_bytes.try_into().unwrap();
if magic_bytes != MAGIC_BYTES {
return Err(LoroError::DecodeError("Invalid header bytes".into()));
}
let (version, input) = input.split_at(1);
if version != [ENCODE_SCHEMA_VERSION] {
return Err(LoroError::DecodeError("Invalid version".into()));
}
let mode: EncodeMode = input[0].try_into()?;
Ok((&input[1..], mode))
}
impl Default for LoroDoc {
fn default() -> Self {
Self::new()

View file

@ -208,7 +208,7 @@ impl OpLog {
pub(super) fn to_local_op(change: Change<RemoteOp>, converter: &mut OpConverter) -> Change {
let mut ops = RleVec::new();
for op in change.ops {
let mut lamport = change.lamport;
let lamport = change.lamport;
let content = op.content;
let op = converter.convert_single_op(
&op.container,
@ -217,7 +217,6 @@ pub(super) fn to_local_op(change: Change<RemoteOp>, converter: &mut OpConverter)
lamport,
content,
);
lamport += op.atom_len() as Lamport;
ops.push(op);
}
Change {

View file

@ -43,6 +43,21 @@ fn import_after_init_handlers() {
a.import(&b.export_snapshot()).unwrap();
a.commit();
}
fn test_from_snapshot() {
let a = LoroDoc::new_auto_commit();
a.get_text("text").insert_(0, "0").unwrap();
let snapshot = a.export_snapshot();
let c = LoroDoc::from_snapshot(&snapshot).unwrap();
assert_eq!(a.get_deep_value(), c.get_deep_value());
assert_eq!(a.oplog_frontiers(), c.oplog_frontiers());
assert_eq!(a.state_frontiers(), c.state_frontiers());
let updates = a.export_from(&Default::default());
let d = match LoroDoc::from_snapshot(&updates) {
Ok(_) => panic!(),
Err(e) => e,
};
assert!(matches!(d, loro_common::LoroError::DecodeError(..)));
}
#[test]
fn test_pending() {

View file

@ -146,6 +146,12 @@ impl Loro {
Self(doc)
}
#[wasm_bindgen(js_name = "fromSnapshot")]
pub fn from_snapshot(snapshot: &[u8]) -> JsResult<Loro> {
let doc = LoroDoc::from_snapshot(snapshot)?;
Ok(Loro(doc))
}
pub fn attach(&mut self) {
self.0.attach();
}