Skip to main content

loro_internal/
oplog.rs

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
38/// [OpLog] store all the ops i.e. the history.
39/// It allows multiple [AppState] to attach to it.
40/// So you can derive different versions of the state from the [OpLog].
41/// It allows us to build a version control system.
42///
43/// The causal graph should always be a DAG and complete. So we can always find a common ancestor version.
44/// If deps are missing, we can't import the change. It will be put into the `pending_changes`.
45pub 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    /// Pending changes that haven't been applied to the dag.
52    /// A change can be imported only when all its deps are already imported.
53    /// Key is the ID of the missing dep
54    pub(crate) pending_changes: PendingChanges,
55    /// Whether we are importing a batch of changes.
56    /// If so the Dag's frontiers won't be updated until the batch is finished.
57    pub(crate) batch_importing: bool,
58    pub(crate) configure: Configure,
59    /// The uncommitted change, it's a placeholder for the change
60    /// that is being edited in pre-commit callback.
61    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    /// Incrementally bump the cached visible op count for newly applied *local*
134    /// ops. Local ops are always visible (never behind the shallow root), so the
135    /// visible count grows by exactly `delta`. This avoids a per-op full
136    /// recompute via [`Self::calc_visible_op_count`], which iterates the version
137    /// vectors and heap-allocates an `im::HashMap` iterator on every call.
138    #[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    /// Get the change with the given peer and lamport.
160    ///
161    /// If not found, return the change with the greatest lamport that is smaller than the given lamport.
162    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    /// This is the **only** place to update the `OpLog.changes`
191    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        // Any newly applied change can unlock pending changes whose ops are not
278        // visible in `changes`, so include pending in the rollback decision.
279        // Keep this narrow: text/map-only pending changes cannot return a
280        // state-apply error, and forcing rollback there adds lock traffic to
281        // small sync/import workloads.
282        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    /// Import a change.
352    ///
353    /// Pending changes that haven't been applied to the dag.
354    /// A change can be imported only when all its deps are already imported.
355    /// Key is the ID of the missing dep
356    ///
357    /// # Err
358    ///
359    /// - Return Err(LoroError::UsedOpID) when the change's id is occupied
360    /// - Return Err(LoroError::DecodeError) when the change's deps are missing
361    pub(crate) fn import_local_change(&mut self, change: Change) -> Result<(), LoroError> {
362        self.insert_new_change(change, true);
363        Ok(())
364    }
365
366    /// Trim the known part of change
367    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    /// Ensure the new change is greater than the last peer's id and the counter is continuous.
395    ///
396    /// It can be false when users use detached editing mode and use a custom peer id.
397    // This method might be slow and can be optimized if needed in the future.
398    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    /// - Ordering::Less means self is less than target or parallel
446    /// - Ordering::Equal means versions equal
447    /// - Ordering::Greater means self's version is greater than target
448    pub fn cmp_with_frontiers(&self, other: &Frontiers) -> Ordering {
449        self.dag.cmp_with_frontiers(other)
450    }
451
452    /// Compare two [Frontiers] causally.
453    ///
454    /// If one of the [Frontiers] are not included, it will return [FrontiersNotIncluded].
455    #[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    /// lookup change by id.
533    ///
534    /// if id does not included in this oplog, return None
535    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    /// Iterates causally over all changes between the replay base (a common
581    /// ancestor version chosen by `find_common_ancestor`; ideally the latest
582    /// critical version in the Eg-walker sense, see
583    /// `docs/critical-version-spec.md`) and the merged version of `from`/`to`.
584    ///
585    /// Tht iterator will include a version vector when the change is applied
586    ///
587    /// returns: (common_ancestor_vv, iterator)
588    ///
589    /// Note: the change returned by the iterator may include redundant ops at the beginning, you should trim it by yourself.
590    /// You can trim it by the provided counter value. It should start with the counter.
591    ///
592    /// If frontiers are provided, it will be faster (because we don't need to calculate it from version vector
593    #[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            // The replay base cannot point before shallow history because those
624            // ops are no longer available to the causal iterator.
625            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        // go from the replay base to merged_vv
632        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                    // FIXME: PERF: it looks slow for large vv, like 10000+ entries
644                    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    /// NOTE: This may return a op that includes the given id, not necessarily start with the given id
752    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    /// The cached `visible_op_count` (bumped incrementally for local ops, and
980    /// the only value read in release builds where `can_lock_in_this_thread`
981    /// returns false) must always equal a from-scratch recompute.
982    #[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        // Import keeps the cached count exact via full refresh; subsequent local
1002        // edits then increment from that exact base.
1003        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}