loro/crates/loro-internal/src/encoding.rs

134 lines
4.2 KiB
Rust
Raw Normal View History

use fxhash::FxHashMap;
use loro_common::PeerID;
use crate::{change::Change, op::RemoteOp};
pub(crate) type RemoteClientChanges<'a> = FxHashMap<PeerID, Vec<Change<RemoteOp<'a>>>>;
mod encode_enhanced;
pub(crate) mod encode_snapshot;
mod encode_updates;
use rle::HasLength;
use crate::{oplog::OpLog, LoroError, VersionVector};
use self::encode_updates::decode_oplog_updates;
2023-08-29 02:38:48 +00:00
pub(crate) use encode_enhanced::{decode_oplog_v2, encode_oplog_v2};
pub(crate) use encode_updates::encode_oplog_updates;
pub(crate) const COMPRESS_RLE_THRESHOLD: usize = 20 * 1024;
// TODO: Test this threshold
Feat: Peritext-like rich text support (#123) * feat: richtext wip * feat: add insert to style range map wip * feat: richtext state * fix: fix style state inserting and style map * fix: tiny vec merge err * fix: comment err * refactor: use new generic-btree & refine impl * feat: fugue tracker * feat: tracker * feat: tracker * fix: fix a few err in impl * feat: init richtext content state * feat: refactor arena * feat: extract anchor_type info out of style flag * refactor: state apply op more efficiently we can now reuse the repr in state and op * fix: new clippy errors * refactor: use state chunk as delta item * refactor: use two op to insert style start and style end * feat: diff calc * feat: handler * fix: tracker checkout err * fix: pass basic richtext handler tests * fix: pass handler basic marking tests * fix: pass all peritext criteria * feat: snapshot encoding for richtext init * refactor: replace Text with Richtext * refacotr: rm text code * fix: richtext checkout err * refactor: diff of text and map * refactor: del span * refactor: event * fix: fuzz err * fix: pass all tests * fix: fuzz err * fix: list child cache err * chore: rm debug code * fix: encode enhanced err * fix: encode enchanced * fix: fix several richtext issue * fix: richtext anchor err * chore: rm debug code * fix: richtext fuzz err * feat: speedup text snapshot decode * perf: optimize snapshot encoding * perf: speed up decode & insert * fix: fugue span merge err * perf: speedup delete & id cursor map * fix: fugue merge err * chore: update utils * perf: speedup text insert / del * fix: cursor cache * perf: reduce conversion by introducing InsertText * perf: speed up by refined cursor cache * chore: update gbtree dep * refactor(wasm): use quill delta format * chore: fix warnings
2023-10-29 06:02:13 +00:00
#[cfg(not(test))]
pub(crate) const UPDATE_ENCODE_THRESHOLD: usize = 32;
Feat: Peritext-like rich text support (#123) * feat: richtext wip * feat: add insert to style range map wip * feat: richtext state * fix: fix style state inserting and style map * fix: tiny vec merge err * fix: comment err * refactor: use new generic-btree & refine impl * feat: fugue tracker * feat: tracker * feat: tracker * fix: fix a few err in impl * feat: init richtext content state * feat: refactor arena * feat: extract anchor_type info out of style flag * refactor: state apply op more efficiently we can now reuse the repr in state and op * fix: new clippy errors * refactor: use state chunk as delta item * refactor: use two op to insert style start and style end * feat: diff calc * feat: handler * fix: tracker checkout err * fix: pass basic richtext handler tests * fix: pass handler basic marking tests * fix: pass all peritext criteria * feat: snapshot encoding for richtext init * refactor: replace Text with Richtext * refacotr: rm text code * fix: richtext checkout err * refactor: diff of text and map * refactor: del span * refactor: event * fix: fuzz err * fix: pass all tests * fix: fuzz err * fix: list child cache err * chore: rm debug code * fix: encode enhanced err * fix: encode enchanced * fix: fix several richtext issue * fix: richtext anchor err * chore: rm debug code * fix: richtext fuzz err * feat: speedup text snapshot decode * perf: optimize snapshot encoding * perf: speed up decode & insert * fix: fugue span merge err * perf: speedup delete & id cursor map * fix: fugue merge err * chore: update utils * perf: speedup text insert / del * fix: cursor cache * perf: reduce conversion by introducing InsertText * perf: speed up by refined cursor cache * chore: update gbtree dep * refactor(wasm): use quill delta format * chore: fix warnings
2023-10-29 06:02:13 +00:00
#[cfg(test)]
pub(crate) const UPDATE_ENCODE_THRESHOLD: usize = 16;
pub(crate) const MAGIC_BYTES: [u8; 4] = [0x6c, 0x6f, 0x72, 0x6f];
pub(crate) const ENCODE_SCHEMA_VERSION: u8 = 0;
2023-08-30 04:27:23 +00:00
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum EncodeMode {
// This is a config option, it won't be used in encoding.
Auto = 255,
Updates = 0,
Snapshot = 1,
RleUpdates = 2,
2023-08-30 04:27:23 +00:00
CompressedRleUpdates = 3,
}
impl EncodeMode {
2023-08-30 04:27:23 +00:00
pub fn to_byte(self) -> u8 {
match self {
2023-08-30 04:27:23 +00:00
EncodeMode::Auto => 255,
EncodeMode::Updates => 0,
EncodeMode::Snapshot => 1,
EncodeMode::RleUpdates => 2,
2023-08-30 04:27:23 +00:00
EncodeMode::CompressedRleUpdates => 3,
}
}
}
2023-08-30 08:41:04 +00:00
impl TryFrom<u8> for EncodeMode {
type Error = LoroError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
2023-08-30 08:41:04 +00:00
0 => Ok(EncodeMode::Updates),
1 => Ok(EncodeMode::Snapshot),
2 => Ok(EncodeMode::RleUpdates),
2023-08-30 08:41:04 +00:00
3 => Ok(EncodeMode::CompressedRleUpdates),
_ => Err(LoroError::DecodeError("Unknown encode mode".into())),
}
}
}
2023-08-30 04:27:23 +00:00
pub(crate) fn encode_oplog(oplog: &OpLog, vv: &VersionVector, mode: EncodeMode) -> Vec<u8> {
let version = ENCODE_SCHEMA_VERSION;
let mut ans = Vec::from(MAGIC_BYTES);
// maybe u8 is enough
ans.push(version);
let mode = match mode {
2023-08-30 04:27:23 +00:00
EncodeMode::Auto => {
let self_vv = oplog.vv();
2023-08-30 04:27:23 +00:00
let diff = self_vv.diff(vv);
let update_total_len = diff
.left
.values()
.map(|value| value.atom_len())
.sum::<usize>();
2023-08-29 09:15:41 +00:00
// EncodeMode::RleUpdates(vv)
if update_total_len <= UPDATE_ENCODE_THRESHOLD {
2023-08-30 04:27:23 +00:00
EncodeMode::Updates
} else if update_total_len <= COMPRESS_RLE_THRESHOLD {
EncodeMode::RleUpdates
} else {
EncodeMode::CompressedRleUpdates
}
}
mode => mode,
};
feat: movable tree support (#120) * feat: tree state * feat: tree value * feat: tree handler * fix: tree diff * test: fuzz tree * feat: tree snapshot * fix: tree default value * fix: test new node * fix: tree diff * fix: tree unresolved value * fix: tree fuzz * fix: tree fuzz move * fix: sort by tree id * fix: tree diff sorted by lamport * fix: sort roots before tree converted to string * fix: rebase main * fix: tree fuzz * fix: delete undo * fix: tree to json children sorted * fix: diff calculate * fix: diff cycle move * fix: tree old parent cache * feat: cache * fix: local op add tree cache * fix: don't add same tree move to cache * fix: need update cache * feat: new cache * bench: add checkout bench * chore: clean * fix: apply node uncheck * perf: lamport bound * fix: calc old parent * feat: tree wasm * fix: change tree diff * fix: tree diff retreat * fix: tree diff should not apply when add node * feat: new tree loro value * chore: typo * fix: tree deep value * fix: snapshot tree index -1 * fix: decode tree snapshot use state * fix: release state lock when emit event * fix: tree node meta container * fix: need set map container when covert to local tree op * fix: tree value add deleted * fix: more then one op in a change * fix: tree fuzz deleted equal * fix: tree calc min lamport * feat: tree encoding v2 * doc: movable tree * fix: test tree meta * test: remove import bytes check * refactor: diff of text and map * refactor: del span * perf: tree state use deleted cache * fix: some details * fix: loro js tree create * feat: add un exist tree node * bench: tree depth * fix: check out should emit event * refactor: event * fix: fuzz err * fix: pass all tests * fix: fuzz err * fix: list child cache err * chore: rm debug code * fix: encode enhanced err * fix: encode enchanced * fix: fix several richtext issue * fix: richtext anchor err * chore: rm debug code * fix: richtext fuzz err * feat: speedup text snapshot decode * perf: optimize snapshot encoding * perf: speed up decode & insert * fix: fugue span merge err * perf: speedup delete & id cursor map * fix: fugue merge err * chore: update utils * fix: fix merge * fix: return err apply op * fix: fix merge * fix: get map container as tree meta
2023-10-30 03:13:52 +00:00
let encoded = match &mode {
2023-08-30 04:27:23 +00:00
EncodeMode::Updates => encode_oplog_updates(oplog, vv),
EncodeMode::RleUpdates => encode_oplog_v2(oplog, vv),
2023-08-30 04:27:23 +00:00
EncodeMode::CompressedRleUpdates => {
let bytes = encode_oplog_v2(oplog, vv);
miniz_oxide::deflate::compress_to_vec(&bytes, 7)
}
_ => unreachable!(),
};
ans.push(mode.to_byte());
ans.extend(encoded);
ans
}
pub(crate) fn decode_oplog(oplog: &mut OpLog, input: &[u8]) -> Result<(), LoroError> {
2023-08-30 08:41:04 +00:00
if input.len() < 6 {
return Err(LoroError::DecodeError("".into()));
}
let (magic_bytes, input) = input.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()));
}
2023-08-30 08:41:04 +00:00
let mode: EncodeMode = input[0].try_into()?;
let decoded = &input[1..];
match mode {
2023-08-30 04:27:23 +00:00
EncodeMode::Updates => decode_oplog_updates(oplog, decoded),
EncodeMode::Snapshot => unimplemented!(),
EncodeMode::RleUpdates => decode_oplog_v2(oplog, decoded),
EncodeMode::CompressedRleUpdates => miniz_oxide::inflate::decompress_to_vec(decoded)
2023-08-30 04:27:23 +00:00
.map_err(|_| LoroError::DecodeError("Invalid compressed data".into()))
.and_then(|bytes| decode_oplog_v2(oplog, &bytes)),
EncodeMode::Auto => unreachable!(),
}
}