├── .github └── workflows │ └── ci.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE ├── README.md ├── benches └── traces.rs ├── examples └── basic.rs ├── rustfmt.toml ├── src ├── anchor.rs ├── backlog.rs ├── deletion.rs ├── encode.rs ├── encoded_replica.rs ├── gtree.rs ├── insertion.rs ├── lib.rs ├── replica.rs ├── replica_id.rs ├── run_indices.rs ├── run_tree.rs ├── text.rs ├── utils.rs └── version_map.rs ├── tests ├── anchor.rs ├── common │ └── mod.rs ├── concurrent_traces.rs ├── deletions.rs ├── downstream_traces.rs ├── encode.rs ├── insertions.rs ├── sequential_traces.rs └── serde.rs └── traces ├── Cargo.toml ├── concurrent └── friendsforever.json.gz ├── sequential ├── automerge-paper.json.gz ├── rustcode.json.gz ├── seph-blog1.json.gz └── sveltecomponent.json.gz └── src ├── concurrent.rs ├── lib.rs └── sequential.rs /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | schedule: 11 | - cron: '0 0 1 * *' 12 | 13 | jobs: 14 | test: 15 | name: test 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v2 19 | - uses: dtolnay/rust-toolchain@nightly 20 | - run: cargo test --all-features --no-fail-fast --release 21 | 22 | bench: 23 | name: bench 24 | runs-on: ubuntu-latest 25 | steps: 26 | - uses: actions/checkout@v2 27 | - uses: dtolnay/rust-toolchain@nightly 28 | - run: cargo bench --no-run 29 | 30 | clippy: 31 | name: clippy 32 | runs-on: ubuntu-latest 33 | steps: 34 | - uses: actions/checkout@v2 35 | - uses: dtolnay/rust-toolchain@nightly 36 | with: 37 | components: clippy 38 | - run: cargo clippy --all-features --all-targets -- -D warnings 39 | 40 | docs: 41 | name: docs 42 | runs-on: ubuntu-latest 43 | steps: 44 | - uses: actions/checkout@v2 45 | - uses: dtolnay/rust-toolchain@nightly 46 | - run: RUSTDOCFLAGS="--cfg docsrs" cargo doc --all-features 47 | 48 | format: 49 | name: format 50 | runs-on: ubuntu-latest 51 | steps: 52 | - uses: actions/checkout@v2 53 | - uses: dtolnay/rust-toolchain@nightly 54 | with: 55 | components: rustfmt 56 | - run: cargo fmt --check 57 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [Unreleased] 4 | 5 | ## [0.5.0] - Nov 4 2024 6 | 7 | ### Added 8 | 9 | - a `as_bytes()` method on `EncodedReplica` to get its underlying bytes; 10 | - a `from_bytes()` method on `EncodedReplica` to create one from a byte slice; 11 | 12 | ### Changed 13 | 14 | - the `EncodedReplica` struct now has a lifetime parameter tied to the 15 | underlying buffer; 16 | 17 | ## [0.4.6] - Oct 31 2024 18 | 19 | ### Added 20 | 21 | - `AnchorBias` now implements `Not`; 22 | 23 | - a `Insertion::inserted_by()` method to get the `ReplicaId` of the peer that 24 | performed the insertion; 25 | 26 | ## [0.4.5] - Apr 3 2024 27 | 28 | ### Added 29 | 30 | - a `Clone` impl for `Replica`; 31 | 32 | ### Changed 33 | 34 | - `Replica::fork()` now panics if the given `ReplicaId` is the same as the one 35 | of the `Replica` being forked; 36 | 37 | ## [0.4.4] - Feb 1 2024 38 | 39 | ### Changed 40 | 41 | - the `Deletion`'s internal `VersionMap` now only includes entries for the 42 | `ReplicaId`s that were between the start and the end of the deleted range, 43 | instead of including every single `ReplicaId` the `Replica` had ever seen. 44 | This results in much smaller `Deletion`s when deleting small ranges in 45 | buffers edited by many peers ([#9](https://github.com/nomad/cola/pull/9)); 46 | 47 | ## [0.4.3] - Jan 31 2024 48 | 49 | ### Fixed 50 | 51 | - a typo in the docs 52 | 53 | ## [0.4.2] - Jan 31 2024 54 | 55 | ### Added 56 | 57 | - a new `Deletion::deleted_by()` to get the `ReplicaId` of the peer that 58 | performed the deletion; 59 | 60 | ## [0.4.0] - Jan 28 2024 61 | 62 | ### Changed 63 | 64 | - the `Serialize` impl of `Deletion` now produces ~3x smaller payloads, 65 | depending on the data format used 66 | ([#7](https://github.com/nomad/cola/pull/7)); 67 | 68 | - the `Serialize` impl of `EncodedReplica` now produces ~7x smaller payloads, 69 | depending on the data format used 70 | ([#6](https://github.com/nomad/cola/pull/6)); 71 | 72 | - the `Serialize` impl of `Insertion` now produces 3-4x smaller payloads, 73 | depending on the data format used ([#5](https://github.com/nomad/cola/pull/5)); 74 | 75 | ## [0.3.0] - Jan 9 2024 76 | 77 | ### Added 78 | 79 | - a new `Replica::create_anchor()` method to create an `Anchor` from an offset 80 | and an `AnchorBias`; 81 | 82 | - a new `Replica::resolve_anchor()` method to resolve an `Anchor` into an 83 | offset; 84 | 85 | ## [0.2.1] - Jan 2 2024 86 | 87 | ### Added 88 | 89 | - `Debug` impl for `EncodedReplica`; 90 | 91 | - `Display` and `Error` impls for `DecodeError`; 92 | 93 | ### Changed 94 | 95 | - `Replica::encode()` makes fewer transient allocations; 96 | 97 | - the stack size of `EncodedReplica` was decreased from 56 to 32; 98 | 99 | ## [0.2.0] - Dec 23 2023 100 | 101 | ### Added 102 | 103 | - `Eq` impls for `Insertion` and `Deletion` 104 | 105 | ### Bug fixes 106 | 107 | - fixed a bug that would cause `Replica::decode()` to fail if it was encoded 108 | on a machine with a different pointer size (#1); 109 | 110 | [Unreleased]: https://github.com/nomad/cola/compare/v0.5.0...HEAD 111 | [0.5.0]: https://github.com/nomad/cola/compare/v0.4.6...v0.5.0 112 | [0.4.6]: https://github.com/nomad/cola/compare/v0.4.5...v0.4.6 113 | [0.4.5]: https://github.com/nomad/cola/compare/v0.4.4...v0.4.5 114 | [0.4.4]: https://github.com/nomad/cola/compare/v0.4.3...v0.4.4 115 | [0.4.3]: https://github.com/nomad/cola/compare/v0.4.2...v0.4.3 116 | [0.4.2]: https://github.com/nomad/cola/compare/v0.4.0...v0.4.2 117 | [0.4.0]: https://github.com/nomad/cola/compare/v0.3.0...v0.4.0 118 | [0.3.0]: https://github.com/nomad/cola/compare/v0.2.1...v0.3.0 119 | [0.2.1]: https://github.com/nomad/cola/compare/v0.2.0...v0.2.1 120 | [0.2.0]: https://github.com/nomad/cola/compare/v0.1.0...v0.2.0 121 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cola" 3 | version = "0.5.0" 4 | edition = "2021" 5 | authors = ["Riccardo Mazzarini "] 6 | description = "A text CRDT for real-time collaborative editing" 7 | documentation = "https://docs.rs/cola" 8 | repository = "https://github.com/nomad/cola" 9 | readme = "README.md" 10 | license = "MIT" 11 | keywords = ["crdt", "collaboration", "text", "editor", "tree"] 12 | categories = ["data-structures", "text-editors", "text-processing"] 13 | exclude = ["/.github/*", "/examples/**", "/fuzz/**", "/tests/**"] 14 | 15 | [package.metadata.docs.rs] 16 | features = ["serde"] 17 | rustdoc-args = ["--cfg", "docsrs"] 18 | 19 | [features] 20 | encode = ["dep:sha2", "dep:varint-simd"] 21 | serde = ["encode", "dep:serde"] 22 | 23 | [dependencies] 24 | serde = { version = "1.0", optional = true } 25 | sha2 = { version = "0.10", optional = true } 26 | varint-simd = { version = "0.4", optional = true } 27 | 28 | [dev-dependencies] 29 | bincode = "1.3" 30 | criterion = "0.5" 31 | rand = "0.8" 32 | rand_chacha = "0.3" 33 | serde_json = "1" 34 | traces = { path = "./traces" } 35 | zstd = "0.13" 36 | 37 | [[bench]] 38 | name = "traces" 39 | harness = false 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Riccardo Mazzarini 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🥤 cola 2 | 3 | [![Latest version]](https://crates.io/crates/cola) 4 | [![Docs badge]][docs] 5 | [![CI]](https://github.com/nomad/cola/actions) 6 | 7 | [Latest version]: https://img.shields.io/crates/v/cola.svg 8 | [Docs badge]: https://docs.rs/cola/badge.svg 9 | [CI]: https://github.com/nomad/cola/actions/workflows/ci.yml/badge.svg 10 | 11 | cola is a Conflict-free Replicated Data Type specialized for real-time 12 | collaborative editing of plain text documents. 13 | 14 | It allows multiple peers on a distributed network to concurrently edit the same 15 | text document, making sure that they all converge to the same final state 16 | without relying on a central server to coordinate the edits. 17 | 18 | Check out [the docs][docs] to learn about cola's API, or [this blog post][blog] 19 | for a deeper dive into its design and implementation. 20 | 21 | ## Example usage 22 | 23 | ```rust 24 | use std::ops::Range; 25 | 26 | use cola::{Deletion, Replica, ReplicaId}; 27 | 28 | struct Document { 29 | buffer: String, 30 | crdt: Replica, 31 | } 32 | 33 | struct Insertion { 34 | text: String, 35 | crdt: cola::Insertion, 36 | } 37 | 38 | impl Document { 39 | fn new>(text: S, replica_id: ReplicaId) -> Self { 40 | let buffer = text.into(); 41 | let crdt = Replica::new(replica_id, buffer.len()); 42 | Document { buffer, crdt } 43 | } 44 | 45 | fn fork(&self, new_replica_id: ReplicaId) -> Self { 46 | let crdt = self.crdt.fork(new_replica_id); 47 | Document { buffer: self.buffer.clone(), crdt } 48 | } 49 | 50 | fn insert>( 51 | &mut self, 52 | insert_at: usize, 53 | text: S, 54 | ) -> Insertion { 55 | let text = text.into(); 56 | self.buffer.insert_str(insert_at, &text); 57 | let insertion = self.crdt.inserted(insert_at, text.len()); 58 | Insertion { text, crdt: insertion } 59 | } 60 | 61 | fn delete(&mut self, range: Range) -> Deletion { 62 | self.buffer.replace_range(range.clone(), ""); 63 | self.crdt.deleted(range) 64 | } 65 | 66 | fn integrate_insertion(&mut self, insertion: Insertion) { 67 | if let Some(offset) = self.crdt.integrate_insertion(&insertion.crdt) { 68 | self.buffer.insert_str(offset, &insertion.text); 69 | } 70 | } 71 | 72 | fn integrate_deletion(&mut self, deletion: Deletion) { 73 | let ranges = self.crdt.integrate_deletion(&deletion); 74 | for range in ranges.into_iter().rev() { 75 | self.buffer.replace_range(range, ""); 76 | } 77 | } 78 | } 79 | 80 | fn main() { 81 | let mut peer_1 = Document::new("Hello, world", 1); 82 | let mut peer_2 = peer_1.fork(2); 83 | 84 | let delete_comma = peer_1.delete(5..6); 85 | let insert_exclamation = peer_2.insert(12, "!"); 86 | 87 | peer_1.integrate_insertion(insert_exclamation); 88 | peer_2.integrate_deletion(delete_comma); 89 | 90 | assert_eq!(peer_1.buffer, "Hello world!"); 91 | assert_eq!(peer_2.buffer, "Hello world!"); 92 | } 93 | ``` 94 | 95 | [docs]: https://docs.rs/cola 96 | [blog]: https://nomad.foo/blog/cola 97 | -------------------------------------------------------------------------------- /benches/traces.rs: -------------------------------------------------------------------------------- 1 | use cola::Replica; 2 | use criterion::measurement::WallTime; 3 | use criterion::{ 4 | criterion_group, 5 | criterion_main, 6 | BenchmarkGroup, 7 | BenchmarkId, 8 | Criterion, 9 | Throughput, 10 | }; 11 | use traces::SequentialTrace; 12 | 13 | fn bench_upstream( 14 | group: &mut BenchmarkGroup, 15 | trace: &SequentialTrace, 16 | trace_name: &str, 17 | ) { 18 | let trace = trace.chars_to_bytes(); 19 | 20 | group.throughput(Throughput::Elements(trace.num_edits() as u64)); 21 | 22 | group.bench_function(BenchmarkId::new("upstream", trace_name), |b| { 23 | b.iter(|| { 24 | let mut replica = Replica::new(1, trace.start_content().len()); 25 | 26 | for (start, end, text) in trace.edits() { 27 | let _ = replica.deleted(start..end); 28 | let _ = replica.inserted(start, text.len()); 29 | } 30 | 31 | assert_eq!(replica.len(), trace.end_content().len()); 32 | }) 33 | }); 34 | } 35 | 36 | fn bench_downstream( 37 | group: &mut BenchmarkGroup, 38 | trace: &SequentialTrace, 39 | trace_name: &str, 40 | ) { 41 | enum Edit { 42 | Insertion(cola::Insertion), 43 | Deletion(cola::Deletion), 44 | } 45 | 46 | let trace = trace.chars_to_bytes(); 47 | 48 | let mut upstream = Replica::new(1, trace.start_content().len()); 49 | 50 | let edits = trace 51 | .edits() 52 | .flat_map(|(start, end, text)| { 53 | let mut edits = Vec::new(); 54 | if end > start { 55 | edits.push(Edit::Deletion(upstream.deleted(start..end))); 56 | } 57 | if !text.is_empty() { 58 | edits.push(Edit::Insertion( 59 | upstream.inserted(start, text.len()), 60 | )); 61 | } 62 | edits 63 | }) 64 | .collect::>(); 65 | 66 | group.throughput(Throughput::Elements(edits.len() as u64)); 67 | 68 | group.bench_function(BenchmarkId::new("downstream", trace_name), |b| { 69 | b.iter(|| { 70 | let upstream = Replica::new(1, trace.start_content().len()); 71 | 72 | let mut downstream = upstream.fork(2); 73 | 74 | for edit in &edits { 75 | match edit { 76 | Edit::Insertion(insertion) => { 77 | let _ = downstream.integrate_insertion(insertion); 78 | }, 79 | Edit::Deletion(deletion) => { 80 | let _ = downstream.integrate_deletion(deletion); 81 | }, 82 | }; 83 | } 84 | 85 | assert_eq!(downstream.len(), trace.end_content().len()); 86 | }) 87 | }); 88 | } 89 | 90 | fn upstream_automerge(c: &mut Criterion) { 91 | let mut group = c.benchmark_group("traces"); 92 | bench_upstream(&mut group, &traces::automerge(), "automerge"); 93 | } 94 | 95 | fn upstream_rustcode(c: &mut Criterion) { 96 | let mut group = c.benchmark_group("traces"); 97 | bench_upstream(&mut group, &traces::rustcode(), "rustcode"); 98 | } 99 | 100 | fn upstream_seph_blog(c: &mut Criterion) { 101 | let mut group = c.benchmark_group("traces"); 102 | bench_upstream(&mut group, &traces::seph_blog(), "seph_blog"); 103 | } 104 | 105 | fn upstream_sveltecomponent(c: &mut Criterion) { 106 | let mut group = c.benchmark_group("traces"); 107 | bench_upstream(&mut group, &traces::sveltecomponent(), "sveltecomponent"); 108 | } 109 | 110 | fn downstream_automerge(c: &mut Criterion) { 111 | let mut group = c.benchmark_group("traces"); 112 | bench_downstream(&mut group, &traces::automerge(), "automerge"); 113 | } 114 | 115 | fn downstream_rustcode(c: &mut Criterion) { 116 | let mut group = c.benchmark_group("traces"); 117 | bench_downstream(&mut group, &traces::rustcode(), "rustcode"); 118 | } 119 | 120 | fn downstream_seph_blog(c: &mut Criterion) { 121 | let mut group = c.benchmark_group("traces"); 122 | bench_downstream(&mut group, &traces::seph_blog(), "seph_blog"); 123 | } 124 | 125 | fn downstream_sveltecomponent(c: &mut Criterion) { 126 | let mut group = c.benchmark_group("traces"); 127 | bench_downstream( 128 | &mut group, 129 | &traces::sveltecomponent(), 130 | "sveltecomponent", 131 | ); 132 | } 133 | 134 | criterion_group!( 135 | benches, 136 | upstream_automerge, 137 | upstream_rustcode, 138 | upstream_seph_blog, 139 | upstream_sveltecomponent, 140 | downstream_automerge, 141 | downstream_rustcode, 142 | downstream_seph_blog, 143 | downstream_sveltecomponent, 144 | ); 145 | criterion_main!(benches); 146 | -------------------------------------------------------------------------------- /examples/basic.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Range; 2 | 3 | use cola::{Deletion, Replica, ReplicaId}; 4 | 5 | struct Document { 6 | buffer: String, 7 | crdt: Replica, 8 | } 9 | 10 | struct Insertion { 11 | text: String, 12 | crdt: cola::Insertion, 13 | } 14 | 15 | impl Document { 16 | fn new>(text: S, replica_id: ReplicaId) -> Self { 17 | let buffer = text.into(); 18 | let crdt = Replica::new(replica_id, buffer.len()); 19 | Document { buffer, crdt } 20 | } 21 | 22 | fn fork(&self, new_replica_id: ReplicaId) -> Self { 23 | let crdt = self.crdt.fork(new_replica_id); 24 | Document { buffer: self.buffer.clone(), crdt } 25 | } 26 | 27 | fn insert>( 28 | &mut self, 29 | insert_at: usize, 30 | text: S, 31 | ) -> Insertion { 32 | let text = text.into(); 33 | self.buffer.insert_str(insert_at, &text); 34 | let insertion = self.crdt.inserted(insert_at, text.len()); 35 | Insertion { text, crdt: insertion } 36 | } 37 | 38 | fn delete(&mut self, range: Range) -> Deletion { 39 | self.buffer.replace_range(range.clone(), ""); 40 | self.crdt.deleted(range) 41 | } 42 | 43 | fn integrate_insertion(&mut self, insertion: Insertion) { 44 | if let Some(offset) = self.crdt.integrate_insertion(&insertion.crdt) { 45 | self.buffer.insert_str(offset, &insertion.text); 46 | } 47 | } 48 | 49 | fn integrate_deletion(&mut self, deletion: Deletion) { 50 | let ranges = self.crdt.integrate_deletion(&deletion); 51 | for range in ranges.into_iter().rev() { 52 | self.buffer.replace_range(range, ""); 53 | } 54 | } 55 | } 56 | 57 | fn main() { 58 | let mut peer_1 = Document::new("Hello, world", 1); 59 | let mut peer_2 = peer_1.fork(2); 60 | 61 | let delete_comma = peer_1.delete(5..6); 62 | let insert_exclamation = peer_2.insert(12, "!"); 63 | 64 | peer_1.integrate_insertion(insert_exclamation); 65 | peer_2.integrate_deletion(delete_comma); 66 | 67 | assert_eq!(peer_1.buffer, "Hello world!"); 68 | assert_eq!(peer_2.buffer, "Hello world!"); 69 | } 70 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | format_code_in_doc_comments = true 2 | format_strings = true 3 | group_imports = "StdExternalCrate" 4 | imports_layout = "HorizontalVertical" 5 | match_block_trailing_comma = true 6 | max_width = 79 7 | unstable_features = true 8 | use_field_init_shorthand = true 9 | use_small_heuristics = "Max" 10 | -------------------------------------------------------------------------------- /src/anchor.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | 3 | /// A stable reference to a position in a [`Replica`]. 4 | /// 5 | /// After its creation, an `Anchor` can be given to a `Replica` to 6 | /// retrieve the current offset of the position it refers to, taking into 7 | /// account all the edits that have been applied to the `Replica` in the 8 | /// meantime. 9 | /// 10 | /// This property makes `Anchor`s useful to implement things like cursors and 11 | /// selections in collaborative editing environments. 12 | // 13 | /// For more information, see the documentation of 14 | /// [`Replica::create_anchor()`][crate::Replica::create_anchor] and 15 | /// [`Replica::resolve_anchor()`][crate::Replica::resolve_anchor]. 16 | #[derive(Debug, Copy, Clone, PartialEq, Eq)] 17 | pub struct Anchor { 18 | /// TODO: docs 19 | inner: InnerAnchor, 20 | 21 | bias: AnchorBias, 22 | } 23 | 24 | impl Anchor { 25 | #[inline(always)] 26 | pub(crate) fn bias(&self) -> AnchorBias { 27 | self.bias 28 | } 29 | 30 | #[inline(always)] 31 | pub(crate) fn end_of_document() -> Self { 32 | Self::new(InnerAnchor::zero(), AnchorBias::Right) 33 | } 34 | 35 | #[inline(always)] 36 | pub(crate) fn inner(&self) -> InnerAnchor { 37 | self.inner 38 | } 39 | 40 | #[inline(always)] 41 | pub(crate) fn is_end_of_document(&self) -> bool { 42 | self.inner.is_zero() && self.bias == AnchorBias::Right 43 | } 44 | 45 | #[inline(always)] 46 | pub(crate) fn is_start_of_document(&self) -> bool { 47 | self.inner.is_zero() && self.bias == AnchorBias::Left 48 | } 49 | 50 | #[inline(always)] 51 | pub(crate) fn new(inner: InnerAnchor, bias: AnchorBias) -> Self { 52 | Self { inner, bias } 53 | } 54 | 55 | #[inline(always)] 56 | pub(crate) fn start_of_document() -> Self { 57 | Self::new(InnerAnchor::zero(), AnchorBias::Left) 58 | } 59 | } 60 | 61 | /// A bias to use when creating an [`Anchor`]. 62 | /// 63 | /// This is used in the 64 | /// [`Replica::create_anchor()`][crate::Replica::create_anchor] method to 65 | /// create a new [`Anchor`]. See the documentation of that method for more 66 | /// information. 67 | #[derive(Debug, Copy, Clone, PartialEq, Eq)] 68 | pub enum AnchorBias { 69 | /// The anchor should attach to the left. 70 | Left, 71 | 72 | /// The anchor should attach to the right. 73 | Right, 74 | } 75 | 76 | impl core::ops::Not for AnchorBias { 77 | type Output = Self; 78 | 79 | #[inline] 80 | fn not(self) -> Self::Output { 81 | match self { 82 | Self::Left => Self::Right, 83 | Self::Right => Self::Left, 84 | } 85 | } 86 | } 87 | 88 | /// TODO: docs 89 | #[derive(Copy, Clone, PartialEq, Eq)] 90 | pub(crate) struct InnerAnchor { 91 | /// TODO: docs 92 | replica_id: ReplicaId, 93 | 94 | /// The [`RunTs`] of the [`EditRun`] containing this [`Anchor`]. 95 | contained_in: RunTs, 96 | 97 | /// TODO: docs 98 | offset: Length, 99 | } 100 | 101 | impl core::fmt::Debug for InnerAnchor { 102 | fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { 103 | if self == &Self::zero() { 104 | write!(f, "zero") 105 | } else if f.alternate() { 106 | write!( 107 | f, 108 | "{:x}.{} in {}", 109 | self.replica_id, self.offset, self.contained_in 110 | ) 111 | } else { 112 | write!(f, "{:x}.{}", self.replica_id, self.offset) 113 | } 114 | } 115 | } 116 | 117 | impl InnerAnchor { 118 | #[inline(always)] 119 | pub(crate) fn is_zero(&self) -> bool { 120 | self.replica_id == 0 121 | } 122 | 123 | #[inline(always)] 124 | pub(crate) fn new( 125 | replica_id: ReplicaId, 126 | offset: Length, 127 | run_ts: RunTs, 128 | ) -> Self { 129 | Self { replica_id, offset, contained_in: run_ts } 130 | } 131 | 132 | #[inline(always)] 133 | pub(crate) fn offset(&self) -> Length { 134 | self.offset 135 | } 136 | 137 | #[inline(always)] 138 | pub(crate) fn replica_id(&self) -> ReplicaId { 139 | self.replica_id 140 | } 141 | 142 | #[inline(always)] 143 | pub(crate) fn run_ts(&self) -> RunTs { 144 | self.contained_in 145 | } 146 | 147 | /// A special value used to create an anchor at the start of the document. 148 | #[inline] 149 | pub const fn zero() -> Self { 150 | Self { replica_id: 0, offset: 0, contained_in: 0 } 151 | } 152 | } 153 | 154 | #[cfg(feature = "encode")] 155 | mod encode { 156 | use super::*; 157 | use crate::encode::{BoolDecodeError, Decode, Encode, IntDecodeError}; 158 | 159 | impl Encode for Anchor { 160 | #[inline] 161 | fn encode(&self, buf: &mut Vec) { 162 | self.inner.encode(buf); 163 | self.bias.encode(buf); 164 | } 165 | } 166 | 167 | pub(crate) enum AnchorDecodeError { 168 | Bool(BoolDecodeError), 169 | Int(IntDecodeError), 170 | } 171 | 172 | impl From for AnchorDecodeError { 173 | #[inline(always)] 174 | fn from(err: BoolDecodeError) -> Self { 175 | Self::Bool(err) 176 | } 177 | } 178 | 179 | impl From for AnchorDecodeError { 180 | #[inline(always)] 181 | fn from(err: IntDecodeError) -> Self { 182 | Self::Int(err) 183 | } 184 | } 185 | 186 | impl core::fmt::Display for AnchorDecodeError { 187 | #[inline] 188 | fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { 189 | let err: &dyn core::fmt::Display = match self { 190 | Self::Bool(err) => err, 191 | Self::Int(err) => err, 192 | }; 193 | 194 | write!(f, "Anchor couldn't be decoded: {err}") 195 | } 196 | } 197 | 198 | impl Decode for Anchor { 199 | type Value = Self; 200 | 201 | type Error = AnchorDecodeError; 202 | 203 | #[inline] 204 | fn decode(buf: &[u8]) -> Result<(Self, &[u8]), Self::Error> { 205 | let (inner, buf) = InnerAnchor::decode(buf)?; 206 | let (bias, buf) = AnchorBias::decode(buf)?; 207 | let anchor = Self::new(inner, bias); 208 | Ok((anchor, buf)) 209 | } 210 | } 211 | 212 | impl Encode for InnerAnchor { 213 | #[inline] 214 | fn encode(&self, buf: &mut Vec) { 215 | self.replica_id().encode(buf); 216 | self.run_ts().encode(buf); 217 | self.offset().encode(buf); 218 | } 219 | } 220 | 221 | impl Decode for InnerAnchor { 222 | type Value = Self; 223 | 224 | type Error = IntDecodeError; 225 | 226 | #[inline] 227 | fn decode(buf: &[u8]) -> Result<(Self, &[u8]), Self::Error> { 228 | let (replica_id, buf) = ReplicaId::decode(buf)?; 229 | let (run_ts, buf) = RunTs::decode(buf)?; 230 | let (offset, buf) = Length::decode(buf)?; 231 | let anchor = Self::new(replica_id, offset, run_ts); 232 | Ok((anchor, buf)) 233 | } 234 | } 235 | 236 | impl Encode for AnchorBias { 237 | #[inline] 238 | fn encode(&self, buf: &mut Vec) { 239 | matches!(self, Self::Right).encode(buf); 240 | } 241 | } 242 | 243 | impl Decode for AnchorBias { 244 | type Value = Self; 245 | 246 | type Error = BoolDecodeError; 247 | 248 | #[inline] 249 | fn decode(buf: &[u8]) -> Result<(Self, &[u8]), Self::Error> { 250 | let (is_right, buf) = bool::decode(buf)?; 251 | let bias = if is_right { Self::Right } else { Self::Left }; 252 | Ok((bias, buf)) 253 | } 254 | } 255 | } 256 | 257 | #[cfg(feature = "serde")] 258 | mod serde { 259 | crate::encode::impl_serialize!(super::Anchor); 260 | crate::encode::impl_deserialize!(super::Anchor); 261 | } 262 | -------------------------------------------------------------------------------- /src/backlog.rs: -------------------------------------------------------------------------------- 1 | use alloc::collections::VecDeque; 2 | use core::ops::Range; 3 | 4 | use crate::*; 5 | 6 | /// A [`Replica`]'s backlog of remote edits that have been received from other 7 | /// replicas but have not yet been merged. 8 | /// 9 | /// See [`Replica::backlogged`] for more information. 10 | #[derive(Debug, Clone, Default, PartialEq)] 11 | pub(crate) struct Backlog { 12 | insertions: ReplicaIdMap, 13 | deletions: ReplicaIdMap, 14 | } 15 | 16 | impl Backlog { 17 | pub fn assert_invariants( 18 | &self, 19 | version_map: &VersionMap, 20 | deletion_map: &DeletionMap, 21 | ) { 22 | for (&id, insertions) in self.insertions.iter() { 23 | insertions.assert_invariants(id, version_map); 24 | } 25 | for (&id, deletions) in self.deletions.iter() { 26 | deletions.assert_invariants(id, deletion_map); 27 | } 28 | } 29 | 30 | /// Inserts a new [`Deletion`] into the backlog. 31 | /// 32 | /// Runs in `O(n)` in the number of deletions already in the backlog, with 33 | /// a best-case of `O(log n)`. 34 | /// 35 | /// # Panics 36 | /// 37 | /// Panics if the deletion has already been backlogged. 38 | #[inline] 39 | pub fn insert_deletion(&mut self, deletion: Deletion) { 40 | self.deletions 41 | .entry(deletion.deleted_by()) 42 | .or_default() 43 | .insert(deletion); 44 | } 45 | 46 | /// Inserts a new [`Insertion`] into the backlog. 47 | /// 48 | /// Runs in `O(n)` in the number of insertions already in the backlog, with 49 | /// a best-case of `O(log n)`. 50 | /// 51 | /// # Panics 52 | /// 53 | /// Panics if the insertion has already been backlogged. 54 | #[inline] 55 | pub fn insert_insertion(&mut self, insertion: Insertion) { 56 | self.insertions 57 | .entry(insertion.inserted_by()) 58 | .or_default() 59 | .insert(insertion); 60 | } 61 | 62 | /// Creates a new, empty `Backlog`. 63 | #[inline] 64 | pub fn new() -> Self { 65 | Self::default() 66 | } 67 | } 68 | 69 | /// Stores the backlogged [`Insertion`]s of a particular replica. 70 | #[derive(Clone, Default, PartialEq)] 71 | struct InsertionsBacklog { 72 | insertions: VecDeque, 73 | } 74 | 75 | impl core::fmt::Debug for InsertionsBacklog { 76 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 77 | f.debug_list() 78 | .entries(self.insertions.iter().map(|i| i.text().temporal_range())) 79 | .finish() 80 | } 81 | } 82 | 83 | impl InsertionsBacklog { 84 | fn assert_invariants(&self, id: ReplicaId, version_map: &VersionMap) { 85 | let Some(first) = self.insertions.front() else { 86 | return; 87 | }; 88 | 89 | assert!(version_map.get(id) <= first.start()); 90 | 91 | let mut prev_end = 0; 92 | 93 | for insertion in &self.insertions { 94 | assert_eq!(insertion.inserted_by(), id); 95 | assert!(insertion.start() >= prev_end); 96 | prev_end = insertion.end(); 97 | } 98 | } 99 | 100 | /// # Panics 101 | /// 102 | /// Panics if the insertion has already been inserted. 103 | #[inline] 104 | fn insert(&mut self, insertion: Insertion) { 105 | let offset = self 106 | .insertions 107 | .binary_search_by(|probe| probe.start().cmp(&insertion.start())) 108 | .unwrap_err(); 109 | 110 | self.insertions.insert(offset, insertion); 111 | } 112 | } 113 | 114 | /// Stores the backlogged [`Deletion`]s of a particular replica. 115 | #[derive(Clone, Default, PartialEq)] 116 | struct DeletionsBacklog { 117 | deletions: VecDeque, 118 | } 119 | 120 | impl core::fmt::Debug for DeletionsBacklog { 121 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 122 | f.debug_list() 123 | .entries(self.deletions.iter().map(|d| d.deletion_ts())) 124 | .finish() 125 | } 126 | } 127 | 128 | impl DeletionsBacklog { 129 | fn assert_invariants(&self, id: ReplicaId, deletion_map: &DeletionMap) { 130 | let Some(first) = self.deletions.front() else { 131 | return; 132 | }; 133 | 134 | assert!(deletion_map.get(id) <= first.deletion_ts()); 135 | 136 | let mut prev_ts = 0; 137 | 138 | for deletion in &self.deletions { 139 | assert_eq!(deletion.deleted_by(), id); 140 | assert!(deletion.deletion_ts() > prev_ts); 141 | prev_ts = deletion.deletion_ts(); 142 | } 143 | } 144 | 145 | /// # Panics 146 | /// 147 | /// Panics if the deletion has already inserted. 148 | #[inline] 149 | fn insert(&mut self, deletion: Deletion) { 150 | let offset = self 151 | .deletions 152 | .binary_search_by(|probe| { 153 | probe.deletion_ts().cmp(&deletion.deletion_ts()) 154 | }) 155 | .unwrap_err(); 156 | 157 | self.deletions.insert(offset, deletion); 158 | } 159 | } 160 | 161 | /// An iterator over the backlogged deletions that are ready to be 162 | /// applied to a [`Replica`]. 163 | /// 164 | /// This struct is created by the 165 | /// [`backlogged_deletions`](Replica::backlogged_deletions) method on 166 | /// [`Replica`]. See its documentation for more information. 167 | pub struct BackloggedDeletions<'a> { 168 | replica: &'a mut Replica, 169 | current: Option<&'a mut DeletionsBacklog>, 170 | iter: ReplicaIdMapValuesMut<'a, DeletionsBacklog>, 171 | } 172 | 173 | impl<'a> BackloggedDeletions<'a> { 174 | #[inline] 175 | pub(crate) fn from_replica(replica: &'a mut Replica) -> Self { 176 | let backlog = replica.backlog_mut(); 177 | 178 | // We transmute the exclusive reference to the backlog into the same 179 | // type to get around the borrow checker. 180 | // 181 | // SAFETY: this is safe because in the `Iterator` implementation we 182 | // never access the backlog through the `Replica`, neither directly nor 183 | // by calling any methods on `Replica` that would access the backlog. 184 | let backlog = unsafe { 185 | core::mem::transmute::<&mut Backlog, &mut Backlog>(backlog) 186 | }; 187 | 188 | let mut iter = backlog.deletions.values_mut(); 189 | 190 | let current = iter.next(); 191 | 192 | Self { replica, iter, current } 193 | } 194 | } 195 | 196 | impl Iterator for BackloggedDeletions<'_> { 197 | type Item = Vec>; 198 | 199 | #[inline] 200 | fn next(&mut self) -> Option { 201 | let deletions = self.current.as_mut()?; 202 | 203 | let Some(first) = deletions.deletions.front() else { 204 | self.current = self.iter.next(); 205 | return self.next(); 206 | }; 207 | 208 | if self.replica.can_merge_deletion(first) { 209 | let first = deletions.deletions.pop_front().unwrap(); 210 | let ranges = self.replica.merge_unchecked_deletion(&first); 211 | if ranges.is_empty() { 212 | self.next() 213 | } else { 214 | Some(ranges) 215 | } 216 | } else { 217 | self.current = self.iter.next(); 218 | self.next() 219 | } 220 | } 221 | } 222 | 223 | impl core::iter::FusedIterator for BackloggedDeletions<'_> {} 224 | 225 | /// An iterator over the backlogged insertions that are ready to be 226 | /// applied to a [`Replica`]. 227 | /// 228 | /// This struct is created by the 229 | /// [`backlogged_insertion`](Replica::backlogged_insertions) method on 230 | /// [`Replica`]. See its documentation for more information. 231 | pub struct BackloggedInsertions<'a> { 232 | replica: &'a mut Replica, 233 | current: Option<&'a mut InsertionsBacklog>, 234 | iter: ReplicaIdMapValuesMut<'a, InsertionsBacklog>, 235 | } 236 | 237 | impl<'a> BackloggedInsertions<'a> { 238 | #[inline] 239 | pub(crate) fn from_replica(replica: &'a mut Replica) -> Self { 240 | let backlog = replica.backlog_mut(); 241 | 242 | // We transmute the exclusive reference to the backlog into the same 243 | // type to get around the borrow checker. 244 | // 245 | // SAFETY: this is safe because in the `Iterator` implementation we 246 | // never access the backlog through the `Replica`, neither directly nor 247 | // by calling any methods on `Replica` that would access the backlog. 248 | let backlog = unsafe { 249 | core::mem::transmute::<&mut Backlog, &mut Backlog>(backlog) 250 | }; 251 | 252 | let mut iter = backlog.insertions.values_mut(); 253 | 254 | let current = iter.next(); 255 | 256 | Self { replica, current, iter } 257 | } 258 | } 259 | 260 | impl Iterator for BackloggedInsertions<'_> { 261 | type Item = (Text, Length); 262 | 263 | #[inline] 264 | fn next(&mut self) -> Option { 265 | let insertions = self.current.as_mut()?; 266 | 267 | let Some(first) = insertions.insertions.front() else { 268 | self.current = self.iter.next(); 269 | return self.next(); 270 | }; 271 | 272 | if self.replica.can_merge_insertion(first) { 273 | let first = insertions.insertions.pop_front().unwrap(); 274 | let edit = self.replica.merge_unchecked_insertion(&first); 275 | Some((first.text().clone(), edit)) 276 | } else { 277 | self.current = self.iter.next(); 278 | self.next() 279 | } 280 | } 281 | } 282 | 283 | impl core::iter::FusedIterator for BackloggedInsertions<'_> {} 284 | 285 | #[cfg(feature = "encode")] 286 | pub(crate) mod encode { 287 | use super::*; 288 | use crate::encode::{Decode, DecodeWithCtx, Encode, IntDecodeError}; 289 | use crate::version_map::encode::BaseMapDecodeError; 290 | 291 | impl InsertionsBacklog { 292 | #[inline(always)] 293 | fn iter(&self) -> impl Iterator + '_ { 294 | self.insertions.iter() 295 | } 296 | 297 | #[inline(always)] 298 | fn len(&self) -> usize { 299 | self.insertions.len() 300 | } 301 | 302 | #[inline(always)] 303 | fn push(&mut self, insertion: Insertion) { 304 | self.insertions.push_back(insertion); 305 | } 306 | } 307 | 308 | impl DeletionsBacklog { 309 | #[inline(always)] 310 | fn iter(&self) -> impl Iterator + '_ { 311 | self.deletions.iter() 312 | } 313 | 314 | #[inline(always)] 315 | fn len(&self) -> usize { 316 | self.deletions.len() 317 | } 318 | 319 | #[inline(always)] 320 | fn push(&mut self, deletion: Deletion) { 321 | self.deletions.push_back(deletion); 322 | } 323 | } 324 | 325 | impl Encode for Backlog { 326 | #[inline] 327 | fn encode(&self, buf: &mut Vec) { 328 | (self.insertions.len() as u64).encode(buf); 329 | 330 | for (id, insertions) in &self.insertions { 331 | ReplicaIdInsertions::new(*id, insertions).encode(buf); 332 | } 333 | 334 | (self.deletions.len() as u64).encode(buf); 335 | 336 | for (id, deletions) in &self.deletions { 337 | ReplicaIdDeletions::new(*id, deletions).encode(buf); 338 | } 339 | } 340 | } 341 | 342 | pub(crate) enum BacklogDecodeError { 343 | Int(IntDecodeError), 344 | VersionMap(BaseMapDecodeError), 345 | } 346 | 347 | impl From for BacklogDecodeError { 348 | #[inline(always)] 349 | fn from(err: IntDecodeError) -> Self { 350 | Self::Int(err) 351 | } 352 | } 353 | 354 | impl From> for BacklogDecodeError { 355 | #[inline(always)] 356 | fn from(err: BaseMapDecodeError) -> Self { 357 | Self::VersionMap(err) 358 | } 359 | } 360 | 361 | impl core::fmt::Display for BacklogDecodeError { 362 | #[inline] 363 | fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { 364 | let err: &dyn core::fmt::Display = match self { 365 | Self::VersionMap(err) => err, 366 | Self::Int(err) => err, 367 | }; 368 | 369 | write!(f, "Backlog: couldn't be decoded: {err}") 370 | } 371 | } 372 | 373 | impl Decode for Backlog { 374 | type Value = Self; 375 | 376 | type Error = BacklogDecodeError; 377 | 378 | #[inline] 379 | fn decode(buf: &[u8]) -> Result<(Self::Value, &[u8]), Self::Error> { 380 | let (num_replicas, mut buf) = u64::decode(buf)?; 381 | 382 | let mut insertions = ReplicaIdMap::default(); 383 | 384 | for _ in 0..num_replicas { 385 | let ((), new_buf) = 386 | ReplicaIdInsertions::decode(buf, &mut insertions)?; 387 | buf = new_buf; 388 | } 389 | 390 | let (num_replicas, mut buf) = u64::decode(buf)?; 391 | 392 | let mut deletions = ReplicaIdMap::default(); 393 | 394 | for _ in 0..num_replicas { 395 | let ((), new_buf) = 396 | ReplicaIdDeletions::decode(buf, &mut deletions)?; 397 | buf = new_buf; 398 | } 399 | 400 | let this = Self { insertions, deletions }; 401 | 402 | Ok((this, buf)) 403 | } 404 | } 405 | 406 | struct ReplicaIdInsertions<'a> { 407 | replica_id: ReplicaId, 408 | insertions: &'a InsertionsBacklog, 409 | } 410 | 411 | impl<'a> ReplicaIdInsertions<'a> { 412 | #[inline] 413 | fn new( 414 | replica_id: ReplicaId, 415 | insertions: &'a InsertionsBacklog, 416 | ) -> Self { 417 | Self { replica_id, insertions } 418 | } 419 | } 420 | 421 | impl Encode for ReplicaIdInsertions<'_> { 422 | #[inline] 423 | fn encode(&self, buf: &mut Vec) { 424 | self.replica_id.encode(buf); 425 | 426 | (self.insertions.len() as u64).encode(buf); 427 | 428 | for insertion in self.insertions.iter() { 429 | insertion.anchor().encode(buf); 430 | let range = insertion.text().temporal_range(); 431 | range.start.encode(buf); 432 | range.len().encode(buf); 433 | insertion.run_ts().encode(buf); 434 | insertion.lamport_ts().encode(buf); 435 | } 436 | } 437 | } 438 | 439 | impl DecodeWithCtx for ReplicaIdInsertions<'_> { 440 | type Value = (); 441 | 442 | type Ctx = ReplicaIdMap; 443 | 444 | type Error = BacklogDecodeError; 445 | 446 | #[inline] 447 | fn decode<'buf>( 448 | buf: &'buf [u8], 449 | ctx: &mut Self::Ctx, 450 | ) -> Result<((), &'buf [u8]), Self::Error> { 451 | let (replica_id, buf) = ReplicaId::decode(buf)?; 452 | 453 | let (num_insertions, mut buf) = u64::decode(buf)?; 454 | 455 | let mut insertions = InsertionsBacklog::default(); 456 | 457 | for _ in 0..num_insertions { 458 | let (anchor, new_buf) = InnerAnchor::decode(buf)?; 459 | let (start, new_buf) = Length::decode(new_buf)?; 460 | let (len, new_buf) = Length::decode(new_buf)?; 461 | let (run_ts, new_buf) = RunTs::decode(new_buf)?; 462 | let (lamport_ts, new_buf) = LamportTs::decode(new_buf)?; 463 | 464 | let insertion = Insertion::new( 465 | anchor, 466 | Text::new(replica_id, start..start + len), 467 | lamport_ts, 468 | run_ts, 469 | ); 470 | 471 | insertions.push(insertion); 472 | 473 | buf = new_buf; 474 | } 475 | 476 | ctx.insert(replica_id, insertions); 477 | 478 | Ok(((), buf)) 479 | } 480 | } 481 | 482 | struct ReplicaIdDeletions<'a> { 483 | replica_id: ReplicaId, 484 | deletions: &'a DeletionsBacklog, 485 | } 486 | 487 | impl<'a> ReplicaIdDeletions<'a> { 488 | #[inline] 489 | fn new( 490 | replica_id: ReplicaId, 491 | deletions: &'a DeletionsBacklog, 492 | ) -> Self { 493 | Self { replica_id, deletions } 494 | } 495 | } 496 | 497 | impl Encode for ReplicaIdDeletions<'_> { 498 | #[inline] 499 | fn encode(&self, buf: &mut Vec) { 500 | self.replica_id.encode(buf); 501 | 502 | (self.deletions.len() as u64).encode(buf); 503 | 504 | for deletion in self.deletions.iter() { 505 | deletion.start().encode(buf); 506 | deletion.end().encode(buf); 507 | deletion.version_map().encode(buf); 508 | deletion.deletion_ts().encode(buf); 509 | } 510 | } 511 | } 512 | 513 | impl DecodeWithCtx for ReplicaIdDeletions<'_> { 514 | type Value = (); 515 | 516 | type Ctx = ReplicaIdMap; 517 | 518 | type Error = BacklogDecodeError; 519 | 520 | #[inline] 521 | fn decode<'buf>( 522 | buf: &'buf [u8], 523 | ctx: &mut Self::Ctx, 524 | ) -> Result<((), &'buf [u8]), Self::Error> { 525 | let (replica_id, buf) = ReplicaId::decode(buf)?; 526 | 527 | let (num_deletions, mut buf) = u64::decode(buf)?; 528 | 529 | let mut deletions = DeletionsBacklog::default(); 530 | 531 | for _ in 0..num_deletions { 532 | let (start, new_buf) = InnerAnchor::decode(buf)?; 533 | let (end, new_buf) = InnerAnchor::decode(new_buf)?; 534 | let (version_map, new_buf) = VersionMap::decode(new_buf)?; 535 | let (deletion_ts, new_buf) = DeletionTs::decode(new_buf)?; 536 | 537 | let deletion = 538 | Deletion::new(start, end, version_map, deletion_ts); 539 | 540 | deletions.push(deletion); 541 | 542 | buf = new_buf; 543 | } 544 | 545 | ctx.insert(replica_id, deletions); 546 | 547 | Ok(((), buf)) 548 | } 549 | } 550 | } 551 | 552 | #[cfg(feature = "serde")] 553 | mod serde { 554 | crate::encode::impl_serialize!(super::Backlog); 555 | crate::encode::impl_deserialize!(super::Backlog); 556 | } 557 | -------------------------------------------------------------------------------- /src/deletion.rs: -------------------------------------------------------------------------------- 1 | use crate::anchor::InnerAnchor as Anchor; 2 | use crate::*; 3 | 4 | /// A deletion in CRDT coordinates. 5 | /// 6 | /// This struct is created by the [`deleted`](Replica::deleted) method on the 7 | /// [`Replica`] owned by the peer that performed the deletion, and can be 8 | /// integrated by another [`Replica`] via the 9 | /// [`integrate_deletion`](Replica::integrate_deletion) method. 10 | /// 11 | /// See the documentation of those methods for more information. 12 | #[derive(Debug, Clone, PartialEq, Eq)] 13 | pub struct Deletion { 14 | /// The anchor point of the start of the deleted range. 15 | start: Anchor, 16 | 17 | /// The anchor point of the end of the deleted range. 18 | end: Anchor, 19 | 20 | /// The version map of the replica at the time of the deletion. This is 21 | /// used by a `Replica` merging this deletion to determine: 22 | /// 23 | /// a) if it has all the text that the `Replica` who created the 24 | /// deletion had at the time of the deletion, and 25 | /// 26 | /// b) if there's some additional text within the deleted range that 27 | /// the `Replica` who created the deletion didn't have at the time 28 | /// of the deletion. 29 | version_map: VersionMap, 30 | 31 | /// The timestamp of this deletion. 32 | deletion_ts: DeletionTs, 33 | } 34 | 35 | impl Deletion { 36 | /// Returns the [`ReplicaId`] of the [`Replica`] that performed the 37 | /// deletion. 38 | /// 39 | /// # Examples 40 | /// 41 | /// ``` 42 | /// # use cola::Replica; 43 | /// let mut replica1 = Replica::new(1, 10); 44 | /// 45 | /// let deletion = replica1.deleted(3..7); 46 | /// 47 | /// assert_eq!(deletion.deleted_by(), replica1.id()); 48 | /// ``` 49 | #[inline(always)] 50 | pub fn deleted_by(&self) -> ReplicaId { 51 | self.version_map.this_id() 52 | } 53 | 54 | #[inline(always)] 55 | pub(crate) fn deletion_ts(&self) -> DeletionTs { 56 | self.deletion_ts 57 | } 58 | 59 | #[inline(always)] 60 | pub(crate) fn end(&self) -> Anchor { 61 | self.end 62 | } 63 | 64 | #[inline] 65 | pub(crate) fn is_no_op(&self) -> bool { 66 | self.end.is_zero() 67 | } 68 | 69 | #[inline] 70 | pub(crate) fn new( 71 | start: Anchor, 72 | end: Anchor, 73 | version_map: VersionMap, 74 | deletion_ts: DeletionTs, 75 | ) -> Self { 76 | Deletion { start, end, version_map, deletion_ts } 77 | } 78 | 79 | #[inline] 80 | pub(crate) fn no_op() -> Self { 81 | Self::new(Anchor::zero(), Anchor::zero(), VersionMap::new(0, 0), 0) 82 | } 83 | 84 | #[inline(always)] 85 | pub(crate) fn start(&self) -> Anchor { 86 | self.start 87 | } 88 | 89 | #[inline(always)] 90 | pub(crate) fn version_map(&self) -> &VersionMap { 91 | &self.version_map 92 | } 93 | } 94 | 95 | #[cfg(feature = "encode")] 96 | mod encode { 97 | use super::*; 98 | use crate::encode::{Decode, Encode, IntDecodeError}; 99 | use crate::version_map::encode::BaseMapDecodeError; 100 | 101 | impl Encode for Deletion { 102 | #[inline] 103 | fn encode(&self, buf: &mut Vec) { 104 | Anchors::new(&self.start, &self.end).encode(buf); 105 | self.version_map.encode(buf); 106 | self.deletion_ts.encode(buf); 107 | } 108 | } 109 | 110 | pub(crate) enum DeletionDecodeError { 111 | Anchors(AnchorsDecodeError), 112 | Int(IntDecodeError), 113 | VersionMap(BaseMapDecodeError), 114 | } 115 | 116 | impl From for DeletionDecodeError { 117 | #[inline(always)] 118 | fn from(err: AnchorsDecodeError) -> Self { 119 | Self::Anchors(err) 120 | } 121 | } 122 | 123 | impl From for DeletionDecodeError { 124 | #[inline(always)] 125 | fn from(err: IntDecodeError) -> Self { 126 | Self::Int(err) 127 | } 128 | } 129 | 130 | impl From> for DeletionDecodeError { 131 | #[inline(always)] 132 | fn from(err: BaseMapDecodeError) -> Self { 133 | Self::VersionMap(err) 134 | } 135 | } 136 | 137 | impl core::fmt::Display for DeletionDecodeError { 138 | #[inline] 139 | fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { 140 | let err: &dyn core::fmt::Display = match self { 141 | Self::Anchors(err) => err, 142 | Self::Int(err) => err, 143 | Self::VersionMap(err) => err, 144 | }; 145 | 146 | write!(f, "Deletion couldn't be decoded: {err}") 147 | } 148 | } 149 | 150 | impl Decode for Deletion { 151 | type Value = Self; 152 | 153 | type Error = DeletionDecodeError; 154 | 155 | #[inline] 156 | fn decode(buf: &[u8]) -> Result<(Self, &[u8]), Self::Error> { 157 | let ((start, end), buf) = Anchors::decode(buf)?; 158 | let (version_map, buf) = VersionMap::decode(buf)?; 159 | let (deletion_ts, buf) = DeletionTs::decode(buf)?; 160 | let this = Self::new(start, end, version_map, deletion_ts); 161 | Ok((this, buf)) 162 | } 163 | } 164 | 165 | /// TODO: docs 166 | #[allow(dead_code)] 167 | struct Anchors<'a> { 168 | start: &'a InnerAnchor, 169 | end: &'a InnerAnchor, 170 | } 171 | 172 | impl<'a> Anchors<'a> { 173 | #[inline] 174 | fn new(start: &'a InnerAnchor, end: &'a InnerAnchor) -> Self { 175 | Self { start, end } 176 | } 177 | } 178 | 179 | impl Encode for Anchors<'_> { 180 | #[inline] 181 | fn encode(&self, buf: &mut Vec) { 182 | let flag = AnchorsFlag::from_anchors(self); 183 | 184 | flag.encode(buf); 185 | 186 | match flag { 187 | AnchorsFlag::DifferentReplicaIds => { 188 | self.start.encode(buf); 189 | self.end.encode(buf); 190 | }, 191 | AnchorsFlag::SameReplicaId => { 192 | self.start.replica_id().encode(buf); 193 | self.start.run_ts().encode(buf); 194 | self.start.offset().encode(buf); 195 | self.end.run_ts().encode(buf); 196 | self.end.offset().encode(buf); 197 | }, 198 | AnchorsFlag::SameReplicaIdAndRunTs => { 199 | self.start.replica_id().encode(buf); 200 | self.start.run_ts().encode(buf); 201 | self.start.offset().encode(buf); 202 | // The start and end are on the same run, so we know that 203 | // the end has a greater offset than the start. 204 | let length = self.end.offset() - self.start.offset(); 205 | length.encode(buf); 206 | }, 207 | } 208 | } 209 | } 210 | 211 | pub(crate) enum AnchorsDecodeError { 212 | Int(IntDecodeError), 213 | Flag(AnchorsFlagDecodeError), 214 | } 215 | 216 | impl From for AnchorsDecodeError { 217 | #[inline(always)] 218 | fn from(err: IntDecodeError) -> Self { 219 | Self::Int(err) 220 | } 221 | } 222 | 223 | impl From for AnchorsDecodeError { 224 | #[inline(always)] 225 | fn from(err: AnchorsFlagDecodeError) -> Self { 226 | Self::Flag(err) 227 | } 228 | } 229 | 230 | impl core::fmt::Display for AnchorsDecodeError { 231 | #[inline] 232 | fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { 233 | let err: &dyn core::fmt::Display = match self { 234 | Self::Int(err) => err, 235 | Self::Flag(err) => err, 236 | }; 237 | 238 | write!(f, "Anchors couldn't be decoded: {err}") 239 | } 240 | } 241 | 242 | impl Decode for Anchors<'_> { 243 | type Value = (Anchor, Anchor); 244 | 245 | type Error = AnchorsDecodeError; 246 | 247 | #[inline] 248 | fn decode(buf: &[u8]) -> Result<(Self::Value, &[u8]), Self::Error> { 249 | let (flag, buf) = AnchorsFlag::decode(buf)?; 250 | 251 | match flag { 252 | AnchorsFlag::DifferentReplicaIds => { 253 | let (start, buf) = InnerAnchor::decode(buf)?; 254 | let (end, buf) = InnerAnchor::decode(buf)?; 255 | Ok(((start, end), buf)) 256 | }, 257 | AnchorsFlag::SameReplicaId => { 258 | let (replica_id, buf) = ReplicaId::decode(buf)?; 259 | let (start_run_ts, buf) = RunTs::decode(buf)?; 260 | let (start_offset, buf) = Length::decode(buf)?; 261 | let (end_run_ts, buf) = RunTs::decode(buf)?; 262 | let (end_offset, buf) = Length::decode(buf)?; 263 | 264 | let start = InnerAnchor::new( 265 | replica_id, 266 | start_offset, 267 | start_run_ts, 268 | ); 269 | 270 | let end = 271 | InnerAnchor::new(replica_id, end_offset, end_run_ts); 272 | 273 | Ok(((start, end), buf)) 274 | }, 275 | AnchorsFlag::SameReplicaIdAndRunTs => { 276 | let (replica_id, buf) = ReplicaId::decode(buf)?; 277 | let (run_ts, buf) = RunTs::decode(buf)?; 278 | let (offset, buf) = Length::decode(buf)?; 279 | let (length, buf) = Length::decode(buf)?; 280 | let start = InnerAnchor::new(replica_id, offset, run_ts); 281 | let end = 282 | InnerAnchor::new(replica_id, offset + length, run_ts); 283 | Ok(((start, end), buf)) 284 | }, 285 | } 286 | } 287 | } 288 | 289 | enum AnchorsFlag { 290 | SameReplicaId, 291 | SameReplicaIdAndRunTs, 292 | DifferentReplicaIds, 293 | } 294 | 295 | impl AnchorsFlag { 296 | #[inline(always)] 297 | fn from_anchors(anchors: &Anchors) -> Self { 298 | if anchors.start.replica_id() == anchors.end.replica_id() { 299 | if anchors.start.run_ts() == anchors.end.run_ts() { 300 | Self::SameReplicaIdAndRunTs 301 | } else { 302 | Self::SameReplicaId 303 | } 304 | } else { 305 | Self::DifferentReplicaIds 306 | } 307 | } 308 | } 309 | 310 | impl Encode for AnchorsFlag { 311 | #[inline] 312 | fn encode(&self, buf: &mut Vec) { 313 | let flag: u8 = match self { 314 | Self::DifferentReplicaIds => 0, 315 | Self::SameReplicaId => 1, 316 | Self::SameReplicaIdAndRunTs => 2, 317 | }; 318 | 319 | buf.push(flag); 320 | } 321 | } 322 | 323 | pub(crate) enum AnchorsFlagDecodeError { 324 | EmptyBuffer, 325 | InvalidByte(u8), 326 | } 327 | 328 | impl core::fmt::Display for AnchorsFlagDecodeError { 329 | #[inline] 330 | fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { 331 | match self { 332 | Self::EmptyBuffer => write!( 333 | f, 334 | "AnchorsFlag can't be decoded from an empty buffer" 335 | ), 336 | Self::InvalidByte(byte) => write!( 337 | f, 338 | "AnchorsFlag can't be decoded from {byte}, it must be 0, \ 339 | 1, or 2", 340 | ), 341 | } 342 | } 343 | } 344 | 345 | impl Decode for AnchorsFlag { 346 | type Value = Self; 347 | 348 | type Error = AnchorsFlagDecodeError; 349 | 350 | #[inline] 351 | fn decode(buf: &[u8]) -> Result<(Self::Value, &[u8]), Self::Error> { 352 | let (&flag, buf) = buf 353 | .split_first() 354 | .ok_or(AnchorsFlagDecodeError::EmptyBuffer)?; 355 | 356 | let this = match flag { 357 | 0 => Self::DifferentReplicaIds, 358 | 1 => Self::SameReplicaId, 359 | 2 => Self::SameReplicaIdAndRunTs, 360 | _ => return Err(AnchorsFlagDecodeError::InvalidByte(flag)), 361 | }; 362 | 363 | Ok((this, buf)) 364 | } 365 | } 366 | } 367 | 368 | #[cfg(feature = "serde")] 369 | mod serde { 370 | crate::encode::impl_serialize!(super::Deletion); 371 | crate::encode::impl_deserialize!(super::Deletion); 372 | } 373 | -------------------------------------------------------------------------------- /src/encode.rs: -------------------------------------------------------------------------------- 1 | use core::fmt::Display; 2 | 3 | /// TODO: docs 4 | pub(crate) trait Encode { 5 | /// TODO: docs 6 | fn encode(&self, buf: &mut Vec); 7 | } 8 | 9 | /// TODO: docs 10 | pub(crate) trait Decode { 11 | type Value: Sized; 12 | 13 | type Error: Display; 14 | 15 | /// TODO: docs 16 | fn decode(buf: &[u8]) -> Result<(Self::Value, &[u8]), Self::Error>; 17 | } 18 | 19 | /// TODO: docs 20 | pub(crate) trait DecodeWithCtx { 21 | type Value: Sized; 22 | 23 | type Error: Display; 24 | 25 | type Ctx; 26 | 27 | /// TODO: docs 28 | fn decode<'buf>( 29 | buf: &'buf [u8], 30 | ctx: &mut Self::Ctx, 31 | ) -> Result<(Self::Value, &'buf [u8]), Self::Error>; 32 | } 33 | 34 | impl Encode for bool { 35 | #[inline] 36 | fn encode(&self, buf: &mut Vec) { 37 | buf.push(*self as u8); 38 | } 39 | } 40 | 41 | impl Decode for bool { 42 | type Value = bool; 43 | 44 | type Error = BoolDecodeError; 45 | 46 | #[inline] 47 | fn decode(buf: &[u8]) -> Result<(bool, &[u8]), Self::Error> { 48 | let (&byte, buf) = 49 | buf.split_first().ok_or(BoolDecodeError::EmptyBuffer)?; 50 | 51 | match byte { 52 | 0 => Ok((false, buf)), 53 | 1 => Ok((true, buf)), 54 | _ => Err(BoolDecodeError::InvalidByte(byte)), 55 | } 56 | } 57 | } 58 | 59 | pub(crate) enum BoolDecodeError { 60 | EmptyBuffer, 61 | InvalidByte(u8), 62 | } 63 | 64 | impl core::fmt::Display for BoolDecodeError { 65 | #[inline] 66 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 67 | match self { 68 | Self::EmptyBuffer => f.write_str( 69 | "bool couldn't be decoded because the buffer is empty", 70 | ), 71 | Self::InvalidByte(byte) => { 72 | write!( 73 | f, 74 | "bool cannot be decoded from byte {byte}, it must be 0 \ 75 | or 1", 76 | ) 77 | }, 78 | } 79 | } 80 | } 81 | 82 | impl_int_encode!(u16); 83 | impl_int_encode!(u32); 84 | impl_int_encode!(u64); 85 | 86 | impl_int_decode!(u16); 87 | impl_int_decode!(u32); 88 | impl_int_decode!(u64); 89 | 90 | impl Encode for usize { 91 | #[inline(always)] 92 | fn encode(&self, buf: &mut Vec) { 93 | (*self as u64).encode(buf) 94 | } 95 | } 96 | 97 | impl Decode for usize { 98 | type Value = usize; 99 | 100 | type Error = IntDecodeError; 101 | 102 | #[inline(always)] 103 | fn decode(buf: &[u8]) -> Result<(usize, &[u8]), Self::Error> { 104 | u64::decode(buf).map(|(value, rest)| (value as usize, rest)) 105 | } 106 | } 107 | 108 | macro_rules! impl_int_encode { 109 | ($ty:ty) => { 110 | impl Encode for $ty { 111 | #[inline] 112 | fn encode(&self, buf: &mut Vec) { 113 | let (array, len) = varint_simd::encode(*self); 114 | buf.extend_from_slice(&array[..len as usize]); 115 | } 116 | } 117 | }; 118 | } 119 | 120 | use impl_int_encode; 121 | 122 | macro_rules! impl_int_decode { 123 | ($ty:ty) => { 124 | impl Decode for $ty { 125 | type Value = Self; 126 | 127 | type Error = $crate::encode::IntDecodeError; 128 | 129 | #[inline] 130 | fn decode(buf: &[u8]) -> Result<($ty, &[u8]), Self::Error> { 131 | let (decoded, len) = varint_simd::decode::(buf) 132 | .map_err(IntDecodeError)?; 133 | 134 | // TODO: this check shouldn't be necessary, `decode` should 135 | // fail. Open an issue. 136 | let Some(rest) = buf.get(len as usize..) else { 137 | return Err(IntDecodeError( 138 | varint_simd::VarIntDecodeError::NotEnoughBytes, 139 | )); 140 | }; 141 | 142 | Ok((decoded, rest)) 143 | } 144 | } 145 | }; 146 | } 147 | 148 | use impl_int_decode; 149 | 150 | /// An error that can occur when decoding an [`Int`]. 151 | pub(crate) struct IntDecodeError(varint_simd::VarIntDecodeError); 152 | 153 | impl Display for IntDecodeError { 154 | #[inline] 155 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 156 | Display::fmt(&self.0, f) 157 | } 158 | } 159 | 160 | #[cfg(feature = "serde")] 161 | pub(crate) use serde::{impl_deserialize, impl_serialize}; 162 | 163 | #[cfg(feature = "serde")] 164 | mod serde { 165 | macro_rules! impl_deserialize { 166 | ($ty:ty) => { 167 | impl<'de> ::serde::de::Deserialize<'de> for $ty { 168 | #[inline] 169 | fn deserialize(deserializer: D) -> Result 170 | where 171 | D: ::serde::de::Deserializer<'de>, 172 | { 173 | struct Visitor; 174 | 175 | impl<'de> ::serde::de::Visitor<'de> for Visitor { 176 | type Value = <$ty as $crate::encode::Decode>::Value; 177 | 178 | #[inline] 179 | fn expecting( 180 | &self, 181 | formatter: &mut ::core::fmt::Formatter, 182 | ) -> ::core::fmt::Result { 183 | formatter.write_str("a byte slice") 184 | } 185 | 186 | #[inline] 187 | fn visit_bytes( 188 | self, 189 | v: &[u8], 190 | ) -> Result 191 | where 192 | E: ::serde::de::Error, 193 | { 194 | ::decode(v) 195 | .map(|(value, _rest)| value) 196 | .map_err(E::custom) 197 | } 198 | 199 | #[inline] 200 | fn visit_seq( 201 | self, 202 | mut seq: A, 203 | ) -> Result 204 | where 205 | A: ::serde::de::SeqAccess<'de>, 206 | { 207 | let size = seq.size_hint().unwrap_or(0); 208 | let mut buf = 209 | ::alloc::vec::Vec::::with_capacity(size); 210 | while let Some(byte) = seq.next_element()? { 211 | buf.push(byte); 212 | } 213 | ::decode( 214 | &buf, 215 | ) 216 | .map(|(value, _rest)| value) 217 | .map_err(::custom) 218 | } 219 | } 220 | 221 | deserializer.deserialize_bytes(Visitor) 222 | } 223 | } 224 | }; 225 | } 226 | 227 | macro_rules! impl_serialize { 228 | ($ty:ty) => { 229 | impl ::serde::ser::Serialize for $ty { 230 | #[inline] 231 | fn serialize( 232 | &self, 233 | serializer: S, 234 | ) -> Result 235 | where 236 | S: ::serde::ser::Serializer, 237 | { 238 | let mut buf = Vec::new(); 239 | ::encode(&self, &mut buf); 240 | serializer.serialize_bytes(&buf) 241 | } 242 | } 243 | }; 244 | } 245 | 246 | pub(crate) use impl_deserialize; 247 | pub(crate) use impl_serialize; 248 | } 249 | 250 | #[cfg(test)] 251 | mod tests { 252 | use super::*; 253 | 254 | impl PartialEq for IntDecodeError { 255 | fn eq(&self, other: &Self) -> bool { 256 | use varint_simd::VarIntDecodeError::*; 257 | matches!( 258 | (&self.0, &other.0), 259 | (Overflow, Overflow) | (NotEnoughBytes, NotEnoughBytes) 260 | ) 261 | } 262 | } 263 | 264 | impl core::fmt::Debug for IntDecodeError { 265 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 266 | core::fmt::Display::fmt(self, f) 267 | } 268 | } 269 | 270 | /// Tests that some integers can be encoded with a single byte. 271 | #[test] 272 | fn encode_int_single_byte() { 273 | let mut buf = Vec::new(); 274 | 275 | for int in 0..1 << 6 { 276 | int.encode(&mut buf); 277 | assert_eq!(buf.len(), 1); 278 | let (decoded, rest) = u64::decode(&buf).unwrap(); 279 | assert_eq!(int, decoded); 280 | assert!(rest.is_empty()); 281 | buf.clear(); 282 | } 283 | } 284 | 285 | /// Tests the encoding-decoding roundtrip on a number of inputs. 286 | #[test] 287 | fn encode_int_roundtrip() { 288 | let ints = (1..=8).chain([ 289 | 0, 290 | (1 << 6) - 1, 291 | 1 << 6, 292 | (1 << 6) + 1, 293 | (1 << 15) - 1, 294 | 1 << 15, 295 | (1 << 15) + 1, 296 | u16::MAX as u64 - 1, 297 | u16::MAX as u64, 298 | u16::MAX as u64 + 1, 299 | u32::MAX as u64, 300 | u32::MAX as u64 + 1, 301 | u64::MAX, 302 | ]); 303 | 304 | let mut buf = Vec::new(); 305 | 306 | for int in ints { 307 | int.encode(&mut buf); 308 | let (decoded, rest) = u64::decode(&buf).unwrap(); 309 | assert_eq!(int, decoded); 310 | assert!(rest.is_empty()); 311 | buf.clear(); 312 | } 313 | } 314 | 315 | /// Tests that decoding an integer fails if the buffer is empty. 316 | #[test] 317 | fn encode_int_fails_if_buffer_empty() { 318 | let mut buf = Vec::new(); 319 | 320 | 42u32.encode(&mut buf); 321 | 322 | buf.clear(); 323 | 324 | assert_eq!( 325 | u32::decode(&buf).unwrap_err(), 326 | IntDecodeError(varint_simd::VarIntDecodeError::NotEnoughBytes), 327 | ); 328 | } 329 | 330 | /// Tests that decoding an integer fails if the length specified in the 331 | /// prefix is greater than the actual length of the buffer. 332 | #[test] 333 | fn encode_int_fails_if_buffer_too_short() { 334 | let mut buf = Vec::new(); 335 | 336 | u16::MAX.encode(&mut buf); 337 | 338 | buf.pop(); 339 | 340 | assert_eq!( 341 | u32::decode(&buf).unwrap_err(), 342 | IntDecodeError(varint_simd::VarIntDecodeError::NotEnoughBytes), 343 | ); 344 | } 345 | } 346 | -------------------------------------------------------------------------------- /src/encoded_replica.rs: -------------------------------------------------------------------------------- 1 | use core::fmt; 2 | use core::ops::Deref; 3 | 4 | use sha2::{Digest, Sha256}; 5 | 6 | use crate::encode::{Decode, Encode}; 7 | use crate::*; 8 | 9 | type Checksum = [u8; 32]; 10 | 11 | const CHECKSUM_LEN: usize = core::mem::size_of::(); 12 | 13 | /// A [`Replica`] encoded into a compact binary format suitable for 14 | /// transmission over the network. 15 | /// 16 | /// This struct is created by [`encode`](Replica::encode)ing a [`Replica`] and 17 | /// can be decoded back into a [`Replica`] by calling 18 | /// [`decode`](Replica::decode). See the documentation of those methods for 19 | /// more information. 20 | #[cfg_attr(docsrs, doc(cfg(feature = "encode")))] 21 | #[derive(Clone)] 22 | pub struct EncodedReplica<'buf> { 23 | bytes: Bytes<'buf>, 24 | } 25 | 26 | #[derive(Clone)] 27 | enum Bytes<'a> { 28 | Owned(Box<[u8]>), 29 | Borrowed(&'a [u8]), 30 | } 31 | 32 | impl<'buf> EncodedReplica<'buf> { 33 | /// Returns the raw bytes of the encoded replica. 34 | #[inline] 35 | pub fn as_bytes(&self) -> &[u8] { 36 | &self.bytes 37 | } 38 | 39 | /// Creates an `EncodedReplica` from the given bytes. 40 | #[inline] 41 | pub fn from_bytes(bytes: &'buf [u8]) -> Self { 42 | bytes.into() 43 | } 44 | 45 | /// Copies the underlying bytes into a new `EncodedReplica` with a static 46 | /// lifetime. 47 | #[inline] 48 | pub fn to_static(&self) -> EncodedReplica<'static> { 49 | EncodedReplica { 50 | bytes: match &self.bytes { 51 | Bytes::Owned(bytes) => Bytes::Owned(bytes.clone()), 52 | Bytes::Borrowed(bytes) => Bytes::Owned((*bytes).into()), 53 | }, 54 | } 55 | } 56 | 57 | #[inline] 58 | pub(crate) fn to_replica( 59 | &self, 60 | ) -> Result<::Value, DecodeError> { 61 | let bytes = &*self.bytes; 62 | 63 | let (protocol_version, buf) = ProtocolVersion::decode(bytes) 64 | .map_err(|_| DecodeError::InvalidData)?; 65 | 66 | if protocol_version != crate::PROTOCOL_VERSION { 67 | return Err(DecodeError::DifferentProtocol { 68 | encoded_on: protocol_version, 69 | decoding_on: crate::PROTOCOL_VERSION, 70 | }); 71 | } 72 | 73 | if buf.len() < CHECKSUM_LEN { 74 | return Err(DecodeError::InvalidData); 75 | } 76 | 77 | let (checksum_slice, buf) = buf.split_at(CHECKSUM_LEN); 78 | 79 | if checksum_slice != checksum(buf) { 80 | return Err(DecodeError::ChecksumFailed); 81 | } 82 | 83 | ::decode(buf) 84 | .map(|(value, _rest)| value) 85 | .map_err(|_| DecodeError::InvalidData) 86 | } 87 | } 88 | 89 | impl EncodedReplica<'static> { 90 | #[inline] 91 | pub(crate) fn from_replica(replica: &Replica) -> Self { 92 | let mut bytes = Vec::new(); 93 | crate::PROTOCOL_VERSION.encode(&mut bytes); 94 | let protocol_len = bytes.len(); 95 | let dummy_checksum = Checksum::default(); 96 | bytes.extend_from_slice(&dummy_checksum); 97 | Encode::encode(replica, &mut bytes); 98 | let replica_start = protocol_len + CHECKSUM_LEN; 99 | let checksum = checksum(&bytes[replica_start..]); 100 | bytes[protocol_len..protocol_len + CHECKSUM_LEN] 101 | .copy_from_slice(&checksum); 102 | Self { bytes: Bytes::Owned(bytes.into()) } 103 | } 104 | } 105 | 106 | impl fmt::Debug for EncodedReplica<'_> { 107 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 108 | f.debug_struct("EncodedReplica").finish_non_exhaustive() 109 | } 110 | } 111 | 112 | impl<'buf> From<&'buf [u8]> for EncodedReplica<'buf> { 113 | #[inline] 114 | fn from(bytes: &'buf [u8]) -> Self { 115 | Self { bytes: Bytes::Borrowed(bytes) } 116 | } 117 | } 118 | 119 | impl From> for EncodedReplica<'static> { 120 | #[inline] 121 | fn from(bytes: Box<[u8]>) -> Self { 122 | Self { bytes: Bytes::Owned(bytes) } 123 | } 124 | } 125 | 126 | impl AsRef<[u8]> for EncodedReplica<'_> { 127 | #[inline] 128 | fn as_ref(&self) -> &[u8] { 129 | &*self.bytes 130 | } 131 | } 132 | 133 | impl Deref for EncodedReplica<'_> { 134 | type Target = [u8]; 135 | 136 | #[inline] 137 | fn deref(&self) -> &Self::Target { 138 | &*self.bytes 139 | } 140 | } 141 | 142 | impl PartialEq for EncodedReplica<'_> { 143 | #[inline] 144 | fn eq(&self, other: &Self) -> bool { 145 | *self.bytes == *other.bytes 146 | } 147 | } 148 | 149 | impl Eq for EncodedReplica<'_> {} 150 | 151 | impl Encode for EncodedReplica<'_> { 152 | #[inline] 153 | fn encode(&self, buf: &mut Vec) { 154 | debug_assert!(buf.is_empty()); 155 | buf.extend_from_slice(&*self.bytes); 156 | } 157 | } 158 | 159 | impl Decode for EncodedReplica<'static> { 160 | type Value = Self; 161 | type Error = core::convert::Infallible; 162 | 163 | #[inline] 164 | fn decode(buf: &[u8]) -> Result<(Self, &[u8]), Self::Error> { 165 | Ok((EncodedReplica::from_bytes(buf).to_static(), &[])) 166 | } 167 | } 168 | 169 | impl Deref for Bytes<'_> { 170 | type Target = [u8]; 171 | 172 | #[inline] 173 | fn deref(&self) -> &Self::Target { 174 | match self { 175 | Bytes::Owned(bytes) => bytes, 176 | Bytes::Borrowed(bytes) => bytes, 177 | } 178 | } 179 | } 180 | 181 | /// The type of error that can occur when [`decode`](Replica::decode)ing an 182 | /// [`EncodedReplica`]. 183 | #[cfg_attr(docsrs, doc(cfg(feature = "encode")))] 184 | #[derive(Debug, Clone, PartialEq, Eq)] 185 | pub enum DecodeError { 186 | /// This error occurs when the internal checksum of the [`EncodedReplica`] 187 | /// fails. 188 | /// 189 | /// This typically means that the [`EncodedReplica`] was corrupted during 190 | /// transmission. 191 | ChecksumFailed, 192 | 193 | /// This error occurs when the machine that created the [`EncodedReplica`] 194 | /// and the one that is trying to [`decode`](Replica::decode) it are using 195 | /// two incompatible versions of cola. 196 | DifferentProtocol { 197 | /// The `ProtocolVersion` of cola on the machine that created the 198 | /// `EncodedReplica`. 199 | encoded_on: ProtocolVersion, 200 | 201 | /// The `ProtocolVersion` of cola on the machine that is trying to 202 | /// decode the `EncodedReplica`. 203 | decoding_on: ProtocolVersion, 204 | }, 205 | 206 | /// This error is an umbrella variant that encompasses all other errors 207 | /// that can occur when the binary data wrapped by the [`EncodedReplica`] 208 | /// cannot be decoded into a `Replica`. 209 | /// 210 | /// This is returned when the checksum and protocol version checks both 211 | /// succeed, *and yet* the data is still invalid. The only way this can 212 | /// occur in practice is if the `EncodedReplica` passed to 213 | /// [`decode`](Replica::decode) was deserialized from a byte vector that 214 | /// was not the result of serializing an `EncodedReplica`. 215 | /// 216 | /// As long as you're not doing that (and you shouldn't be) this variant 217 | /// can be ignored. 218 | InvalidData, 219 | } 220 | 221 | impl fmt::Display for DecodeError { 222 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 223 | match self { 224 | DecodeError::ChecksumFailed => f.write_str("checksum failed"), 225 | DecodeError::DifferentProtocol { encoded_on, decoding_on } => { 226 | write!( 227 | f, 228 | "different protocol: encoded on {encoded_on:?}, decoding \ 229 | on {decoding_on:?}", 230 | ) 231 | }, 232 | DecodeError::InvalidData => f.write_str("invalid data"), 233 | } 234 | } 235 | } 236 | 237 | impl std::error::Error for DecodeError {} 238 | 239 | #[inline(always)] 240 | pub(crate) fn checksum(bytes: &[u8]) -> Checksum { 241 | let checksum = Sha256::digest(bytes); 242 | *checksum.as_ref() 243 | } 244 | 245 | #[cfg(feature = "serde")] 246 | mod serde { 247 | crate::encode::impl_serialize!(super::EncodedReplica<'_>); 248 | crate::encode::impl_deserialize!(super::EncodedReplica<'static>); 249 | } 250 | -------------------------------------------------------------------------------- /src/insertion.rs: -------------------------------------------------------------------------------- 1 | use crate::anchor::InnerAnchor as Anchor; 2 | use crate::{LamportTs, Length, ReplicaId, RunTs, Text}; 3 | 4 | /// An insertion in CRDT coordinates. 5 | /// 6 | /// This struct is created by the [`inserted`] method on the [`Replica`] owned 7 | /// by the peer that performed the insertion, and can be integrated by another 8 | /// [`Replica`] via the [`integrate_insertion`] method. 9 | /// 10 | /// See the documentation of those methods for more information. 11 | /// 12 | /// [`Replica`]: crate::Replica 13 | /// [`inserted`]: crate::Replica::inserted 14 | /// [`integrate_insertion`]: crate::Replica::integrate_insertion 15 | #[derive(Debug, Clone, PartialEq, Eq)] 16 | pub struct Insertion { 17 | /// The anchor point of the insertion. 18 | anchor: Anchor, 19 | 20 | /// Contains the replica that made the insertion and the temporal range 21 | /// of the text that was inserted. 22 | text: Text, 23 | 24 | /// The run timestamp of this insertion. 25 | run_ts: RunTs, 26 | 27 | /// The Lamport timestamp of this insertion. 28 | lamport_ts: LamportTs, 29 | } 30 | 31 | impl Insertion { 32 | #[inline(always)] 33 | pub(crate) fn anchor(&self) -> Anchor { 34 | self.anchor 35 | } 36 | 37 | #[inline(always)] 38 | pub(crate) fn end(&self) -> Length { 39 | self.text.range.end 40 | } 41 | 42 | /// Returns the [`ReplicaId`] of the [`Replica`](crate::Replica) that 43 | /// performed the insertion. 44 | /// 45 | /// # Examples 46 | /// 47 | /// ``` 48 | /// # use cola::Replica; 49 | /// let mut replica = Replica::new(1, 3); 50 | /// let insertion = replica.inserted(3, 7); 51 | /// assert_eq!(insertion.inserted_by(), replica.id()); 52 | /// ``` 53 | #[inline] 54 | pub fn inserted_by(&self) -> ReplicaId { 55 | self.text.inserted_by() 56 | } 57 | 58 | #[inline] 59 | pub(crate) fn is_no_op(&self) -> bool { 60 | self.len() == 0 61 | } 62 | 63 | #[inline(always)] 64 | pub(crate) fn run_ts(&self) -> RunTs { 65 | self.run_ts 66 | } 67 | 68 | #[inline(always)] 69 | pub(crate) fn lamport_ts(&self) -> LamportTs { 70 | self.lamport_ts 71 | } 72 | 73 | #[inline] 74 | pub(crate) fn len(&self) -> Length { 75 | self.text.len() 76 | } 77 | 78 | #[inline] 79 | pub(crate) fn new( 80 | anchor: Anchor, 81 | text: Text, 82 | lamport_ts: LamportTs, 83 | run_ts: RunTs, 84 | ) -> Self { 85 | Self { anchor, text, lamport_ts, run_ts } 86 | } 87 | 88 | #[inline] 89 | pub(crate) fn no_op() -> Self { 90 | Self::new(Anchor::zero(), Text::new(0, 0..0), 0, 0) 91 | } 92 | 93 | #[inline] 94 | pub(crate) fn start(&self) -> Length { 95 | self.text.range.start 96 | } 97 | 98 | /// The [`Text`] of this insertion. 99 | #[inline] 100 | pub fn text(&self) -> &Text { 101 | &self.text 102 | } 103 | } 104 | 105 | #[cfg(feature = "encode")] 106 | mod encode { 107 | use super::*; 108 | use crate::encode::{BoolDecodeError, Decode, Encode, IntDecodeError}; 109 | 110 | impl Insertion { 111 | #[inline] 112 | fn encode_anchor(&self, run: InsertionRun, buf: &mut Vec) { 113 | match run { 114 | InsertionRun::BeginsNew => self.anchor.encode(buf), 115 | InsertionRun::ContinuesExisting => {}, 116 | } 117 | } 118 | 119 | #[inline] 120 | fn decode_anchor<'buf>( 121 | run: InsertionRun, 122 | text: &Text, 123 | run_ts: RunTs, 124 | buf: &'buf [u8], 125 | ) -> Result<(Anchor, &'buf [u8]), ::Error> { 126 | match run { 127 | InsertionRun::BeginsNew => Anchor::decode(buf), 128 | 129 | InsertionRun::ContinuesExisting => { 130 | let anchor = 131 | Anchor::new(text.inserted_by(), text.start(), run_ts); 132 | Ok((anchor, buf)) 133 | }, 134 | } 135 | } 136 | } 137 | 138 | impl Encode for Insertion { 139 | #[inline] 140 | fn encode(&self, buf: &mut Vec) { 141 | self.text.encode(buf); 142 | self.run_ts.encode(buf); 143 | self.lamport_ts.encode(buf); 144 | let run = InsertionRun::new(self); 145 | run.encode(buf); 146 | self.encode_anchor(run, buf); 147 | } 148 | } 149 | 150 | pub(crate) enum InsertionDecodeError { 151 | Int(IntDecodeError), 152 | Run(BoolDecodeError), 153 | } 154 | 155 | impl From for InsertionDecodeError { 156 | #[inline] 157 | fn from(err: IntDecodeError) -> Self { 158 | Self::Int(err) 159 | } 160 | } 161 | 162 | impl From for InsertionDecodeError { 163 | #[inline] 164 | fn from(err: BoolDecodeError) -> Self { 165 | Self::Run(err) 166 | } 167 | } 168 | 169 | impl core::fmt::Display for InsertionDecodeError { 170 | #[inline] 171 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 172 | let err: &dyn core::fmt::Display = match self { 173 | Self::Int(err) => err, 174 | Self::Run(err) => err, 175 | }; 176 | 177 | write!(f, "InsertionRun couldn't be decoded: {err}") 178 | } 179 | } 180 | 181 | impl Decode for Insertion { 182 | type Value = Self; 183 | 184 | type Error = InsertionDecodeError; 185 | 186 | #[inline] 187 | fn decode(buf: &[u8]) -> Result<(Self, &[u8]), Self::Error> { 188 | let (text, buf) = Text::decode(buf)?; 189 | let (run_ts, buf) = RunTs::decode(buf)?; 190 | let (lamport_ts, buf) = LamportTs::decode(buf)?; 191 | let (run, buf) = InsertionRun::decode(buf)?; 192 | let (anchor, buf) = Self::decode_anchor(run, &text, run_ts, buf)?; 193 | let insertion = Self::new(anchor, text, lamport_ts, run_ts); 194 | Ok((insertion, buf)) 195 | } 196 | } 197 | 198 | /// Whether an [`Insertion`] begins a new run or continues an existing one. 199 | /// 200 | /// This is used when encoding and decoding [`Insertion`]s to determine 201 | /// whether their [`Anchor`] needs to be encoded. 202 | /// 203 | /// Most of the time when people edit a document they insert a bunch of 204 | /// characters in a single run before moving the cursor or deleting some 205 | /// text, and we can use this pattern to save some bytes. 206 | /// 207 | /// For example, if someone types "foo" sequentially in a blank document, 208 | /// we'll create the following insertions (assuming a `ReplicaId` of 1 and 209 | /// omitting fields that aren't relevant to this discussion): 210 | /// 211 | /// ```text 212 | /// f -> Insertion { anchor: zero, text: 1.0..1, .. }, 213 | /// o -> Insertion { anchor: 1.1, text: 1.1..2, .. }, 214 | /// o -> Insertion { anchor: 1.2, text: 1.2..3, .. }, 215 | /// ``` 216 | /// 217 | /// The first insertion begins a new run, but from then on every 218 | /// Insertion's anchor is the same as the start of its text. 219 | /// 220 | /// This means that we can save space when encoding by omitting the anchor 221 | /// and adding a flag that indicates that it should be derived from the 222 | /// text and the run timestamp. 223 | /// 224 | /// This enum corresponds to that flag. 225 | enum InsertionRun { 226 | /// The [`Insertion`] begins a new run. 227 | /// 228 | /// In this case we also encode the insertion's [`Anchor`]. 229 | BeginsNew, 230 | 231 | /// The [`Insertion`] continues an existing run. 232 | /// 233 | /// In this case we can avoid encoding the insertion's [`Anchor`] 234 | /// because it can be fully decoded from the insertion's [`Text`] and 235 | /// [`RunTs`]. 236 | ContinuesExisting, 237 | } 238 | 239 | impl InsertionRun { 240 | #[inline] 241 | fn new(insertion: &Insertion) -> Self { 242 | // To determine whether this insertion is a continuation of an 243 | // existing insertion run we simply check: 244 | // 245 | // 1: the `ReplicaId`s of the anchor and the text. Clearly they 246 | // must match because you can't continue someone else's 247 | // insertion; 248 | // 249 | // 2: the `RunTs` of the anchor and the insertion. Since that 250 | // counter is only incremented when a new insertion run begins, 251 | // we know that if they match then this insertion must continue 252 | // an existing run. 253 | let is_continuation = insertion.anchor.replica_id() 254 | == insertion.text.inserted_by() 255 | && insertion.anchor.run_ts() == insertion.run_ts(); 256 | 257 | if is_continuation { 258 | Self::ContinuesExisting 259 | } else { 260 | Self::BeginsNew 261 | } 262 | } 263 | } 264 | 265 | impl Encode for InsertionRun { 266 | #[inline] 267 | fn encode(&self, buf: &mut Vec) { 268 | matches!(self, Self::ContinuesExisting).encode(buf); 269 | } 270 | } 271 | 272 | impl Decode for InsertionRun { 273 | type Value = Self; 274 | 275 | type Error = BoolDecodeError; 276 | 277 | #[inline] 278 | fn decode(buf: &[u8]) -> Result<(Self, &[u8]), Self::Error> { 279 | let (is_continuation, rest) = bool::decode(buf)?; 280 | let this = if is_continuation { 281 | Self::ContinuesExisting 282 | } else { 283 | Self::BeginsNew 284 | }; 285 | Ok((this, rest)) 286 | } 287 | } 288 | } 289 | 290 | #[cfg(feature = "serde")] 291 | mod serde { 292 | crate::encode::impl_deserialize!(super::Insertion); 293 | crate::encode::impl_serialize!(super::Insertion); 294 | } 295 | 296 | #[cfg(all(test, feature = "encode"))] 297 | mod encode_tests { 298 | use super::*; 299 | use crate::encode::{Decode, Encode}; 300 | 301 | impl core::fmt::Debug for encode::InsertionDecodeError { 302 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 303 | core::fmt::Display::fmt(self, f) 304 | } 305 | } 306 | 307 | #[test] 308 | fn encode_insertion_round_trip_0() { 309 | let anchor = Anchor::new(1, 1, 1); 310 | let text = Text::new(2, 0..1); 311 | let insertion = Insertion::new(anchor, text, 3, 0); 312 | let mut buf = Vec::new(); 313 | insertion.encode(&mut buf); 314 | let (decoded, rest) = Insertion::decode(&buf).unwrap(); 315 | assert_eq!(insertion, decoded); 316 | assert!(rest.is_empty()); 317 | } 318 | } 319 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! cola is a Conflict-free Replicated Data Type ([CRDT]) for real-time 2 | //! collaborative editing of plain text documents. 3 | //! 4 | //! CRDTs can be roughly divided into two categories: state-based and 5 | //! operation-based. cola falls in the latter category. 6 | //! 7 | //! The basic idea behind an Operation-based CRDT -- also known as a 8 | //! *Commutative* Replicated Data Type or C*m*RDT -- is to design the core data 9 | //! structure and the operations applied to it in such a way that they 10 | //! *commute*, so that the order in which they're applied at each peer doesn't 11 | //! matter. 12 | //! 13 | //! Commutativity makes the final state of the data structure only a function 14 | //! of its initial state and the *set* of operations applied to it, but *not* 15 | //! of the order in which they were applied. This ensures that once all peers 16 | //! have received all operations from all other peers they're guaranteed to 17 | //! converge to the same final state. 18 | //! 19 | //! In cola, the core data structure which represents the state of the document 20 | //! at each peer is the [`Replica`], and the operations which the peers 21 | //! exchange to communicate their local edits are [`Insertion`]s and 22 | //! [`Deletion`]s. 23 | //! 24 | //! If you're new to this crate, reading the documentations of the 25 | //! [`Replica`] struct and its methods would be a good place to start. 26 | //! 27 | //! For a deeper dive into cola's design and implementation you can check out 28 | //! [this blog post][cola]. 29 | //! 30 | //! # Example usage 31 | //! 32 | //! ```rust 33 | //! use std::ops::Range; 34 | //! 35 | //! use cola::{Deletion, Replica, ReplicaId}; 36 | //! 37 | //! struct Document { 38 | //! buffer: String, 39 | //! crdt: Replica, 40 | //! } 41 | //! 42 | //! struct Insertion { 43 | //! text: String, 44 | //! crdt: cola::Insertion, 45 | //! } 46 | //! 47 | //! impl Document { 48 | //! fn new>(text: S, replica_id: ReplicaId) -> Self { 49 | //! let buffer = text.into(); 50 | //! let crdt = Replica::new(replica_id, buffer.len()); 51 | //! Document { buffer, crdt } 52 | //! } 53 | //! 54 | //! fn fork(&self, new_replica_id: ReplicaId) -> Self { 55 | //! let crdt = self.crdt.fork(new_replica_id); 56 | //! Document { buffer: self.buffer.clone(), crdt } 57 | //! } 58 | //! 59 | //! fn insert>( 60 | //! &mut self, 61 | //! insert_at: usize, 62 | //! text: S, 63 | //! ) -> Insertion { 64 | //! let text = text.into(); 65 | //! self.buffer.insert_str(insert_at, &text); 66 | //! let insertion = self.crdt.inserted(insert_at, text.len()); 67 | //! Insertion { text, crdt: insertion } 68 | //! } 69 | //! 70 | //! fn delete(&mut self, range: Range) -> Deletion { 71 | //! self.buffer.replace_range(range.clone(), ""); 72 | //! self.crdt.deleted(range) 73 | //! } 74 | //! 75 | //! fn integrate_insertion(&mut self, insertion: Insertion) { 76 | //! if let Some(offset) = 77 | //! self.crdt.integrate_insertion(&insertion.crdt) 78 | //! { 79 | //! self.buffer.insert_str(offset, &insertion.text); 80 | //! } 81 | //! } 82 | //! 83 | //! fn integrate_deletion(&mut self, deletion: Deletion) { 84 | //! let ranges = self.crdt.integrate_deletion(&deletion); 85 | //! for range in ranges.into_iter().rev() { 86 | //! self.buffer.replace_range(range, ""); 87 | //! } 88 | //! } 89 | //! } 90 | //! 91 | //! fn main() { 92 | //! let mut peer_1 = Document::new("Hello, world", 1); 93 | //! let mut peer_2 = peer_1.fork(2); 94 | //! 95 | //! let delete_comma = peer_1.delete(5..6); 96 | //! let insert_exclamation = peer_2.insert(12, "!"); 97 | //! 98 | //! peer_1.integrate_insertion(insert_exclamation); 99 | //! peer_2.integrate_deletion(delete_comma); 100 | //! 101 | //! assert_eq!(peer_1.buffer, "Hello world!"); 102 | //! assert_eq!(peer_2.buffer, "Hello world!"); 103 | //! } 104 | //! ``` 105 | //! 106 | //! # Feature flags 107 | //! 108 | //! - `encode`: enables the [`encode`](Replica::encode) and 109 | //! [`decode`](Replica::decode) methods on [`Replica`] (disabled by default); 110 | //! 111 | //! - `serde`: enables the [`Serialize`] and [`Deserialize`] impls for 112 | //! [`Insertion`], [`Deletion`] and [`EncodedReplica`] (disabled by default). 113 | //! 114 | //! [CRDT]: https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type 115 | //! [cola]: https://nomad.foo/blog/cola 116 | //! [`Serialize`]: https://docs.rs/serde/latest/serde/trait.Serialize.html 117 | //! [`Deserialize`]: https://docs.rs/serde/latest/serde/trait.Deserialize.html 118 | 119 | #![allow(clippy::explicit_auto_deref)] 120 | #![allow(clippy::module_inception)] 121 | #![allow(clippy::needless_doctest_main)] 122 | #![cfg_attr(docsrs, feature(doc_cfg))] 123 | #![deny(missing_docs)] 124 | #![deny(rustdoc::broken_intra_doc_links)] 125 | #![deny(rustdoc::private_intra_doc_links)] 126 | 127 | extern crate alloc; 128 | 129 | mod anchor; 130 | mod backlog; 131 | mod deletion; 132 | #[cfg(feature = "encode")] 133 | mod encode; 134 | #[cfg(feature = "encode")] 135 | mod encoded_replica; 136 | mod gtree; 137 | mod insertion; 138 | mod replica; 139 | mod replica_id; 140 | mod run_indices; 141 | mod run_tree; 142 | mod text; 143 | mod utils; 144 | mod version_map; 145 | 146 | use anchor::*; 147 | pub use anchor::{Anchor, AnchorBias}; 148 | use backlog::Backlog; 149 | pub use backlog::{BackloggedDeletions, BackloggedInsertions}; 150 | pub use deletion::Deletion; 151 | #[cfg(feature = "encode")] 152 | pub use encoded_replica::{DecodeError, EncodedReplica}; 153 | use gtree::{Gtree, LeafIdx}; 154 | pub use insertion::Insertion; 155 | pub use replica::Replica; 156 | use replica::*; 157 | pub use replica_id::ReplicaId; 158 | use replica_id::{ReplicaIdMap, ReplicaIdMapValuesMut}; 159 | use run_indices::RunIndices; 160 | use run_tree::*; 161 | pub use text::Text; 162 | use utils::*; 163 | use version_map::{DeletionMap, VersionMap}; 164 | 165 | /// The version of the protocol cola uses to represent `EncodedReplica`s and 166 | /// `CrdtEdit`s. 167 | /// 168 | /// You can think of this as a version number for cola's internal 169 | /// representation of the subset of its data structures that are exchanged 170 | /// between peers. 171 | /// 172 | /// If different peers are using versions of cola with the same protocol number 173 | /// they're compatible. If not, decoding `EncodedReplica`s and deserializing 174 | /// `CrdtEdit`s will fail. 175 | /// 176 | /// # Protocol stability 177 | /// 178 | /// cola is still far away from reaching stability, and until that happens its 179 | /// internal `ProtocolVersion` might change very frequently. After the 1.0 180 | /// release the protocol version will only be allowed to be incremented in 181 | /// major releases (if at all). 182 | pub type ProtocolVersion = u64; 183 | 184 | /// The length of a piece of text according to some user-defined metric. 185 | /// 186 | /// The meaning of a unit of length is decided by you, the user of this 187 | /// library, depending on the kind of buffer you're using cola with. This 188 | /// allows cola to work with buffers using a variety of encodings (UTF-8, 189 | /// UTF-16, etc.) and indexing metrics (bytes, codepoints, graphemes, etc.). 190 | /// 191 | /// While the particular meaning of a unit of length is up to the user, it is 192 | /// important that it is consistent across all peers. For example, if one peer 193 | /// uses bytes as its unit of length, all other peers must also use bytes or 194 | /// the contents of their buffers will diverge. 195 | /// 196 | /// # Examples 197 | /// 198 | /// In this example all peers use the same metric (codepoints) and everything 199 | /// works as expected: 200 | /// 201 | /// ``` 202 | /// # use cola::Replica; 203 | /// fn insert_at_codepoint(s: &mut String, offset: usize, insert: &str) { 204 | /// let byte_offset = s.chars().take(offset).map(char::len_utf8).sum(); 205 | /// s.insert_str(byte_offset, insert); 206 | /// } 207 | /// 208 | /// // Peer 1 uses a String as its buffer and codepoints as its unit of 209 | /// // length. 210 | /// let mut buf1 = String::from("àc"); 211 | /// let mut replica1 = Replica::new(1, 2); // "àc" has 2 codepoints. 212 | /// 213 | /// let mut buf2 = buf1.clone(); 214 | /// let mut replica2 = replica1.fork(2); 215 | /// 216 | /// // Peer 1 inserts a 'b' between 'à' and 'c' and sends the edit over to the 217 | /// // other peer. 218 | /// let b = "b"; 219 | /// insert_at_codepoint(&mut buf1, 1, b); 220 | /// let insert_b = replica1.inserted(1, 1); 221 | /// 222 | /// // Peer 2 receives the edit. 223 | /// let offset = replica2.integrate_insertion(&insert_b).unwrap(); 224 | /// 225 | /// assert_eq!(offset, 1); 226 | /// 227 | /// // Peer 2 also uses codepoints as its unit of length, so it inserts the 228 | /// // 'b' after the 'à' as expected. 229 | /// insert_at_codepoint(&mut buf2, offset, b); 230 | /// 231 | /// // If all the peers use the same metric they'll always converge to the 232 | /// // same state. 233 | /// assert_eq!(buf1, "àbc"); 234 | /// assert_eq!(buf2, "àbc"); 235 | /// ``` 236 | /// 237 | /// If different peers use different metrics, however, their buffers can 238 | /// diverge or even cause the program to crash, like in the following example: 239 | /// 240 | /// ```should_panic 241 | /// # use cola::Replica; 242 | /// # let b = "b"; 243 | /// # let mut buf2 = String::from("àc"); 244 | /// # let mut replica1 = Replica::new(1, 2); 245 | /// # let mut replica2 = replica1.fork(2); 246 | /// # let insert_b = replica1.inserted(1, 1); 247 | /// // ..same as before. 248 | /// 249 | /// assert_eq!(buf2, "àc"); 250 | /// 251 | /// // Peer 2 receives the edit. 252 | /// let offset = replica2.integrate_insertion(&insert_b).unwrap(); 253 | /// 254 | /// assert_eq!(offset, 1); 255 | /// 256 | /// // Now let's say peer 2 interprets `offset` as a byte offset even though 257 | /// // the insertion of the 'b' was done using codepoint offsets on peer 1. 258 | /// // 259 | /// // In this case the program just panics because a byte offset of 1 is not a 260 | /// // valid insertion point in the string "àc" since it falls in the middle of 261 | /// // the 'à' codepoint, which is 2 bytes long. 262 | /// // 263 | /// // In other cases the program might not panic but instead cause the peers 264 | /// // to silently diverge, which is arguably worse. 265 | /// 266 | /// buf2.insert_str(offset, b); // 💥 panics! 267 | /// ``` 268 | pub type Length = usize; 269 | 270 | /// The protocol version of the current cola release. 271 | /// 272 | /// See [`ProtocolVersion`] for more infos. 273 | #[cfg(feature = "encode")] 274 | const PROTOCOL_VERSION: ProtocolVersion = 3; 275 | -------------------------------------------------------------------------------- /src/replica_id.rs: -------------------------------------------------------------------------------- 1 | use core::hash::BuildHasherDefault; 2 | use std::collections::HashMap; 3 | 4 | pub type ReplicaIdMap = 5 | HashMap>; 6 | 7 | pub type ReplicaIdMapValuesMut<'a, T> = 8 | std::collections::hash_map::ValuesMut<'a, ReplicaId, T>; 9 | 10 | /// A unique identifier for a [`Replica`](crate::Replica). 11 | /// 12 | /// It's very important that all [`Replica`](crate::Replica)s in the same 13 | /// collaborative session have unique [`ReplicaId`]s as this type is used to 14 | /// distinguish between them when integrating remote edits. 15 | /// 16 | /// Guaranteeing uniqueness is up to you. 17 | /// 18 | /// If your editing session is not proxied through a server you control you can 19 | /// generate a random `u64` every time a new [`Replica`](crate::Replica) is 20 | /// created and be reasonably sure[^collisions] that there won't be any 21 | /// collisions. 22 | /// 23 | /// [^collisions]: you'd have to have almost [200k peers][table] in the same 24 | /// editing session to reach a one-in-a-billion chance of a single collision, 25 | /// which is more than good enough for the kind of use cases this library is 26 | /// designed for. 27 | /// 28 | /// [table]: https://en.wikipedia.org/wiki/Birthday_problem#Probability_table 29 | pub type ReplicaId = u64; 30 | 31 | #[derive(Default)] 32 | pub struct ReplicaIdHasher(u64); 33 | 34 | impl core::hash::Hasher for ReplicaIdHasher { 35 | fn write(&mut self, _bytes: &[u8]) { 36 | unreachable!(); 37 | } 38 | 39 | #[inline] 40 | fn finish(&self) -> u64 { 41 | self.0 42 | } 43 | 44 | #[inline] 45 | fn write_u64(&mut self, i: u64) { 46 | self.0 = i; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/run_indices.rs: -------------------------------------------------------------------------------- 1 | use core::ops::{Index, IndexMut}; 2 | 3 | use crate::anchor::InnerAnchor as Anchor; 4 | use crate::*; 5 | 6 | /// A data structure used when merging remote edits to efficiently map 7 | /// an [`Anchor`] to the [`LeafIdx`] of the [`EditRun`] that contains it. 8 | #[derive(Clone, Default, PartialEq)] 9 | pub(crate) struct RunIndices { 10 | map: ReplicaIdMap, 11 | } 12 | 13 | impl core::fmt::Debug for RunIndices { 14 | fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { 15 | self.map.fmt(f) 16 | } 17 | } 18 | 19 | impl RunIndices { 20 | pub fn assert_invariants(&self, run_tree: &RunTree) { 21 | for (&replica_id, indices) in self.map.iter() { 22 | indices.assert_invariants(); 23 | 24 | let mut offset = 0; 25 | 26 | for (idx, splits) in indices.splits().enumerate() { 27 | for split in splits.iter() { 28 | let run = run_tree.run(split.idx); 29 | assert_eq!(replica_id, run.replica_id()); 30 | assert_eq!(split.len, run.len()); 31 | assert_eq!(offset, run.start()); 32 | assert_eq!(idx, run.run_ts() as usize); 33 | offset += split.len; 34 | } 35 | } 36 | } 37 | } 38 | 39 | #[inline] 40 | pub fn get_mut(&mut self, id: ReplicaId) -> &mut ReplicaIndices { 41 | self.map.entry(id).or_default() 42 | } 43 | 44 | /// Returns the [`LeafIdx`] of the [`EditRun`] that contains the given 45 | /// [`Anchor`]. 46 | #[inline] 47 | pub fn idx_at_anchor( 48 | &self, 49 | anchor: Anchor, 50 | bias: AnchorBias, 51 | ) -> LeafIdx { 52 | self.map.get(&anchor.replica_id()).unwrap().idx_at_offset( 53 | anchor.run_ts(), 54 | anchor.offset(), 55 | bias, 56 | ) 57 | } 58 | 59 | #[cfg(feature = "encode")] 60 | #[inline] 61 | pub(crate) fn iter( 62 | &self, 63 | ) -> impl ExactSizeIterator + '_ 64 | { 65 | self.map.iter() 66 | } 67 | 68 | #[inline] 69 | pub fn new() -> Self { 70 | Self { map: ReplicaIdMap::default() } 71 | } 72 | } 73 | 74 | /// Contains the [`LeafIdx`]s of all the [`EditRun`]s that have been inserted 75 | /// by a given `Replica`. 76 | #[derive(Clone, Default, PartialEq)] 77 | pub(crate) struct ReplicaIndices { 78 | /// The [`Fragments`] are stored sequentially and in order of insertion. 79 | /// 80 | /// When a new [`EditRun`] is created we append a new [`Fragments`] to the 81 | /// vector. As long as the following insertions continue that run we simply 82 | /// increase the length of the last [`Fragments`]. 83 | /// 84 | /// Once that run ends we append new [`Fragments`], and so on. 85 | /// 86 | /// The `Length` field in the tuple is the cumulative length of all the 87 | /// previous [`Fragments`] up to but not including the current one. 88 | vec: Vec<(Fragments, Length)>, 89 | } 90 | 91 | impl core::fmt::Debug for ReplicaIndices { 92 | fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { 93 | f.debug_list() 94 | .entries(self.vec.iter().map(|(splits, _)| splits)) 95 | .finish() 96 | } 97 | } 98 | 99 | /// We can use the `RunTs` as an index into the `vec` because it is only 100 | /// incremented when the current run is interrupted and a new one is started. 101 | /// 102 | /// Using the `RunTs` as an index allows us to find the `Fragments` 103 | /// corresponding to a given offset in `O(1)` instead of having to do a binary 104 | /// search. 105 | impl Index for ReplicaIndices { 106 | type Output = (Fragments, Length); 107 | 108 | #[inline] 109 | fn index(&self, run_ts: RunTs) -> &Self::Output { 110 | &self.vec[run_ts as usize] 111 | } 112 | } 113 | 114 | impl IndexMut for ReplicaIndices { 115 | #[inline] 116 | fn index_mut(&mut self, run_ts: RunTs) -> &mut Self::Output { 117 | &mut self.vec[run_ts as usize] 118 | } 119 | } 120 | 121 | impl ReplicaIndices { 122 | #[inline] 123 | pub fn append(&mut self, len: Length, idx: LeafIdx) { 124 | let fragment = Fragment::new(len, idx); 125 | 126 | let new_last = Fragments::from_first_fragment(fragment); 127 | 128 | let (last_offset, last_len) = self 129 | .vec 130 | .last() 131 | .map(|(fragments, offset)| (fragments.len(), *offset)) 132 | .unwrap_or((0, 0)); 133 | 134 | self.vec.push((new_last, last_offset + last_len)); 135 | } 136 | 137 | #[inline] 138 | pub fn append_to_last(&mut self, len: Length, idx: LeafIdx) { 139 | let split = Fragment::new(len, idx); 140 | self.vec.last_mut().unwrap().0.append(split); 141 | } 142 | 143 | fn assert_invariants(&self) { 144 | let mut offset = 0; 145 | 146 | for &(ref splits, splits_offset) in self.vec.iter() { 147 | assert_eq!(splits_offset, offset); 148 | splits.assert_invariants(); 149 | offset += splits.len(); 150 | } 151 | } 152 | 153 | #[inline] 154 | pub fn extend_last(&mut self, extend_by: Length) { 155 | self.vec.last_mut().unwrap().0.extend(extend_by); 156 | } 157 | 158 | #[inline] 159 | fn idx_at_offset( 160 | &self, 161 | run_ts: RunTs, 162 | at_offset: Length, 163 | bias: AnchorBias, 164 | ) -> LeafIdx { 165 | let (splits, offset) = &self[run_ts]; 166 | splits.fragment_at_offset(at_offset - offset, bias).idx 167 | } 168 | 169 | #[cfg(feature = "encode")] 170 | #[inline(always)] 171 | pub(crate) fn iter( 172 | &self, 173 | ) -> impl Iterator + '_ { 174 | self.vec.iter() 175 | } 176 | 177 | #[inline] 178 | pub fn len(&self) -> usize { 179 | self.vec.len() 180 | } 181 | 182 | #[inline] 183 | pub fn move_len_to_next_split( 184 | &mut self, 185 | run_ts: RunTs, 186 | split_at_offset: Length, 187 | len_moved: Length, 188 | ) { 189 | let (splits, offset) = &mut self[run_ts]; 190 | splits.move_len_to_next_fragment(split_at_offset - *offset, len_moved); 191 | } 192 | 193 | #[inline] 194 | pub fn move_len_to_prev_split( 195 | &mut self, 196 | run_ts: RunTs, 197 | split_at_offset: Length, 198 | len_moved: Length, 199 | ) { 200 | let (splits, offset) = &mut self[run_ts]; 201 | splits.move_len_to_prev_split(split_at_offset - *offset, len_moved); 202 | } 203 | 204 | #[cfg(feature = "encode")] 205 | #[inline(always)] 206 | pub(crate) fn new(vec: Vec<(Fragments, Length)>) -> Self { 207 | Self { vec } 208 | } 209 | 210 | #[inline] 211 | pub fn split( 212 | &mut self, 213 | run_ts: RunTs, 214 | at_offset: Length, 215 | right_idx: LeafIdx, 216 | ) { 217 | let (splits, offset) = &mut self[run_ts]; 218 | splits.split(at_offset - *offset, right_idx); 219 | } 220 | 221 | #[inline] 222 | fn splits(&self) -> impl Iterator { 223 | self.vec.iter().map(|(splits, _)| splits) 224 | } 225 | } 226 | 227 | const FRAGMENTS_INLINE: usize = 8; 228 | 229 | pub(crate) type Fragments = fragments::Fragments; 230 | 231 | mod fragments { 232 | use super::*; 233 | 234 | /// The `Fragment`s that an insertion run has been fragmented into. 235 | #[derive(Clone, PartialEq)] 236 | pub(crate) enum Fragments { 237 | /// The first `INLINE` fragments are stored inline to avoid 238 | /// allocating a `Gtree` for runs that are not heavily fragmented. 239 | Array(Array), 240 | 241 | /// Once the number of fragments exceeds `INLINE` we switch to a 242 | /// `Gtree`. 243 | Gtree(Gtree), 244 | } 245 | 246 | impl core::fmt::Debug for Fragments { 247 | fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { 248 | match self { 249 | Self::Array(array) => array.fmt(f), 250 | 251 | Self::Gtree(gtree) => f 252 | .debug_list() 253 | .entries(gtree.leaves_from_first().map(|(_, split)| split)) 254 | .finish(), 255 | } 256 | } 257 | } 258 | 259 | impl Default for Fragments { 260 | #[inline] 261 | fn default() -> Self { 262 | Self::Array(Array { 263 | fragments: [Fragment::null(); N], 264 | len: 0, 265 | total_len: 0, 266 | }) 267 | } 268 | } 269 | 270 | impl Fragments { 271 | pub fn assert_invariants(&self) { 272 | match self { 273 | Self::Array(array) => array.assert_invariants(), 274 | Self::Gtree(gtree) => gtree.assert_invariants(), 275 | } 276 | } 277 | 278 | #[inline] 279 | pub fn append(&mut self, fragment: Fragment) { 280 | match self { 281 | Fragments::Array(array) => { 282 | if array.len == INLINE { 283 | let mut gtree = Gtree::from_leaves( 284 | array.fragments().iter().copied(), 285 | array.total_len, 286 | ); 287 | gtree.append(fragment); 288 | *self = Self::Gtree(gtree); 289 | } else { 290 | array.append(fragment); 291 | } 292 | }, 293 | 294 | Fragments::Gtree(gtree) => { 295 | gtree.append(fragment); 296 | }, 297 | } 298 | } 299 | 300 | #[inline] 301 | pub fn extend(&mut self, extend_by: Length) { 302 | match self { 303 | Fragments::Array(array) => { 304 | array.extend_last(extend_by); 305 | }, 306 | 307 | Fragments::Gtree(gtree) => { 308 | gtree.with_last_leaf_mut(|last| { 309 | last.len += extend_by; 310 | }); 311 | }, 312 | } 313 | } 314 | 315 | #[inline] 316 | pub fn fragment_at_offset( 317 | &self, 318 | at_offset: Length, 319 | bias: AnchorBias, 320 | ) -> &Fragment { 321 | debug_assert!( 322 | at_offset < self.len() 323 | || at_offset == self.len() && bias == AnchorBias::Left 324 | ); 325 | 326 | match self { 327 | Self::Array(array) => { 328 | array.fragment_at_offset(at_offset, bias) 329 | }, 330 | 331 | Self::Gtree(gtree) => { 332 | let (leaf_idx, fragment_offset) = 333 | gtree.leaf_at_offset(at_offset); 334 | 335 | let fragment = gtree.leaf(leaf_idx); 336 | 337 | if fragment_offset + fragment.len == at_offset 338 | && bias == AnchorBias::Right 339 | { 340 | gtree.leaf(gtree.next_leaf(leaf_idx).unwrap()) 341 | } else { 342 | fragment 343 | } 344 | }, 345 | } 346 | } 347 | 348 | #[inline] 349 | pub fn from_first_fragment(fragment: Fragment) -> Self { 350 | let mut this = Self::default(); 351 | this.append(fragment); 352 | this 353 | } 354 | 355 | #[inline] 356 | pub fn iter(&self) -> FragmentsIter<'_, INLINE> { 357 | match self { 358 | Self::Array(array) => { 359 | FragmentsIter::Array(array.fragments().iter()) 360 | }, 361 | 362 | Self::Gtree(gtree) => { 363 | FragmentsIter::Gtree(gtree.leaves_from_first()) 364 | }, 365 | } 366 | } 367 | 368 | #[inline] 369 | pub fn len(&self) -> Length { 370 | match self { 371 | Self::Array(array) => array.total_len, 372 | Self::Gtree(splits) => splits.len(), 373 | } 374 | } 375 | 376 | #[inline] 377 | pub fn move_len_to_next_fragment( 378 | &mut self, 379 | fragment_at_offset: Length, 380 | len_move: Length, 381 | ) { 382 | debug_assert!(fragment_at_offset < self.len()); 383 | debug_assert!(len_move > 0); 384 | 385 | match self { 386 | Self::Array(array) => { 387 | array.move_len_to_next_fragment( 388 | fragment_at_offset, 389 | len_move, 390 | ); 391 | }, 392 | 393 | Self::Gtree(gtree) => { 394 | let (leaf_idx, _) = 395 | gtree.leaf_at_offset(fragment_at_offset); 396 | 397 | let next_idx = gtree.next_leaf(leaf_idx).unwrap(); 398 | 399 | gtree.with_two_mut(leaf_idx, next_idx, |this, next| { 400 | this.len -= len_move; 401 | next.len += len_move; 402 | }); 403 | }, 404 | } 405 | } 406 | 407 | #[inline] 408 | pub fn move_len_to_prev_split( 409 | &mut self, 410 | at_offset: Length, 411 | len_move: Length, 412 | ) { 413 | debug_assert!(at_offset < self.len()); 414 | debug_assert!(len_move > 0); 415 | 416 | match self { 417 | Self::Array(array) => { 418 | array.move_len_to_prev_fragment(at_offset, len_move) 419 | }, 420 | 421 | Self::Gtree(gtree) => { 422 | let (leaf_idx, _) = gtree.leaf_at_offset(at_offset); 423 | 424 | let prev_idx = gtree.prev_leaf(leaf_idx).unwrap(); 425 | 426 | gtree.with_two_mut(prev_idx, leaf_idx, |prev, this| { 427 | this.len -= len_move; 428 | prev.len += len_move; 429 | }); 430 | }, 431 | } 432 | } 433 | 434 | #[cfg(feature = "encode")] 435 | #[inline] 436 | pub(crate) fn num_fragments(&self) -> usize { 437 | match self { 438 | Self::Array(array) => array.len, 439 | Self::Gtree(gtree) => gtree.num_leaves(), 440 | } 441 | } 442 | 443 | #[inline] 444 | pub fn split(&mut self, at_offset: Length, new_idx: LeafIdx) { 445 | match self { 446 | Fragments::Array(array) => { 447 | if array.len == INLINE { 448 | let gtree = Gtree::from_leaves( 449 | array.fragments().iter().copied(), 450 | array.total_len, 451 | ); 452 | *self = Fragments::Gtree(gtree); 453 | self.split(at_offset, new_idx); 454 | } else { 455 | array.split(at_offset, new_idx); 456 | } 457 | }, 458 | 459 | Fragments::Gtree(gtree) => { 460 | let (fragment_idx, fragment_offset) = 461 | gtree.leaf_at_offset(at_offset); 462 | 463 | gtree.split_leaf(fragment_idx, |fragment| { 464 | fragment.split(at_offset - fragment_offset, new_idx) 465 | }); 466 | }, 467 | }; 468 | } 469 | } 470 | 471 | impl gtree::Join for Fragments {} 472 | 473 | impl gtree::Leaf for Fragments { 474 | #[inline] 475 | fn len(&self) -> Length { 476 | self.len() 477 | } 478 | } 479 | 480 | pub(crate) enum FragmentsIter<'a, const N: usize> { 481 | Array(core::slice::Iter<'a, Fragment>), 482 | Gtree(crate::gtree::Leaves<'a, N, Fragment>), 483 | } 484 | 485 | impl<'a, const N: usize> Iterator for FragmentsIter<'a, N> { 486 | type Item = &'a Fragment; 487 | 488 | #[inline] 489 | fn next(&mut self) -> Option { 490 | match self { 491 | Self::Array(iter) => iter.next(), 492 | 493 | Self::Gtree(iter) => { 494 | iter.next().map(|(_idx, fragment)| fragment) 495 | }, 496 | } 497 | } 498 | } 499 | 500 | #[derive(Clone, PartialEq)] 501 | pub(crate) struct Array { 502 | fragments: [Fragment; N], 503 | 504 | /// The number of non-null `Fragment`s in the array. 505 | len: usize, 506 | 507 | /// The total length of all `Fragment`s in the array. 508 | total_len: Length, 509 | } 510 | 511 | impl core::fmt::Debug for Array { 512 | fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { 513 | self.fragments().fmt(f) 514 | } 515 | } 516 | 517 | impl Array { 518 | #[inline] 519 | fn assert_invariants(&self) { 520 | let mut total_len = 0; 521 | 522 | for fragment in &self.fragments[0..self.len] { 523 | total_len += fragment.len; 524 | assert!(!fragment.is_null()); 525 | } 526 | 527 | assert_eq!(self.total_len, total_len); 528 | 529 | for fragment in &self.fragments[self.len..] { 530 | assert!(fragment.is_null()); 531 | } 532 | } 533 | 534 | #[inline] 535 | fn append(&mut self, fragment: Fragment) { 536 | debug_assert!(self.len < N); 537 | 538 | self.total_len += fragment.len; 539 | self.fragments[self.len] = fragment; 540 | self.len += 1; 541 | } 542 | 543 | #[inline] 544 | fn extend_last(&mut self, extend_by: Length) { 545 | self.fragments[self.len - 1].len += extend_by; 546 | self.total_len += extend_by; 547 | } 548 | 549 | #[inline] 550 | fn fragment_at_offset( 551 | &self, 552 | at_offset: Length, 553 | bias: AnchorBias, 554 | ) -> &Fragment { 555 | let (idx, fragment_offset) = self.idx_at_offset(at_offset); 556 | let fragment = &self.fragments[idx]; 557 | if fragment_offset + fragment.len == at_offset 558 | && bias == AnchorBias::Right 559 | { 560 | &self.fragments[idx + 1] 561 | } else { 562 | fragment 563 | } 564 | } 565 | 566 | #[inline] 567 | fn fragments(&self) -> &[Fragment] { 568 | &self.fragments[..self.len] 569 | } 570 | 571 | #[inline] 572 | fn fragments_mut(&mut self) -> &mut [Fragment] { 573 | &mut self.fragments[..self.len] 574 | } 575 | 576 | #[inline] 577 | fn idx_at_offset(&self, at_offset: Length) -> (usize, Length) { 578 | let mut offset = 0; 579 | for (idx, fragment) in self.fragments().iter().enumerate() { 580 | offset += fragment.len; 581 | if offset >= at_offset { 582 | return (idx, offset - fragment.len); 583 | } 584 | } 585 | unreachable!(); 586 | } 587 | 588 | #[inline] 589 | fn move_len_to_next_fragment( 590 | &mut self, 591 | fragment_at_offset: Length, 592 | len_move: Length, 593 | ) { 594 | let (this, _) = self.idx_at_offset(fragment_at_offset); 595 | let next = this + 1; 596 | let (this, next) = 597 | crate::get_two_mut(self.fragments_mut(), this, next); 598 | this.len -= len_move; 599 | next.len += len_move; 600 | } 601 | 602 | #[inline] 603 | fn move_len_to_prev_fragment( 604 | &mut self, 605 | fragment_at_offset: Length, 606 | len_move: Length, 607 | ) { 608 | let (this, _) = self.idx_at_offset(fragment_at_offset); 609 | let prev = this - 1; 610 | let (prev, this) = 611 | crate::get_two_mut(self.fragments_mut(), prev, this); 612 | this.len -= len_move; 613 | prev.len += len_move; 614 | } 615 | 616 | #[inline] 617 | fn split(&mut self, at_offset: Length, new_idx: LeafIdx) { 618 | let (idx, fragment_offset) = self.idx_at_offset(at_offset); 619 | 620 | self.len += 1; 621 | 622 | let fragments = &mut self.fragments[0..self.len]; 623 | 624 | let fragment = &mut fragments[idx]; 625 | 626 | let new_fragment = 627 | fragment.split(at_offset - fragment_offset, new_idx); 628 | 629 | crate::insert_in_slice(fragments, new_fragment, idx + 1); 630 | } 631 | } 632 | } 633 | 634 | /// The length and [`LeafIdx`] of a fragment of a single insertion run. 635 | #[derive(Copy, Clone, PartialEq)] 636 | pub(crate) struct Fragment { 637 | len: Length, 638 | idx: LeafIdx, 639 | } 640 | 641 | impl core::fmt::Debug for Fragment { 642 | fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { 643 | write!(f, "{} @ {:?}", self.len, self.idx) 644 | } 645 | } 646 | 647 | impl Fragment { 648 | #[inline] 649 | const fn null() -> Self { 650 | Self { len: 0, idx: LeafIdx::dangling() } 651 | } 652 | 653 | #[inline] 654 | fn is_null(&self) -> bool { 655 | *self == Self::null() 656 | } 657 | 658 | #[cfg(feature = "encode")] 659 | #[inline(always)] 660 | pub(crate) fn leaf_idx(&self) -> LeafIdx { 661 | self.idx 662 | } 663 | 664 | #[inline] 665 | pub(crate) fn new(len: Length, idx: LeafIdx) -> Self { 666 | Self { idx, len } 667 | } 668 | 669 | #[inline] 670 | fn split( 671 | &mut self, 672 | at_offset: Length, 673 | right_idx: LeafIdx, 674 | ) -> Self { 675 | debug_assert!(at_offset < self.len); 676 | let right_len = self.len - at_offset; 677 | self.len = at_offset; 678 | Self { idx: right_idx, len: right_len } 679 | } 680 | } 681 | 682 | impl gtree::Join for Fragment {} 683 | 684 | impl gtree::Leaf for Fragment { 685 | #[inline] 686 | fn len(&self) -> Length { 687 | self.len 688 | } 689 | } 690 | -------------------------------------------------------------------------------- /src/text.rs: -------------------------------------------------------------------------------- 1 | use core::ops::Range; 2 | 3 | use crate::*; 4 | 5 | /// A range of text inserted into a [`Replica`]. 6 | /// 7 | /// Despite the name, this type does not contain the text string itself, only 8 | /// the [`ReplicaId`] of the [`Replica`] that inserted it and its temporal 9 | /// range in it. These can be accessed via the 10 | /// [`inserted_by`](Text::inserted_by) and 11 | /// [`temporal_range`](Text::temporal_range) methods respectively. 12 | #[derive(Clone, PartialEq, Eq, Hash)] 13 | pub struct Text { 14 | pub(crate) inserted_by: ReplicaId, 15 | pub(crate) range: Range, 16 | } 17 | 18 | impl core::fmt::Debug for Text { 19 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 20 | write!(f, "{:x}.{:?}", self.inserted_by, self.range) 21 | } 22 | } 23 | 24 | impl Text { 25 | #[inline] 26 | pub(crate) fn end(&self) -> Length { 27 | self.range.end 28 | } 29 | 30 | /// Returns the [`ReplicaId`] of the [`Replica`] that inserted this text. 31 | /// 32 | /// # Examples 33 | /// 34 | /// ``` 35 | /// # use cola::Replica; 36 | /// let mut replica1 = Replica::new(1, 0); 37 | /// 38 | /// let insertion = replica1.inserted(0, 1); 39 | /// 40 | /// assert_eq!(insertion.text().inserted_by(), replica1.id()); 41 | /// ``` 42 | #[inline] 43 | pub fn inserted_by(&self) -> ReplicaId { 44 | self.inserted_by 45 | } 46 | 47 | #[inline] 48 | pub(crate) fn len(&self) -> Length { 49 | self.range.len() 50 | } 51 | 52 | #[inline] 53 | pub(crate) fn new(inserted_by: ReplicaId, range: Range) -> Self { 54 | Self { inserted_by, range } 55 | } 56 | 57 | #[inline] 58 | pub(crate) fn start(&self) -> Length { 59 | self.range.start 60 | } 61 | 62 | /// Returns the temporal range of this text in the `Replica` that inserted 63 | /// it. 64 | /// 65 | /// Each `Replica` keeps an internal character clock that starts at zero 66 | /// and is incremented each time the [`inserted`](crate::Replica::inserted) 67 | /// method is called by an amount equal to the length of the inserted text. 68 | /// 69 | /// Since the insertion history of *a single* `Replica` is linear and 70 | /// immutable, this clock can be used to uniquely identify each character 71 | /// in the document without knowing what the actual text associated with 72 | /// its insertion is. 73 | /// 74 | /// Note that this range has absolutely nothing to do with the *spatial 75 | /// offset* at which the text was inserted. Its start and end simply refer 76 | /// to the values of the character clock of the `Replica` that inserted the 77 | /// `Text` before and after the insertion. 78 | /// 79 | /// It's up to you to decide how to map these temporal ranges to the actual 80 | /// text contents inserted by the various peers. 81 | /// 82 | /// # Examples 83 | /// 84 | /// ``` 85 | /// # use cola::Replica; 86 | /// let mut replica1 = Replica::new(1, 0); 87 | /// 88 | /// // Peer 1 inserts 1, 2, 3 and 4 characters at the start of the 89 | /// // document. 90 | /// let insertion1 = replica1.inserted(0, 1); 91 | /// let insertion2 = replica1.inserted(0, 2); 92 | /// let insertion3 = replica1.inserted(0, 3); 93 | /// let insertion4 = replica1.inserted(0, 4); 94 | /// 95 | /// // Notice how: 96 | /// // - the temporal range of the first insertion starts at zero; 97 | /// // - the start of each range is equal to the end of the previous one; 98 | /// // - the length of each range matches the one passed to 99 | /// // `replica1.inserted`. 100 | /// 101 | /// assert_eq!(insertion1.text().temporal_range(), 0..1); 102 | /// assert_eq!(insertion2.text().temporal_range(), 1..3); 103 | /// assert_eq!(insertion3.text().temporal_range(), 3..6); 104 | /// assert_eq!(insertion4.text().temporal_range(), 6..10); 105 | /// ``` 106 | #[inline] 107 | pub fn temporal_range(&self) -> Range { 108 | self.range.clone() 109 | } 110 | } 111 | 112 | #[cfg(feature = "encode")] 113 | mod encode { 114 | use super::*; 115 | use crate::encode::{Decode, Encode, IntDecodeError}; 116 | 117 | impl Encode for Text { 118 | #[inline] 119 | fn encode(&self, buf: &mut Vec) { 120 | self.inserted_by.encode(buf); 121 | self.start().encode(buf); 122 | // We encode the length of the text because it's often smaller than 123 | // its end, especially for longer editing sessions. 124 | // 125 | // For example, if a user inserts a character after already having 126 | // inserted 1000 before, it's better to encode `1000, 1` rather 127 | // than `1000, 1001`. 128 | let len = self.end() - self.start(); 129 | len.encode(buf); 130 | } 131 | } 132 | 133 | impl Decode for Text { 134 | type Value = Self; 135 | 136 | type Error = IntDecodeError; 137 | 138 | #[inline] 139 | fn decode(buf: &[u8]) -> Result<(Self, &[u8]), Self::Error> { 140 | let (inserted_by, buf) = ReplicaId::decode(buf)?; 141 | let (start, buf) = usize::decode(buf)?; 142 | let (len, buf) = usize::decode(buf)?; 143 | let text = Self { inserted_by, range: start..start + len }; 144 | Ok((text, buf)) 145 | } 146 | } 147 | } 148 | 149 | #[cfg(feature = "serde")] 150 | mod serde { 151 | crate::encode::impl_deserialize!(super::Text); 152 | crate::encode::impl_serialize!(super::Text); 153 | } 154 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | use core::fmt::{Debug, Formatter, Result as FmtResult}; 2 | use core::ops::{Add, Range as StdRange, RangeBounds, Sub}; 3 | 4 | use crate::Length; 5 | 6 | #[derive(Clone, Copy, PartialEq, Eq, Hash)] 7 | pub(crate) struct Range { 8 | pub start: T, 9 | pub end: T, 10 | } 11 | 12 | impl Debug for Range { 13 | fn fmt(&self, f: &mut Formatter) -> FmtResult { 14 | write!(f, "{:?}..{:?}", self.start, self.end) 15 | } 16 | } 17 | 18 | impl From> for Range { 19 | #[inline] 20 | fn from(range: StdRange) -> Self { 21 | Range { start: range.start, end: range.end } 22 | } 23 | } 24 | 25 | impl From> for StdRange { 26 | #[inline] 27 | fn from(range: Range) -> Self { 28 | StdRange { start: range.start, end: range.end } 29 | } 30 | } 31 | 32 | impl + Copy> Sub for Range { 33 | type Output = Range; 34 | 35 | #[inline] 36 | fn sub(self, value: T) -> Self::Output { 37 | Range { start: self.start - value, end: self.end - value } 38 | } 39 | } 40 | 41 | impl + Copy> Add for Range { 42 | type Output = Range; 43 | 44 | #[inline] 45 | fn add(self, value: T) -> Self::Output { 46 | Range { start: self.start + value, end: self.end + value } 47 | } 48 | } 49 | 50 | impl Range { 51 | #[inline] 52 | pub fn len(&self) -> T 53 | where 54 | T: Sub + Copy, 55 | { 56 | self.end - self.start 57 | } 58 | } 59 | 60 | pub(crate) trait RangeExt { 61 | fn contains_range(&self, range: Range) -> bool; 62 | } 63 | 64 | impl RangeExt for StdRange { 65 | #[inline] 66 | fn contains_range(&self, other: Range) -> bool { 67 | self.start <= other.start && self.end >= other.end 68 | } 69 | } 70 | 71 | /// TODO: docs 72 | #[inline] 73 | pub(crate) fn get_two_mut( 74 | slice: &mut [T], 75 | first_idx: usize, 76 | second_idx: usize, 77 | ) -> (&mut T, &mut T) { 78 | debug_assert!(first_idx != second_idx); 79 | 80 | if first_idx < second_idx { 81 | debug_assert!(second_idx < slice.len()); 82 | let split_at = first_idx + 1; 83 | let (first, second) = slice.split_at_mut(split_at); 84 | (&mut first[first_idx], &mut second[second_idx - split_at]) 85 | } else { 86 | debug_assert!(first_idx < slice.len()); 87 | let split_at = second_idx + 1; 88 | let (first, second) = slice.split_at_mut(split_at); 89 | (&mut second[first_idx - split_at], &mut first[second_idx]) 90 | } 91 | } 92 | 93 | /// TODO: docs 94 | #[inline] 95 | pub(crate) fn insert_in_slice(slice: &mut [T], elem: T, at_offset: usize) { 96 | debug_assert!(at_offset < slice.len()); 97 | slice[at_offset..].rotate_right(1); 98 | slice[at_offset] = elem; 99 | } 100 | 101 | /// TODO: docs 102 | #[inline(always)] 103 | pub(crate) fn range_bounds_to_start_end( 104 | range: R, 105 | lo: Length, 106 | hi: Length, 107 | ) -> (Length, Length) 108 | where 109 | R: RangeBounds, 110 | { 111 | use core::ops::Bound; 112 | 113 | let start = match range.start_bound() { 114 | Bound::Included(&n) => n, 115 | Bound::Excluded(&n) => n + 1, 116 | Bound::Unbounded => lo, 117 | }; 118 | 119 | let end = match range.end_bound() { 120 | Bound::Included(&n) => n + 1, 121 | Bound::Excluded(&n) => n, 122 | Bound::Unbounded => hi, 123 | }; 124 | 125 | (start, end) 126 | } 127 | 128 | pub mod panic_messages { 129 | use crate::Length; 130 | 131 | #[track_caller] 132 | #[cold] 133 | #[inline(never)] 134 | pub(crate) fn offset_out_of_bounds(offset: Length, len: Length) -> ! { 135 | debug_assert!(offset > len); 136 | panic!( 137 | "offset out of bounds: the offset is {offset} but the length is \ 138 | {len}" 139 | ); 140 | } 141 | 142 | #[track_caller] 143 | #[cold] 144 | #[inline(never)] 145 | pub(crate) fn replica_id_equal_to_forked() -> ! { 146 | panic!( 147 | "invalid ReplicaId: must not be equal to the one of the forked \ 148 | Replica, consider cloning instead" 149 | ); 150 | } 151 | 152 | #[track_caller] 153 | #[cold] 154 | #[inline(never)] 155 | pub(crate) fn replica_id_is_zero() -> ! { 156 | panic!("invalid ReplicaId: must not be zero"); 157 | } 158 | 159 | #[track_caller] 160 | #[cold] 161 | #[inline(never)] 162 | pub(crate) fn start_greater_than_end(start: Length, end: Length) -> ! { 163 | debug_assert!(start > end); 164 | panic!( 165 | "offset range's start is greater than its end: the start is \ 166 | {start} but the end is {end}" 167 | ); 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /src/version_map.rs: -------------------------------------------------------------------------------- 1 | use core::cmp::Ordering; 2 | 3 | use crate::{DeletionTs, Length, ReplicaId, ReplicaIdMap}; 4 | 5 | pub type DeletionMap = BaseMap; 6 | 7 | pub type VersionMap = BaseMap; 8 | 9 | /// A struct equivalent to a `HashMap`, but with the `ReplicaId` 10 | /// of the local `Replica` (and the corresponding `T`) stored separately from 11 | /// the `HashMap` itself. 12 | #[derive(Clone, PartialEq, Eq)] 13 | pub struct BaseMap { 14 | /// The `ReplicaId` of the `Replica` that this map is used in. 15 | this_id: ReplicaId, 16 | 17 | /// The local value. 18 | this_value: T, 19 | 20 | /// The values of the remote `Replica`s. 21 | rest: ReplicaIdMap, 22 | } 23 | 24 | impl BaseMap { 25 | #[inline] 26 | pub fn get(&self, replica_id: ReplicaId) -> T 27 | where 28 | T: Default, 29 | { 30 | if replica_id == self.this_id { 31 | self.this_value 32 | } else { 33 | self.rest.get(&replica_id).copied().unwrap_or_default() 34 | } 35 | } 36 | 37 | #[inline] 38 | pub fn get_mut(&mut self, replica_id: ReplicaId) -> &mut T 39 | where 40 | T: Default, 41 | { 42 | if replica_id == self.this_id { 43 | &mut self.this_value 44 | } else { 45 | self.rest.entry(replica_id).or_default() 46 | } 47 | } 48 | 49 | #[inline] 50 | pub fn fork(&self, new_id: ReplicaId, restart_at: T) -> Self { 51 | let mut forked = self.clone(); 52 | forked.fork_in_place(new_id, restart_at); 53 | forked 54 | } 55 | 56 | #[inline] 57 | pub fn fork_in_place(&mut self, new_id: ReplicaId, restart_at: T) { 58 | self.rest.insert(self.this_id, self.this_value); 59 | self.this_id = new_id; 60 | self.this_value = restart_at; 61 | } 62 | 63 | #[inline] 64 | pub fn insert(&mut self, replica_id: ReplicaId, value: T) { 65 | if replica_id != self.this_id { 66 | self.rest.insert(replica_id, value); 67 | } 68 | } 69 | 70 | #[inline] 71 | fn iter(&self) -> impl Iterator + '_ { 72 | let this_entry = core::iter::once((self.this_id, self.this_value)); 73 | this_entry.chain(self.rest.iter().map(|(&id, &value)| (id, value))) 74 | } 75 | 76 | #[inline] 77 | pub(crate) fn iter_mut( 78 | &mut self, 79 | ) -> impl Iterator + '_ { 80 | let this_entry = 81 | core::iter::once((self.this_id, &mut self.this_value)); 82 | this_entry.chain(self.rest.iter_mut().map(|(&id, value)| (id, value))) 83 | } 84 | 85 | #[inline] 86 | pub fn new(this_id: ReplicaId, this_value: T) -> Self { 87 | Self { this_id, this_value, rest: ReplicaIdMap::default() } 88 | } 89 | 90 | #[inline] 91 | pub fn this(&self) -> T { 92 | self.this_value 93 | } 94 | 95 | #[inline] 96 | pub fn this_id(&self) -> ReplicaId { 97 | self.this_id 98 | } 99 | 100 | #[inline] 101 | pub fn this_mut(&mut self) -> &mut T { 102 | &mut self.this_value 103 | } 104 | } 105 | 106 | impl core::fmt::Debug for BaseMap { 107 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 108 | let this_entry = core::iter::once((&self.this_id, &self.this_value)); 109 | f.debug_map().entries(this_entry.chain(self.rest.iter())).finish() 110 | } 111 | } 112 | 113 | impl PartialOrd for VersionMap { 114 | #[inline] 115 | fn partial_cmp(&self, other: &Self) -> Option { 116 | #[inline] 117 | fn update_order(iter: I, mut order: Ordering) -> Option 118 | where 119 | I: Iterator, 120 | { 121 | for (this_value, other_value) in iter { 122 | match this_value.cmp(&other_value) { 123 | Ordering::Greater => { 124 | if order == Ordering::Less { 125 | return None; 126 | } 127 | order = Ordering::Greater; 128 | }, 129 | 130 | Ordering::Less => { 131 | if order == Ordering::Greater { 132 | return None; 133 | } 134 | order = Ordering::Less; 135 | }, 136 | 137 | Ordering::Equal => {}, 138 | } 139 | } 140 | 141 | Some(order) 142 | } 143 | 144 | let mut order = Ordering::Equal; 145 | 146 | let iter = self.iter().filter(|(_, value)| *value > 0).map( 147 | |(this_id, this_value)| { 148 | let other_value = other.get(this_id); 149 | (this_value, other_value) 150 | }, 151 | ); 152 | 153 | order = update_order(iter, order)?; 154 | 155 | let iter = other.iter().filter(|(_, value)| *value > 0).map( 156 | |(other_id, other_value)| { 157 | let this_value = self.get(other_id); 158 | (this_value, other_value) 159 | }, 160 | ); 161 | 162 | update_order(iter, order) 163 | } 164 | } 165 | 166 | #[cfg(feature = "encode")] 167 | pub(crate) mod encode { 168 | use super::*; 169 | use crate::encode::{Decode, Encode, IntDecodeError}; 170 | 171 | impl Encode for BaseMap { 172 | #[inline] 173 | fn encode(&self, buf: &mut Vec) { 174 | self.this_id.encode(buf); 175 | self.this_value.encode(buf); 176 | (self.rest.len() as u64).encode(buf); 177 | for (id, value) in self.rest.iter() { 178 | id.encode(buf); 179 | value.encode(buf); 180 | } 181 | } 182 | } 183 | 184 | pub(crate) enum BaseMapDecodeError { 185 | Key(IntDecodeError), 186 | Value(T::Error), 187 | } 188 | 189 | impl From for BaseMapDecodeError { 190 | #[inline(always)] 191 | fn from(err: IntDecodeError) -> Self { 192 | Self::Key(err) 193 | } 194 | } 195 | 196 | impl core::fmt::Display for BaseMapDecodeError { 197 | #[inline] 198 | fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { 199 | let err: &dyn core::fmt::Display = match self { 200 | Self::Key(err) => err, 201 | Self::Value(err) => err, 202 | }; 203 | 204 | write!( 205 | f, 206 | "BaseMap<{:?}>: couldn't be decoded: {err}", 207 | core::any::type_name::() 208 | ) 209 | } 210 | } 211 | 212 | impl> Decode for BaseMap { 213 | type Value = Self; 214 | 215 | type Error = BaseMapDecodeError; 216 | 217 | #[inline] 218 | fn decode(buf: &[u8]) -> Result<(Self::Value, &[u8]), Self::Error> { 219 | let (this_id, buf) = ReplicaId::decode(buf)?; 220 | 221 | let (this_value, buf) = 222 | T::decode(buf).map_err(BaseMapDecodeError::Value)?; 223 | 224 | let (rest_len, mut buf) = u64::decode(buf)?; 225 | 226 | let mut rest = ReplicaIdMap::default(); 227 | 228 | for _ in 0..rest_len { 229 | let (id, new_buf) = ReplicaId::decode(buf)?; 230 | 231 | let (value, new_buf) = 232 | T::decode(new_buf).map_err(BaseMapDecodeError::Value)?; 233 | 234 | rest.insert(id, value); 235 | 236 | buf = new_buf; 237 | } 238 | 239 | let this = Self { this_id, this_value, rest }; 240 | 241 | Ok((this, buf)) 242 | } 243 | } 244 | } 245 | 246 | #[cfg(feature = "serde")] 247 | mod serde { 248 | crate::encode::impl_serialize!(super::DeletionMap); 249 | crate::encode::impl_deserialize!(super::DeletionMap); 250 | 251 | crate::encode::impl_serialize!(super::VersionMap); 252 | crate::encode::impl_deserialize!(super::VersionMap); 253 | } 254 | -------------------------------------------------------------------------------- /tests/anchor.rs: -------------------------------------------------------------------------------- 1 | use cola::{AnchorBias, Replica}; 2 | 3 | /// Tests that an anchor set at the start of the document with left bias always 4 | /// resolves to the start of the document. 5 | #[test] 6 | fn anchor_start_left_bias() { 7 | let mut replica = Replica::new(1, 0); 8 | 9 | let _ = replica.inserted(0, 5); 10 | 11 | let anchor = replica.create_anchor(0, AnchorBias::Left); 12 | 13 | let _ = replica.inserted(0, 5); 14 | let _ = replica.inserted(0, 5); 15 | 16 | assert_eq!(replica.resolve_anchor(anchor).unwrap(), 0); 17 | } 18 | 19 | /// Tests that an anchor set at the start of the document with right bias 20 | /// resolves to the start of the first visible run at the time of creation. 21 | #[test] 22 | fn anchor_start_right_bias() { 23 | let mut replica = Replica::new(1, 0); 24 | 25 | let _ = replica.inserted(0, 5); 26 | 27 | let anchor = replica.create_anchor(0, AnchorBias::Right); 28 | 29 | let _ = replica.inserted(0, 5); 30 | let _ = replica.inserted(0, 5); 31 | 32 | assert_eq!(replica.resolve_anchor(anchor).unwrap(), 10); 33 | 34 | let _ = replica.deleted(0..3); 35 | 36 | assert_eq!(replica.resolve_anchor(anchor).unwrap(), 7); 37 | } 38 | 39 | /// Tests that deleted runs are skipped when anchoring to the start of the 40 | /// document with right bias. 41 | #[test] 42 | fn anchor_start_right_bias_skip_deleted() { 43 | let mut replica = Replica::new(1, 0); 44 | 45 | let _ = replica.inserted(0, 5); 46 | 47 | let _ = replica.deleted(0..2); 48 | 49 | let anchor = replica.create_anchor(0, AnchorBias::Right); 50 | 51 | let _ = replica.inserted(0, 5); 52 | 53 | assert_eq!(replica.resolve_anchor(anchor).unwrap(), 5); 54 | } 55 | 56 | /// Tests that an anchor set at the end of the document with left bias resolves 57 | /// to the end of the last visible run at the time of creation. 58 | #[test] 59 | fn anchor_end_left_bias() { 60 | let mut replica = Replica::new(1, 0); 61 | 62 | let _ = replica.inserted(0, 5); 63 | 64 | let anchor = replica.create_anchor(5, AnchorBias::Left); 65 | 66 | let _ = replica.inserted(5, 5); 67 | let _ = replica.inserted(10, 5); 68 | 69 | assert_eq!(replica.resolve_anchor(anchor).unwrap(), 5); 70 | } 71 | 72 | /// Tests that an anchor set at the en of the document with right bias always 73 | /// resolves to the end of the document. 74 | #[test] 75 | fn anchor_end_right_bias() { 76 | let mut replica = Replica::new(1, 0); 77 | 78 | let _ = replica.inserted(0, 5); 79 | 80 | let anchor = replica.create_anchor(5, AnchorBias::Right); 81 | 82 | let _ = replica.inserted(5, 5); 83 | let _ = replica.inserted(10, 5); 84 | 85 | assert_eq!(replica.resolve_anchor(anchor).unwrap(), 15); 86 | } 87 | 88 | /// Tests that we stop before the deleted runs when anchoring to the end of the 89 | /// document with left bias. 90 | #[test] 91 | fn anchor_end_left_bias_stop_before_deleted() { 92 | let mut replica = Replica::new(1, 0); 93 | 94 | let _ = replica.inserted(0, 5); 95 | let _ = replica.inserted(0, 5); 96 | let _ = replica.inserted(0, 5); 97 | let _ = replica.deleted(10..15); 98 | let _ = replica.deleted(5..10); 99 | 100 | let anchor = replica.create_anchor(5, AnchorBias::Left); 101 | 102 | let _ = replica.inserted(5, 5); 103 | let _ = replica.inserted(10, 5); 104 | 105 | assert_eq!(replica.resolve_anchor(anchor).unwrap(), 5); 106 | } 107 | 108 | /// Tests that the offset of the anchor in the run containing it is not added 109 | /// to the total if the run is deleted. 110 | #[test] 111 | fn anchor_inside_deleted_run() { 112 | let mut replica = Replica::new(1, 0); 113 | 114 | let _ = replica.inserted(0, 5); 115 | let _ = replica.inserted(0, 5); 116 | 117 | let anchor = replica.create_anchor(8, AnchorBias::Left); 118 | 119 | let _ = replica.deleted(6..9); 120 | 121 | assert_eq!(replica.resolve_anchor(anchor).unwrap(), 6); 122 | } 123 | 124 | /// Tests that an anchor set at the border between two runs with left bias 125 | /// resolves to the end of the run on the left. 126 | #[test] 127 | fn anchor_at_run_border_left_bias() { 128 | let mut replica = Replica::new(1, 0); 129 | 130 | let _ = replica.inserted(0, 5); 131 | let _ = replica.inserted(0, 5); 132 | 133 | let anchor = replica.create_anchor(5, AnchorBias::Left); 134 | 135 | let _ = replica.inserted(5, 5); 136 | 137 | assert_eq!(replica.resolve_anchor(anchor).unwrap(), 5); 138 | } 139 | 140 | /// Tests that an anchor set at the border between two runs with right bias 141 | /// resolves to the start of the run on the right. 142 | #[test] 143 | fn anchor_at_run_border_right_bias() { 144 | let mut replica = Replica::new(1, 0); 145 | 146 | let _ = replica.inserted(0, 5); 147 | let _ = replica.inserted(0, 5); 148 | 149 | let anchor = replica.create_anchor(5, AnchorBias::Right); 150 | 151 | let _ = replica.inserted(5, 5); 152 | 153 | assert_eq!(replica.resolve_anchor(anchor).unwrap(), 10); 154 | } 155 | 156 | /// Tests that an anchor set inside a run with left bias resolves to the end of 157 | /// the left fragment if the run is split. 158 | #[test] 159 | fn anchor_in_split_run_left_bias() { 160 | let mut replica = Replica::new(1, 0); 161 | 162 | let _ = replica.inserted(0, 10); 163 | 164 | let anchor = replica.create_anchor(5, AnchorBias::Left); 165 | 166 | let _ = replica.inserted(5, 5); 167 | 168 | assert_eq!(replica.resolve_anchor(anchor).unwrap(), 5); 169 | } 170 | 171 | /// Tests that an anchor set inside a run with right bias resolves to the start 172 | /// of the right fragment if the run is split. 173 | #[test] 174 | fn anchor_in_split_run_right_bias() { 175 | let mut replica = Replica::new(1, 0); 176 | 177 | let _ = replica.inserted(0, 10); 178 | 179 | let anchor = replica.create_anchor(5, AnchorBias::Right); 180 | 181 | let _ = replica.inserted(5, 5); 182 | 183 | assert_eq!(replica.resolve_anchor(anchor).unwrap(), 10); 184 | } 185 | -------------------------------------------------------------------------------- /tests/common/mod.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::fmt::Debug; 3 | use std::ops::Range; 4 | 5 | use cola::{ReplicaId, Text}; 6 | use rand::Rng; 7 | 8 | pub struct Replica { 9 | pub buffer: String, 10 | pub crdt: cola::Replica, 11 | backlog: HashMap, 12 | } 13 | 14 | impl Debug for Replica { 15 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 16 | f.debug_struct("Replica") 17 | .field("buffer", &self.buffer) 18 | .field("crdt", &self.crdt.debug_as_btree()) 19 | .finish() 20 | } 21 | } 22 | 23 | impl PartialEq for Replica { 24 | fn eq(&self, rhs: &Replica) -> bool { 25 | self.buffer == rhs.buffer 26 | } 27 | } 28 | 29 | impl PartialEq<&str> for Replica { 30 | fn eq(&self, rhs: &&str) -> bool { 31 | self.buffer == *rhs 32 | } 33 | } 34 | 35 | impl PartialEq for &str { 36 | fn eq(&self, rhs: &Replica) -> bool { 37 | rhs.buffer == *self 38 | } 39 | } 40 | 41 | #[derive(Clone, Debug, PartialEq, Eq)] 42 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 43 | pub enum Edit { 44 | Insertion(cola::Insertion, String), 45 | Deletion(cola::Deletion), 46 | } 47 | 48 | impl Replica { 49 | pub fn assert_invariants(&self) { 50 | self.crdt.assert_invariants(); 51 | assert_eq!(self.buffer.len(), self.crdt.len()); 52 | } 53 | 54 | fn char_to_byte(&self, char_offset: usize) -> usize { 55 | self.buffer.chars().take(char_offset).map(char::len_utf8).sum() 56 | } 57 | 58 | pub fn edit(&mut self, edit: RandomEdit) -> Edit { 59 | match edit { 60 | RandomEdit::Insertion(byte_offset, text) => { 61 | self.insert(byte_offset, text) 62 | }, 63 | 64 | RandomEdit::Deletion(byte_range) => self.delete(byte_range), 65 | } 66 | } 67 | 68 | pub fn delete(&mut self, byte_range: Range) -> Edit { 69 | self.buffer.replace_range(byte_range.clone(), ""); 70 | let deletion = self.crdt.deleted(byte_range); 71 | Edit::Deletion(deletion) 72 | } 73 | 74 | pub fn fork(&self, id: ReplicaId) -> Self { 75 | Self { 76 | buffer: self.buffer.clone(), 77 | crdt: self.crdt.fork(id), 78 | backlog: self.backlog.clone(), 79 | } 80 | } 81 | 82 | pub fn insert>( 83 | &mut self, 84 | byte_offset: usize, 85 | text: T, 86 | ) -> Edit { 87 | let text = text.into(); 88 | self.buffer.insert_str(byte_offset, text.as_str()); 89 | let insertion = self.crdt.inserted(byte_offset, text.len()); 90 | Edit::Insertion(insertion, text) 91 | } 92 | 93 | pub fn len(&self) -> usize { 94 | self.buffer.len() 95 | } 96 | 97 | pub fn merge(&mut self, edit: &Edit) { 98 | match edit { 99 | Edit::Insertion(insertion, string) => { 100 | if let Some(offset) = self.crdt.integrate_insertion(insertion) 101 | { 102 | self.buffer.insert_str(offset, string); 103 | } else { 104 | self.backlog 105 | .insert(insertion.text().clone(), string.clone()); 106 | } 107 | }, 108 | 109 | Edit::Deletion(deletion) => { 110 | for range in 111 | self.crdt.integrate_deletion(deletion).into_iter().rev() 112 | { 113 | self.buffer.replace_range(range, ""); 114 | } 115 | }, 116 | } 117 | } 118 | 119 | pub fn merge_backlogged(&mut self) { 120 | for (text, offset) in self.crdt.backlogged_insertions() { 121 | let s = self.backlog.get(&text).unwrap(); 122 | self.buffer.insert_str(offset, s); 123 | } 124 | 125 | for ranges in self.crdt.backlogged_deletions() { 126 | for range in ranges.into_iter().rev() { 127 | self.buffer.replace_range(range, ""); 128 | } 129 | } 130 | } 131 | 132 | pub fn new>(id: ReplicaId, text: T) -> Self { 133 | let buffer = text.into(); 134 | let crdt = cola::Replica::new(id, buffer.len()); 135 | let history = HashMap::new(); 136 | Self { buffer, crdt, backlog: history } 137 | } 138 | 139 | pub fn new_with_len( 140 | id: ReplicaId, 141 | max_len: usize, 142 | rng: &mut impl Rng, 143 | ) -> Self { 144 | let string = 145 | (0..max_len).map(|_| rng.gen_range('a'..='z')).collect::(); 146 | Self::new(id, string) 147 | } 148 | 149 | pub fn random_insert( 150 | &self, 151 | rng: &mut impl rand::Rng, 152 | max_len: usize, 153 | ) -> (usize, String) { 154 | assert!(max_len > 0); 155 | let offset = rng.gen_range(0..=self.buffer.len()); 156 | let text_len = rng.gen_range(1..=max_len); 157 | let letter = rng.gen_range('a'..='z'); 158 | let text = (0..text_len).map(|_| letter).collect::(); 159 | (offset, text) 160 | } 161 | 162 | pub fn random_delete( 163 | &self, 164 | rng: &mut impl rand::Rng, 165 | max_len: usize, 166 | ) -> Range { 167 | assert!(!self.buffer.is_empty()); 168 | 169 | let start = rng.gen_range(0..self.buffer.len()); 170 | 171 | let len = rng.gen_range(1..=max_len); 172 | 173 | let end = if start + len > self.buffer.len() { 174 | self.buffer.len() 175 | } else { 176 | start + len 177 | }; 178 | 179 | start..end 180 | } 181 | 182 | pub fn random_edit( 183 | &self, 184 | rng: &mut impl rand::Rng, 185 | max_insertion_len: usize, 186 | max_deletion_len: usize, 187 | ) -> RandomEdit { 188 | let create_insertion = rng.gen::() || self.buffer.is_empty(); 189 | 190 | if create_insertion { 191 | let (offset, text) = self.random_insert(rng, max_insertion_len); 192 | RandomEdit::Insertion(offset, text) 193 | } else { 194 | let range = self.random_delete(rng, max_deletion_len); 195 | RandomEdit::Deletion(range) 196 | } 197 | } 198 | } 199 | 200 | pub enum RandomEdit { 201 | Insertion(usize, String), 202 | Deletion(Range), 203 | } 204 | 205 | impl traces::Crdt for Replica { 206 | type EDIT = Edit; 207 | 208 | fn from_str(id: u64, s: &str) -> Self { 209 | Self::new(id, s) 210 | } 211 | 212 | fn fork(&self, new_id: u64) -> Self { 213 | self.fork(new_id) 214 | } 215 | 216 | fn local_insert(&mut self, offset: usize, text: &str) -> Self::EDIT { 217 | let offset = self.char_to_byte(offset); 218 | self.insert(offset, text) 219 | } 220 | 221 | fn local_delete(&mut self, start: usize, end: usize) -> Self::EDIT { 222 | let start = self.char_to_byte(start); 223 | let end = self.char_to_byte(end); 224 | self.delete(start..end) 225 | } 226 | 227 | fn remote_merge(&mut self, remote_edit: &Self::EDIT) { 228 | self.merge(remote_edit) 229 | } 230 | } 231 | 232 | #[macro_export] 233 | macro_rules! assert_convergence { 234 | ($slice:expr) => {{ 235 | for replica in $slice[1..].iter() { 236 | if &$slice[0] != replica { 237 | panic!("left: {:#?}\nright: {:#?}", &$slice[0], replica); 238 | } 239 | } 240 | }}; 241 | 242 | ($one:expr, $two:expr) => {{ 243 | if $one != $two { 244 | panic!("left: {:#?}\nright: {:#?}", $one, $two); 245 | } 246 | }}; 247 | 248 | ($one:expr, $two:expr, $three:expr) => {{ 249 | assert_eq!($one, $two, "{:#?} vs {:#?}", $one, $two); 250 | assert_eq!($two, $three, "{:#?} vs {:#?}", $two, $three); 251 | }}; 252 | 253 | ($one:expr, $two:expr, $three:expr, $four:expr) => {{ 254 | assert_eq!($one, $two, "{:#?} vs {:#?}", $one, $two); 255 | assert_eq!($two, $three, "{:#?} vs {:#?}", $two, $three); 256 | assert_eq!($three, $four, "{:#?} vs {:#?}", $three, $four); 257 | }}; 258 | 259 | ($one:expr, $two:expr, $three:expr, $four:expr, $five:expr) => {{ 260 | assert_eq!($one, $two, "{:#?} vs {:#?}", $one, $two); 261 | assert_eq!($two, $three, "{:#?} vs {:#?}", $two, $three); 262 | assert_eq!($three, $four, "{:#?} vs {:#?}", $three, $four); 263 | assert_eq!($four, $five, "{:#?} vs {:#?}", $four, $five); 264 | }}; 265 | } 266 | -------------------------------------------------------------------------------- /tests/concurrent_traces.rs: -------------------------------------------------------------------------------- 1 | mod common; 2 | 3 | use common::Replica; 4 | use traces::{ConcurrentTraceInfos, Crdt, Edit}; 5 | 6 | fn test_trace(trace: ConcurrentTraceInfos) { 7 | let ConcurrentTraceInfos { trace, mut peers, final_content, .. } = trace; 8 | 9 | for edit in trace.edits() { 10 | match edit { 11 | Edit::Insertion(idx, offset, text) => { 12 | peers[*idx].local_insert(*offset, text); 13 | peers[*idx].assert_invariants(); 14 | }, 15 | Edit::Deletion(idx, start, end) => { 16 | peers[*idx].local_delete(*start, *end); 17 | peers[*idx].assert_invariants(); 18 | }, 19 | Edit::Merge(idx, edit) => { 20 | peers[*idx].remote_merge(edit); 21 | peers[*idx].assert_invariants(); 22 | }, 23 | } 24 | } 25 | 26 | for replica in &mut peers { 27 | replica.merge_backlogged(); 28 | } 29 | 30 | for replica in &peers { 31 | assert_eq!(replica.buffer, final_content); 32 | } 33 | } 34 | 35 | #[test] 36 | fn test_friends_forever() { 37 | test_trace(traces::friends_forever()); 38 | } 39 | -------------------------------------------------------------------------------- /tests/deletions.rs: -------------------------------------------------------------------------------- 1 | mod common; 2 | 3 | use cola::ReplicaId; 4 | use common::Replica; 5 | use rand::seq::SliceRandom; 6 | use rand::{Rng, SeedableRng}; 7 | use rand_chacha::ChaCha8Rng; 8 | 9 | #[test] 10 | fn join_consecutive_deletions() { 11 | let mut replica1 = Replica::new(1, "abc"); 12 | let mut replica2 = replica1.fork(2); 13 | 14 | let del_c = replica1.delete(2..3); 15 | let del_b = replica1.delete(1..2); 16 | 17 | replica2.merge(&del_c); 18 | replica2.merge(&del_b); 19 | 20 | assert_eq!(replica1.crdt.num_runs(), 2); 21 | assert_eq!(replica2.crdt.num_runs(), 2); 22 | } 23 | 24 | #[test] 25 | fn deletion_start_continued() { 26 | let mut replica1 = Replica::new(1, ""); 27 | let mut replica2 = replica1.fork(2); 28 | 29 | let ins_ff = replica1.insert(0, "ff"); 30 | let ins_s = replica2.insert(0, "s"); 31 | 32 | replica1.merge(&ins_s); 33 | replica2.merge(&ins_ff); 34 | 35 | let ins_kkk = replica1.insert(2, "kkk"); 36 | let del_fs = replica2.delete(1..3); 37 | 38 | replica1.merge(&del_fs); 39 | replica2.merge(&ins_kkk); 40 | 41 | assert_convergence!(replica1, replica2, "fkkk"); 42 | } 43 | 44 | #[test] 45 | fn random_deletions() { 46 | let seed = rand::random::(); 47 | println!("seed: {seed}"); 48 | let mut rng = ChaCha8Rng::seed_from_u64(seed); 49 | test_random_deletions(&mut rng, 200_000, 5, 1000, 5, 5); 50 | } 51 | 52 | fn test_random_deletions( 53 | rng: &mut impl Rng, 54 | initial_len: usize, 55 | num_replicas: usize, 56 | num_cycles: usize, 57 | deletions_per_cycle: usize, 58 | max_deletion_len: usize, 59 | ) { 60 | assert!(num_replicas > 1); 61 | assert!(max_deletion_len > 0); 62 | assert!(deletions_per_cycle > 0); 63 | assert!( 64 | num_replicas * deletions_per_cycle * max_deletion_len * num_cycles 65 | <= initial_len 66 | ); 67 | 68 | let first_replica = Replica::new_with_len(1, initial_len, rng); 69 | 70 | let mut replicas = vec![first_replica]; 71 | 72 | for i in 1..num_replicas { 73 | replicas.push(replicas[0].fork(i as ReplicaId + 1)); 74 | } 75 | 76 | let mut merge_order = (0..deletions_per_cycle).collect::>(); 77 | 78 | for _ in 0..num_cycles { 79 | let deletions = replicas 80 | .iter_mut() 81 | .map(|replica| { 82 | (0..deletions_per_cycle) 83 | .map(|_| { 84 | let range = 85 | replica.random_delete(rng, max_deletion_len); 86 | replica.delete(range) 87 | }) 88 | .collect::>() 89 | }) 90 | .collect::>(); 91 | 92 | for (replica_idx, replica) in replicas.iter_mut().enumerate() { 93 | let mut remote_order = (0..num_replicas) 94 | .filter(|&idx| idx != replica_idx) 95 | .collect::>(); 96 | 97 | remote_order.shuffle(rng); 98 | 99 | for deletions in remote_order.iter().map(|&idx| &deletions[idx]) { 100 | merge_order.shuffle(rng); 101 | 102 | for deletion in merge_order.iter().map(|&idx| &deletions[idx]) 103 | { 104 | replica.merge(deletion); 105 | } 106 | } 107 | 108 | replica.merge_backlogged(); 109 | } 110 | 111 | for replica in &replicas { 112 | replica.assert_invariants(); 113 | } 114 | 115 | assert_convergence!(replicas); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /tests/downstream_traces.rs: -------------------------------------------------------------------------------- 1 | mod common; 2 | 3 | use common::Replica; 4 | use traces::SequentialTrace; 5 | 6 | fn test_trace(trace: &SequentialTrace) { 7 | let trace = trace.chars_to_bytes(); 8 | 9 | let mut upstream = Replica::new(1, trace.start_content()); 10 | 11 | let mut len = 0; 12 | 13 | let edits = trace 14 | .edits() 15 | .flat_map(|(start, end, text)| { 16 | let mut edits = Vec::new(); 17 | if end > start { 18 | let edit = upstream.delete(start..end); 19 | len -= end - start; 20 | edits.push((edit, len)); 21 | } 22 | if !text.is_empty() { 23 | let edit = upstream.insert(start, text); 24 | len += text.len(); 25 | edits.push((edit, len)); 26 | } 27 | edits 28 | }) 29 | .collect::>(); 30 | 31 | let upstream = Replica::new(1, trace.start_content()); 32 | 33 | let mut downstream = upstream.fork(2); 34 | 35 | for (edit, len) in &edits { 36 | downstream.merge(edit); 37 | downstream.assert_invariants(); 38 | assert_eq!(downstream.len(), *len); 39 | } 40 | 41 | assert_eq!(downstream, trace.end_content()); 42 | } 43 | 44 | #[test] 45 | fn downstream_automerge() { 46 | test_trace(&traces::automerge()); 47 | } 48 | 49 | #[test] 50 | fn downstream_rustcode() { 51 | test_trace(&traces::rustcode()); 52 | } 53 | 54 | #[test] 55 | fn downstream_seph_blog() { 56 | test_trace(&traces::seph_blog()); 57 | } 58 | 59 | #[test] 60 | fn downstream_sveltecomponent() { 61 | test_trace(&traces::sveltecomponent()); 62 | } 63 | -------------------------------------------------------------------------------- /tests/encode.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "encode")] 2 | mod encode { 3 | use cola::Replica; 4 | 5 | /// Tests an encode-decode round-trip of an empty `Replica`. 6 | #[test] 7 | fn encode_empty() { 8 | let replica = Replica::new(1, 42); 9 | let encoded = replica.encode(); 10 | let decoded = Replica::decode(2, &encoded).unwrap(); 11 | assert!(replica.eq_decoded(&decoded)); 12 | } 13 | 14 | /// Tests an encode-decode round-trip of a `Replica` that has gone through 15 | /// the `automerge` trace. 16 | #[test] 17 | fn encode_automerge() { 18 | let automerge = traces::automerge().chars_to_bytes(); 19 | 20 | let mut replica = Replica::new(1, automerge.start_content().len()); 21 | 22 | for (start, end, text) in automerge.edits() { 23 | let _ = replica.deleted(start..end); 24 | let _ = replica.inserted(start, text.len()); 25 | } 26 | 27 | let encoded = replica.encode(); 28 | 29 | let decoded = Replica::decode(2, &encoded).unwrap(); 30 | 31 | assert!(replica.eq_decoded(&decoded)); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /tests/insertions.rs: -------------------------------------------------------------------------------- 1 | mod common; 2 | 3 | use cola::ReplicaId; 4 | use common::Replica; 5 | use rand::seq::SliceRandom; 6 | use rand::{Rng, SeedableRng}; 7 | use rand_chacha::ChaCha8Rng; 8 | 9 | /// Tests the convergence of the state illustrated in Figure 2 of the WOOT 10 | /// paper. 11 | #[test] 12 | fn woot_figure_2() { 13 | let mut peer1 = Replica::new(1, "ab"); 14 | let mut peer2 = peer1.fork(2); 15 | let mut peer3 = peer1.fork(3); 16 | 17 | // Peer 1 inserts '1' between 'a' and 'b'. 18 | let op1 = peer1.insert(1, '1'); 19 | 20 | // Peer 2 inserts '2' between 'a' and 'b'. 21 | let op2 = peer2.insert(1, '2'); 22 | 23 | // Peer 1's edit arrives at Peer 3 and gets merged. 24 | peer3.merge(&op1); 25 | 26 | assert_eq!(peer3, "a1b"); 27 | 28 | // Next, Peer 3 inserts '3' between 'a' and '1'. 29 | let op3 = peer3.insert(1, '3'); 30 | 31 | assert_eq!(peer3, "a31b"); 32 | 33 | // Now Peer 2's edit arrives at Peer 3, and the '2' should be inserted 34 | // after the '1', not before the '3'. 35 | peer3.merge(&op2); 36 | 37 | // The figure ends here but we also complete Peers 1 and 2. 38 | 39 | peer1.merge(&op2); 40 | peer1.merge(&op3); 41 | 42 | peer2.merge(&op3); 43 | peer2.merge(&op1); 44 | 45 | assert_convergence!(peer1, peer2, peer3, "a312b"); 46 | } 47 | 48 | #[test] 49 | fn conflicting_insertions() { 50 | let peer1 = Replica::new(1, "aa"); 51 | let mut peer2 = peer1.fork(2); 52 | let mut peer3 = peer1.fork(3); 53 | 54 | let bb = peer2.insert(2, "bb"); 55 | let cc = peer2.insert(2, "cc"); 56 | let dd = peer2.insert(6, "dd"); 57 | let ee = peer2.insert(4, "ee"); 58 | 59 | let ff = peer3.insert(2, "ff"); 60 | 61 | peer2.merge(&ff); 62 | 63 | peer3.merge(&bb); 64 | peer3.merge(&cc); 65 | peer3.merge(&dd); 66 | peer3.merge(&ee); 67 | 68 | assert_convergence!(peer2, peer3); 69 | } 70 | 71 | #[test] 72 | fn conflicting_insertions_2() { 73 | let mut peer1 = Replica::new(1, ""); 74 | let mut peer2 = peer1.fork(2); 75 | 76 | let sss = peer1.insert(0, "sss"); 77 | let d = peer1.insert(2, "d"); 78 | 79 | let m = peer2.insert(0, "m"); 80 | let xx = peer2.insert(0, "xx"); 81 | 82 | peer1.merge(&m); 83 | peer1.merge(&xx); 84 | 85 | peer2.merge(&sss); 86 | peer2.merge(&d); 87 | 88 | assert_convergence!(peer1, peer2, "xxssdsm"); 89 | } 90 | 91 | #[test] 92 | fn random_insertions() { 93 | let seed = rand::random::(); 94 | println!("seed: {seed}"); 95 | let mut rng = ChaCha8Rng::seed_from_u64(seed); 96 | test_random_insertions(&mut rng, 5, 1000, 5, 5); 97 | } 98 | 99 | fn test_random_insertions( 100 | rng: &mut impl Rng, 101 | num_replicas: usize, 102 | num_cycles: usize, 103 | insertions_per_cycle: usize, 104 | max_insertion_len: usize, 105 | ) { 106 | assert!(num_replicas > 1); 107 | assert!(max_insertion_len > 0); 108 | assert!(insertions_per_cycle > 0); 109 | 110 | let first_replica = Replica::new(1, ""); 111 | 112 | let mut replicas = vec![first_replica]; 113 | 114 | for i in 1..num_replicas { 115 | replicas.push(replicas[0].fork(i as ReplicaId + 1)); 116 | } 117 | 118 | let mut merge_order = (0..insertions_per_cycle).collect::>(); 119 | 120 | for _ in 0..num_cycles { 121 | let insertions = replicas 122 | .iter_mut() 123 | .map(|replica| { 124 | (0..insertions_per_cycle) 125 | .map(|_| { 126 | let (offset, text) = 127 | replica.random_insert(rng, max_insertion_len); 128 | replica.insert(offset, text) 129 | }) 130 | .collect::>() 131 | }) 132 | .collect::>(); 133 | 134 | for (replica_idx, replica) in replicas.iter_mut().enumerate() { 135 | let mut remote_order = (0..num_replicas) 136 | .filter(|&idx| idx != replica_idx) 137 | .collect::>(); 138 | 139 | remote_order.shuffle(rng); 140 | 141 | for insertions in remote_order.iter().map(|&idx| &insertions[idx]) 142 | { 143 | merge_order.shuffle(rng); 144 | 145 | for insertion in 146 | merge_order.iter().map(|&idx| &insertions[idx]) 147 | { 148 | replica.merge(insertion); 149 | } 150 | } 151 | 152 | replica.merge_backlogged(); 153 | } 154 | 155 | for replica in &replicas { 156 | replica.assert_invariants(); 157 | } 158 | 159 | assert_convergence!(replicas); 160 | } 161 | } 162 | 163 | #[test] 164 | fn random_edits() { 165 | let seed = rand::random::(); 166 | println!("seed: {seed}"); 167 | let mut rng = ChaCha8Rng::seed_from_u64(seed); 168 | test_random_edits(&mut rng, 5, 1000, 5, 10, 10); 169 | } 170 | 171 | fn test_random_edits( 172 | rng: &mut impl Rng, 173 | num_replicas: usize, 174 | num_cycles: usize, 175 | edits_per_cycle: usize, 176 | max_insertion_len: usize, 177 | max_deletion_len: usize, 178 | ) { 179 | let first_replica = Replica::new(1, ""); 180 | 181 | let mut replicas = vec![first_replica]; 182 | 183 | for i in 1..num_replicas { 184 | replicas.push(replicas[0].fork(i as ReplicaId + 1)); 185 | } 186 | 187 | let mut merge_order = (0..edits_per_cycle).collect::>(); 188 | 189 | for _ in 0..num_cycles { 190 | let edits = replicas 191 | .iter_mut() 192 | .map(|replica| { 193 | (0..edits_per_cycle) 194 | .map(|_| { 195 | let edit = replica.random_edit( 196 | rng, 197 | max_insertion_len, 198 | max_deletion_len, 199 | ); 200 | replica.edit(edit) 201 | }) 202 | .collect::>() 203 | }) 204 | .collect::>(); 205 | 206 | for (replica_idx, replica) in replicas.iter_mut().enumerate() { 207 | let mut remote_order = (0..num_replicas) 208 | .filter(|&idx| idx != replica_idx) 209 | .collect::>(); 210 | 211 | remote_order.shuffle(rng); 212 | 213 | for edits in remote_order.iter().map(|&idx| &edits[idx]) { 214 | merge_order.shuffle(rng); 215 | 216 | for edit in merge_order.iter().map(|&idx| &edits[idx]) { 217 | replica.merge(edit); 218 | } 219 | } 220 | 221 | replica.merge_backlogged(); 222 | } 223 | 224 | for replica in &replicas { 225 | replica.assert_invariants(); 226 | } 227 | 228 | assert_convergence!(replicas); 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /tests/sequential_traces.rs: -------------------------------------------------------------------------------- 1 | mod common; 2 | 3 | use common::Replica; 4 | use traces::SequentialTrace; 5 | 6 | fn test_trace(trace: &SequentialTrace) { 7 | let trace = trace.chars_to_bytes(); 8 | 9 | let mut replica = Replica::new(1, trace.start_content()); 10 | 11 | for i in 0..1 { 12 | for (start, end, text) in trace.edits() { 13 | if end > start { 14 | replica.delete(start..end); 15 | replica.assert_invariants(); 16 | } 17 | 18 | if !text.is_empty() { 19 | replica.insert(start, text); 20 | replica.assert_invariants(); 21 | } 22 | } 23 | 24 | if i == 0 { 25 | assert_eq!(replica, trace.end_content()); 26 | } else { 27 | assert_eq!(replica.len(), (trace.end_content().len() * (i + 1))); 28 | } 29 | } 30 | } 31 | 32 | #[test] 33 | fn trace_automerge() { 34 | test_trace(&traces::automerge()); 35 | } 36 | 37 | #[test] 38 | fn trace_rustcode() { 39 | test_trace(&traces::rustcode()); 40 | } 41 | 42 | #[test] 43 | fn trace_seph_blog() { 44 | test_trace(&traces::seph_blog()); 45 | } 46 | 47 | #[test] 48 | fn trace_sveltecomponent() { 49 | test_trace(&traces::sveltecomponent()); 50 | } 51 | -------------------------------------------------------------------------------- /tests/serde.rs: -------------------------------------------------------------------------------- 1 | mod common; 2 | 3 | #[cfg(feature = "serde")] 4 | mod serde { 5 | use std::io::{self, Write}; 6 | 7 | use serde::de::DeserializeOwned; 8 | use serde::ser::Serialize; 9 | use traces::{ConcurrentTraceInfos, Crdt, Edit, SequentialTrace}; 10 | 11 | use super::common; 12 | 13 | trait Encoder { 14 | const NAME: &'static str; 15 | fn name() -> impl std::fmt::Display { 16 | Self::NAME 17 | } 18 | fn encode(value: &T) -> Vec; 19 | fn decode(buf: Vec) -> T; 20 | } 21 | 22 | struct SerdeJson; 23 | 24 | impl Encoder for SerdeJson { 25 | const NAME: &'static str = "serde_json"; 26 | 27 | fn encode(value: &T) -> Vec { 28 | serde_json::to_vec(value).unwrap() 29 | } 30 | 31 | fn decode(buf: Vec) -> T { 32 | serde_json::from_slice(&buf).unwrap() 33 | } 34 | } 35 | 36 | struct Bincode; 37 | 38 | impl Encoder for Bincode { 39 | const NAME: &'static str = "bincode"; 40 | 41 | fn encode(value: &T) -> Vec { 42 | bincode::serialize(value).unwrap() 43 | } 44 | 45 | fn decode(buf: Vec) -> T { 46 | bincode::deserialize(&buf).unwrap() 47 | } 48 | } 49 | 50 | struct Zstd(std::marker::PhantomData); 51 | 52 | impl Encoder for Zstd { 53 | const NAME: &'static str = "zstd'd "; 54 | 55 | fn name() -> impl std::fmt::Display { 56 | format!("{}{}", Self::NAME, E::name()) 57 | } 58 | 59 | fn encode(value: &T) -> Vec { 60 | zstd::stream::encode_all(&*E::encode(value), 0).unwrap() 61 | } 62 | 63 | fn decode(buf: Vec) -> T { 64 | E::decode(zstd::stream::decode_all(&*buf).unwrap()) 65 | } 66 | } 67 | 68 | fn test_trace( 69 | trace: ConcurrentTraceInfos, 70 | ) { 71 | let ConcurrentTraceInfos { trace, mut peers, final_content, .. } = 72 | trace; 73 | 74 | for edit in trace.edits() { 75 | match edit { 76 | Edit::Insertion(idx, offset, text) => { 77 | peers[*idx].local_insert(*offset, text); 78 | peers[*idx].assert_invariants(); 79 | }, 80 | Edit::Deletion(idx, start, end) => { 81 | peers[*idx].local_delete(*start, *end); 82 | peers[*idx].assert_invariants(); 83 | }, 84 | Edit::Merge(idx, edit) => { 85 | let encoded = E::encode(edit); 86 | let decoded = E::decode(encoded); 87 | peers[*idx].remote_merge(&decoded); 88 | peers[*idx].assert_invariants(); 89 | }, 90 | } 91 | } 92 | 93 | for replica in &mut peers { 94 | replica.merge_backlogged(); 95 | } 96 | 97 | for replica in &peers { 98 | assert_eq!(replica.buffer, final_content); 99 | } 100 | } 101 | 102 | /// Tests that the `friends-forever` trace converges if we serialize and 103 | /// deserialize every edit before applying it. 104 | #[test] 105 | fn serde_friends_forever_round_trip() { 106 | test_trace::<2, SerdeJson>(traces::friends_forever()); 107 | } 108 | 109 | /// Runs a trace and prints the total size of the serialized `Insertion`s 110 | /// and `Deletion`s. 111 | fn serde_sizes(trace: &SequentialTrace) { 112 | let trace = trace.chars_to_bytes(); 113 | 114 | let mut replica = cola::Replica::new(1, trace.start_content().len()); 115 | 116 | let mut insertions = Vec::new(); 117 | 118 | let mut deletions = Vec::new(); 119 | 120 | for (start, end, text) in trace.edits() { 121 | if end > start { 122 | let deletion = replica.deleted(start..end); 123 | deletions.push(E::encode(&deletion)); 124 | } 125 | 126 | if !text.is_empty() { 127 | let insertion = replica.inserted(start, text.len()); 128 | insertions.push(E::encode(&insertion)); 129 | } 130 | } 131 | 132 | let printed_size = |num_bytes: usize| { 133 | let num_bytes = num_bytes as f64; 134 | 135 | if num_bytes < 1024.0 { 136 | format!("{num_bytes} B") 137 | } else if num_bytes < 1024.0 * 1024.0 { 138 | format!("{:.2} KB", num_bytes / 1024.0) 139 | } else if num_bytes < 1024.0 * 1024.0 * 1024.0 { 140 | format!("{:.2} MB", num_bytes / 1024.0 / 1024.0) 141 | } else { 142 | format!("{:.2} GB", num_bytes / 1024.0 / 1024.0 / 1024.0) 143 | } 144 | }; 145 | 146 | let mut stdout = io::stdout(); 147 | 148 | let replica_size = E::encode(&replica.encode()).len(); 149 | 150 | let _ = writeln!( 151 | &mut stdout, 152 | "{} | Replica: {}", 153 | E::name(), 154 | printed_size(replica_size) 155 | ); 156 | 157 | let total_insertions_size = 158 | insertions.iter().map(Vec::len).sum::(); 159 | 160 | let _ = writeln!( 161 | &mut stdout, 162 | "{} | Total insertions: {}", 163 | E::name(), 164 | printed_size(total_insertions_size) 165 | ); 166 | 167 | let total_deletions_size = 168 | deletions.iter().map(Vec::len).sum::(); 169 | 170 | let _ = writeln!( 171 | &mut stdout, 172 | "{} | Total deletions: {}", 173 | E::name(), 174 | printed_size(total_deletions_size) 175 | ); 176 | } 177 | 178 | // `cargo t --release --features=serde serde_automerge_json_sizes -- --nocapture` 179 | #[test] 180 | fn serde_automerge_json_sizes() { 181 | serde_sizes::(&traces::automerge()); 182 | } 183 | 184 | // `cargo t --release --features=serde serde_automerge_bincode_sizes -- --nocapture` 185 | #[test] 186 | fn serde_automerge_bincode_sizes() { 187 | serde_sizes::(&traces::automerge()); 188 | } 189 | 190 | // `cargo t --release --features=serde serde_automerge_compressed_json_sizes -- --nocapture` 191 | #[test] 192 | fn serde_automerge_compressed_json_sizes() { 193 | serde_sizes::>(&traces::automerge()); 194 | } 195 | 196 | // `cargo t --release --features=serde serde_automerge_compressed_bincode_sizes -- --nocapture` 197 | #[test] 198 | fn serde_automerge_compressed_bincode_sizes() { 199 | serde_sizes::>(&traces::automerge()); 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /traces/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "traces" 3 | version = "0.1.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [dependencies] 8 | crdt-testdata = { git = "https://github.com/josephg/jumprope-rs" } 9 | flate2 = { version = "1.0", features = ["zlib-ng-compat"], default-features = false } 10 | serde = { version = "1.0", features = ["derive"] } 11 | serde_json = "1.0" 12 | -------------------------------------------------------------------------------- /traces/concurrent/friendsforever.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomad/cola/db56d7933e71bb1a305d8bb15b75d78ff7cadc60/traces/concurrent/friendsforever.json.gz -------------------------------------------------------------------------------- /traces/sequential/automerge-paper.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomad/cola/db56d7933e71bb1a305d8bb15b75d78ff7cadc60/traces/sequential/automerge-paper.json.gz -------------------------------------------------------------------------------- /traces/sequential/rustcode.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomad/cola/db56d7933e71bb1a305d8bb15b75d78ff7cadc60/traces/sequential/rustcode.json.gz -------------------------------------------------------------------------------- /traces/sequential/seph-blog1.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomad/cola/db56d7933e71bb1a305d8bb15b75d78ff7cadc60/traces/sequential/seph-blog1.json.gz -------------------------------------------------------------------------------- /traces/sequential/sveltecomponent.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomad/cola/db56d7933e71bb1a305d8bb15b75d78ff7cadc60/traces/sequential/sveltecomponent.json.gz -------------------------------------------------------------------------------- /traces/src/concurrent.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::io::Read; 3 | 4 | use flate2::bufread::GzDecoder; 5 | use serde::Deserialize; 6 | 7 | type AgentIdx = usize; 8 | 9 | /// An index into the `txns` vector of a `ConcurrentDataSet`. 10 | type TxnIdx = usize; 11 | 12 | #[derive(Deserialize)] 13 | #[serde(rename_all = "camelCase")] 14 | pub(crate) struct ConcurrentDataSet { 15 | kind: String, 16 | end_content: String, 17 | num_agents: usize, 18 | txns: Vec, 19 | } 20 | 21 | impl ConcurrentDataSet { 22 | pub fn decode_from_gzipped_json(bytes: &[u8]) -> Self { 23 | let mut decoder = GzDecoder::new(bytes); 24 | let mut json = Vec::new(); 25 | decoder.read_to_end(&mut json).unwrap(); 26 | let this: Self = serde_json::from_reader(json.as_slice()).unwrap(); 27 | assert_eq!(this.kind, "concurrent"); 28 | this 29 | } 30 | } 31 | 32 | #[derive(Deserialize)] 33 | #[serde(rename_all = "camelCase")] 34 | struct Transaction { 35 | parents: Vec, 36 | agent: AgentIdx, 37 | patches: Vec, 38 | } 39 | 40 | /// (position, deleted, text) 41 | #[derive(Deserialize)] 42 | struct Patch(usize, usize, String); 43 | 44 | pub trait Crdt: core::fmt::Debug + Sized { 45 | type EDIT: Clone + core::fmt::Debug; 46 | 47 | fn from_str(id: u64, s: &str) -> Self; 48 | fn fork(&self, new_id: u64) -> Self; 49 | fn local_insert(&mut self, offset: usize, text: &str) -> Self::EDIT; 50 | fn local_delete(&mut self, start: usize, end: usize) -> Self::EDIT; 51 | fn remote_merge(&mut self, remote_edit: &Self::EDIT); 52 | } 53 | 54 | pub struct ConcurrentTraceInfos { 55 | pub trace: ConcurrentTrace, 56 | pub peers: Vec, 57 | pub final_content: String, 58 | } 59 | 60 | impl ConcurrentTraceInfos { 61 | pub(crate) fn from_data_set(data: ConcurrentDataSet) -> Self { 62 | let ConcurrentDataSet { end_content, num_agents, txns, .. } = data; 63 | assert_eq!(num_agents, NUM_PEERS); 64 | let (trace, peers) = ConcurrentTrace::from_txns(txns); 65 | Self { trace, peers, final_content: end_content } 66 | } 67 | } 68 | 69 | pub struct ConcurrentTrace { 70 | edits: Vec>, 71 | } 72 | 73 | #[derive(Debug)] 74 | pub enum Edit { 75 | Insertion(AgentIdx, usize, String), 76 | Deletion(AgentIdx, usize, usize), 77 | Merge(AgentIdx, C::EDIT), 78 | } 79 | 80 | impl ConcurrentTrace { 81 | pub fn edits(&self) -> impl Iterator> { 82 | self.edits.iter() 83 | } 84 | 85 | fn from_txns(txns: Vec) -> (Self, Vec) { 86 | let mut edits_in_txns = Vec::new(); 87 | 88 | let mut version_vectors = HashMap::new(); 89 | 90 | let mut agents = Self::init_peers(""); 91 | 92 | let mut ops = Vec::new(); 93 | 94 | for (txn_idx, txn) in txns.iter().enumerate() { 95 | let agent = &mut agents[txn.agent]; 96 | 97 | let version_vector = 98 | version_vectors.entry(txn.agent).or_insert_with(Vec::new); 99 | 100 | for &parent_idx in &txn.parents { 101 | recursive_merge( 102 | agent, 103 | txn.agent, 104 | txns.as_slice(), 105 | &edits_in_txns, 106 | version_vector, 107 | &mut ops, 108 | parent_idx, 109 | ); 110 | } 111 | 112 | let mut edits = Vec::new(); 113 | 114 | for &Patch(pos, del, ref text) in &txn.patches { 115 | if del > 0 { 116 | edits.push(agent.local_delete(pos, pos + del)); 117 | ops.push(Edit::Deletion(txn.agent, pos, pos + del)); 118 | } 119 | if !text.is_empty() { 120 | edits.push(agent.local_insert(pos, text)); 121 | ops.push(Edit::Insertion(txn.agent, pos, text.clone())); 122 | } 123 | } 124 | 125 | edits_in_txns.push(edits); 126 | 127 | version_vector.push(txn_idx); 128 | } 129 | 130 | for (agent_idx, agent) in agents.iter_mut().enumerate() { 131 | let version_vector = 132 | version_vectors.entry(agent_idx).or_insert_with(Vec::new); 133 | 134 | for txn_idx in 0..txns.len() { 135 | recursive_merge( 136 | agent, 137 | agent_idx, 138 | txns.as_slice(), 139 | &edits_in_txns, 140 | version_vector, 141 | &mut ops, 142 | txn_idx, 143 | ); 144 | } 145 | } 146 | 147 | (Self { edits: ops }, Self::init_peers("")) 148 | } 149 | 150 | fn init_peers(starting_text: &str) -> Vec { 151 | let mut peers = Vec::with_capacity(NUM_PEERS); 152 | 153 | let first_peer = C::from_str(1, starting_text); 154 | 155 | peers.push(first_peer); 156 | 157 | for i in 1..NUM_PEERS { 158 | let first_peer = &peers[0]; 159 | peers.push(first_peer.fork(i as u64 + 1)); 160 | } 161 | 162 | assert_eq!(peers.len(), NUM_PEERS); 163 | 164 | peers 165 | } 166 | 167 | pub fn num_edits(&self) -> usize { 168 | self.edits.len() 169 | } 170 | } 171 | 172 | fn recursive_merge( 173 | agent: &mut C, 174 | agent_idx: AgentIdx, 175 | txns: &[Transaction], 176 | edits_in_txns: &[Vec], 177 | version_vector: &mut Vec, 178 | ops: &mut Vec>, 179 | txn_idx: TxnIdx, 180 | ) { 181 | if version_vector.contains(&txn_idx) { 182 | return; 183 | } 184 | 185 | let txn = &txns[txn_idx]; 186 | 187 | for &parent_idx in &txn.parents { 188 | recursive_merge::( 189 | agent, 190 | agent_idx, 191 | txns, 192 | edits_in_txns, 193 | version_vector, 194 | ops, 195 | parent_idx, 196 | ); 197 | } 198 | 199 | let edits = &edits_in_txns[txn_idx]; 200 | 201 | for edit in edits { 202 | agent.remote_merge(edit); 203 | ops.push(Edit::Merge(agent_idx, edit.clone())); 204 | } 205 | 206 | version_vector.push(txn_idx); 207 | } 208 | 209 | pub use traces::*; 210 | 211 | mod traces { 212 | use super::*; 213 | 214 | static FRIENDS_FOREVER: &[u8] = 215 | include_bytes!("../concurrent/friendsforever.json.gz"); 216 | 217 | pub fn friends_forever() -> ConcurrentTraceInfos<2, C> { 218 | let set = ConcurrentDataSet::decode_from_gzipped_json(FRIENDS_FOREVER); 219 | ConcurrentTraceInfos::from_data_set(set) 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /traces/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod concurrent; 2 | mod sequential; 3 | 4 | pub use concurrent::*; 5 | pub use sequential::*; 6 | -------------------------------------------------------------------------------- /traces/src/sequential.rs: -------------------------------------------------------------------------------- 1 | use std::io::Read; 2 | 3 | pub use crdt_testdata::{TestData, TestPatch}; 4 | use flate2::bufread::GzDecoder; 5 | use serde::Deserialize; 6 | 7 | #[derive(Deserialize)] 8 | pub struct SequentialTrace(TestData); 9 | 10 | impl SequentialTrace { 11 | pub fn chars_to_bytes(&self) -> Self { 12 | Self(self.0.chars_to_bytes()) 13 | } 14 | 15 | pub(crate) fn decode_from_gzipped_json(bytes: &[u8]) -> Self { 16 | let mut decoder = GzDecoder::new(bytes); 17 | let mut json = Vec::new(); 18 | decoder.read_to_end(&mut json).unwrap(); 19 | let this = serde_json::from_reader(json.as_slice()).unwrap(); 20 | Self(this) 21 | } 22 | 23 | pub fn edits(&self) -> impl Iterator { 24 | self.0.txns.iter().flat_map(|txn| { 25 | txn.patches.iter().map(move |TestPatch(pos, del, text)| { 26 | (*pos, pos + del, text.as_str()) 27 | }) 28 | }) 29 | } 30 | 31 | pub fn end_content(&self) -> &str { 32 | self.0.end_content.as_str() 33 | } 34 | 35 | pub fn num_edits(&self) -> usize { 36 | self.0.txns.iter().flat_map(|txn| txn.patches.iter()).count() 37 | } 38 | 39 | pub fn start_content(&self) -> &str { 40 | self.0.start_content.as_str() 41 | } 42 | } 43 | 44 | pub use traces::*; 45 | 46 | mod traces { 47 | use super::*; 48 | 49 | static AUTOMERGE: &[u8] = 50 | include_bytes!("../sequential/automerge-paper.json.gz"); 51 | 52 | static RUSTCODE: &[u8] = include_bytes!("../sequential/rustcode.json.gz"); 53 | 54 | static SEPH_BLOG: &[u8] = 55 | include_bytes!("../sequential/seph-blog1.json.gz"); 56 | 57 | static SVELTECOMPONENT: &[u8] = 58 | include_bytes!("../sequential/sveltecomponent.json.gz"); 59 | 60 | pub fn automerge() -> SequentialTrace { 61 | SequentialTrace::decode_from_gzipped_json(AUTOMERGE) 62 | } 63 | 64 | pub fn rustcode() -> SequentialTrace { 65 | SequentialTrace::decode_from_gzipped_json(RUSTCODE) 66 | } 67 | 68 | pub fn seph_blog() -> SequentialTrace { 69 | SequentialTrace::decode_from_gzipped_json(SEPH_BLOG) 70 | } 71 | 72 | pub fn sveltecomponent() -> SequentialTrace { 73 | SequentialTrace::decode_from_gzipped_json(SVELTECOMPONENT) 74 | } 75 | } 76 | --------------------------------------------------------------------------------