diff --git a/crates/loro-internal/src/arena.rs b/crates/loro-internal/src/arena.rs index 2be6ba9e..1cfa156e 100644 --- a/crates/loro-internal/src/arena.rs +++ b/crates/loro-internal/src/arena.rs @@ -10,7 +10,7 @@ use crate::{ container::{ idx::ContainerIdx, list::list_op::{InnerListOp, ListOp}, - map::InnerMapSet, + map::{InnerMapSet, MapSet}, text::text_content::SliceRange, ContainerID, }, @@ -81,15 +81,16 @@ impl<'a> OpConverter<'a> { }; match content { - crate::op::RawOpContent::Map(map) => { - let value = _alloc_value(&mut self.values, map.value) as u32; + crate::op::RawOpContent::Map(MapSet { key, value }) => { + let value = if let Some(value) = value { + Some(_alloc_value(&mut self.values, value) as u32) + } else { + None + }; Op { counter, container, - content: crate::op::InnerContent::Map(InnerMapSet { - key: map.key, - value, - }), + content: crate::op::InnerContent::Map(InnerMapSet { key, value }), } } crate::op::RawOpContent::List(list) => match list { @@ -291,15 +292,16 @@ impl SharedArena { container: ContainerIdx, ) -> Op { match content { - crate::op::RawOpContent::Map(map) => { - let value = self.alloc_value(map.value) as u32; + crate::op::RawOpContent::Map(MapSet { key, value }) => { + let value = if let Some(value) = value { + Some(self.alloc_value(value) as u32) + } else { + None + }; Op { counter, container, - content: crate::op::InnerContent::Map(InnerMapSet { - key: map.key, - value, - }), + content: crate::op::InnerContent::Map(InnerMapSet { key, value }), } } crate::op::RawOpContent::List(list) => match list { diff --git a/crates/loro-internal/src/container/map/map_content.rs b/crates/loro-internal/src/container/map/map_content.rs index d1f31dc7..1a829305 100644 --- a/crates/loro-internal/src/container/map/map_content.rs +++ b/crates/loro-internal/src/container/map/map_content.rs @@ -7,14 +7,15 @@ use crate::{InternalString, LoroValue}; #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct MapSet { pub(crate) key: InternalString, - pub(crate) value: LoroValue, + // the key is deleted if value is None + pub(crate) value: Option, } #[derive(Clone, Debug, PartialEq, Eq)] pub struct InnerMapSet { pub(crate) key: InternalString, - // FIXME: how to set None? - pub(crate) value: u32, + // the key is deleted if value is None + pub(crate) value: Option, } impl Mergable for MapSet {} @@ -50,9 +51,9 @@ mod test { fn fix_fields_order() { let map_set = vec![MapSet { key: "key".to_string().into(), - value: "value".to_string().into(), + value: Some("value".to_string().into()), }]; - let map_set_buf = vec![1, 3, 107, 101, 121, 4, 5, 118, 97, 108, 117, 101]; + let map_set_buf = vec![1, 3, 107, 101, 121, 1, 4, 5, 118, 97, 108, 117, 101]; assert_eq!( postcard::from_bytes::>(&map_set_buf).unwrap(), map_set diff --git a/crates/loro-internal/src/diff_calc.rs b/crates/loro-internal/src/diff_calc.rs index c16e4200..41d3fc47 100644 --- a/crates/loro-internal/src/diff_calc.rs +++ b/crates/loro-internal/src/diff_calc.rs @@ -293,7 +293,7 @@ impl DiffCalculatorTrait for MapDiffCalculator { for (key, value) in changed { let value = value .map(|v| { - let value = oplog.arena.get_value(v.value as usize); + let value = v.value.map(|v| oplog.arena.get_value(v as usize)).flatten(); MapValue { counter: v.counter, value, @@ -317,7 +317,7 @@ struct CompactMapValue { lamport: Lamport, peer: PeerID, counter: Counter, - value: u32, + value: Option, } impl HasId for CompactMapValue { diff --git a/crates/loro-internal/src/encoding/encode_changes.rs b/crates/loro-internal/src/encoding/encode_changes.rs index 3aca7211..c64e16d4 100644 --- a/crates/loro-internal/src/encoding/encode_changes.rs +++ b/crates/loro-internal/src/encoding/encode_changes.rs @@ -51,7 +51,7 @@ struct OpEncoding { /// key index or insert/delete pos #[columnar(strategy = "DeltaRle")] prop: usize, - value: LoroValue, + value: Option, } #[columnar(vec, ser, de, iterable)] @@ -169,16 +169,16 @@ pub(super) fn encode_oplog_changes(oplog: &OpLog, vv: &VersionVector) -> Vec ListOp::Insert { slice, pos } => ( pos, // TODO: perf may be optimized by using borrow type instead - match slice { + Some(match slice { ListSlice::RawData(v) => LoroValue::List(Arc::new(v.to_vec())), ListSlice::RawStr { str, unicode_len: _, } => LoroValue::String(Arc::new(str.to_string())), - }, + }), ), ListOp::Delete(span) => { - (span.pos as usize, LoroValue::I32(span.len as i32)) + (span.pos as usize, Some(LoroValue::I32(span.len as i32))) } }, }; @@ -270,6 +270,7 @@ pub(super) fn decode_changes_to_inner_format_oplog( } ContainerType::List | ContainerType::Text => { let pos = prop; + let value = value.unwrap(); let list_op = match value { LoroValue::I32(len) => ListOp::Delete(DeleteSpan { pos: pos as isize, diff --git a/crates/loro-internal/src/encoding/encode_enhanced.rs b/crates/loro-internal/src/encoding/encode_enhanced.rs index 29e8c099..0039c8b7 100644 --- a/crates/loro-internal/src/encoding/encode_enhanced.rs +++ b/crates/loro-internal/src/encoding/encode_enhanced.rs @@ -110,7 +110,7 @@ struct DocEncoding<'a> { #[columnar(borrow)] root_containers: VarZeroVec<'a, RootContainerULE, Index32>, start_counter: Vec, - values: Vec, + values: Vec>, clients: Vec, keys: Vec, } @@ -200,7 +200,7 @@ pub fn encode_oplog_v2(oplog: &OpLog, vv: &VersionVector) -> Vec { let mut len = 0; match slice { ListSlice::RawData(v) => { - values.push(LoroValue::List(Arc::new(v.to_vec()))); + values.push(Some(LoroValue::List(Arc::new(v.to_vec())))); } ListSlice::RawStr { str, @@ -451,7 +451,7 @@ pub fn decode_oplog_v2(oplog: &mut OpLog, input: &[u8]) -> Result<(), LoroError> }) } ContainerType::List => { - let value = value_iter.next().unwrap(); + let value = value_iter.next().flatten().unwrap(); RawOpContent::List(ListOp::Insert { slice: ListSlice::RawData(Cow::Owned( match Arc::try_unwrap(value.into_list().unwrap()) { diff --git a/crates/loro-internal/src/handler.rs b/crates/loro-internal/src/handler.rs index 30f61104..90db2239 100644 --- a/crates/loro-internal/src/handler.rs +++ b/crates/loro-internal/src/handler.rs @@ -527,7 +527,7 @@ impl MapHandler { self.container_idx, crate::op::RawOpContent::Map(crate::container::map::MapSet { key: key.into(), - value, + value: Some(value), }), None, &self.state, @@ -548,7 +548,7 @@ impl MapHandler { self.container_idx, crate::op::RawOpContent::Map(crate::container::map::MapSet { key: key.into(), - value: LoroValue::Container(container_id), + value: Some(LoroValue::Container(container_id)), }), None, &self.state, @@ -562,8 +562,7 @@ impl MapHandler { self.container_idx, crate::op::RawOpContent::Map(crate::container::map::MapSet { key: key.into(), - // TODO: use another special value to delete? - value: LoroValue::Null, + value: None, }), None, &self.state, diff --git a/crates/loro-internal/src/op/content.rs b/crates/loro-internal/src/op/content.rs index 0eb4b3f0..abd7327f 100644 --- a/crates/loro-internal/src/op/content.rs +++ b/crates/loro-internal/src/op/content.rs @@ -198,10 +198,10 @@ mod test { RawOpContent::List(ListOp::Delete(DeleteSpan { pos: 0, len: 1 })), RawOpContent::Map(MapSet { key: "a".to_string().into(), - value: "b".to_string().into(), + value: Some("b".to_string().into()), }), ]; - let remote_content_buf = vec![2, 1, 1, 0, 2, 0, 1, 97, 4, 1, 98]; + let remote_content_buf = vec![2, 1, 1, 0, 2, 0, 1, 97, 1, 4, 1, 98]; assert_eq!( postcard::from_bytes::>(&remote_content_buf).unwrap(), remote_content diff --git a/crates/loro-internal/src/oplog.rs b/crates/loro-internal/src/oplog.rs index cded14de..db429570 100644 --- a/crates/loro-internal/src/oplog.rs +++ b/crates/loro-internal/src/oplog.rs @@ -382,10 +382,13 @@ impl OpLog { } }, crate::op::InnerContent::Map(map) => { - let value = self.arena.get_value(map.value as usize); + let value = map + .value + .map(|v| self.arena.get_value(v as usize)) + .flatten(); contents.push(RawOpContent::Map(crate::container::map::MapSet { key: map.key.clone(), - value: value.unwrap_or(crate::LoroValue::Null), // TODO: decide map delete value + value, })) } }; diff --git a/crates/loro-internal/src/snapshot_encode.rs b/crates/loro-internal/src/snapshot_encode.rs index 3f87bd03..492b107f 100644 --- a/crates/loro-internal/src/snapshot_encode.rs +++ b/crates/loro-internal/src/snapshot_encode.rs @@ -145,7 +145,7 @@ pub fn decode_oplog( id, InnerContent::Map(InnerMapSet { key: (&*keys[key]).into(), - value: value_idx_plus_one - 1, + value: value_idx_plus_one.map(|v| v - 1), }), container_idx, ), @@ -326,14 +326,26 @@ struct EncodedSnapshotOp { // List: 0 | value index // Map: value index #[columnar(strategy = "DeltaRle")] - value: usize, + value: isize, } enum SnapshotOp { - TextInsert { pos: usize, len: usize }, - ListInsert { pos: usize, value_idx: u32 }, - TextOrListDelete { pos: usize, len: isize }, - Map { key: usize, value_idx_plus_one: u32 }, + TextInsert { + pos: usize, + len: usize, + }, + ListInsert { + pos: usize, + value_idx: u32, + }, + TextOrListDelete { + pos: usize, + len: isize, + }, + Map { + key: usize, + value_idx_plus_one: Option, + }, } impl EncodedSnapshotOp { @@ -366,9 +378,14 @@ impl EncodedSnapshotOp { } pub fn get_map(&self) -> SnapshotOp { + let value_idx_plus_one = if self.value < 0 { + None + } else { + Some(self.value as u32) + }; SnapshotOp::Map { key: self.prop, - value_idx_plus_one: self.value as u32, + value_idx_plus_one, } } @@ -382,7 +399,7 @@ impl EncodedSnapshotOp { prop: pos, len: 0, is_del: false, - value: start as usize, + value: start as isize, }, SnapshotOp::TextOrListDelete { pos, len } => Self { container, @@ -394,13 +411,16 @@ impl EncodedSnapshotOp { SnapshotOp::Map { key, value_idx_plus_one: value, - } => Self { - container, - prop: key, - len: 0, - is_del: false, - value: value as usize, - }, + } => { + let value = if let Some(v) = value { v as isize } else { -1 }; + Self { + container, + prop: key, + len: 0, + is_del: false, + value, + } + } SnapshotOp::TextInsert { pos, len } => Self { container, prop: pos, @@ -666,17 +686,19 @@ fn encode_oplog(oplog: &OpLog, state_ref: Option) -> FinalPhase }, InnerContent::Map(map) => { let key = record_key(&map.key); - let value = oplog.arena.get_value(map.value as usize); - // FIXME: delete in map + let value = map + .value + .map(|v| oplog.arena.get_value(v as usize)) + .flatten(); let value = if let Some(value) = value { - record_value(&value) + 1 + Some((record_value(&value) + 1) as u32) } else { - 0 + None }; encoded_ops.push(EncodedSnapshotOp::from( SnapshotOp::Map { key, - value_idx_plus_one: value as u32, + value_idx_plus_one: value, }, op.container.to_index(), )); diff --git a/crates/loro-internal/src/state/map_state.rs b/crates/loro-internal/src/state/map_state.rs index 70a801b4..b55d206d 100644 --- a/crates/loro-internal/src/state/map_state.rs +++ b/crates/loro-internal/src/state/map_state.rs @@ -5,7 +5,7 @@ use loro_common::ContainerID; use crate::{ arena::SharedArena, - container::idx::ContainerIdx, + container::{idx::ContainerIdx, map::MapSet}, delta::MapValue, event::{Diff, Index}, op::{RawOp, RawOpContent}, @@ -39,18 +39,30 @@ impl ContainerState for MapState { fn apply_op(&mut self, op: RawOp, arena: &SharedArena) { match op.content { - RawOpContent::Map(map) => { - if map.value.is_container() { - let idx = arena.register_container(map.value.as_container().unwrap()); + RawOpContent::Map(MapSet { key, value }) => { + if value.is_none() { + self.insert( + key, + MapValue { + lamport: (op.lamport, op.id.peer), + counter: op.id.counter, + value: None, + }, + ); + return; + } + let value = value.unwrap(); + if value.is_container() { + let idx = arena.register_container(value.as_container().unwrap()); arena.set_parent(idx, Some(self.idx)); } self.insert( - map.key, + key, MapValue { lamport: (op.lamport, op.id.peer), counter: op.id.counter, - value: Some(map.value), + value: Some(value), }, ) } diff --git a/crates/loro-internal/src/txn.rs b/crates/loro-internal/src/txn.rs index b78afaa9..5bb61770 100644 --- a/crates/loro-internal/src/txn.rs +++ b/crates/loro-internal/src/txn.rs @@ -342,13 +342,13 @@ fn change_to_diff( ), }, crate::op::InnerContent::Map(map) => { - let value = arena.get_value(map.value as usize).unwrap(); + let value = map.value.map(|v| arena.get_value(v as usize)).flatten(); let mut updated: FxHashMap<_, _> = Default::default(); updated.insert( map.key.clone(), MapValue { counter, - value: Some(value), + value, lamport: (lamport, peer), }, );