1mod change_store;
2pub(crate) mod loro_dag;
3mod pending_changes;
4
5use crate::sync::{AtomicUsize, Mutex};
6use bytes::Bytes;
7use std::borrow::Cow;
8use std::cell::RefCell;
9use std::cmp::Ordering;
10use std::rc::Rc;
11use std::sync::Arc;
12use tracing::trace_span;
13
14use self::change_store::iter::MergedChangeIter;
15use self::pending_changes::{PendingChanges, PendingChangesRollback};
16use super::arena::{SharedArena, SharedArenaRollback};
17use crate::change::{get_sys_timestamp, Change, Lamport, Timestamp};
18use crate::configure::Configure;
19use crate::container::list::list_op;
20use crate::dag::{Dag, DagUtils};
21use crate::diff_calc::DiffMode;
22use crate::encoding::decode_oplog;
23use crate::encoding::{ImportStatus, ParsedHeaderAndBody};
24use crate::history_cache::ContainerHistoryCache;
25use crate::id::{Counter, PeerID, ID};
26use crate::op::{FutureInnerContent, ListSlice, RawOpContent, RemoteOp, RichOp};
27use crate::span::{HasCounterSpan, HasLamportSpan};
28use crate::version::{Frontiers, ImVersionVector, VersionVector};
29use crate::LoroError;
30use change_store::{BlockOpRef, ChangeStoreRollback};
31use loro_common::{ContainerType, HasIdSpan, IdLp, IdSpan};
32use rle::{HasLength, RleVec, Sliceable};
33use smallvec::SmallVec;
34
35pub use self::loro_dag::{AppDag, AppDagNode, FrontiersNotIncluded};
36pub use change_store::{BlockChangeRef, ChangeStore};
37
38pub struct OpLog {
46 pub(crate) dag: AppDag,
47 pub(crate) arena: SharedArena,
48 visible_op_count: Arc<AtomicUsize>,
49 change_store: ChangeStore,
50 history_cache: Mutex<ContainerHistoryCache>,
51 pub(crate) pending_changes: PendingChanges,
55 pub(crate) batch_importing: bool,
58 pub(crate) configure: Configure,
59 pub(crate) uncommitted_change: Option<Change>,
62 pub(crate) import_rollback: Option<ImportRollback>,
63}
64
65pub(crate) struct ImportRollback {
66 old_vv: VersionVector,
67 arena: SharedArenaRollback,
68 change_store: ChangeStoreRollback,
69 pending: PendingChangesRollback,
70}
71
72#[derive(Debug, Default, Clone, Copy)]
73pub(crate) struct ImportChangesPreflight {
74 pub applies_to_dag: bool,
75 pub has_deps_before_shallow_root: bool,
76 pub needs_state_apply_rollback: bool,
77}
78
79impl std::fmt::Debug for OpLog {
80 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
81 f.debug_struct("OpLog")
82 .field("dag", &self.dag)
83 .field("pending_changes", &self.pending_changes)
84 .finish()
85 }
86}
87
88impl OpLog {
89 #[inline]
90 pub(crate) fn new(visible_op_count: Arc<AtomicUsize>) -> Self {
91 let arena = SharedArena::new();
92 let cfg = Configure::default();
93 let change_store = ChangeStore::new_mem(&arena, cfg.merge_interval_in_s.clone());
94 Self {
95 visible_op_count,
96 history_cache: Mutex::new(ContainerHistoryCache::new(change_store.clone(), None)),
97 dag: AppDag::new(change_store.clone()),
98 change_store,
99 arena,
100 pending_changes: Default::default(),
101 batch_importing: false,
102 configure: cfg,
103 uncommitted_change: None,
104 import_rollback: None,
105 }
106 }
107
108 #[inline]
109 fn calc_visible_op_count(&self) -> usize {
110 let total = self.dag.vv().values().sum::<i32>() as usize;
111 let shallow = self
112 .dag
113 .shallow_since_vv()
114 .iter()
115 .map(|(_, ops)| *ops)
116 .sum::<i32>() as usize;
117 total - shallow
118 }
119
120 #[inline]
121 pub(crate) fn visible_op_count_exact(&self) -> usize {
122 self.calc_visible_op_count()
123 }
124
125 #[inline]
126 pub(crate) fn refresh_visible_op_count(&self) -> usize {
127 let count = self.calc_visible_op_count();
128 self.visible_op_count
129 .store(count, std::sync::atomic::Ordering::Release);
130 count
131 }
132
133 #[inline]
139 pub(crate) fn inc_visible_op_count(&self, delta: usize) {
140 self.visible_op_count
141 .fetch_add(delta, std::sync::atomic::Ordering::Release);
142 }
143
144 #[cfg(test)]
145 pub(crate) fn cached_visible_op_count(&self) -> usize {
146 self.visible_op_count
147 .load(std::sync::atomic::Ordering::Acquire)
148 }
149
150 #[inline]
151 pub fn dag(&self) -> &AppDag {
152 &self.dag
153 }
154
155 pub fn change_store(&self) -> &ChangeStore {
156 &self.change_store
157 }
158
159 pub fn get_change_with_lamport_lte(
163 &self,
164 peer: PeerID,
165 lamport: Lamport,
166 ) -> Option<BlockChangeRef> {
167 let ans = self
168 .change_store
169 .get_change_by_lamport_lte(IdLp::new(peer, lamport))?;
170 debug_assert!(ans.lamport <= lamport);
171 Some(ans)
172 }
173
174 pub fn get_timestamp_of_version(&self, f: &Frontiers) -> Timestamp {
175 let mut timestamp = Timestamp::default();
176 for id in f.iter() {
177 if let Some(change) = self.lookup_change(id) {
178 timestamp = timestamp.max(change.timestamp);
179 }
180 }
181
182 timestamp
183 }
184
185 #[inline]
186 pub fn is_empty(&self) -> bool {
187 self.dag.is_empty() && self.arena.can_import_snapshot()
188 }
189
190 pub(crate) fn insert_new_change(&mut self, change: Change, from_local: bool) {
192 let s = trace_span!(
193 "insert_new_change",
194 id = ?change.id,
195 lamport = change.lamport,
196 deps = ?change.deps
197 );
198 let _enter = s.enter();
199 let rollback_old_vv = self
200 .import_rollback
201 .as_ref()
202 .and_then(|x| (!x.old_vv.is_empty()).then_some(&x.old_vv));
203 self.dag
204 .handle_new_change(&change, from_local, rollback_old_vv);
205 self.history_cache
206 .lock()
207 .insert_by_new_change(&change, true, true);
208 self.register_container_and_parent_link(&change);
209 if let Some(rollback) = self.import_rollback.as_mut() {
210 self.change_store.insert_change_with_rollback(
211 change,
212 true,
213 from_local,
214 &mut rollback.change_store,
215 );
216 } else {
217 self.change_store.insert_change(change, true, from_local);
218 }
219 self.refresh_visible_op_count();
220 }
221
222 pub(crate) fn begin_import_rollback(&mut self) {
223 let arena = self.arena.checkpoint_for_rollback();
224 self.begin_import_rollback_with_arena(arena);
225 }
226
227 pub(crate) fn begin_import_rollback_with_arena(&mut self, arena: SharedArenaRollback) {
228 debug_assert!(self.import_rollback.is_none());
229 let old_vv = self.vv().clone();
230 self.dag.begin_import_rollback();
231 self.import_rollback = Some(ImportRollback {
232 old_vv: old_vv.clone(),
233 arena,
234 change_store: ChangeStoreRollback::new(old_vv),
235 pending: Default::default(),
236 });
237 }
238
239 pub(crate) fn commit_import_rollback(&mut self) {
240 self.dag.commit_import_rollback();
241 self.import_rollback = None;
242 }
243
244 pub(crate) fn preflight_import_changes(&self, changes: &[Change]) -> ImportChangesPreflight {
245 let mut ans = ImportChangesPreflight::default();
246 let pending_needs_state_apply_rollback =
247 self.pending_changes.has_state_apply_rollback_ops();
248 for change in changes {
249 if change.ctr_end() <= self.vv().get(&change.id.peer).copied().unwrap_or(0) {
250 continue;
251 }
252
253 if self.dag.import_deps_before_shallow_root(&change.deps) {
254 ans.has_deps_before_shallow_root = true;
255 continue;
256 }
257
258 if self
259 .dag
260 .get_change_lamport_from_deps(&change.deps)
261 .is_none()
262 {
263 continue;
264 }
265
266 ans.applies_to_dag = true;
267 if change.ops.iter().any(|op| {
268 matches!(
269 op.container.get_type(),
270 ContainerType::List | ContainerType::Tree
271 )
272 }) {
273 ans.needs_state_apply_rollback = true;
274 }
275 }
276
277 if ans.applies_to_dag && pending_needs_state_apply_rollback {
283 ans.needs_state_apply_rollback = true;
284 }
285
286 #[cfg(test)]
287 if ans.applies_to_dag {
288 ans.needs_state_apply_rollback = true;
289 }
290
291 ans
292 }
293
294 pub(crate) fn rollback_import(&mut self) {
295 let Some(rollback) = self.import_rollback.take() else {
296 return;
297 };
298
299 self.change_store.rollback_import(rollback.change_store);
300 self.dag.rollback_import();
301 rollback.pending.rollback(&mut self.pending_changes);
302 self.history_cache.lock().free_all();
303 self.arena.rollback(rollback.arena);
304 self.refresh_visible_op_count();
305 }
306
307 pub(crate) fn reset_to_empty_for_failed_snapshot_import(
308 &mut self,
309 arena_checkpoint: SharedArenaRollback,
310 ) {
311 let arena = self.arena.clone();
312 let configure = self.configure.clone();
313 arena.rollback(arena_checkpoint);
314 let change_store = ChangeStore::new_mem(&arena, configure.merge_interval_in_s.clone());
315 self.history_cache = Mutex::new(ContainerHistoryCache::new(change_store.clone(), None));
316 self.dag = AppDag::new(change_store.clone());
317 self.change_store = change_store;
318 self.pending_changes = Default::default();
319 self.batch_importing = false;
320 self.configure = configure;
321 self.uncommitted_change = None;
322 self.import_rollback = None;
323 self.visible_op_count
324 .store(0, std::sync::atomic::Ordering::Release);
325 }
326
327 #[inline(always)]
328 pub(crate) fn with_history_cache<F, R>(&self, f: F) -> R
329 where
330 F: FnOnce(&mut ContainerHistoryCache) -> R,
331 {
332 let mut history_cache = self.history_cache.lock();
333 f(&mut history_cache)
334 }
335
336 pub fn has_history_cache(&self) -> bool {
337 self.history_cache.lock().has_cache()
338 }
339
340 pub fn free_history_cache(&self) {
341 let mut history_cache = self.history_cache.lock();
342 history_cache.free();
343 }
344
345 #[cfg(test)]
346 #[allow(dead_code)]
347 pub(crate) fn pending_changes_len(&self) -> usize {
348 self.pending_changes.len()
349 }
350
351 pub(crate) fn import_local_change(&mut self, change: Change) -> Result<(), LoroError> {
362 self.insert_new_change(change, true);
363 Ok(())
364 }
365
366 pub(crate) fn trim_the_known_part_of_change(&self, change: Change) -> Option<Change> {
368 let Some(&end) = self.dag.vv().get(&change.id.peer) else {
369 return Some(change);
370 };
371
372 if change.id.counter >= end {
373 return Some(change);
374 }
375
376 if change.ctr_end() <= end {
377 return None;
378 }
379
380 let offset = (end - change.id.counter) as usize;
381 Some(change.slice(offset, change.atom_len()))
382 }
383
384 #[allow(unused)]
385 fn check_id_is_not_duplicated(&self, id: ID) -> Result<(), LoroError> {
386 let cur_end = self.dag.vv().get(&id.peer).cloned().unwrap_or(0);
387 if cur_end > id.counter {
388 return Err(LoroError::UsedOpID { id });
389 }
390
391 Ok(())
392 }
393
394 pub(crate) fn check_change_greater_than_last_peer_id(
399 &self,
400 peer: PeerID,
401 counter: Counter,
402 deps: &Frontiers,
403 ) -> Result<(), LoroError> {
404 if counter == 0 {
405 return Ok(());
406 }
407
408 if !self.configure.detached_editing() {
409 return Ok(());
410 }
411
412 let mut max_last_counter = -1;
413 for dep in deps.iter() {
414 let dep_vv = self
415 .dag
416 .get_vv(dep)
417 .ok_or(LoroError::FrontiersNotFound(dep))?;
418 max_last_counter = max_last_counter.max(dep_vv.get(&peer).cloned().unwrap_or(0) - 1);
419 }
420
421 if counter != max_last_counter + 1 {
422 return Err(LoroError::ConcurrentOpsWithSamePeerID {
423 peer,
424 last_counter: max_last_counter,
425 current: counter,
426 });
427 }
428
429 Ok(())
430 }
431
432 pub(crate) fn next_id(&self, peer: PeerID) -> ID {
433 let cnt = self.dag.vv().get(&peer).copied().unwrap_or(0);
434 ID::new(peer, cnt)
435 }
436
437 pub(crate) fn vv(&self) -> &VersionVector {
438 self.dag.vv()
439 }
440
441 pub(crate) fn frontiers(&self) -> &Frontiers {
442 self.dag.frontiers()
443 }
444
445 pub fn cmp_with_frontiers(&self, other: &Frontiers) -> Ordering {
449 self.dag.cmp_with_frontiers(other)
450 }
451
452 #[inline]
456 pub fn cmp_frontiers(
457 &self,
458 a: &Frontiers,
459 b: &Frontiers,
460 ) -> Result<Option<Ordering>, FrontiersNotIncluded> {
461 self.dag.cmp_frontiers(a, b)
462 }
463
464 pub(crate) fn get_min_lamport_at(&self, id: ID) -> Lamport {
465 self.get_change_at(id).map(|c| c.lamport).unwrap_or(0)
466 }
467
468 pub(crate) fn get_lamport_at(&self, id: ID) -> Option<Lamport> {
469 self.get_change_at(id)
470 .map(|c| c.lamport + (id.counter - c.id.counter) as Lamport)
471 }
472
473 pub(crate) fn iter_ops(&self, id_span: IdSpan) -> impl Iterator<Item = RichOp<'static>> + '_ {
474 let change_iter = self.change_store.iter_changes(id_span);
475 change_iter.flat_map(move |c| RichOp::new_iter_by_cnt_range(c, id_span.counter))
476 }
477
478 pub(crate) fn get_max_lamport_at(&self, id: ID) -> Lamport {
479 self.get_change_at(id)
480 .map(|c| {
481 let change_counter = c.id.counter as u32;
482 c.lamport + c.ops().last().map(|op| op.counter).unwrap_or(0) as u32 - change_counter
483 })
484 .unwrap_or(Lamport::MAX)
485 }
486
487 pub fn get_change_at(&self, id: ID) -> Option<BlockChangeRef> {
488 self.change_store.get_change(id)
489 }
490
491 pub(crate) fn set_uncommitted_change(&mut self, change: Change) {
492 self.uncommitted_change = Some(change);
493 }
494
495 pub(crate) fn get_uncommitted_change_in_span(
496 &self,
497 id_span: IdSpan,
498 ) -> Option<Cow<'_, Change>> {
499 self.uncommitted_change.as_ref().and_then(|c| {
500 if c.id_span() == id_span {
501 Some(Cow::Borrowed(c))
502 } else if let Some((start, end)) = id_span.get_slice_range_on(&c.id_span()) {
503 Some(Cow::Owned(c.slice(start, end)))
504 } else {
505 None
506 }
507 })
508 }
509
510 pub fn get_deps_of(&self, id: ID) -> Option<Frontiers> {
511 self.get_change_at(id).map(|c| {
512 if c.id.counter == id.counter {
513 c.deps.clone()
514 } else {
515 Frontiers::from_id(id.inc(-1))
516 }
517 })
518 }
519
520 pub fn get_remote_change_at(&self, id: ID) -> Option<Change<RemoteOp<'static>>> {
521 let change = self.get_change_at(id)?;
522 Some(convert_change_to_remote(&self.arena, &change))
523 }
524
525 pub(crate) fn import_unknown_lamport_pending_changes(
526 &mut self,
527 remote_changes: Vec<Change>,
528 ) -> Result<(), LoroError> {
529 self.extend_pending_changes_with_unknown_lamport(remote_changes)
530 }
531
532 pub(crate) fn lookup_change(&self, id: ID) -> Option<BlockChangeRef> {
536 self.change_store.get_change(id)
537 }
538
539 #[inline(always)]
540 pub(crate) fn export_change_store_from(&self, vv: &VersionVector, f: &Frontiers) -> Bytes {
541 self.change_store
542 .export_from(vv, f, self.vv(), self.frontiers())
543 }
544
545 #[inline(always)]
546 pub(crate) fn export_change_store_in_range(
547 &self,
548 vv: &VersionVector,
549 f: &Frontiers,
550 to_vv: &VersionVector,
551 to_frontiers: &Frontiers,
552 ) -> Bytes {
553 self.change_store.export_from(vv, f, to_vv, to_frontiers)
554 }
555
556 #[inline(always)]
557 pub(crate) fn export_blocks_from<W: std::io::Write>(&self, vv: &VersionVector, w: &mut W) {
558 self.change_store
559 .export_blocks_from(vv, self.shallow_since_vv(), self.vv(), w)
560 }
561
562 #[inline(always)]
563 pub(crate) fn export_blocks_in_range<W: std::io::Write>(&self, spans: &[IdSpan], w: &mut W) {
564 self.change_store.export_blocks_in_range(spans, w)
565 }
566
567 pub(crate) fn fork_changes_up_to(&self, frontiers: &Frontiers) -> Option<Bytes> {
568 let vv = self.dag.frontiers_to_vv(frontiers)?;
569 Some(
570 self.change_store
571 .fork_changes_up_to(self.dag.shallow_since_vv(), frontiers, &vv),
572 )
573 }
574
575 #[inline(always)]
576 pub(crate) fn decode(&mut self, data: ParsedHeaderAndBody) -> Result<ImportStatus, LoroError> {
577 decode_oplog(self, data)
578 }
579
580 #[allow(clippy::type_complexity)]
594 pub(crate) fn iter_from_replay_base_causally(
595 &self,
596 from: &VersionVector,
597 from_frontiers: &Frontiers,
598 to: &VersionVector,
599 to_frontiers: &Frontiers,
600 ) -> (
601 VersionVector,
602 DiffMode,
603 impl Iterator<
604 Item = (
605 BlockChangeRef,
606 (Counter, Counter),
607 Rc<RefCell<VersionVector>>,
608 ),
609 > + '_,
610 ) {
611 let mut merged_vv = from.clone();
612 merged_vv.merge(to);
613 loro_common::debug!("to_frontiers={:?} vv={:?}", &to_frontiers, to);
614 let (mut replay_base_frontiers, mut diff_mode) =
615 self.dag.find_common_ancestor(from_frontiers, to_frontiers);
616 if diff_mode == DiffMode::Checkout && to > from {
617 diff_mode = DiffMode::Import;
618 }
619
620 let mut replay_base_vv = self.dag.frontiers_to_vv(&replay_base_frontiers).unwrap();
621 let shallow_since_vv = self.dag.shallow_since_vv().to_vv();
622 if !replay_base_vv.includes_vv(&shallow_since_vv) {
623 replay_base_frontiers = self.dag.shallow_since_frontiers().clone();
626 replay_base_vv = self
627 .dag
628 .frontiers_to_vv(&replay_base_frontiers)
629 .unwrap_or(shallow_since_vv);
630 }
631 let diff = replay_base_vv.diff(&merged_vv).forward;
633 let mut iter = self.dag.iter_causal(replay_base_frontiers, diff);
634 let mut node = iter.next();
635 let mut cur_cnt = 0;
636 let vv = Rc::new(RefCell::new(VersionVector::default()));
637 (
638 replay_base_vv.clone(),
639 diff_mode,
640 std::iter::from_fn(move || {
641 if let Some(inner) = &node {
642 let mut inner_vv = vv.borrow_mut();
643 inner_vv.clear();
645 self.dag.ensure_vv_for(&inner.data);
646 inner_vv.extend_to_include_vv(inner.data.vv.get().unwrap().iter());
647 let peer = inner.data.peer;
648 let cnt = inner
649 .data
650 .cnt
651 .max(cur_cnt)
652 .max(replay_base_vv.get(&peer).copied().unwrap_or(0));
653 let dag_node_end = (inner.data.cnt + inner.data.len as Counter)
654 .min(merged_vv.get(&peer).copied().unwrap_or(0));
655 let change = self.change_store.get_change(ID::new(peer, cnt)).unwrap();
656
657 if change.ctr_end() < dag_node_end {
658 cur_cnt = change.ctr_end();
659 } else {
660 node = iter.next();
661 cur_cnt = 0;
662 }
663
664 inner_vv.extend_to_include_end_id(change.id);
665
666 Some((change, (cnt, dag_node_end), vv.clone()))
667 } else {
668 None
669 }
670 }),
671 )
672 }
673
674 pub fn len_changes(&self) -> usize {
675 self.change_store.change_num()
676 }
677
678 pub fn diagnose_size(&self) -> SizeInfo {
679 let mut total_changes = 0;
680 let mut total_ops = 0;
681 let mut total_atom_ops = 0;
682 let total_dag_node = self.dag.total_parsed_dag_node();
683 self.change_store.visit_all_changes(&mut |change| {
684 total_changes += 1;
685 total_ops += change.ops.len();
686 total_atom_ops += change.atom_len();
687 });
688
689 println!("total changes: {}", total_changes);
690 println!("total ops: {}", total_ops);
691 println!("total atom ops: {}", total_atom_ops);
692 println!("total dag node: {}", total_dag_node);
693 SizeInfo {
694 total_changes,
695 total_ops,
696 total_atom_ops,
697 total_dag_node,
698 }
699 }
700
701 pub(crate) fn iter_changes_peer_by_peer<'a>(
702 &'a self,
703 from: &VersionVector,
704 to: &VersionVector,
705 ) -> impl Iterator<Item = BlockChangeRef> + 'a {
706 let spans: Vec<_> = from.diff_iter(to).1.collect();
707 spans
708 .into_iter()
709 .flat_map(move |span| self.change_store.iter_changes(span))
710 }
711
712 #[allow(dead_code)]
713 pub(crate) fn iter_changes_causally_rev<'a>(
714 &'a self,
715 from: &VersionVector,
716 to: &VersionVector,
717 ) -> impl Iterator<Item = BlockChangeRef> + 'a {
718 MergedChangeIter::new_change_iter_rev(self, from, to)
719 }
720
721 pub fn get_timestamp_for_next_txn(&self) -> Timestamp {
722 if self.configure.record_timestamp() {
723 get_timestamp_now_txn()
724 } else {
725 0
726 }
727 }
728
729 #[inline(never)]
730 pub(crate) fn idlp_to_id(&self, id: loro_common::IdLp) -> Option<ID> {
731 let change = self.change_store.get_change_by_lamport_lte(id)?;
732
733 if change.lamport > id.lamport || change.lamport_end() <= id.lamport {
734 return None;
735 }
736
737 Some(ID::new(
738 change.id.peer,
739 (id.lamport - change.lamport) as Counter + change.id.counter,
740 ))
741 }
742
743 #[allow(unused)]
744 pub(crate) fn id_to_idlp(&self, id_start: ID) -> IdLp {
745 let change = self.get_change_at(id_start).unwrap();
746 let lamport = change.lamport + (id_start.counter - change.id.counter) as Lamport;
747 let peer = id_start.peer;
748 loro_common::IdLp { peer, lamport }
749 }
750
751 pub(crate) fn get_op_that_includes(&self, id: ID) -> Option<BlockOpRef> {
753 let change = self.get_change_at(id)?;
754 change.get_op_with_counter(id.counter)
755 }
756
757 pub(crate) fn split_span_based_on_deps(&self, id_span: IdSpan) -> Vec<(IdSpan, Frontiers)> {
758 let peer = id_span.peer;
759 let mut counter = id_span.counter.min();
760 let span_end = id_span.counter.norm_end();
761 let mut ans = Vec::new();
762
763 while counter < span_end {
764 let id = ID::new(peer, counter);
765 let node = self.dag.get(id).unwrap();
766
767 let f = if node.cnt == counter {
768 node.deps.clone()
769 } else if counter > 0 {
770 id.inc(-1).into()
771 } else {
772 unreachable!()
773 };
774
775 let cur_end = node.cnt + node.len as Counter;
776 let len = cur_end.min(span_end) - counter;
777 ans.push((id.to_span(len as usize), f));
778 counter += len;
779 }
780
781 ans
782 }
783
784 #[inline]
785 pub fn compact_change_store(&mut self) {
786 self.change_store
787 .flush_and_compact(self.dag.vv(), self.dag.frontiers());
788 }
789
790 #[inline]
791 pub fn change_store_kv_size(&self) -> usize {
792 self.change_store.kv_size()
793 }
794
795 pub fn encode_change_store(&self) -> bytes::Bytes {
796 self.change_store
797 .encode_all(self.dag.vv(), self.dag.frontiers())
798 }
799
800 pub fn check_dag_correctness(&self) {
801 self.dag.check_dag_correctness();
802 }
803
804 pub fn shallow_since_vv(&self) -> &ImVersionVector {
805 self.dag.shallow_since_vv()
806 }
807
808 pub fn shallow_since_frontiers(&self) -> &Frontiers {
809 self.dag.shallow_since_frontiers()
810 }
811
812 pub fn is_shallow(&self) -> bool {
813 !self.dag.shallow_since_vv().is_empty()
814 }
815
816 pub fn get_greatest_timestamp(&self, frontiers: &Frontiers) -> Timestamp {
817 let mut max_timestamp = Timestamp::default();
818 for id in frontiers.iter() {
819 let change = self.get_change_at(id).unwrap();
820 if change.timestamp > max_timestamp {
821 max_timestamp = change.timestamp;
822 }
823 }
824
825 max_timestamp
826 }
827}
828
829#[derive(Debug)]
830pub struct SizeInfo {
831 pub total_changes: usize,
832 pub total_ops: usize,
833 pub total_atom_ops: usize,
834 pub total_dag_node: usize,
835}
836
837pub(crate) fn convert_change_to_remote(
838 arena: &SharedArena,
839 change: &Change,
840) -> Change<RemoteOp<'static>> {
841 let mut ops = RleVec::new();
842 for op in change.ops.iter() {
843 for op in local_op_to_remote(arena, op) {
844 ops.push(op);
845 }
846 }
847
848 Change {
849 ops,
850 id: change.id,
851 deps: change.deps.clone(),
852 lamport: change.lamport,
853 timestamp: change.timestamp,
854 commit_msg: change.commit_msg.clone(),
855 }
856}
857
858pub(crate) fn local_op_to_remote(
859 arena: &SharedArena,
860 op: &crate::op::Op,
861) -> SmallVec<[RemoteOp<'static>; 1]> {
862 let container = arena.get_container_id(op.container).unwrap();
863 let mut contents: SmallVec<[_; 1]> = SmallVec::new();
864 match &op.content {
865 crate::op::InnerContent::List(list) => match list {
866 list_op::InnerListOp::Insert { slice, pos } => match container.container_type() {
867 loro_common::ContainerType::Text => {
868 let str = arena
869 .slice_str_by_unicode_range(slice.0.start as usize..slice.0.end as usize);
870 contents.push(RawOpContent::List(list_op::ListOp::Insert {
871 slice: ListSlice::RawStr {
872 unicode_len: str.chars().count(),
873 str: Cow::Owned(str),
874 },
875 pos: *pos,
876 }));
877 }
878 loro_common::ContainerType::List | loro_common::ContainerType::MovableList => {
879 contents.push(RawOpContent::List(list_op::ListOp::Insert {
880 slice: ListSlice::RawData(Cow::Owned(
881 arena.get_values(slice.0.start as usize..slice.0.end as usize),
882 )),
883 pos: *pos,
884 }))
885 }
886 _ => unreachable!(),
887 },
888 list_op::InnerListOp::InsertText {
889 slice,
890 unicode_len: len,
891 unicode_start: _,
892 pos,
893 } => match container.container_type() {
894 loro_common::ContainerType::Text => {
895 contents.push(RawOpContent::List(list_op::ListOp::Insert {
896 slice: ListSlice::RawStr {
897 unicode_len: *len as usize,
898 str: Cow::Owned(std::str::from_utf8(slice).unwrap().to_owned()),
899 },
900 pos: *pos as usize,
901 }));
902 }
903 _ => unreachable!(),
904 },
905 list_op::InnerListOp::Delete(del) => {
906 contents.push(RawOpContent::List(list_op::ListOp::Delete(*del)))
907 }
908 list_op::InnerListOp::StyleStart {
909 start,
910 end,
911 key,
912 value,
913 info,
914 } => contents.push(RawOpContent::List(list_op::ListOp::StyleStart {
915 start: *start,
916 end: *end,
917 key: key.clone(),
918 value: value.clone(),
919 info: *info,
920 })),
921 list_op::InnerListOp::StyleEnd => {
922 contents.push(RawOpContent::List(list_op::ListOp::StyleEnd))
923 }
924 list_op::InnerListOp::Move {
925 from,
926 elem_id: from_id,
927 to,
928 } => contents.push(RawOpContent::List(list_op::ListOp::Move {
929 from: *from,
930 elem_id: *from_id,
931 to: *to,
932 })),
933 list_op::InnerListOp::Set { elem_id, value } => {
934 contents.push(RawOpContent::List(list_op::ListOp::Set {
935 elem_id: *elem_id,
936 value: value.clone(),
937 }))
938 }
939 },
940 crate::op::InnerContent::Map(map) => {
941 let value = map.value.clone();
942 contents.push(RawOpContent::Map(crate::container::map::MapSet {
943 key: map.key.clone(),
944 value,
945 }))
946 }
947 crate::op::InnerContent::Tree(tree) => contents.push(RawOpContent::Tree(tree.clone())),
948 crate::op::InnerContent::Future(f) => match f {
949 #[cfg(feature = "counter")]
950 crate::op::FutureInnerContent::Counter(c) => contents.push(RawOpContent::Counter(*c)),
951 FutureInnerContent::Unknown { prop, value } => {
952 contents.push(crate::op::RawOpContent::Unknown {
953 prop: *prop,
954 value: (**value).clone(),
955 })
956 }
957 },
958 };
959
960 let mut ans = SmallVec::with_capacity(contents.len());
961 for content in contents {
962 ans.push(RemoteOp {
963 container: container.clone(),
964 content,
965 counter: op.counter,
966 })
967 }
968 ans
969}
970
971pub(crate) fn get_timestamp_now_txn() -> Timestamp {
972 (get_sys_timestamp() as Timestamp + 500) / 1000
973}
974
975#[cfg(test)]
976mod visible_op_count_tests {
977 use crate::{cursor::PosType, loro::ExportMode, LoroDoc};
978
979 #[test]
983 fn cached_visible_op_count_matches_exact() {
984 let doc = LoroDoc::new();
985 let text = doc.get_text("text");
986 let mut txn = doc.txn().unwrap();
987 for i in 0..50 {
988 text.insert_with_txn(&mut txn, i, "a", PosType::Unicode)
989 .unwrap();
990 }
991 txn.commit().unwrap();
992 {
993 let oplog = doc.oplog().lock();
994 assert_eq!(
995 oplog.cached_visible_op_count(),
996 oplog.visible_op_count_exact(),
997 "after local edits"
998 );
999 }
1000
1001 let doc2 = LoroDoc::new();
1004 doc2.import(&doc.export(ExportMode::all_updates()).unwrap())
1005 .unwrap();
1006 let text2 = doc2.get_text("text");
1007 let mut txn2 = doc2.txn().unwrap();
1008 text2
1009 .insert_with_txn(&mut txn2, 0, "bbb", PosType::Unicode)
1010 .unwrap();
1011 txn2.commit().unwrap();
1012 {
1013 let oplog = doc2.oplog().lock();
1014 assert_eq!(
1015 oplog.cached_visible_op_count(),
1016 oplog.visible_op_count_exact(),
1017 "after import + local edits"
1018 );
1019 }
1020 }
1021}