Skip to main content

indigo_kernel/
crdt_vibed_length.rs

1//! Length-based FugueMax CRDT.
2//!
3//! Unlike [`crate::crdt_vibed_not_length`], the CRDT here stores no text at all — only
4//! byte lengths and byte positions. Local edits return
5//! [`Insertion`]/[`Deletion`] values to send to other replicas, and
6//! integrating a remote op returns [`BufferEdit`]s telling the caller what to
7//! do to its own text buffer. Note \[Spans\]
8//!
9//! ## References:
10//!
11//! - The FugueMax paper: <https://arxiv.org/abs/2305.00583>
12//! - Joseph Gentle's simple FugueMax implementation: <https://github.com/josephg/crdt-from-scratch>
13//! - cola, whose length-based, operation-based API this module borrows
14//!   (but not its RGA-style ordering): <https://github.com/nomad/cola>
15
16#![expect(clippy::doc_markdown)] // Clippy thinks "FugueMax" is missing backticks
17
18use std::{num::NonZeroU32, ops::Range};
19
20/// Widens a stored `u32` to a spatial `usize`; infallible on the platforms
21/// indigo supports.
22fn wide(bytes: u32) -> usize {
23    usize::try_from(bytes).expect("usize is at least 32 bits")
24}
25
26#[derive(Clone)]
27pub struct Replica {
28    id: ReplicaId,
29    spans: Vec<Span>,
30    version: Version,
31    // Note [Pending ops]
32    pending_insertions: Vec<Insertion>,
33    pending_deletions: Vec<Deletion>,
34}
35
36// Public API
37impl Replica {
38    /// The id must be unique among replicas of the same document. The initial
39    /// content is credited to this replica's clock, so replicas joining an
40    /// existing session must [`fork`](Self::fork) one that's already in it
41    /// rather than call `new` themselves.
42    #[must_use]
43    pub fn new(id: NonZeroU32, initial_length: usize) -> Self {
44        let mut replica = Self {
45            id: ReplicaId(id),
46            spans: Vec::new(),
47            version: Version::default(),
48            pending_insertions: Vec::new(),
49            pending_deletions: Vec::new(),
50        };
51        replica
52            .inserted(0, initial_length)
53            .expect("byte offset 0 is valid and the initial length fits the clock");
54        replica
55    }
56
57    /// A copy of this replica that authors its own edits under a new id.
58    #[must_use]
59    pub fn fork(&self, new_id: NonZeroU32) -> Self {
60        Self {
61            id: ReplicaId(new_id),
62            ..self.clone()
63        }
64    }
65
66    /// The visible length of the document, in bytes.
67    #[must_use]
68    pub fn len(&self) -> usize {
69        self.spans.iter().map(Span::visible_length).sum()
70    }
71
72    #[must_use]
73    pub fn is_empty(&self) -> bool {
74        self.len() == 0
75    }
76
77    /// Informs the replica that `length` bytes were inserted at the given
78    /// visible offset in the caller's buffer, returning the op to send to the
79    /// other replicas.
80    pub fn inserted(&mut self, byte_offset: usize, length: usize) -> anyhow::Result<Insertion> {
81        let Ok(length) = u32::try_from(length) else {
82            anyhow::bail!("insertion length {length} exceeds the u32 byte clock");
83        };
84        let byte_index = self.version.count(self.id);
85        if byte_index.checked_add(length).is_none() {
86            anyhow::bail!("inserting {length} bytes would overflow the u32 byte clock");
87        }
88
89        // Find the gap in `spans` at the given offset, splitting the span it
90        // lands inside. When deleted spans straddle the position, insert to
91        // their left.
92        let mut remaining = byte_offset;
93        let mut index = 0;
94        while remaining > 0 {
95            let Some(span) = self.spans.get(index) else {
96                anyhow::bail!("byte offset {byte_offset} is past the end of the document");
97            };
98            if !span.deleted {
99                if remaining < wide(span.length) {
100                    let at = u32::try_from(remaining).expect("fits in a span");
101                    let right = self.spans[index].split(at);
102                    self.spans.insert(index + 1, right);
103                    index += 1;
104                    break;
105                }
106                remaining -= wide(span.length);
107            }
108            index += 1;
109        }
110
111        let op = Insertion {
112            start: ByteId {
113                replica: self.id,
114                byte_index,
115            },
116            length,
117            origin_left: index.checked_sub(1).map(|left| self.spans[left].last_id()),
118            origin_right: self.spans.get(index).map(|right| right.id),
119        };
120        self.version.record(op.start, op.length);
121        // The origins are the physical neighbors, so no integration scan is
122        // needed: the span lands exactly where the caller put it, extending
123        // its predecessor when it simply continues it. Note [Integration scan]
124        if length > 0 {
125            match index.checked_sub(1) {
126                Some(left) if self.spans[left].can_append(&op) => {
127                    self.spans[left].length += length;
128                }
129                _ => self.spans.insert(index, Span::of(&op)),
130            }
131        }
132        Ok(op)
133    }
134
135    /// Informs the replica that the given visible byte range was deleted from
136    /// the caller's buffer, returning the op to send to the other replicas.
137    /// Note [Deletion by id]
138    pub fn deleted(&mut self, byte_range: Range<usize>) -> anyhow::Result<Deletion> {
139        if byte_range.start > byte_range.end || byte_range.end > self.len() {
140            anyhow::bail!("invalid byte range {byte_range:?}");
141        }
142        let mut targets = Vec::<Run>::new();
143        if byte_range.is_empty() {
144            return Ok(Deletion { targets });
145        }
146        let mut visible = 0;
147        let mut index = 0;
148        while index < self.spans.len() && visible < byte_range.end {
149            let span = &self.spans[index];
150            if span.deleted {
151                index += 1;
152                continue;
153            }
154            if visible + wide(span.length) <= byte_range.start {
155                visible += wide(span.length);
156                index += 1;
157                continue;
158            }
159            // The range starts mid-span: split off the head and revisit.
160            let head = byte_range.start.saturating_sub(visible);
161            if head > 0 {
162                let at = u32::try_from(head).expect("fits in a span");
163                let right = self.spans[index].split(at);
164                self.spans.insert(index + 1, right);
165                visible += head;
166                index += 1;
167                continue;
168            }
169            // The span starts inside the range: split off any tail past it,
170            // then the span is wholly covered.
171            let take = (byte_range.end - visible).min(wide(self.spans[index].length));
172            if take < wide(self.spans[index].length) {
173                let at = u32::try_from(take).expect("fits in a span");
174                let right = self.spans[index].split(at);
175                self.spans.insert(index + 1, right);
176            }
177            let span = &mut self.spans[index];
178            span.deleted = true;
179            match targets.last_mut() {
180                Some(run) if run.end() == span.id => run.length += span.length,
181                _ => targets.push(Run {
182                    start: span.id,
183                    length: span.length,
184                }),
185            }
186            visible += wide(span.length);
187            index += 1;
188        }
189        Ok(Deletion { targets })
190    }
191
192    /// Integrates another replica's [`Insertion`], returning the edits to
193    /// apply to the caller's buffer, in order: the op itself and any pending
194    /// ops it unblocked. Empty when the op is a duplicate, empty, or must wait
195    /// for its causal dependencies. Note [Pending ops]
196    #[must_use]
197    pub fn integrate_insertion(&mut self, op: &Insertion) -> Vec<BufferEdit> {
198        if op.length == 0 || self.has_applied(op) {
199            return Vec::new();
200        }
201        if !self.can_apply_insertion(op) {
202            self.pending_insertions.push(op.clone());
203            return Vec::new();
204        }
205        let mut edits = vec![self.apply_insertion(op)];
206        self.drain_pending(&mut edits);
207        edits
208    }
209
210    /// Integrates another replica's [`Deletion`], returning the edits to apply
211    /// to the caller's buffer. Empty when nothing new was deleted or the op
212    /// must wait for its causal dependencies. Note [Deletion by id]
213    #[must_use]
214    pub fn integrate_deletion(&mut self, op: &Deletion) -> Vec<BufferEdit> {
215        if !self.can_apply_deletion(op) {
216            self.pending_deletions.push(op.clone());
217            return Vec::new();
218        }
219        match self.apply_deletion(op) {
220            Some(edit) => vec![edit],
221            None => Vec::new(),
222        }
223    }
224}
225
226// Private API
227impl Replica {
228    fn span_containing(&self, id: ByteId) -> usize {
229        self.spans
230            .iter()
231            .position(|span| span.contains(id))
232            .expect("byte exists")
233    }
234
235    /// The position `id` would have if every span were expanded to one item
236    /// per byte. Note [Integration scan]
237    fn position_of(&self, id: ByteId) -> usize {
238        let mut position = 0;
239        for span in &self.spans {
240            if span.contains(id) {
241                return position + wide(id.byte_index - span.id.byte_index);
242            }
243            position += wide(span.length);
244        }
245        unreachable!("byte exists")
246    }
247
248    /// The expanded position one past every byte in the document.
249    fn expansion_len(&self) -> usize {
250        self.spans.iter().map(|span| wide(span.length)).sum()
251    }
252
253    /// The number of visible bytes before the span at `index`.
254    fn visible_prefix(&self, index: usize) -> usize {
255        self.spans[..index].iter().map(Span::visible_length).sum()
256    }
257
258    /// Splits so `id` is the last byte of its span, returning that span's
259    /// index.
260    fn split_after(&mut self, id: ByteId) -> usize {
261        let index = self.span_containing(id);
262        let keep = id.byte_index - self.spans[index].id.byte_index + 1;
263        if keep < self.spans[index].length {
264            let right = self.spans[index].split(keep);
265            self.spans.insert(index + 1, right);
266        }
267        index
268    }
269
270    /// Splits so `id` is the first byte of its span, returning that span's
271    /// index.
272    fn split_before(&mut self, id: ByteId) -> usize {
273        let index = self.span_containing(id);
274        let keep = id.byte_index - self.spans[index].id.byte_index;
275        if keep == 0 {
276            index
277        } else {
278            let right = self.spans[index].split(keep);
279            self.spans.insert(index + 1, right);
280            index + 1
281        }
282    }
283
284    /// Whether this insertion is already part of the document. Ops are atomic:
285    /// seeing the first byte means seeing them all.
286    fn has_applied(&self, op: &Insertion) -> bool {
287        self.version.contains(op.start)
288    }
289
290    /// Whether all of an insertion's causal dependencies are in this document:
291    /// its author's previous bytes, and both origins.
292    fn can_apply_insertion(&self, op: &Insertion) -> bool {
293        self.version.count(op.start.replica) == op.start.byte_index
294            && op.origin_left.is_none_or(|id| self.version.contains(id))
295            && op.origin_right.is_none_or(|id| self.version.contains(id))
296    }
297
298    /// Whether every byte a deletion targets is in this document.
299    fn can_apply_deletion(&self, op: &Deletion) -> bool {
300        op.targets
301            .iter()
302            .all(|run| self.version.contains(run.last()))
303    }
304
305    // Note [Integration scan]
306    fn apply_insertion(&mut self, op: &Insertion) -> BufferEdit {
307        let left = op.origin_left.map(|id| self.split_after(id));
308        let right = op
309            .origin_right
310            .map_or(self.spans.len(), |id| self.split_before(id));
311
312        let left_position = op.origin_left.map(|id| self.position_of(id));
313        let right_position = op
314            .origin_right
315            .map_or(self.expansion_len(), |id| self.position_of(id));
316        let mut destination = left.map_or(0, |index| index + 1);
317        let mut scanning = false;
318        for index in destination.. {
319            if !scanning {
320                destination = index;
321            }
322            if index == self.spans.len() || index == right {
323                break;
324            }
325            let other = &self.spans[index];
326            let other_left = other.origin_left.map(|id| self.position_of(id));
327            let other_right = other
328                .origin_right
329                .map_or(self.expansion_len(), |id| self.position_of(id));
330            if other_left < left_position
331                || (other_left == left_position
332                    && other_right == right_position
333                    && op.start.replica < other.id.replica)
334            {
335                break;
336            }
337            if other_left == left_position {
338                scanning = other_right < right_position;
339            }
340        }
341
342        self.version.record(op.start, op.length);
343        let byte_offset = self.visible_prefix(destination);
344        match destination.checked_sub(1) {
345            Some(previous) if self.spans[previous].can_append(op) => {
346                self.spans[previous].length += op.length;
347            }
348            _ => self.spans.insert(destination, Span::of(op)),
349        }
350        BufferEdit::Insert {
351            byte_offset,
352            author: op.start.replica,
353            bytes: op.bytes(),
354        }
355    }
356
357    /// Marks the targeted bytes deleted, reporting the visible ranges of the
358    /// bytes this call actually hid — in pre-deletion coordinates, ascending —
359    /// or `None` if every target was already dead. Note [Deletion by id]
360    fn apply_deletion(&mut self, op: &Deletion) -> Option<BufferEdit> {
361        let mut byte_ranges = Vec::<Range<usize>>::new();
362        let mut visible = 0;
363        let mut index = 0;
364        while index < self.spans.len() {
365            let span = &self.spans[index];
366            if span.deleted {
367                index += 1;
368                continue;
369            }
370            let Some((from, to)) = op.earliest_overlap(span) else {
371                visible += wide(span.length);
372                index += 1;
373                continue;
374            };
375            // The overlap starts mid-span: split off the head and revisit.
376            if from > 0 {
377                let right = self.spans[index].split(from);
378                self.spans.insert(index + 1, right);
379                visible += wide(from);
380                index += 1;
381                continue;
382            }
383            // The span starts inside the overlap: split off any tail past it,
384            // then the span is wholly covered. Any further overlaps with the
385            // tail are found when the walk reaches it.
386            if to < self.spans[index].length {
387                let right = self.spans[index].split(to);
388                self.spans.insert(index + 1, right);
389            }
390            let span = &mut self.spans[index];
391            span.deleted = true;
392            match byte_ranges.last_mut() {
393                Some(range) if range.end == visible => range.end += wide(span.length),
394                _ => byte_ranges.push(visible..visible + wide(span.length)),
395            }
396            visible += wide(span.length);
397            index += 1;
398        }
399        (!byte_ranges.is_empty()).then_some(BufferEdit::Delete { byte_ranges })
400    }
401
402    // Note [Pending ops]
403    fn drain_pending(&mut self, edits: &mut Vec<BufferEdit>) {
404        loop {
405            // An op delivered repeatedly while unready is stashed repeatedly;
406            // once one copy applies, the rest become duplicates. Drop them.
407            let version = &self.version;
408            self.pending_insertions
409                .retain(|op| !version.contains(op.start));
410
411            if let Some(index) = self
412                .pending_insertions
413                .iter()
414                .position(|op| self.can_apply_insertion(op))
415            {
416                let op = self.pending_insertions.swap_remove(index);
417                edits.push(self.apply_insertion(&op));
418            } else if let Some(index) = self
419                .pending_deletions
420                .iter()
421                .position(|op| self.can_apply_deletion(op))
422            {
423                let op = self.pending_deletions.swap_remove(index);
424                edits.extend(self.apply_deletion(&op));
425            } else {
426                return;
427            }
428        }
429    }
430}
431
432/// A replica's insertion of a run of bytes, in CRDT coordinates. Carries no
433/// text: the caller pairs the op with the inserted text, identified by
434/// [`author`](Self::author) and [`bytes`](Self::bytes).
435#[derive(Clone)]
436pub struct Insertion {
437    /// The first byte's id; the rest of the run follows it temporally.
438    start: ByteId,
439    length: u32,
440    /// The origin of the run's first byte.
441    origin_left: Option<ByteId>,
442    /// The origin shared by every byte in the run.
443    origin_right: Option<ByteId>,
444}
445
446impl Insertion {
447    /// The replica that authored this insertion.
448    #[must_use]
449    pub fn author(&self) -> ReplicaId {
450        self.start.replica
451    }
452
453    /// The temporal range of the inserted bytes in the author's byte clock.
454    /// Together with [`author`](Self::author) this uniquely identifies the
455    /// insertion — and therefore its text — across all replicas.
456    #[must_use]
457    pub fn bytes(&self) -> Range<usize> {
458        wide(self.start.byte_index)..wide(self.start.byte_index + self.length)
459    }
460}
461
462/// A replica's deletion of a set of bytes, in CRDT coordinates: the exact ids
463/// of the deleted bytes, as runs of temporally-consecutive ids.
464/// Note [Deletion by id]
465#[derive(Clone)]
466pub struct Deletion {
467    targets: Vec<Run>,
468}
469
470impl Deletion {
471    /// The earliest intra-span byte range covered by any of this deletion's
472    /// target runs, if any.
473    fn earliest_overlap(&self, span: &Span) -> Option<(u32, u32)> {
474        let mut earliest: Option<(u32, u32)> = None;
475        for run in &self.targets {
476            if run.start.replica != span.id.replica {
477                continue;
478            }
479            let from = run.start.byte_index.max(span.id.byte_index);
480            let to = (run.start.byte_index + run.length).min(span.id.byte_index + span.length);
481            if from < to {
482                let overlap = (from - span.id.byte_index, to - span.id.byte_index);
483                if earliest.is_none_or(|(start, _)| overlap.0 < start) {
484                    earliest = Some(overlap);
485                }
486            }
487        }
488        earliest
489    }
490}
491
492/// A run of bytes inserted consecutively by one replica.
493#[derive(Clone, Copy)]
494struct Run {
495    start: ByteId,
496    length: u32,
497}
498
499impl Run {
500    /// The id one past this run, i.e. where a continuation would start.
501    fn end(&self) -> ByteId {
502        self.start.plus(self.length)
503    }
504
505    fn last(&self) -> ByteId {
506        self.start.plus(self.length - 1)
507    }
508}
509
510/// An edit to apply to the text buffer paired with a [`Replica`]. Produced by
511/// the `integrate_*` methods; apply edits in the order given.
512#[derive(Debug)]
513pub enum BufferEdit {
514    /// Insert the text of the insertion identified by `author` and `bytes` at
515    /// this visible gap position.
516    Insert {
517        byte_offset: usize,
518        author: ReplicaId,
519        bytes: Range<usize>,
520    },
521    /// Remove these visible byte ranges, which are ascending and expressed in
522    /// pre-deletion coordinates: apply them back to front.
523    Delete { byte_ranges: Vec<Range<usize>> },
524}
525
526// The `NonZero` gives `Option<ByteId>` its niche, halving every origin field.
527#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Debug)]
528pub struct ReplicaId(NonZeroU32);
529
530#[derive(Clone, Copy, PartialEq)]
531struct ByteId {
532    replica: ReplicaId,
533    /// The value of the author's byte clock when the byte was inserted —
534    /// temporal, nothing to do with document position. Note [Spans]
535    byte_index: u32,
536}
537
538impl ByteId {
539    fn plus(self, bytes: u32) -> Self {
540        Self {
541            replica: self.replica,
542            byte_index: self.byte_index + bytes,
543        }
544    }
545}
546
547/// A run of contiguously authored document bytes (the caller knows which text
548/// they stand for; the replica never does). Note [Spans]
549#[derive(Clone)]
550struct Span {
551    /// The first byte's id.
552    id: ByteId,
553    /// Always at least 1.
554    length: u32,
555    /// The origin of the first byte; interior bytes implicitly chain to their
556    /// predecessor.
557    origin_left: Option<ByteId>,
558    /// The origin shared by every byte in the span.
559    origin_right: Option<ByteId>,
560    /// Whole-span: spans split before partial deletion.
561    deleted: bool,
562}
563
564impl Span {
565    fn of(op: &Insertion) -> Self {
566        Self {
567            id: op.start,
568            length: op.length,
569            origin_left: op.origin_left,
570            origin_right: op.origin_right,
571            deleted: false,
572        }
573    }
574
575    fn contains(&self, id: ByteId) -> bool {
576        self.id.replica == id.replica
577            && (self.id.byte_index..self.id.byte_index + self.length).contains(&id.byte_index)
578    }
579
580    /// The id one past this span's last byte, i.e. where a continuation
581    /// starts.
582    fn end_id(&self) -> ByteId {
583        self.id.plus(self.length)
584    }
585
586    fn last_id(&self) -> ByteId {
587        self.id.plus(self.length - 1)
588    }
589
590    fn visible_length(&self) -> usize {
591        if self.deleted { 0 } else { wide(self.length) }
592    }
593
594    /// Splits off and returns everything from `at` bytes in. Lossless: the
595    /// right half's derived origins are exactly the implicit origins its bytes
596    /// already had. Note [Spans]
597    fn split(&mut self, at: u32) -> Self {
598        debug_assert!(0 < at && at < self.length);
599        let right = Self {
600            id: self.id.plus(at),
601            length: self.length - at,
602            origin_left: Some(self.id.plus(at - 1)),
603            origin_right: self.origin_right,
604            deleted: self.deleted,
605        };
606        self.length = at;
607        right
608    }
609
610    /// Whether the op directly continues this span, i.e. appending it is
611    /// exactly what a later split would undo. Note [Spans]
612    fn can_append(&self, op: &Insertion) -> bool {
613        !self.deleted
614            && self.end_id() == op.start
615            && op.origin_left == Some(self.last_id())
616            && op.origin_right == self.origin_right
617    }
618}
619
620// Storage-dominant type sizes, asserted so layout changes are deliberate.
621const _: () = assert!(size_of::<ByteId>() == 8);
622const _: () = assert!(size_of::<Option<ByteId>>() == 8);
623const _: () = assert!(size_of::<Span>() == 32);
624const _: () = assert!(size_of::<Insertion>() == 28);
625const _: () = assert!(size_of::<Run>() == 12);
626
627/// How many bytes this replica has seen from each other replica. Bytes from
628/// one author are totally ordered, so a count fully describes which of its
629/// bytes we've seen. A vec, not a map: it holds one entry per replica ever
630/// seen, i.e. a handful.
631#[derive(Clone, Default)]
632struct Version(Vec<(ReplicaId, u32)>);
633
634impl Version {
635    fn count(&self, replica: ReplicaId) -> u32 {
636        self.0
637            .iter()
638            .find(|(counted, _)| *counted == replica)
639            .map_or(0, |(_, count)| *count)
640    }
641
642    fn contains(&self, id: ByteId) -> bool {
643        id.byte_index < self.count(id.replica)
644    }
645
646    /// Bytes from an author must be recorded in order, without gaps.
647    fn record(&mut self, start: ByteId, length: u32) {
648        assert_eq!(
649            start.byte_index,
650            self.count(start.replica),
651            "bytes must be recorded in order",
652        );
653        let count = start
654            .byte_index
655            .checked_add(length)
656            .expect("the byte clock fits in u32");
657        match self
658            .0
659            .iter_mut()
660            .find(|(counted, _)| *counted == start.replica)
661        {
662            Some((_, existing)) => *existing = count,
663            None => self.0.push((start.replica, count)),
664        }
665    }
666}
667
668/*
669Note [Spans]
670------------
671
672Claude: This replica measures the document in bytes. It never sees text — it
673only counts bytes and never inspects one. That's what decouples it from the
674text: the caller owns the buffer (a `String`, a rope, ...) and the replica
675answers *where*, never *what*. It also means the replica can't police char
676boundaries; positions are the caller's responsibility, and every replica of a
677document must of course index the same byte sequence.
678
679Storage is one `Span` per run of contiguously authored bytes — the same shape
680the ops have. A span of n bytes stands, definitionally, for n chained per-byte
681items: interior byte k implicitly has byte k-1 as its left origin and the
682span's shared right origin as its right. Only the first byte's origins are
683stored; the rest are derivable.
684
685Two operations move between coarser and finer runs, and both are lossless.
686`split` materializes a chain boundary: the right half's derived id and origins
687are exactly the implicit values its bytes already had, so nothing about
688identity or ordering changes — which is why replicas may fragment the same
689content differently (one split where another appended) and still agree on the
690byte sequence. `can_append` is split's inverse: its conditions — temporal
691continuation, chained left origin, same right origin — are precisely what
692makes a later split recover the original spans byte-for-byte.
693
694The integration scan can judge whole spans by their first byte alone: a
695decision made at the head extends to the span's tail, because every interior
696byte's left origin points at its in-span predecessor, which lies past any left
697anchor the scan could be comparing against — so interior bytes are always
698skipped and never flip the scan's state. (The same fact is why a whole op
699integrates with a single scan.)
700
701Byte granularity of identity is also why this module converges to the same
702visible sequence as the per-char [`crate::crdt_vibed_not_length`]: each char item there
703corresponds to a run of byte ids here with order-isomorphic origins, and the
704FugueMax comparisons only consult relative order and replica ids, which agree.
705
706Note [Integration scan]
707-----------------------
708
709Claude: `apply_insertion` places a new span among its rivals: spans inserted
710concurrently into the same gap, between the same left and right origins. The
711anchors are first split to span boundaries (`split_after` the left origin,
712`split_before` the right), then the scan starts just after the left anchor's
713span and walks forward, stopping at the latest by the right anchor's span (or
714the end of the document). `destination` trails the walk, marking where the
715span would go if the scan stopped now.
716
717Rival spans are judged by their first byte's origins — Note [Spans] explains
718why the head speaks for the whole span. Origins are mapped to *expansion
719coordinates* — the position a byte would have if every span were expanded to
720one item per byte — so they compare globally even when an origin points into
721the middle of some span elsewhere. A left origin of `None` is the virtual item
722before the document, and `Option`'s derived ordering conveniently puts `None`
723first, just like the reference implementation's `-1` sentinel. A rival whose
724left origin is earlier than ours closes the gap we were inserted into: we
725belong before it. One whose left origin is later sits inside an earlier
726rival's subtree: we skip past it. Rivals sharing our left origin are true
727siblings, ordered by their right origins (later first), with ties broken by
728replica id, smaller first.
729
730A sibling whose right origin is earlier than ours might come before or after
731us depending on what the scan finds next, so the walk enters a "scanning"
732state that leaves `destination` pinned behind: if the scan later decides the
733span belongs before the ambiguous run, it splices at the pinned spot, and if
734it instead finds a sibling proving the span belongs after, `destination`
735catches back up.
736
737Once placed, the span appends onto its left neighbor when it directly
738continues it (the common typing case), keeping runs coalesced on every
739replica. Local insertions skip all of this: a fresh op's origins are its
740physical neighbors, so `inserted` splices — or appends — directly at the gap
741the caller named.
742
743Note [Deletion by id]
744---------------------
745
746Claude: A `Deletion` names the exact byte ids it deleted (cola instead sends
747two anchors plus a version map, a compressed encoding of the same information).
748Naming ids makes the two subtle parts of deletion trivial. Bytes inserted
749concurrently *inside* the deleted range survive automatically — they aren't in
750the list. And integration is idempotent by construction — `apply_deletion`
751reports only bytes that this call flipped from visible to deleted, so replaying
752a deletion, or receiving two concurrent overlapping deletions, deletes nothing
753twice and never mis-reports a buffer range. That idempotence is also why
754deletions need none of the ordering machinery insertions have (no timestamps,
755no per-author sequencing): a deletion is ready as soon as its targets exist.
756
757Targets may cover only part of a span; the span splits at the overlap's edges
758first, so tombstones are always whole spans.
759
760Note [Pending ops]
761------------------
762
763Claude: An op can arrive before what it depends on — its author's earlier
764bytes, or the bytes its origins name (network reordering). Such ops wait in
765`pending_insertions`/`pending_deletions`, invisibly to the caller: whenever an
766insertion applies, `drain_pending` re-checks the queues and applies everything
767newly ready, and the unblocked edits ride out in the same return value as the
768op that unblocked them.
769
770Only insertions advance the version, and readiness only consults the version,
771so nothing can become ready after a deletion — `integrate_deletion` doesn't
772drain. Duplicate deliveries of a waiting op pile up in the queue; each drain
773pass first drops copies whose op has since been applied.
774*/
775
776#[cfg(test)]
777mod tests {
778    use super::*;
779    use crate::IndigoBuildHasher;
780    use hegel::generators as gs;
781    use std::collections::{HashMap, HashSet};
782
783    fn rid(id: usize) -> NonZeroU32 {
784        NonZeroU32::new(u32::try_from(id).expect("test id fits")).expect("nonzero test id")
785    }
786
787    /// Harness-level op identity: (author, author-local op number). Lets
788    /// `sync_from` skip already-known ops without the CRDT needing deletion
789    /// timestamps.
790    type OpId = (usize, usize);
791
792    #[derive(Clone)]
793    enum Op {
794        Insert(Insertion, String),
795        Delete(Deletion),
796    }
797
798    /// How an application drives a [`Replica`] (cola's README pattern): the
799    /// text lives outside the CRDT — here in a `String`.
800    struct Doc {
801        id: usize,
802        buffer: String,
803        replica: Replica,
804        /// Every op this doc knows, own and received, in a causally valid
805        /// order (its own integration order).
806        history: Vec<(OpId, Op)>,
807        known: HashSet<OpId, IndigoBuildHasher>,
808        /// Texts of known insertions, for resolving `BufferEdit::Insert`s.
809        texts: HashMap<(ReplicaId, usize), String, IndigoBuildHasher>,
810        authored: usize,
811    }
812
813    impl Doc {
814        fn new(id: usize) -> Self {
815            Self {
816                id,
817                buffer: String::new(),
818                replica: Replica::new(rid(id), 0),
819                history: Vec::new(),
820                known: HashSet::default(),
821                texts: HashMap::default(),
822                authored: 0,
823            }
824        }
825
826        fn insert(&mut self, byte_offset: usize, text: &str) {
827            self.buffer.insert_str(byte_offset, text);
828            let op = self.replica.inserted(byte_offset, text.len()).unwrap();
829            self.record(Op::Insert(op, text.to_owned()));
830        }
831
832        fn delete(&mut self, byte_range: Range<usize>) {
833            let op = self.replica.deleted(byte_range.clone()).unwrap();
834            self.buffer.replace_range(byte_range, "");
835            self.record(Op::Delete(op));
836        }
837
838        fn record(&mut self, op: Op) {
839            let op_id = (self.id, self.authored);
840            self.authored += 1;
841            self.known.insert(op_id);
842            self.stash_text(&op);
843            self.history.push((op_id, op));
844        }
845
846        /// An empty insertion advances no clock, so it would share its
847        /// `bytes().start` with the author's next insertion — and it never
848        /// produces an `Insert` edit anyway. Don't let it clobber the map.
849        fn stash_text(&mut self, op: &Op) {
850            if let Op::Insert(insertion, text) = op
851                && !text.is_empty()
852            {
853                self.texts
854                    .insert((insertion.author(), insertion.bytes().start), text.clone());
855            }
856        }
857
858        fn receive(&mut self, op_id: OpId, op: &Op) {
859            if !self.known.insert(op_id) {
860                return;
861            }
862            self.stash_text(op);
863            let edits = match op {
864                Op::Insert(insertion, _) => self.replica.integrate_insertion(insertion),
865                Op::Delete(deletion) => self.replica.integrate_deletion(deletion),
866            };
867            self.history.push((op_id, op.clone()));
868            self.apply(edits);
869        }
870
871        fn apply(&mut self, edits: Vec<BufferEdit>) {
872            for edit in edits {
873                match edit {
874                    BufferEdit::Insert {
875                        byte_offset,
876                        author,
877                        bytes,
878                    } => {
879                        let text = &self.texts[&(author, bytes.start)];
880                        self.buffer.insert_str(byte_offset, text);
881                    }
882                    BufferEdit::Delete { byte_ranges } => {
883                        for byte_range in byte_ranges.into_iter().rev() {
884                            self.buffer.replace_range(byte_range, "");
885                        }
886                    }
887                }
888            }
889        }
890
891        fn sync_from(&mut self, other: &Self) {
892            for (op_id, op) in &other.history {
893                self.receive(*op_id, op);
894            }
895        }
896    }
897
898    /// Every byte's author, byte index, and tombstone flag, in document order
899    /// — spans expanded per byte, normalizing fragmentation differences
900    /// between replicas.
901    fn items(replica: &Replica) -> Vec<(u32, u32, bool)> {
902        replica
903            .spans
904            .iter()
905            .flat_map(|span| {
906                (span.id.byte_index..span.id.byte_index + span.length)
907                    .map(move |byte_index| (span.id.replica.0.get(), byte_index, span.deleted))
908            })
909            .collect()
910    }
911
912    /// Draws a byte index in `[0, text.len()]` lying on a `char` boundary.
913    fn draw_boundary(tc: &hegel::TestCase, text: &str) -> usize {
914        let mut index = tc.draw(gs::integers::<usize>().max_value(text.len()));
915        while !text.is_char_boundary(index) {
916            index += 1;
917        }
918        index
919    }
920
921    fn draw_text(tc: &hegel::TestCase, ascii: bool) -> String {
922        if ascii {
923            tc.draw(gs::text().codec("ascii").max_size(8))
924        } else {
925            tc.draw(gs::text().max_size(8))
926        }
927    }
928
929    fn edit_randomly(tc: &hegel::TestCase, doc: &mut Doc) {
930        if doc.buffer.is_empty() || tc.draw(gs::booleans()) {
931            let byte_offset = draw_boundary(tc, &doc.buffer);
932            let text = draw_text(tc, false);
933            doc.insert(byte_offset, &text);
934        } else {
935            let one = draw_boundary(tc, &doc.buffer);
936            let two = draw_boundary(tc, &doc.buffer);
937            doc.delete(one.min(two)..one.max(two));
938        }
939    }
940
941    fn edit_many(tc: &hegel::TestCase, doc: &mut Doc, up_to: usize) {
942        let edits = tc.draw(gs::integers::<usize>().max_value(up_to));
943        for _ in 0..edits {
944            edit_randomly(tc, doc);
945        }
946    }
947
948    /// `docs[into].sync_from(&docs[from])`, which the borrow checker only
949    /// allows through a split.
950    fn sync_pair(docs: &mut [Doc], into: usize, from: usize) {
951        if into < from {
952            let (left, right) = docs.split_at_mut(from);
953            left[into].sync_from(&right[0]);
954        } else {
955            let (left, right) = docs.split_at_mut(into);
956            right[0].sync_from(&left[from]);
957        }
958    }
959
960    #[test]
961    fn ties_broken_by_smaller_replica_id() {
962        let mut doc1 = Doc::new(1);
963        let mut doc2 = Doc::new(2);
964
965        doc1.insert(0, "A");
966        doc2.insert(0, "B");
967
968        doc1.sync_from(&doc2);
969        doc2.sync_from(&doc1);
970
971        assert_eq!(doc1.buffer, "AB");
972        assert_eq!(doc2.buffer, "AB");
973    }
974
975    #[test]
976    fn initial_content_and_fork() {
977        let mut replica1 = Replica::new(rid(1), 5);
978        let mut replica2 = replica1.fork(rid(2));
979        assert_eq!(replica2.len(), 5);
980
981        let op = replica2.inserted(5, 2).unwrap();
982        let edits = replica1.integrate_insertion(&op);
983        assert!(matches!(
984            edits.as_slice(),
985            [BufferEdit::Insert { byte_offset: 5, .. }]
986        ));
987        assert_eq!(replica1.len(), 7);
988    }
989
990    #[test]
991    fn invalid_positions_are_errors() {
992        let mut replica = Replica::new(rid(1), 3);
993        assert!(replica.inserted(4, 1).is_err());
994        assert!(replica.deleted(1..5).is_err());
995        let backwards = (2, 1);
996        assert!(replica.deleted(backwards.0..backwards.1).is_err());
997        assert_eq!(replica.len(), 3);
998    }
999
1000    /// The whole point of spans: runs of typing coalesce into one span, both
1001    /// for the author and for a replica integrating the keystrokes one op at
1002    /// a time.
1003    #[test]
1004    fn typing_coalesces_into_one_span() {
1005        let mut doc1 = Doc::new(1);
1006        for (byte_offset, char) in "hello world".char_indices() {
1007            doc1.insert(byte_offset, &char.to_string());
1008        }
1009        assert_eq!(doc1.replica.spans.len(), 1);
1010
1011        let mut doc2 = Doc::new(2);
1012        doc2.sync_from(&doc1);
1013        assert_eq!(doc2.replica.spans.len(), 1);
1014    }
1015
1016    // Canonical FugueMax scenarios: text typed concurrently at one position
1017    // must never interleave, whether typed forward or backward (the backward
1018    // case is the anomaly FugueMax exists to fix).
1019
1020    #[test]
1021    fn forward_typing_does_not_interleave() {
1022        let mut doc1 = Doc::new(1);
1023        let mut doc2 = Doc::new(2);
1024        for (byte_offset, char) in "Hello".char_indices() {
1025            doc1.insert(byte_offset, &char.to_string());
1026        }
1027        for (byte_offset, char) in "World".char_indices() {
1028            doc2.insert(byte_offset, &char.to_string());
1029        }
1030        doc1.sync_from(&doc2);
1031        doc2.sync_from(&doc1);
1032        assert_eq!(doc1.buffer, "HelloWorld");
1033        assert_eq!(doc2.buffer, "HelloWorld");
1034    }
1035
1036    #[test]
1037    fn backward_typing_does_not_interleave() {
1038        let mut doc1 = Doc::new(1);
1039        let mut doc2 = Doc::new(2);
1040        for char in "Hello".chars().rev() {
1041            doc1.insert(0, &char.to_string());
1042        }
1043        for char in "World".chars().rev() {
1044            doc2.insert(0, &char.to_string());
1045        }
1046        doc1.sync_from(&doc2);
1047        doc2.sync_from(&doc1);
1048        assert_eq!(doc1.buffer, doc2.buffer);
1049        assert!(doc1.buffer.contains("Hello"));
1050        assert!(doc1.buffer.contains("World"));
1051    }
1052
1053    #[test]
1054    fn mixed_typing_does_not_interleave() {
1055        let mut doc1 = Doc::new(1);
1056        let mut doc2 = Doc::new(2);
1057        for (byte_offset, char) in "Hello".char_indices() {
1058            doc1.insert(byte_offset, &char.to_string());
1059        }
1060        for char in "World".chars().rev() {
1061            doc2.insert(0, &char.to_string());
1062        }
1063        doc1.sync_from(&doc2);
1064        doc2.sync_from(&doc1);
1065        assert_eq!(doc1.buffer, doc2.buffer);
1066        assert!(doc1.buffer.contains("Hello"));
1067        assert!(doc1.buffer.contains("World"));
1068    }
1069
1070    /// Types `text` into the document one char-sized op at a time, either
1071    /// forward or backward.
1072    fn type_at(tc: &hegel::TestCase, doc: &mut Doc, byte_offset: usize, text: &str) {
1073        if tc.draw(gs::booleans()) {
1074            let mut byte_offset = byte_offset;
1075            for char in text.chars() {
1076                doc.insert(byte_offset, &char.to_string());
1077                byte_offset += char.len_utf8();
1078            }
1079        } else {
1080            for char in text.chars().rev() {
1081                doc.insert(byte_offset, &char.to_string());
1082            }
1083        }
1084    }
1085
1086    /// The property version of the scenarios above; the disjoint alphabets
1087    /// make the substring checks exact.
1088    #[hegel::test(test_cases = 2_000)]
1089    fn concurrent_typing_does_not_interleave(tc: hegel::TestCase) {
1090        let base: String = tc.draw(gs::text().alphabet("xyz"));
1091        let mut doc1 = Doc::new(1);
1092        doc1.insert(0, &base);
1093        let mut doc2 = Doc::new(2);
1094        doc2.sync_from(&doc1);
1095
1096        let byte_offset = draw_boundary(&tc, &base);
1097        let text1: String = tc.draw(gs::text().alphabet("ABC").min_size(1));
1098        let text2: String = tc.draw(gs::text().alphabet("123").min_size(1));
1099        type_at(&tc, &mut doc1, byte_offset, &text1);
1100        type_at(&tc, &mut doc2, byte_offset, &text2);
1101
1102        doc1.sync_from(&doc2);
1103        doc2.sync_from(&doc1);
1104        assert_eq!(doc1.buffer, doc2.buffer);
1105        assert!(doc1.buffer.contains(&text1));
1106        assert!(doc1.buffer.contains(&text2));
1107    }
1108
1109    /// doc1 types a run, doc2 integrates it, then both continue typing at the
1110    /// same position: doc1 extending its own material (the append path), doc2
1111    /// starting a rival run (the split/sibling path). Neither author's text
1112    /// may be torn apart.
1113    #[hegel::test(test_cases = 2_000)]
1114    fn continued_typing_after_merge_does_not_interleave(tc: hegel::TestCase) {
1115        let mut doc1 = Doc::new(1);
1116        let first: String = tc.draw(gs::text().alphabet("ABC").min_size(1));
1117        type_at(&tc, &mut doc1, 0, &first);
1118        let mut doc2 = Doc::new(2);
1119        doc2.sync_from(&doc1);
1120
1121        let byte_offset = draw_boundary(&tc, &doc1.buffer);
1122        let more: String = tc.draw(gs::text().alphabet("abc").min_size(1));
1123        let rival: String = tc.draw(gs::text().alphabet("123").min_size(1));
1124        type_at(&tc, &mut doc1, byte_offset, &more);
1125        type_at(&tc, &mut doc2, byte_offset, &rival);
1126
1127        doc1.sync_from(&doc2);
1128        doc2.sync_from(&doc1);
1129        assert_eq!(doc1.buffer, doc2.buffer);
1130        assert_eq!(items(&doc1.replica), items(&doc2.replica));
1131        assert!(doc1.buffer.contains(&more));
1132        assert!(doc1.buffer.contains(&rival));
1133    }
1134
1135    #[hegel::test(test_cases = 2_000)]
1136    fn local_edits_match_string(tc: hegel::TestCase) {
1137        let mut doc = Doc::new(1);
1138        let edits = tc.draw(gs::integers::<usize>().max_value(20));
1139        for _ in 0..edits {
1140            edit_randomly(&tc, &mut doc);
1141            assert_eq!(doc.replica.len(), doc.buffer.len());
1142        }
1143
1144        // A mirror doc reconstructs the text purely from length-based edits.
1145        let mut mirror = Doc::new(2);
1146        mirror.sync_from(&doc);
1147        assert_eq!(mirror.buffer, doc.buffer);
1148    }
1149
1150    #[hegel::test(test_cases = 2_000)]
1151    fn concurrent_edits_converge(tc: hegel::TestCase) {
1152        let count = tc.draw(gs::integers::<usize>().min_value(2).max_value(4));
1153        let mut docs = (1..=count).map(Doc::new).collect::<Vec<Doc>>();
1154
1155        let rounds = tc.draw(gs::integers::<usize>().min_value(1).max_value(3));
1156        for _ in 0..rounds {
1157            // Everyone edits concurrently...
1158            for doc in &mut docs {
1159                edit_many(&tc, doc, 3);
1160            }
1161            // ...then two docs gossip.
1162            let into = tc.draw(gs::integers::<usize>().max_value(count - 1));
1163            let from = tc.draw(gs::integers::<usize>().max_value(count - 1));
1164            if into != from {
1165                sync_pair(&mut docs, into, from);
1166            }
1167        }
1168
1169        // After syncing all-to-all, every doc has seen every op.
1170        for into in 0..count {
1171            for from in 0..count {
1172                if into != from {
1173                    sync_pair(&mut docs, into, from);
1174                }
1175            }
1176        }
1177        for pair in docs.windows(2) {
1178            assert_eq!(pair[0].buffer, pair[1].buffer);
1179            assert_eq!(items(&pair[0].replica), items(&pair[1].replica));
1180        }
1181    }
1182
1183    #[hegel::test(test_cases = 5_000)]
1184    fn out_of_order_delivery(tc: hegel::TestCase) {
1185        let mut doc1 = Doc::new(1);
1186        edit_many(&tc, &mut doc1, 6);
1187
1188        // Deliver doc1's ops in a random order; the pending queues absorb the
1189        // reordering invisibly.
1190        let mut doc2 = Doc::new(2);
1191        let mut remaining = (0..doc1.history.len()).collect::<Vec<usize>>();
1192        while !remaining.is_empty() {
1193            let pick = tc.draw(gs::integers::<usize>().max_value(remaining.len() - 1));
1194            let index = remaining.swap_remove(pick);
1195            let (op_id, op) = &doc1.history[index];
1196            doc2.receive(*op_id, op);
1197        }
1198
1199        assert_eq!(doc2.buffer, doc1.buffer);
1200        assert!(doc2.replica.pending_insertions.is_empty());
1201        assert!(doc2.replica.pending_deletions.is_empty());
1202    }
1203
1204    #[hegel::test(test_cases = 5_000)]
1205    fn integration_is_idempotent(tc: hegel::TestCase) {
1206        let mut doc1 = Doc::new(1);
1207        let mut doc2 = Doc::new(2);
1208        edit_many(&tc, &mut doc1, 4);
1209        edit_many(&tc, &mut doc2, 4);
1210
1211        // Share some history, then diverge again, so replays hit partially
1212        // overlapping versions rather than a wholly foreign doc.
1213        doc1.sync_from(&doc2);
1214        doc2.sync_from(&doc1);
1215        edit_many(&tc, &mut doc1, 2);
1216        doc2.sync_from(&doc1);
1217
1218        let buffer = doc2.buffer.clone();
1219        let snapshot = items(&doc2.replica);
1220        for (_, op) in &doc1.history {
1221            let edits = match op {
1222                Op::Insert(insertion, _) => doc2.replica.integrate_insertion(insertion),
1223                Op::Delete(deletion) => doc2.replica.integrate_deletion(deletion),
1224            };
1225            assert!(edits.is_empty());
1226        }
1227        assert_eq!(doc2.buffer, buffer);
1228        assert_eq!(items(&doc2.replica), snapshot);
1229    }
1230
1231    /// The headline requirement: driven by the same edits and the same merge
1232    /// schedule, this module and the per-char `crdt_vibed_not_length` implementation
1233    /// produce identical documents. In ASCII mode (1 char == 1 byte) the full
1234    /// internal item sequences must match too, not just the visible text.
1235    #[hegel::test(test_cases = 1_500)]
1236    fn matches_char_based_implementation(tc: hegel::TestCase) {
1237        use crate::crdt_vibed_not_length;
1238
1239        fn merge_pair_old(docs: &mut [crdt_vibed_not_length::Document], into: usize, from: usize) {
1240            if into < from {
1241                let (left, right) = docs.split_at_mut(from);
1242                left[into].merge(&right[0]);
1243            } else {
1244                let (left, right) = docs.split_at_mut(into);
1245                right[0].merge(&left[from]);
1246            }
1247        }
1248
1249        let ascii = tc.draw(gs::booleans());
1250        let count = tc.draw(gs::integers::<usize>().min_value(2).max_value(3));
1251        let mut olds = (1..=count)
1252            .map(crdt_vibed_not_length::Document::new)
1253            .collect::<Vec<crdt_vibed_not_length::Document>>();
1254        let mut news = (1..=count).map(Doc::new).collect::<Vec<Doc>>();
1255
1256        let rounds = tc.draw(gs::integers::<usize>().min_value(1).max_value(3));
1257        for _ in 0..rounds {
1258            for index in 0..count {
1259                let edits = tc.draw(gs::integers::<usize>().max_value(3));
1260                for _ in 0..edits {
1261                    let old = &mut olds[index];
1262                    let new = &mut news[index];
1263                    if new.buffer.is_empty() || tc.draw(gs::booleans()) {
1264                        let byte_offset = draw_boundary(&tc, &new.buffer);
1265                        let text = draw_text(&tc, ascii);
1266                        old.insert(byte_offset, &text).unwrap();
1267                        new.insert(byte_offset, &text);
1268                    } else {
1269                        let one = draw_boundary(&tc, &new.buffer);
1270                        let two = draw_boundary(&tc, &new.buffer);
1271                        let byte_range = one.min(two)..one.max(two);
1272                        old.delete(byte_range.clone()).unwrap();
1273                        new.delete(byte_range);
1274                    }
1275                }
1276            }
1277            let into = tc.draw(gs::integers::<usize>().max_value(count - 1));
1278            let from = tc.draw(gs::integers::<usize>().max_value(count - 1));
1279            if into != from {
1280                merge_pair_old(&mut olds, into, from);
1281                sync_pair(&mut news, into, from);
1282                assert_eq!(olds[into].text(), news[into].buffer);
1283            }
1284        }
1285
1286        for into in 0..count {
1287            for from in 0..count {
1288                if into != from {
1289                    merge_pair_old(&mut olds, into, from);
1290                    sync_pair(&mut news, into, from);
1291                }
1292            }
1293        }
1294        for index in 0..count {
1295            assert_eq!(olds[index].text(), news[index].buffer);
1296            if ascii {
1297                let old_items = olds[index]
1298                    .item_summaries()
1299                    .into_iter()
1300                    .map(|(replica, sequence, deleted)| {
1301                        (
1302                            u32::try_from(replica).unwrap(),
1303                            u32::try_from(sequence).unwrap(),
1304                            deleted,
1305                        )
1306                    })
1307                    .collect::<Vec<(u32, u32, bool)>>();
1308                assert_eq!(old_items, items(&news[index].replica));
1309            }
1310        }
1311    }
1312}