1use std::sync::Arc;
2
3use enum_as_inner::EnumAsInner;
4use loro_common::{ContainerID, ContainerType, LoroValue};
5use rle::{HasLength, Mergable, Sliceable};
6#[cfg(feature = "wasm")]
7use serde::{Deserialize, Serialize};
8
9use crate::{
10 arena::SharedArena,
11 container::{
12 list::list_op::{InnerListOp, ListOp},
13 map::MapSet,
14 tree::tree_op::TreeOp,
15 },
16 encoding::OwnedValue,
17};
18
19#[derive(EnumAsInner, Debug, Clone)]
20pub enum InnerContent {
21 List(InnerListOp),
22 Map(MapSet),
23 Tree(Arc<TreeOp>),
24 Future(FutureInnerContent),
26}
27
28impl InnerContent {
29 pub fn visit_created_children(&self, arena: &SharedArena, f: &mut dyn FnMut(&ContainerID)) {
30 match self {
31 InnerContent::List(l) => match l {
32 InnerListOp::Insert { slice, .. } => {
33 for v in arena.iter_value_slice(slice.to_range()) {
34 if let LoroValue::Container(c) = v {
35 f(&c);
36 }
37 }
38 }
39 InnerListOp::Set { value, .. } => {
40 if let LoroValue::Container(c) = value {
41 f(c);
42 }
43 }
44
45 InnerListOp::Move { .. } => {}
46 InnerListOp::InsertText { .. } => {}
47 InnerListOp::Delete(_) => {}
48 InnerListOp::StyleStart { .. } => {}
49 InnerListOp::StyleEnd => {}
50 },
51 crate::op::InnerContent::Map(m) => {
52 if let Some(LoroValue::Container(c)) = &m.value {
53 f(c);
54 }
55 }
56 crate::op::InnerContent::Tree(t) => {
57 if let TreeOp::Create { target, .. } = t.as_ref() {
58 let id = target.associated_meta_container();
59 f(&id);
60 }
61 }
62 crate::op::InnerContent::Future(f) => match &f {
63 #[cfg(feature = "counter")]
64 crate::op::FutureInnerContent::Counter(_) => {}
65 crate::op::FutureInnerContent::Unknown { .. } => {}
66 },
67 }
68 }
69}
70
71impl InnerContent {
72 pub fn estimate_storage_size(&self, kind: ContainerType) -> usize {
73 match self {
74 InnerContent::List(l) => l.estimate_storage_size(kind),
75 InnerContent::Map(_) => 3,
76 InnerContent::Tree(_) => 8,
77 InnerContent::Future(f) => f.estimate_storage_size(),
78 }
79 }
80}
81
82#[derive(EnumAsInner, Debug, Clone)]
83pub enum FutureInnerContent {
84 #[cfg(feature = "counter")]
85 Counter(f64),
86 Unknown {
87 prop: i32,
88 value: Box<OwnedValue>,
89 },
90}
91impl FutureInnerContent {
92 fn estimate_storage_size(&self) -> usize {
93 match self {
94 #[cfg(feature = "counter")]
95 FutureInnerContent::Counter(_) => 4,
96 FutureInnerContent::Unknown { .. } => 6,
97 }
98 }
99}
100
101#[derive(EnumAsInner, Debug, PartialEq)]
103#[cfg_attr(feature = "wasm", derive(Serialize, Deserialize,))]
104pub enum RawOpContent<'a> {
105 Map(MapSet),
106 List(ListOp<'a>),
107 Tree(Arc<TreeOp>),
108 #[cfg(feature = "counter")]
109 Counter(f64),
110 Unknown {
111 prop: i32,
112 value: OwnedValue,
113 },
114}
115
116impl Clone for RawOpContent<'_> {
117 fn clone(&self) -> Self {
118 match self {
119 Self::Map(arg0) => Self::Map(arg0.clone()),
120 Self::List(arg0) => Self::List(arg0.clone()),
121 Self::Tree(arg0) => Self::Tree(arg0.clone()),
122 #[cfg(feature = "counter")]
123 Self::Counter(x) => Self::Counter(*x),
124 Self::Unknown { prop, value } => Self::Unknown {
125 prop: *prop,
126 value: value.clone(),
127 },
128 }
129 }
130}
131
132impl RawOpContent<'_> {
133 pub fn to_static(&self) -> RawOpContent<'static> {
134 match self {
135 Self::Map(arg0) => RawOpContent::Map(arg0.clone()),
136 Self::List(arg0) => match arg0 {
137 ListOp::Insert { slice, pos } => RawOpContent::List(ListOp::Insert {
138 slice: slice.to_static(),
139 pos: *pos,
140 }),
141 ListOp::Delete(x) => RawOpContent::List(ListOp::Delete(*x)),
142 ListOp::StyleStart {
143 start,
144 end,
145 key,
146 value,
147 info,
148 } => RawOpContent::List(ListOp::StyleStart {
149 start: *start,
150 end: *end,
151 key: key.clone(),
152 value: value.clone(),
153 info: *info,
154 }),
155 ListOp::StyleEnd => RawOpContent::List(ListOp::StyleEnd),
156 ListOp::Move {
157 from,
158 to,
159 elem_id: from_id,
160 } => RawOpContent::List(ListOp::Move {
161 from: *from,
162 to: *to,
163 elem_id: *from_id,
164 }),
165 ListOp::Set { elem_id, value } => RawOpContent::List(ListOp::Set {
166 elem_id: *elem_id,
167 value: value.clone(),
168 }),
169 },
170 Self::Tree(arg0) => RawOpContent::Tree(arg0.clone()),
171 #[cfg(feature = "counter")]
172 Self::Counter(x) => RawOpContent::Counter(*x),
173 Self::Unknown { prop, value } => RawOpContent::Unknown {
174 prop: *prop,
175 value: value.clone(),
176 },
177 }
178 }
179}
180
181impl HasLength for RawOpContent<'_> {
182 fn content_len(&self) -> usize {
183 match self {
184 RawOpContent::Map(x) => x.content_len(),
185 RawOpContent::List(x) => x.content_len(),
186 RawOpContent::Tree(x) => x.content_len(),
187 #[cfg(feature = "counter")]
188 RawOpContent::Counter(_) => 1,
189 RawOpContent::Unknown { .. } => 1,
190 }
191 }
192}
193
194impl Mergable for RawOpContent<'_> {
195 fn is_mergable(&self, other: &Self, _conf: &()) -> bool
196 where
197 Self: Sized,
198 {
199 match (self, other) {
200 (RawOpContent::List(x), RawOpContent::List(y)) => x.is_mergable(y, &()),
201 (RawOpContent::Tree(x), RawOpContent::Tree(y)) => x.is_mergable(y, &()),
202 _ => false,
203 }
204 }
205
206 fn merge(&mut self, _other: &Self, _conf: &())
207 where
208 Self: Sized,
209 {
210 match self {
211 RawOpContent::List(x) => match _other {
212 RawOpContent::List(y) => x.merge(y, &()),
213 _ => unreachable!(),
214 },
215 _ => unreachable!(),
216 }
217 }
218}
219
220impl HasLength for InnerContent {
221 fn content_len(&self) -> usize {
222 match self {
223 InnerContent::List(list) => list.atom_len(),
224 InnerContent::Map(_) => 1,
225 InnerContent::Tree(_) => 1,
226 InnerContent::Future(_) => 1,
227 }
228 }
229}
230
231impl Sliceable for InnerContent {
232 fn slice(&self, from: usize, to: usize) -> Self {
233 match self {
234 a @ InnerContent::Map(_) => {
235 assert!(from == 0 && to == 1);
236 a.clone()
237 }
238 a @ InnerContent::Tree(_) => {
239 assert!(from == 0 && to == 1);
240 a.clone()
241 }
242 InnerContent::List(x) => InnerContent::List(x.slice(from, to)),
243 InnerContent::Future(f) => {
244 assert!(from == 0 && to == 1);
245 InnerContent::Future(f.clone())
246 }
247 }
248 }
249}
250
251impl Mergable for InnerContent {
252 fn is_mergable(&self, other: &Self, _conf: &()) -> bool
253 where
254 Self: Sized,
255 {
256 match (self, other) {
257 (InnerContent::List(x), InnerContent::List(y)) => x.is_mergable(y, &()),
258 _ => false,
259 }
260 }
261
262 fn merge(&mut self, _other: &Self, _conf: &())
263 where
264 Self: Sized,
265 {
266 match self {
267 InnerContent::List(x) => match _other {
268 InnerContent::List(y) => x.merge(y, &()),
269 _ => unreachable!(),
270 },
271 _ => unreachable!(),
272 }
273 }
274}
275
276#[cfg(test)]
277mod tests {
278 use std::{borrow::Cow, sync::Arc};
279
280 use fractional_index::FractionalIndex;
281 use loro_common::{ContainerID, ContainerType, TreeID};
282 use rle::{HasLength, Mergable, Sliceable};
283
284 use super::*;
285 use crate::{
286 container::{
287 list::list_op::{DeleteSpanWithId, ListOp},
288 map::MapSet,
289 tree::tree_op::TreeOp,
290 },
291 op::{ListSlice, SliceRange},
292 InternalString, LoroValue, ID,
293 };
294
295 #[test]
296 fn raw_op_content_reports_lengths_and_static_content_without_borrowing() {
297 let raw_insert = RawOpContent::List(ListOp::Insert {
298 slice: ListSlice::RawData(Cow::Owned(vec![1_i64.into(), 2_i64.into()])),
299 pos: 3,
300 });
301 assert_eq!(raw_insert.content_len(), 2);
302 match raw_insert.to_static() {
303 RawOpContent::List(ListOp::Insert { slice, pos }) => {
304 assert_eq!(pos, 3);
305 assert_eq!(
306 slice,
307 ListSlice::RawData(Cow::Owned(vec![1_i64.into(), 2_i64.into()]))
308 );
309 }
310 other => panic!("expected list insert, got {other:?}"),
311 }
312
313 let raw_map = RawOpContent::Map(MapSet {
314 key: InternalString::from("title"),
315 value: Some("draft".into()),
316 });
317 assert_eq!(raw_map.content_len(), 1);
318 assert_eq!(raw_map.to_static(), raw_map);
319
320 let raw_tree = RawOpContent::Tree(Arc::new(TreeOp::Delete {
321 target: TreeID::new(2, 9),
322 }));
323 assert_eq!(raw_tree.content_len(), 1);
324 assert_eq!(raw_tree.to_static(), raw_tree);
325
326 let raw_unknown = RawOpContent::Unknown {
327 prop: 7,
328 value: OwnedValue::I64(11),
329 };
330 assert_eq!(raw_unknown.content_len(), 1);
331 assert_eq!(raw_unknown.to_static(), raw_unknown);
332
333 #[cfg(feature = "counter")]
334 {
335 let raw_counter = RawOpContent::Counter(1.25);
336 assert_eq!(raw_counter.content_len(), 1);
337 assert_eq!(raw_counter.to_static(), raw_counter);
338 }
339 }
340
341 #[test]
342 fn raw_op_content_merges_only_mergeable_list_operations() {
343 let mut left =
344 RawOpContent::List(ListOp::Delete(DeleteSpanWithId::new(ID::new(1, 0), 0, 1)));
345 let right = RawOpContent::List(ListOp::Delete(DeleteSpanWithId::new(ID::new(1, 1), 0, 1)));
346 assert!(left.is_mergable(&right, &()));
347 left.merge(&right, &());
348 assert_eq!(left.content_len(), 2);
349
350 let map = RawOpContent::Map(MapSet {
351 key: InternalString::from("k"),
352 value: Some(1_i64.into()),
353 });
354 assert!(!left.is_mergable(&map, &()));
355 }
356
357 #[test]
358 fn inner_content_lengths_storage_estimates_slices_and_merges_follow_inner_ops() {
359 let mut left = InnerContent::List(InnerListOp::Insert {
360 slice: SliceRange(0..2),
361 pos: 0,
362 });
363 let right = InnerContent::List(InnerListOp::Insert {
364 slice: SliceRange(2..4),
365 pos: 2,
366 });
367 assert_eq!(left.content_len(), 2);
368 assert_eq!(left.estimate_storage_size(ContainerType::List), 8);
369 assert!(left.is_mergable(&right, &()));
370 left.merge(&right, &());
371 assert_eq!(left.content_len(), 4);
372
373 let sliced = left.slice(1, 3);
374 match sliced {
375 InnerContent::List(InnerListOp::Insert { slice, pos }) => {
376 assert_eq!(slice, SliceRange(1..3));
377 assert_eq!(pos, 1);
378 }
379 other => panic!("expected sliced list insert, got {other:?}"),
380 }
381
382 let map = InnerContent::Map(MapSet {
383 key: InternalString::from("flag"),
384 value: Some(true.into()),
385 });
386 assert_eq!(map.content_len(), 1);
387 assert_eq!(map.estimate_storage_size(ContainerType::Map), 3);
388 assert!(matches!(map.slice(0, 1), InnerContent::Map(_)));
389
390 let tree = InnerContent::Tree(Arc::new(TreeOp::Delete {
391 target: TreeID::new(4, 5),
392 }));
393 assert_eq!(tree.content_len(), 1);
394 assert_eq!(tree.estimate_storage_size(ContainerType::Tree), 8);
395 assert!(matches!(tree.slice(0, 1), InnerContent::Tree(_)));
396
397 let future = InnerContent::Future(FutureInnerContent::Unknown {
398 prop: 99,
399 value: Box::new(OwnedValue::Null),
400 });
401 assert_eq!(future.content_len(), 1);
402 assert_eq!(future.estimate_storage_size(ContainerType::Unknown(3)), 6);
403 assert!(matches!(future.slice(0, 1), InnerContent::Future(_)));
404
405 #[cfg(feature = "counter")]
406 {
407 let counter = InnerContent::Future(FutureInnerContent::Counter(2.0));
408 assert_eq!(counter.estimate_storage_size(ContainerType::Counter), 4);
409 }
410 }
411
412 #[test]
413 fn visit_created_children_reports_containers_created_by_list_map_and_tree_content() {
414 let arena = SharedArena::new();
415 let list_child = ContainerID::new_normal(ID::new(1, 1), ContainerType::List);
416 let text_child = ContainerID::new_normal(ID::new(1, 2), ContainerType::Text);
417 let values = arena.alloc_values(
418 vec![
419 LoroValue::Container(list_child.clone()),
420 "plain".into(),
421 LoroValue::Container(text_child.clone()),
422 ]
423 .into_iter(),
424 );
425 let list = InnerContent::List(InnerListOp::Insert {
426 slice: SliceRange(values.start as u32..values.end as u32),
427 pos: 0,
428 });
429 let mut children = Vec::new();
430 list.visit_created_children(&arena, &mut |id| children.push(id.clone()));
431 assert_eq!(children, vec![list_child.clone(), text_child.clone()]);
432
433 let map_child = ContainerID::new_normal(ID::new(2, 1), ContainerType::Map);
434 let map = InnerContent::Map(MapSet {
435 key: InternalString::from("child"),
436 value: Some(LoroValue::Container(map_child.clone())),
437 });
438 let mut children = Vec::new();
439 map.visit_created_children(&arena, &mut |id| children.push(id.clone()));
440 assert_eq!(children, vec![map_child]);
441
442 let tree_target = TreeID::new(3, 7);
443 let tree = InnerContent::Tree(Arc::new(TreeOp::Create {
444 target: tree_target,
445 parent: None,
446 position: FractionalIndex::default(),
447 }));
448 let mut children = Vec::new();
449 tree.visit_created_children(&arena, &mut |id| children.push(id.clone()));
450 assert_eq!(children, vec![tree_target.associated_meta_container()]);
451
452 for tree in [
453 TreeOp::Move {
454 target: tree_target,
455 parent: None,
456 position: FractionalIndex::default(),
457 },
458 TreeOp::Delete {
459 target: tree_target,
460 },
461 ] {
462 let tree = InnerContent::Tree(Arc::new(tree));
463 let mut children = Vec::new();
464 tree.visit_created_children(&arena, &mut |id| children.push(id.clone()));
465 assert!(children.is_empty());
466 }
467
468 let future = InnerContent::Future(FutureInnerContent::Unknown {
469 prop: 1,
470 value: Box::new(OwnedValue::False),
471 });
472 let mut children = Vec::new();
473 future.visit_created_children(&arena, &mut |id| children.push(id.clone()));
474 assert!(children.is_empty());
475 }
476}