diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..81e82cc0 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "cSpell.words": [ + "smstring" + ] +} diff --git a/crates/loro-core/Cargo.toml b/crates/loro-core/Cargo.toml index 8c30f7ac..1544cbf0 100644 --- a/crates/loro-core/Cargo.toml +++ b/crates/loro-core/Cargo.toml @@ -14,6 +14,8 @@ fxhash = "0.2.1" ring = "0.16.20" moveit = "0.5.0" pin-project = "1.0.10" +serde = {version = "1.0.140", features = ["derive"]} +thiserror = "1.0.31" [dev-dependencies] proptest = "1.0.0" diff --git a/crates/loro-core/src/container.rs b/crates/loro-core/src/container.rs index a754bceb..a358f3c3 100644 --- a/crates/loro-core/src/container.rs +++ b/crates/loro-core/src/container.rs @@ -5,10 +5,11 @@ //! Every [Container] can take a [Snapshot], which contains [crate::LoroValue] that describes the state. //! use crate::{ - op::OpProxy, snapshot::Snapshot, version::VersionVector, InsertContent, InternalString, - LogStore, Op, SmString, ID, + op::OpProxy, version::VersionVector, InsertContent, InternalString, LogStore, LoroValue, Op, + SmString, ID, }; use rle::{HasLength, Mergable, Sliceable}; +use serde::Serialize; use std::{ alloc::Layout, any::{self, Any, TypeId}, @@ -26,10 +27,12 @@ pub use manager::*; pub trait Container: Debug + Any + Unpin { fn id(&self) -> &ContainerID; - fn container_type(&self) -> ContainerType; + fn type_(&self) -> ContainerType; fn apply(&mut self, op: &OpProxy); - fn snapshot(&mut self) -> Snapshot; - fn checkout_version(&mut self, vv: &VersionVector, log: &LogStore); + fn checkout_version(&mut self, vv: &VersionVector); + fn get_value(&mut self) -> &LoroValue; + // TODO: need a custom serializer + // fn serialize(&self) -> Vec; } pub(crate) trait Cast { @@ -49,7 +52,7 @@ impl Cast for dyn Container { } } -#[derive(Hash, PartialEq, Eq, Debug, Clone)] +#[derive(Hash, PartialEq, Eq, Debug, Clone, Serialize)] pub enum ContainerID { /// Root container does not need a insert op to create. It can be created implicitly. Root { diff --git a/crates/loro-core/src/container/container_content.rs b/crates/loro-core/src/container/container_content.rs index 788734a2..d3444f5f 100644 --- a/crates/loro-core/src/container/container_content.rs +++ b/crates/loro-core/src/container/container_content.rs @@ -1,11 +1,12 @@ -use crate::{InsertContent, SmString, ID}; +use crate::{smstring::SmString, InsertContent, ID}; use rle::{HasLength, Mergable, Sliceable}; +use serde::Serialize; use std::alloc::Layout; #[derive(Debug, Clone)] pub(crate) enum Slot {} -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)] pub enum ContainerType { /// See [`crate::text::TextContent`] Text, diff --git a/crates/loro-core/src/container/map/map_container.rs b/crates/loro-core/src/container/map/map_container.rs index 768b1f21..752be688 100644 --- a/crates/loro-core/src/container/map/map_container.rs +++ b/crates/loro-core/src/container/map/map_container.rs @@ -1,15 +1,16 @@ use std::{pin::Pin, ptr::NonNull, rc::Weak}; use fxhash::FxHashMap; +use serde::Serialize; use crate::{ container::{Container, ContainerID, ContainerType}, - id::ID, + id::{Counter, ID}, op::{utils::downcast_ref, Op}, op::{OpContent, OpProxy}, value::{InsertValue, LoroValue}, version::TotalOrderStamp, - ClientID, InternalString, Lamport, LogStore, OpType, Snapshot, + ClientID, InternalString, Lamport, LogStore, OpType, }; use super::MapInsertContent; @@ -22,12 +23,14 @@ pub struct MapContainer { id: ContainerID, state: FxHashMap, log_store: NonNull, + value: Option, } #[derive(Debug)] struct ValueSlot { value: InsertValue, order: TotalOrderStamp, + counter: Counter, } impl MapContainer { @@ -37,6 +40,7 @@ impl MapContainer { id, state: FxHashMap::default(), log_store: store, + value: None, } } @@ -53,8 +57,10 @@ impl MapContainer { lamport: store.next_lamport(), }; + let id = store.next_id(client_id); + let counter = id.counter; store.append_local_ops(vec![Op { - id: store.next_id(client_id), + id, content: OpContent::Normal { container: self_id, content: Box::new(MapInsertContent { @@ -64,7 +70,14 @@ impl MapContainer { }, }]); - self.state.insert(key, ValueSlot { value, order }); + self.state.insert( + key, + ValueSlot { + value, + order, + counter, + }, + ); } #[inline] @@ -79,7 +92,7 @@ impl Container for MapContainer { &self.id } - fn container_type(&self) -> ContainerType { + fn type_(&self) -> ContainerType { ContainerType::Map } @@ -104,6 +117,7 @@ impl Container for MapContainer { ValueSlot { value: v.value.clone(), order, + counter: op.id().counter, }, ); } @@ -112,16 +126,19 @@ impl Container for MapContainer { } } - fn snapshot(&mut self) -> Snapshot { - let mut map = FxHashMap::default(); - for (key, value) in self.state.iter() { - map.insert(key.clone(), value.value.clone().into()); + fn get_value(&mut self) -> &LoroValue { + if self.value.is_none() { + let mut map = FxHashMap::default(); + for (key, value) in self.state.iter() { + map.insert(key.clone(), value.value.clone().into()); + } + self.value = Some(LoroValue::Map(map)); } - Snapshot::new(LoroValue::Map(map)) + self.value.as_ref().unwrap() } - fn checkout_version(&mut self, _vv: &crate::version::VersionVector, _log: &crate::LogStore) { + fn checkout_version(&mut self, vv: &crate::version::VersionVector) { todo!() } } diff --git a/crates/loro-core/src/container/map/map_content.rs b/crates/loro-core/src/container/map/map_content.rs index 46c46b73..5d038aec 100644 --- a/crates/loro-core/src/container/map/map_content.rs +++ b/crates/loro-core/src/container/map/map_content.rs @@ -1,6 +1,6 @@ use rle::{HasLength, Mergable, Sliceable}; -use crate::{value::InsertValue, ContentType, InsertContent, InternalString}; +use crate::{id::ID, value::InsertValue, ContentType, InsertContent, InternalString}; #[derive(Clone, Debug, PartialEq)] pub struct MapInsertContent { diff --git a/crates/loro-core/src/container/map/tests.rs b/crates/loro-core/src/container/map/tests.rs index fe408baf..10572950 100644 --- a/crates/loro-core/src/container/map/tests.rs +++ b/crates/loro-core/src/container/map/tests.rs @@ -28,7 +28,7 @@ fn basic() { "haha".into() => LoroValue::Integer(1) ); - assert_eq!(*container.snapshot().value(), LoroValue::Map(ans)); + assert_eq!(*container.get_value(), LoroValue::Map(ans)); } #[cfg(not(no_proptest))] @@ -47,9 +47,8 @@ mod map_proptest { for (k, v) in key.iter().zip(value.iter()) { map.insert(k.clone(), v.clone()); container.insert(k.clone().into(), v.clone()); - let snapshot = container.snapshot(); - let snapshot = snapshot.value().to_map().unwrap(); - for (key, value) in snapshot.iter() { + let snapshot = container.get_value(); + for (key, value) in snapshot.to_map().unwrap().iter() { assert_eq!(map.get(&key.to_string()).map(|x|x.clone().into()), Some(value.clone())); } } diff --git a/crates/loro-core/src/error.rs b/crates/loro-core/src/error.rs new file mode 100644 index 00000000..58eaacd9 --- /dev/null +++ b/crates/loro-core/src/error.rs @@ -0,0 +1,11 @@ +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum LoroError { + // #[error("the data for key `{0}` is not available")] + // Redaction(String), + // #[error("invalid header (expected {expected:?}, found {found:?})")] + // InvalidHeader { expected: String, found: String }, + // #[error("unknown data store error")] + // Unknown, +} diff --git a/crates/loro-core/src/id.rs b/crates/loro-core/src/id.rs index a680780d..f2d446e7 100644 --- a/crates/loro-core/src/id.rs +++ b/crates/loro-core/src/id.rs @@ -1,7 +1,9 @@ +use serde::Serialize; + pub type ClientID = u64; pub type Counter = u32; -#[derive(PartialEq, Eq, Hash, Clone, Debug, Copy, PartialOrd, Ord)] +#[derive(PartialEq, Eq, Hash, Clone, Debug, Copy, PartialOrd, Ord, Serialize)] pub struct ID { pub client_id: u64, pub counter: u32, diff --git a/crates/loro-core/src/lib.rs b/crates/loro-core/src/lib.rs index d324a1d6..a8ff0038 100644 --- a/crates/loro-core/src/lib.rs +++ b/crates/loro-core/src/lib.rs @@ -10,9 +10,11 @@ pub mod id; pub mod op; pub mod version; +mod error; mod id_span; mod log_store; mod loro; +mod smstring; mod snapshot; mod tests; mod value; @@ -20,9 +22,8 @@ mod value; pub(crate) mod macros; pub(crate) use change::{Change, Lamport, Timestamp}; pub(crate) use id::{ClientID, ID}; -pub(crate) use snapshot::Snapshot; -pub(crate) type SmString = SmartString; pub(crate) use op::{ContentType, InsertContent, Op, OpContent, OpType}; +pub(crate) use smstring::SmString; pub(crate) type InternalString = DefaultAtom; pub use container::ContainerType; @@ -30,5 +31,4 @@ pub use log_store::LogStore; pub use loro::*; pub use value::LoroValue; -use smartstring::{LazyCompact, SmartString}; use string_cache::DefaultAtom; diff --git a/crates/loro-core/src/smstring.rs b/crates/loro-core/src/smstring.rs new file mode 100644 index 00000000..d2563cd8 --- /dev/null +++ b/crates/loro-core/src/smstring.rs @@ -0,0 +1,46 @@ +use std::ops::DerefMut; + +use std::ops::Deref; + +use serde::Serialize; +use smartstring::LazyCompact; + +use smartstring::SmartString; + +#[repr(transparent)] +#[derive(Debug, PartialEq, PartialOrd, Ord, Eq, Clone)] +pub struct SmString(pub(crate) SmartString); + +impl Deref for SmString { + type Target = SmartString; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for SmString { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl From> for SmString { + fn from(s: SmartString) -> Self { + SmString(s) + } +} + +impl From for SmString { + fn from(s: String) -> Self { + SmString(s.into()) + } +} + +impl Serialize for SmString { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(&self.0) + } +} diff --git a/crates/loro-core/src/snapshot.rs b/crates/loro-core/src/snapshot.rs index 07cec75b..8b137891 100644 --- a/crates/loro-core/src/snapshot.rs +++ b/crates/loro-core/src/snapshot.rs @@ -1,16 +1 @@ -use crate::value::LoroValue; -#[derive(Debug, PartialEq, Clone)] -pub struct Snapshot { - value: LoroValue, -} - -impl Snapshot { - pub fn new(value: LoroValue) -> Self { - Snapshot { value } - } - - pub fn value(&self) -> &LoroValue { - &self.value - } -} diff --git a/crates/loro-core/src/value.rs b/crates/loro-core/src/value.rs index 975c1da2..a1af55b9 100644 --- a/crates/loro-core/src/value.rs +++ b/crates/loro-core/src/value.rs @@ -1,9 +1,9 @@ use fxhash::FxHashMap; -use crate::{container::ContainerID, InternalString, SmString}; +use crate::{container::ContainerID, smstring::SmString, InternalString}; /// [LoroValue] is used to represents the state of CRDT at a given version -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, PartialEq, Clone, serde::Serialize)] pub enum LoroValue { Null, Bool(bool), diff --git a/justfile b/justfile index 73087815..164a1fb4 100644 --- a/justfile +++ b/justfile @@ -7,3 +7,12 @@ test: # test without proptest test-fast: RUSTFLAGS='--cfg no_proptest' cargo nextest run + +check-unsafe: + env RUSTFLAGS="-Funsafe-code --cap-lints=warn" cargo check + +deny: + cargo deny check + +crev: + cargo crev crate check