Skip to main content

loro_internal/
arena.rs

1mod str_arena;
2use self::str_arena::{StrArena, StrArenaCheckpoint};
3use crate::sync::{Mutex, MutexGuard, RwLock, RwLockWriteGuard};
4use crate::{
5    change::Lamport,
6    container::{
7        idx::ContainerIdx,
8        list::list_op::{InnerListOp, ListOp},
9        map::MapSet,
10        ContainerID,
11    },
12    id::Counter,
13    op::{InnerContent, ListSlice, Op, RawOp, RawOpContent, SliceRange},
14    LoroValue,
15};
16use append_only_bytes::BytesSlice;
17use loro_common::PeerID;
18use rustc_hash::FxHashMap;
19use std::fmt;
20use std::{
21    num::NonZeroU16,
22    ops::{Range, RangeBounds},
23    sync::Arc,
24};
25
26pub(crate) struct LoadAllFlag;
27type ParentResolver = dyn Fn(ContainerID) -> Option<ContainerID> + Send + Sync + 'static;
28
29#[derive(Default)]
30struct ArenaContainers {
31    container_idx_to_id: Vec<ContainerID>,
32    /// Cached container depth. `None` means unknown or waiting on an unknown parent depth.
33    /// Use `get_depth()` for authoritative reads; direct access is only a cache fast path.
34    depth: Vec<Option<NonZeroU16>>,
35    container_id_to_idx: FxHashMap<ContainerID, ContainerIdx>,
36    /// The parent of each container.
37    parents: FxHashMap<ContainerIdx, Option<ContainerIdx>>,
38    /// All retention roots: top-level user roots **and** mergeable cids. Used by
39    /// alive-container / shallow-snapshot retention walks that must see both.
40    root_c_idx: Vec<ContainerIdx>,
41    /// Subset of `root_c_idx` containing only top-level (non-mergeable) roots. This is the
42    /// list user-facing APIs enumerate (`preferred_root_containers`, `get_value`,
43    /// `get_deep_value`, jsonpath, ...). Keeping it pre-filtered means those paths do
44    /// not pay a per-mergeable `is_mergeable()` parse on every call.
45    top_level_root_c_idx: Vec<ContainerIdx>,
46    /// Optional resolver used when querying parent for a container that has not been registered yet.
47    /// If set, `get_parent` will try this resolver to lazily fetch and register the parent.
48    ///
49    /// Locking: the resolver may read the state KV store. Code that loads from KV must snapshot
50    /// KV data and release the KV lock before taking the arena lock.
51    parent_resolver: Option<Arc<ParentResolver>>,
52}
53
54#[derive(Default)]
55struct InnerSharedArena {
56    // Container metadata is a single consistency domain. Keep it under one
57    // mutex so container id/index/parent/depth updates cannot acquire locks in
58    // inconsistent orders.
59    containers: RwLock<ArenaContainers>,
60    values: Mutex<Vec<LoroValue>>,
61    str: Arc<Mutex<StrArena>>,
62}
63
64impl fmt::Debug for InnerSharedArena {
65    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66        f.debug_struct("InnerSharedArena")
67            .field("containers", &"<Mutex<_>>")
68            .field("values", &"<Mutex<_>>")
69            .field("str", &"<Arc<Mutex<_>>>")
70            .finish()
71    }
72}
73
74/// This is shared between [OpLog] and [AppState].
75///
76#[derive(Debug, Clone)]
77pub struct SharedArena {
78    inner: Arc<InnerSharedArena>,
79}
80
81pub(crate) struct SharedArenaRollback {
82    container_len: usize,
83    root_len: usize,
84    top_level_root_len: usize,
85    values_len: usize,
86    str: StrArenaCheckpoint,
87}
88
89#[derive(Debug)]
90pub struct StrAllocResult {
91    /// unicode start
92    pub start: usize,
93    /// unicode end
94    pub end: usize,
95}
96
97impl ArenaContainers {
98    /// Add a freshly-registered cid to the retention-root tracking vectors.
99    ///
100    /// Centralizes the `root_c_idx ⊇ top_level_root_c_idx` invariant: `root_c_idx` is the set of
101    /// retention roots (top-level user roots **and** mergeable cids) that seed the alive-walk for
102    /// shallow snapshot; `top_level_root_c_idx` is the user-visible subset enumerated by
103    /// `preferred_root_containers` etc. Every push to either vector should go through this method,
104    /// so the invariant is maintained by construction rather than by remembering to push to two
105    /// vectors at every call site.
106    fn push_root(&mut self, idx: ContainerIdx, is_mergeable: bool) {
107        self.root_c_idx.push(idx);
108        if !is_mergeable {
109            self.top_level_root_c_idx.push(idx);
110        }
111    }
112
113    fn register_container(&mut self, id: &ContainerID) -> ContainerIdx {
114        if let Some(&idx) = self.container_id_to_idx.get(id) {
115            return idx;
116        }
117
118        let idx = self.container_idx_to_id.len();
119        self.container_idx_to_id.push(id.clone());
120        let idx = ContainerIdx::from_index_and_type(idx as u32, id.container_type());
121        self.container_id_to_idx.insert(id.clone(), idx);
122        // Resolve the cid's kind once. `is_mergeable` is non-trivial for mergeable names,
123        // so we avoid calling it twice on the same id.
124        let mergeable_parts = if id.is_root() {
125            id.parse_mergeable()
126        } else {
127            None
128        };
129        match (id.is_root(), mergeable_parts) {
130            (true, None) => {
131                self.push_root(idx, false);
132                self.parents.insert(idx, None);
133                self.depth.push(NonZeroU16::new(1));
134            }
135            (true, Some((parent_id, _key, _kind))) => {
136                // Mergeable Roots are retention roots AND logical children. Push to `root_c_idx`
137                // so shallow snapshot's `retain_keys` does not GC the loser of a concurrent-kind
138                // conflict (whose state is reachable only by its deterministic cid, not through
139                // the parent marker); then `set_parent` for path/event resolution. They are
140                // deliberately absent from `top_level_root_c_idx` so user-facing root
141                // enumeration does not have to filter them out.
142                self.push_root(idx, true);
143                self.depth.push(None);
144                let parent_idx = self.register_container(&parent_id);
145                self.set_parent(idx, Some(parent_idx));
146            }
147            (false, _) => {
148                self.depth.push(None);
149            }
150        }
151        idx
152    }
153
154    fn set_parent(&mut self, child: ContainerIdx, parent: Option<ContainerIdx>) {
155        self.parents.insert(child, parent);
156
157        match parent {
158            Some(p) => {
159                // Keep parent linking side-effect free. Calling `get_depth()` here may invoke the
160                // lazy parent resolver, which can acquire the state KV lock. Import/load paths may
161                // be assembling parent edges from KV snapshots, so resolving depth here would make
162                // arena -> KV and KV -> arena lock orders coexist.
163                if let Some(d) = self.depth[p.to_index() as usize] {
164                    self.depth[child.to_index() as usize] = NonZeroU16::new(d.get() + 1);
165                } else {
166                    self.depth[child.to_index() as usize] = None;
167                }
168            }
169            None => {
170                self.depth[child.to_index() as usize] = NonZeroU16::new(1);
171            }
172        }
173    }
174
175    fn get_depth(&mut self, target: ContainerIdx) -> Option<NonZeroU16> {
176        if let Some(d) = self.depth[target.to_index() as usize] {
177            return Some(d);
178        }
179
180        let parent: Option<ContainerIdx> = if let Some(p) = self.parents.get(&target) {
181            *p
182        } else {
183            let id = self
184                .container_idx_to_id
185                .get(target.to_index() as usize)
186                .unwrap()
187                .clone();
188            if id.is_root() && !id.is_mergeable() {
189                None
190            } else {
191                // Mergeable Roots get registered via the main `register_container` path, which
192                // already wires their parent edge — they should never reach here with a missing
193                // `parents` entry. But for ordinary children whose parent isn't in the arena
194                // yet, fall back to the resolver.
195                let resolver = self.parent_resolver.clone()?;
196                let parent_id = resolver(id)?;
197                // Route through `register_container` so a freshly-discovered parent (especially
198                // a mergeable cid) lands in `root_c_idx`, gets its own parent edge wired up,
199                // and has `parents` initialized — instead of being half-registered with just
200                // `idx_to_id` / `id_to_idx` / `depth` and silently missing from retention walks.
201                let parent_idx =
202                    if let Some(idx) = self.container_id_to_idx.get(&parent_id).copied() {
203                        // Already in the arena. For a top-level root that was hand-registered
204                        // somewhere else without a `parents` entry, ensure it has one.
205                        if parent_id.is_root() && !parent_id.is_mergeable() {
206                            self.parents.entry(idx).or_insert(None);
207                            if self.depth[idx.to_index() as usize].is_none() {
208                                self.depth[idx.to_index() as usize] = NonZeroU16::new(1);
209                            }
210                        }
211                        idx
212                    } else {
213                        self.register_container(&parent_id)
214                    };
215
216                Some(parent_idx)
217            }
218        };
219
220        let d = match parent {
221            Some(p) => NonZeroU16::new(self.get_depth(p)?.get() + 1),
222            None => NonZeroU16::new(1),
223        };
224        self.depth[target.to_index() as usize] = d;
225        d
226    }
227
228    fn container_id(&self, idx: ContainerIdx) -> Option<ContainerID> {
229        self.container_idx_to_id
230            .get(idx.to_index() as usize)
231            .cloned()
232    }
233}
234
235pub(crate) struct ArenaGuards<'a> {
236    containers: RwLockWriteGuard<'a, ArenaContainers>,
237}
238
239impl ArenaGuards<'_> {
240    pub fn register_container(&mut self, id: &ContainerID) -> ContainerIdx {
241        self.containers.register_container(id)
242    }
243
244    pub fn set_parent(&mut self, child: ContainerIdx, parent: Option<ContainerIdx>) {
245        self.containers.set_parent(child, parent);
246    }
247}
248
249impl SharedArena {
250    #[allow(clippy::new_without_default)]
251    pub fn new() -> Self {
252        Self {
253            inner: Arc::new(InnerSharedArena::default()),
254        }
255    }
256
257    pub fn fork(&self) -> Self {
258        Self {
259            inner: Arc::new(InnerSharedArena {
260                containers: RwLock::new({
261                    let containers = self.inner.containers.read();
262                    ArenaContainers {
263                        container_idx_to_id: containers.container_idx_to_id.clone(),
264                        depth: containers.depth.clone(),
265                        container_id_to_idx: containers.container_id_to_idx.clone(),
266                        parents: containers.parents.clone(),
267                        root_c_idx: containers.root_c_idx.clone(),
268                        top_level_root_c_idx: containers.top_level_root_c_idx.clone(),
269                        parent_resolver: containers.parent_resolver.clone(),
270                    }
271                }),
272                values: Mutex::new(self.inner.values.lock().clone()),
273                str: self.inner.str.clone(),
274            }),
275        }
276    }
277
278    pub(crate) fn checkpoint_for_rollback(&self) -> SharedArenaRollback {
279        let containers = self.inner.containers.read();
280        let container_len = containers.container_idx_to_id.len();
281        let root_len = containers.root_c_idx.len();
282        let top_level_root_len = containers.top_level_root_c_idx.len();
283        drop(containers);
284        let values_len = self.inner.values.lock().len();
285        let str = self.inner.str.lock().checkpoint();
286        SharedArenaRollback {
287            container_len,
288            root_len,
289            top_level_root_len,
290            values_len,
291            str,
292        }
293    }
294
295    pub(crate) fn rollback(&self, checkpoint: SharedArenaRollback) {
296        let mut containers = self.inner.containers.write();
297        let removed_ids = containers
298            .container_idx_to_id
299            .split_off(checkpoint.container_len);
300        for id in removed_ids {
301            containers.container_id_to_idx.remove(&id);
302        }
303        containers.depth.truncate(checkpoint.container_len);
304        containers.root_c_idx.truncate(checkpoint.root_len);
305        containers
306            .top_level_root_c_idx
307            .truncate(checkpoint.top_level_root_len);
308        containers.parents.retain(|child, parent| {
309            let child_is_kept = (child.to_index() as usize) < checkpoint.container_len;
310            let parent_is_kept = parent
311                .map(|p| (p.to_index() as usize) < checkpoint.container_len)
312                .unwrap_or(true);
313            child_is_kept && parent_is_kept
314        });
315        drop(containers);
316
317        self.inner.values.lock().truncate(checkpoint.values_len);
318        self.inner.str.lock().rollback(checkpoint.str);
319    }
320
321    pub(crate) fn with_guards(&self, f: impl FnOnce(&mut ArenaGuards)) {
322        let mut guards = self.get_arena_guards();
323        f(&mut guards);
324    }
325
326    fn get_arena_guards(&self) -> ArenaGuards<'_> {
327        ArenaGuards {
328            containers: self.inner.containers.write(),
329        }
330    }
331
332    pub fn register_container(&self, id: &ContainerID) -> ContainerIdx {
333        self.inner.containers.write().register_container(id)
334    }
335
336    pub fn get_container_id(&self, idx: ContainerIdx) -> Option<ContainerID> {
337        self.inner.containers.read().container_id(idx)
338    }
339
340    /// Fast map from `ContainerID` to `ContainerIdx` for containers already registered
341    /// in the arena.
342    ///
343    /// Important: This is not an existence check. Absence here does not imply that a
344    /// container does not exist, since registration can be lazy and containers may
345    /// be persisted only in the state KV store until first use.
346    ///
347    /// For existence-aware lookup that consults persisted state and performs lazy
348    /// registration, prefer `DocState::resolve_idx`.
349    pub fn id_to_idx(&self, id: &ContainerID) -> Option<ContainerIdx> {
350        self.inner
351            .containers
352            .read()
353            .container_id_to_idx
354            .get(id)
355            .copied()
356    }
357
358    #[inline]
359    pub fn idx_to_id(&self, id: ContainerIdx) -> Option<ContainerID> {
360        self.inner.containers.read().container_id(id)
361    }
362
363    #[inline]
364    pub fn with_idx_to_id<R>(&self, f: impl FnOnce(&Vec<ContainerID>) -> R) -> R {
365        let containers = self.inner.containers.read();
366        f(&containers.container_idx_to_id)
367    }
368
369    pub fn alloc_str(&self, str: &str) -> StrAllocResult {
370        let mut text_lock = self.inner.str.lock();
371        _alloc_str(&mut text_lock, str)
372    }
373
374    /// return slice and unicode index
375    pub fn alloc_str_with_slice(&self, str: &str) -> (BytesSlice, StrAllocResult) {
376        let mut text_lock = self.inner.str.lock();
377        _alloc_str_with_slice(&mut text_lock, str)
378    }
379
380    /// alloc str without extra info
381    pub fn alloc_str_fast(&self, bytes: &[u8]) {
382        let mut text_lock = self.inner.str.lock();
383        text_lock.alloc(std::str::from_utf8(bytes).unwrap());
384    }
385
386    #[inline]
387    pub fn utf16_len(&self) -> usize {
388        self.inner.str.lock().len_utf16()
389    }
390
391    #[inline]
392    pub fn alloc_value(&self, value: LoroValue) -> usize {
393        let mut values_lock = self.inner.values.lock();
394        _alloc_value(&mut values_lock, value)
395    }
396
397    #[inline]
398    pub fn alloc_values(&self, values: impl Iterator<Item = LoroValue>) -> std::ops::Range<usize> {
399        let mut values_lock = self.inner.values.lock();
400        _alloc_values(&mut values_lock, values)
401    }
402
403    #[inline]
404    pub fn set_parent(&self, child: ContainerIdx, parent: Option<ContainerIdx>) {
405        self.inner.containers.write().set_parent(child, parent);
406    }
407
408    pub fn log_hierarchy(&self) {
409        if cfg!(debug_assertions) {
410            let containers = self.inner.containers.read();
411            for (c, p) in containers.parents.iter() {
412                tracing::info!(
413                    "container {:?} {:?} {:?}",
414                    c,
415                    containers.container_id(*c),
416                    p.and_then(|x| containers.container_id(x))
417                );
418            }
419        }
420    }
421
422    pub fn log_all_containers(&self) {
423        let containers = self.inner.containers.read();
424        containers.container_id_to_idx.iter().for_each(|(id, idx)| {
425            tracing::info!("container {:?} {:?}", id, idx);
426        });
427        containers
428            .container_idx_to_id
429            .iter()
430            .enumerate()
431            .for_each(|(i, id)| {
432                tracing::info!("container {} {:?}", i, id);
433            });
434    }
435
436    pub fn get_parent(&self, child: ContainerIdx) -> Option<ContainerIdx> {
437        let (child_id, resolver) = {
438            let containers = self.inner.containers.read();
439            let child_id = containers.container_id(child).unwrap();
440            if child_id.is_root() && !child_id.is_mergeable() {
441                // TODO: PERF: we can speed this up by use a special bit in ContainerIdx to indicate
442                // whether the target is a root container
443                return None;
444            }
445
446            // Try fast path first
447            if let Some(p) = containers.parents.get(&child).copied() {
448                return p;
449            }
450
451            // Fallback: try to resolve parent lazily via the resolver if provided.
452            (child_id, containers.parent_resolver.clone())
453        };
454        if let Some(resolver) = resolver {
455            if let Some(parent_id) = resolver(child_id.clone()) {
456                let parent_idx = self.register_container(&parent_id);
457                self.set_parent(child, Some(parent_idx));
458                return Some(parent_idx);
459            }
460        }
461
462        panic!("InternalError: Parent is not registered")
463    }
464
465    /// Return the parent edge already stored in the arena without invoking the lazy resolver.
466    ///
467    /// The outer `Option` distinguishes an unregistered edge from a registered root edge.
468    pub(crate) fn get_registered_parent(
469        &self,
470        child: ContainerIdx,
471    ) -> Option<Option<ContainerIdx>> {
472        self.inner.containers.read().parents.get(&child).copied()
473    }
474
475    /// Call `f` on each ancestor of `container`, including `container` itself.
476    ///
477    /// f(ContainerIdx, is_first)
478    pub fn with_ancestors(&self, container: ContainerIdx, mut f: impl FnMut(ContainerIdx, bool)) {
479        let mut container = Some(container);
480        let mut is_first = true;
481        while let Some(c) = container {
482            f(c, is_first);
483            is_first = false;
484            container = self.get_parent(c)
485        }
486    }
487
488    #[inline]
489    pub fn slice_by_unicode(&self, range: impl RangeBounds<usize>) -> BytesSlice {
490        self.inner.str.lock().slice_by_unicode(range)
491    }
492
493    #[inline]
494    pub fn slice_by_utf8(&self, range: impl RangeBounds<usize>) -> BytesSlice {
495        self.inner.str.lock().slice_bytes(range)
496    }
497
498    #[inline]
499    pub fn slice_str_by_unicode_range(&self, range: Range<usize>) -> String {
500        let mut s = self.inner.str.lock();
501        let s: &mut StrArena = &mut s;
502        let mut ans = String::with_capacity(range.len());
503        ans.push_str(s.slice_str_by_unicode(range));
504        ans
505    }
506
507    #[inline]
508    pub fn with_text_slice(&self, range: Range<usize>, mut f: impl FnMut(&str)) {
509        f(self.inner.str.lock().slice_str_by_unicode(range))
510    }
511
512    #[inline]
513    pub fn get_value(&self, idx: usize) -> Option<LoroValue> {
514        self.inner.values.lock().get(idx).cloned()
515    }
516
517    #[inline]
518    pub fn get_values(&self, range: Range<usize>) -> Vec<LoroValue> {
519        (self.inner.values.lock()[range]).to_vec()
520    }
521
522    /// Borrow the values in `range` without cloning them (unlike
523    /// [`Self::get_values`], which clones into a fresh `Vec`).
524    #[inline]
525    pub fn with_values<R>(&self, range: Range<usize>, f: impl FnOnce(&[LoroValue]) -> R) -> R {
526        f(&self.inner.values.lock()[range])
527    }
528
529    pub fn convert_single_op(
530        &self,
531        container: &ContainerID,
532        peer: PeerID,
533        counter: Counter,
534        lamport: Lamport,
535        content: RawOpContent,
536    ) -> Op {
537        let container = self.register_container(container);
538        self.inner_convert_op(content, peer, counter, lamport, container)
539    }
540
541    pub fn can_import_snapshot(&self) -> bool {
542        let str_empty = self.inner.str.lock().is_empty();
543        let values_empty = self.inner.values.lock().is_empty();
544        str_empty && values_empty
545    }
546
547    fn inner_convert_op(
548        &self,
549        content: RawOpContent<'_>,
550        _peer: PeerID,
551        counter: i32,
552        _lamport: Lamport,
553        container: ContainerIdx,
554    ) -> Op {
555        match content {
556            crate::op::RawOpContent::Map(MapSet { key, value }) => Op {
557                counter,
558                container,
559                content: crate::op::InnerContent::Map(MapSet { key, value }),
560            },
561            crate::op::RawOpContent::List(list) => match list {
562                ListOp::Insert { slice, pos } => match slice {
563                    ListSlice::RawData(values) => {
564                        let range = self.alloc_values(values.iter().cloned());
565                        Op {
566                            counter,
567                            container,
568                            content: crate::op::InnerContent::List(InnerListOp::Insert {
569                                slice: SliceRange::from(range.start as u32..range.end as u32),
570                                pos,
571                            }),
572                        }
573                    }
574                    ListSlice::RawStr { str, unicode_len } => {
575                        let (slice, info) = self.alloc_str_with_slice(&str);
576                        Op {
577                            counter,
578                            container,
579                            content: crate::op::InnerContent::List(InnerListOp::InsertText {
580                                slice,
581                                unicode_start: info.start as u32,
582                                unicode_len: unicode_len as u32,
583                                pos: pos as u32,
584                            }),
585                        }
586                    }
587                },
588                ListOp::Delete(span) => Op {
589                    counter,
590                    container,
591                    content: crate::op::InnerContent::List(InnerListOp::Delete(span)),
592                },
593                ListOp::StyleStart {
594                    start,
595                    end,
596                    info,
597                    key,
598                    value,
599                } => Op {
600                    counter,
601                    container,
602                    content: InnerContent::List(InnerListOp::StyleStart {
603                        start,
604                        end,
605                        key,
606                        info,
607                        value,
608                    }),
609                },
610                ListOp::StyleEnd => Op {
611                    counter,
612                    container,
613                    content: InnerContent::List(InnerListOp::StyleEnd),
614                },
615                ListOp::Move {
616                    from,
617                    to,
618                    elem_id: from_id,
619                } => Op {
620                    counter,
621                    container,
622                    content: InnerContent::List(InnerListOp::Move {
623                        from,
624                        to,
625                        elem_id: from_id,
626                    }),
627                },
628                ListOp::Set { elem_id, value } => Op {
629                    counter,
630                    container,
631                    content: InnerContent::List(InnerListOp::Set { elem_id, value }),
632                },
633            },
634            crate::op::RawOpContent::Tree(tree) => Op {
635                counter,
636                container,
637                content: crate::op::InnerContent::Tree(tree.clone()),
638            },
639            #[cfg(feature = "counter")]
640            crate::op::RawOpContent::Counter(c) => Op {
641                counter,
642                container,
643                content: crate::op::InnerContent::Future(crate::op::FutureInnerContent::Counter(c)),
644            },
645            crate::op::RawOpContent::Unknown { prop, value } => Op {
646                counter,
647                container,
648                content: crate::op::InnerContent::Future(crate::op::FutureInnerContent::Unknown {
649                    prop,
650                    value: Box::new(value),
651                }),
652            },
653        }
654    }
655
656    #[inline]
657    pub fn convert_raw_op(&self, op: &RawOp) -> Op {
658        self.inner_convert_op(
659            op.content.clone(),
660            op.id.peer,
661            op.id.counter,
662            op.lamport,
663            op.container,
664        )
665    }
666
667    #[inline]
668    pub fn export_containers(&self) -> Vec<ContainerID> {
669        self.inner.containers.read().container_idx_to_id.clone()
670    }
671
672    pub fn export_parents(&self) -> Vec<Option<ContainerIdx>> {
673        let containers = self.inner.containers.read();
674        containers
675            .container_idx_to_id
676            .iter()
677            .enumerate()
678            .map(|(x, id)| {
679                let idx = ContainerIdx::from_index_and_type(x as u32, id.container_type());
680                let parent_idx = containers.parents.get(&idx)?;
681                *parent_idx
682            })
683            .collect()
684    }
685
686    /// Returns all the possible root containers of the docs
687    ///
688    /// We need to load all the cached kv in DocState before we can ensure all root contains are covered.
689    /// So we need the flag type here.
690    ///
691    /// This includes mergeable cids (which are also retention roots). Callers that only want
692    /// user-visible top-level roots should use [`Self::top_level_root_containers`].
693    #[inline]
694    pub(crate) fn root_containers(&self, _f: LoadAllFlag) -> Vec<ContainerIdx> {
695        self.inner.containers.read().root_c_idx.clone()
696    }
697
698    /// Returns only the user-visible top-level root containers (excludes mergeable cids).
699    ///
700    /// Used by `preferred_root_containers`, `get_value` / `get_deep_value`, jsonpath, etc. —
701    /// any path that enumerates the doc's top-level roots. Pre-filtering at registration time
702    /// keeps these calls O(top_level_roots) instead of O(top_level_roots + mergeable_cids).
703    #[inline]
704    pub(crate) fn top_level_root_containers(&self, _f: LoadAllFlag) -> Vec<ContainerIdx> {
705        self.inner.containers.read().top_level_root_c_idx.clone()
706    }
707
708    // TODO: this can return a u16 directly now, since the depths are always valid
709    pub(crate) fn get_depth(&self, container: ContainerIdx) -> Option<NonZeroU16> {
710        self.inner.containers.write().get_depth(container)
711    }
712
713    pub(crate) fn iter_value_slice(
714        &self,
715        range: Range<usize>,
716    ) -> impl Iterator<Item = LoroValue> + '_ {
717        let values = self.inner.values.lock();
718        range
719            .into_iter()
720            .map(move |i| values.get(i).unwrap().clone())
721    }
722
723    #[allow(unused)]
724    pub(crate) fn log_all_values(&self) {
725        let values = self.inner.values.lock();
726        for (i, v) in values.iter().enumerate() {
727            loro_common::debug!("value {} {:?}", i, v);
728        }
729    }
730}
731
732fn _alloc_str_with_slice(
733    text_lock: &mut MutexGuard<'_, StrArena>,
734    str: &str,
735) -> (BytesSlice, StrAllocResult) {
736    let start = text_lock.len_bytes();
737    let ans = _alloc_str(text_lock, str);
738    (text_lock.slice_bytes(start..), ans)
739}
740
741fn _alloc_values(
742    values_lock: &mut MutexGuard<'_, Vec<LoroValue>>,
743    values: impl Iterator<Item = LoroValue>,
744) -> Range<usize> {
745    values_lock.reserve(values.size_hint().0);
746    let start = values_lock.len();
747    for value in values {
748        values_lock.push(value);
749    }
750
751    start..values_lock.len()
752}
753
754fn _alloc_value(values_lock: &mut MutexGuard<'_, Vec<LoroValue>>, value: LoroValue) -> usize {
755    values_lock.push(value);
756    values_lock.len() - 1
757}
758
759fn _alloc_str(text_lock: &mut MutexGuard<'_, StrArena>, str: &str) -> StrAllocResult {
760    let start = text_lock.len_unicode();
761    text_lock.alloc(str);
762    StrAllocResult {
763        start,
764        end: text_lock.len_unicode(),
765    }
766}
767
768fn _slice_str(range: Range<usize>, s: &mut StrArena) -> String {
769    let mut ans = String::with_capacity(range.len());
770    ans.push_str(s.slice_str_by_unicode(range));
771    ans
772}
773
774impl SharedArena {
775    /// Register or clear a resolver to lazily determine a container's parent when missing.
776    ///
777    /// - The resolver receives the child `ContainerIdx` and returns an optional `ContainerID` of its parent.
778    /// - If the resolver returns `Some`, `SharedArena` will register the parent in the arena and link it.
779    /// - If the resolver is `None` or returns `None`, `get_parent` will panic for non-root containers as before.
780    pub fn set_parent_resolver<F>(&self, resolver: Option<F>)
781    where
782        F: Fn(ContainerID) -> Option<ContainerID> + Send + Sync + 'static,
783    {
784        self.inner.containers.write().parent_resolver =
785            resolver.map(|f| Arc::new(f) as Arc<ParentResolver>);
786    }
787}
788
789#[cfg(test)]
790mod tests {
791    use super::*;
792    use loro_common::ContainerType;
793    use std::sync::{
794        atomic::{AtomicBool, Ordering},
795        Arc,
796    };
797
798    /// When a non-mergeable child is registered, then later asks for its depth and the parent
799    /// resolver yields a mergeable cid, the mergeable parent must be registered through the
800    /// retention-root path (push to `root_c_idx`, recursively link its own parent) — exactly the
801    /// same way the main `register_container` path would handle a freshly-encountered mergeable
802    /// cid. Without this, shallow snapshot's alive-walk would silently GC the mergeable parent's
803    /// state.
804    #[test]
805    fn get_depth_resolver_registers_mergeable_parent_as_retention_root() {
806        let arena = SharedArena::new();
807
808        let top_root = ContainerID::new_root("state", ContainerType::Map);
809        let mergeable_parent = ContainerID::new_mergeable(&top_root, "profile", ContainerType::Map);
810        let child = ContainerID::new_normal(loro_common::ID::new(1, 0), ContainerType::List);
811
812        let mergeable_parent_for_resolver = mergeable_parent.clone();
813        let child_for_resolver = child.clone();
814        arena.set_parent_resolver(Some(move |q: ContainerID| {
815            if q == child_for_resolver {
816                Some(mergeable_parent_for_resolver.clone())
817            } else {
818                None
819            }
820        }));
821
822        let child_idx = arena.register_container(&child);
823        let _ = arena.get_depth(child_idx);
824
825        let flag = LoadAllFlag;
826        let mergeable_idx = arena
827            .id_to_idx(&mergeable_parent)
828            .expect("resolver should have registered the mergeable parent");
829        assert!(
830            arena.root_containers(flag).contains(&mergeable_idx),
831            "mergeable parent discovered via resolver must be a retention root"
832        );
833
834        let flag = LoadAllFlag;
835        assert!(
836            !arena
837                .top_level_root_containers(flag)
838                .contains(&mergeable_idx),
839            "mergeable parent must not appear in user-facing top-level enumeration"
840        );
841
842        let flag = LoadAllFlag;
843        let top_root_idx = arena
844            .id_to_idx(&top_root)
845            .expect("mergeable parent's own grandparent must also be registered");
846        assert!(
847            arena
848                .top_level_root_containers(flag)
849                .contains(&top_root_idx),
850            "the mergeable parent's grandparent (a top-level root) must be in the top-level list"
851        );
852    }
853
854    #[test]
855    fn set_parent_does_not_resolve_missing_parent_depth() {
856        let arena = SharedArena::new();
857        let parent = ContainerID::new_normal(loro_common::ID::new(1, 0), ContainerType::Map);
858        let child = ContainerID::new_mergeable(&parent, "field", ContainerType::Text);
859        let resolver_called = Arc::new(AtomicBool::new(false));
860        let called = resolver_called.clone();
861        arena.set_parent_resolver(Some(move |_| {
862            called.store(true, Ordering::SeqCst);
863            None
864        }));
865
866        arena.register_container(&child);
867
868        assert!(
869            !resolver_called.load(Ordering::SeqCst),
870            "registering a mergeable child must not invoke the lazy parent resolver"
871        );
872    }
873}