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

207 lines
6.5 KiB
Rust
Raw Normal View History

2022-07-18 05:53:16 +00:00
//! CRDT [Container]. Each container may have different CRDT type [ContainerType].
//! Each [Op] has an associated container. It's the [Container]'s responsibility to
//! calculate the state from the [Op]s.
//!
//! Every [Container] can take a [Snapshot], which contains [crate::LoroValue] that describes the state.
//!
use crate::{arena::SharedArena, InternalString, ID};
2022-09-01 16:59:39 +00:00
pub mod idx {
use super::super::ContainerType;
2022-10-31 14:36:54 +00:00
/// Inner representation for ContainerID.
/// It contains the unique index for the container and the type of the container.
/// It uses top 4 bits to represent the type of the container.
///
/// It's only used inside this crate and should not be exposed to the user.
///
/// TODO: make this type private in this crate only
///
// During a transaction, we may create some containers which are deleted later. And these containers also need a unique ContainerIdx.
// So when we encode snapshot, we need to sort the containers by ContainerIdx and change the `container` of ops to the index of containers.
// An empty store decodes the snapshot, it will create these containers in a sequence of natural numbers so that containers and ops can correspond one-to-one
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)]
pub struct ContainerIdx(u32);
impl std::fmt::Debug for ContainerIdx {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("ContainerIdx")
.field(&self.get_type())
.field(&self.to_index())
.finish()
}
}
impl ContainerIdx {
pub(crate) const TYPE_MASK: u32 = 0b1111 << 28;
pub(crate) const INDEX_MASK: u32 = !Self::TYPE_MASK;
#[allow(unused)]
pub(crate) fn get_type(self) -> ContainerType {
match (self.0 & Self::TYPE_MASK) >> 28 {
0 => ContainerType::Map,
1 => ContainerType::List,
2 => ContainerType::Text,
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
3 => ContainerType::Tree,
_ => unreachable!(),
}
}
#[allow(unused)]
pub(crate) fn to_index(self) -> u32 {
self.0 & Self::INDEX_MASK
}
pub(crate) fn from_index_and_type(index: u32, container_type: ContainerType) -> Self {
let prefix: u32 = match container_type {
ContainerType::Map => 0,
ContainerType::List => 1,
ContainerType::Text => 2,
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
ContainerType::Tree => 3,
} << 28;
Self(prefix | index)
}
}
}
2022-07-18 05:53:16 +00:00
2022-08-26 12:19:38 +00:00
pub mod list;
2022-07-17 17:00:50 +00:00
pub mod map;
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
pub mod richtext;
pub mod tree;
2022-11-18 08:31:00 +00:00
use idx::ContainerIdx;
2022-12-07 13:51:18 +00:00
pub use loro_common::ContainerType;
2022-12-07 13:51:18 +00:00
pub use loro_common::ContainerID;
2022-11-18 09:30:27 +00:00
2022-11-11 14:26:06 +00:00
pub enum ContainerIdRaw {
Root { name: InternalString },
Normal { id: ID },
}
2023-07-28 18:03:51 +00:00
pub trait IntoContainerId {
fn into_container_id(self, arena: &SharedArena, kind: ContainerType) -> ContainerID;
}
impl IntoContainerId for String {
fn into_container_id(self, _arena: &SharedArena, kind: ContainerType) -> ContainerID {
ContainerID::Root {
name: InternalString::from(self.as_str()),
container_type: kind,
}
}
}
impl<'a> IntoContainerId for &'a str {
fn into_container_id(self, _arena: &SharedArena, kind: ContainerType) -> ContainerID {
ContainerID::Root {
name: InternalString::from(self),
container_type: kind,
}
}
}
impl IntoContainerId for ContainerID {
fn into_container_id(self, _arena: &SharedArena, _kind: ContainerType) -> ContainerID {
self
}
}
impl IntoContainerId for &ContainerID {
fn into_container_id(self, _arena: &SharedArena, _kind: ContainerType) -> ContainerID {
self.clone()
}
}
2023-07-28 18:03:51 +00:00
impl IntoContainerId for ContainerIdx {
fn into_container_id(self, arena: &SharedArena, kind: ContainerType) -> ContainerID {
assert_eq!(self.get_type(), kind);
arena.get_container_id(self).unwrap()
}
}
impl IntoContainerId for &ContainerIdx {
fn into_container_id(self, arena: &SharedArena, kind: ContainerType) -> ContainerID {
assert_eq!(self.get_type(), kind);
arena.get_container_id(*self).unwrap()
}
}
impl From<String> for ContainerIdRaw {
fn from(value: String) -> Self {
ContainerIdRaw::Root { name: value.into() }
}
}
impl<'a> From<&'a str> for ContainerIdRaw {
fn from(value: &'a str) -> Self {
2023-01-11 13:40:16 +00:00
ContainerIdRaw::Root { name: value.into() }
2022-11-11 14:26:06 +00:00
}
}
impl From<&ContainerID> for ContainerIdRaw {
fn from(id: &ContainerID) -> Self {
match id {
ContainerID::Root { name, .. } => ContainerIdRaw::Root { name: name.clone() },
ContainerID::Normal { peer, counter, .. } => ContainerIdRaw::Normal {
id: ID::new(*peer, *counter),
},
2022-11-11 14:26:06 +00:00
}
}
}
impl From<ContainerID> for ContainerIdRaw {
fn from(id: ContainerID) -> Self {
match id {
ContainerID::Root { name, .. } => ContainerIdRaw::Root { name },
ContainerID::Normal { peer, counter, .. } => ContainerIdRaw::Normal {
id: ID::new(peer, counter),
},
2022-11-11 14:26:06 +00:00
}
}
}
impl ContainerIdRaw {
pub fn with_type(self, container_type: ContainerType) -> ContainerID {
match self {
ContainerIdRaw::Root { name } => ContainerID::Root {
name,
container_type,
},
ContainerIdRaw::Normal { id } => ContainerID::Normal {
peer: id.peer,
counter: id.counter,
container_type,
},
2022-11-11 14:26:06 +00:00
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn container_id_convert() {
let container_id = ContainerID::new_normal(ID::new(12, 12), ContainerType::List);
let s = container_id.to_string();
assert_eq!(s, "cid:12@12:List");
let actual = ContainerID::try_from(s.as_str()).unwrap();
assert_eq!(actual, container_id);
let container_id = ContainerID::new_root("123", ContainerType::Map);
let s = container_id.to_string();
assert_eq!(s, "cid:root-123:Map");
let actual = ContainerID::try_from(s.as_str()).unwrap();
assert_eq!(actual, container_id);
let container_id = ContainerID::new_root("kkk", ContainerType::Text);
let s = container_id.to_string();
assert_eq!(s, "cid:root-kkk:Text");
let actual = ContainerID::try_from(s.as_str()).unwrap();
assert_eq!(actual, container_id);
}
}