diff --git a/.gitignore b/.gitignore
index ef15086ad3..54d04c0419 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,6 +10,8 @@ build
*.py[cod]
*$py.class
__pycache__/
+3rdparty/loro/target
+3rdparty/mogan-loro-ffi/target
# editor files
*.sln
@@ -60,3 +62,6 @@ record
# ImGui
imgui.ini
+
+# node
+tools/loro-relay/node_modules/
diff --git a/3rdparty/loro/Cargo.lock b/3rdparty/loro/Cargo.lock
new file mode 100644
index 0000000000..e9d15f5aba
--- /dev/null
+++ b/3rdparty/loro/Cargo.lock
@@ -0,0 +1,2716 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "addr2line"
+version = "0.24.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
+dependencies = [
+ "gimli",
+]
+
+[[package]]
+name = "adler"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
+
+[[package]]
+name = "adler2"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
+
+[[package]]
+name = "ahash"
+version = "0.8.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
+dependencies = [
+ "cfg-if",
+ "getrandom 0.3.3",
+ "once_cell",
+ "version_check",
+ "zerocopy",
+]
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "anes"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
+
+[[package]]
+name = "anstyle"
+version = "1.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd"
+
+[[package]]
+name = "anyhow"
+version = "1.0.99"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100"
+
+[[package]]
+name = "append-only-bytes"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac436601d6bdde674a0d7fb593e829ffe7b3387c351b356dd20e2d40f5bf3ee5"
+
+[[package]]
+name = "arbitrary"
+version = "1.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1"
+dependencies = [
+ "derive_arbitrary",
+]
+
+[[package]]
+name = "arrayvec"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
+
+[[package]]
+name = "arref"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2ccd462b64c3c72f1be8305905a85d85403d768e8690c9b8bd3b9009a5761679"
+
+[[package]]
+name = "atomic-polyfill"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4"
+dependencies = [
+ "critical-section",
+]
+
+[[package]]
+name = "atty"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
+dependencies = [
+ "hermit-abi 0.1.19",
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
+
+[[package]]
+name = "backtrace"
+version = "0.3.75"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002"
+dependencies = [
+ "addr2line",
+ "cfg-if",
+ "libc",
+ "miniz_oxide 0.8.9",
+ "object",
+ "rustc-demangle",
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "base64"
+version = "0.22.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
+
+[[package]]
+name = "bit-set"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3"
+dependencies = [
+ "bit-vec",
+]
+
+[[package]]
+name = "bit-vec"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "bitflags"
+version = "2.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394"
+
+[[package]]
+name = "bitmaps"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2"
+dependencies = [
+ "typenum",
+]
+
+[[package]]
+name = "block-buffer"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "bumpalo"
+version = "3.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
+
+[[package]]
+name = "bytecount"
+version = "0.6.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e"
+
+[[package]]
+name = "byteorder"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
+
+[[package]]
+name = "bytes"
+version = "1.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
+
+[[package]]
+name = "cast"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
+
+[[package]]
+name = "cc"
+version = "1.2.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5252b3d2648e5eedbc1a6f501e3c795e07025c1e93bbf8bbdd6eef7f447a6d54"
+dependencies = [
+ "find-msvc-tools",
+ "jobserver",
+ "libc",
+ "shlex",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
+
+[[package]]
+name = "ciborium"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e"
+dependencies = [
+ "ciborium-io",
+ "ciborium-ll",
+ "serde",
+]
+
+[[package]]
+name = "ciborium-io"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757"
+
+[[package]]
+name = "ciborium-ll"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9"
+dependencies = [
+ "ciborium-io",
+ "half",
+]
+
+[[package]]
+name = "clap"
+version = "3.2.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123"
+dependencies = [
+ "bitflags 1.3.2",
+ "clap_lex 0.2.4",
+ "indexmap",
+ "textwrap",
+]
+
+[[package]]
+name = "clap"
+version = "4.5.47"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7eac00902d9d136acd712710d71823fb8ac8004ca445a89e73a41d45aa712931"
+dependencies = [
+ "clap_builder",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.5.47"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2ad9bbf750e73b5884fb8a211a9424a1906c1e156724260fdae972f31d70e1d6"
+dependencies = [
+ "anstyle",
+ "clap_lex 0.7.5",
+]
+
+[[package]]
+name = "clap_lex"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5"
+dependencies = [
+ "os_str_bytes",
+]
+
+[[package]]
+name = "clap_lex"
+version = "0.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675"
+
+[[package]]
+name = "cobs"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1"
+dependencies = [
+ "thiserror 2.0.16",
+]
+
+[[package]]
+name = "color-backtrace"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "150fd80a270c0671379f388c8204deb6a746bb4eac8a6c03fe2460b2c0127ea0"
+dependencies = [
+ "backtrace",
+ "termcolor",
+]
+
+[[package]]
+name = "cpufeatures"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "criterion"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7c76e09c1aae2bc52b3d2f29e13c6572553b30c4aa1b8a49fd70de6412654cb"
+dependencies = [
+ "anes",
+ "atty",
+ "cast",
+ "ciborium",
+ "clap 3.2.25",
+ "criterion-plot 0.5.0",
+ "itertools 0.10.5",
+ "lazy_static",
+ "num-traits",
+ "oorandom",
+ "plotters",
+ "rayon",
+ "regex",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "tinytemplate",
+ "walkdir",
+]
+
+[[package]]
+name = "criterion"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f"
+dependencies = [
+ "anes",
+ "cast",
+ "ciborium",
+ "clap 4.5.47",
+ "criterion-plot 0.5.0",
+ "is-terminal",
+ "itertools 0.10.5",
+ "num-traits",
+ "once_cell",
+ "oorandom",
+ "plotters",
+ "rayon",
+ "regex",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "tinytemplate",
+ "walkdir",
+]
+
+[[package]]
+name = "criterion"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1c047a62b0cc3e145fa84415a3191f628e980b194c2755aa12300a4e6cbd928"
+dependencies = [
+ "anes",
+ "cast",
+ "ciborium",
+ "clap 4.5.47",
+ "criterion-plot 0.6.0",
+ "itertools 0.13.0",
+ "num-traits",
+ "oorandom",
+ "plotters",
+ "rayon",
+ "regex",
+ "serde",
+ "serde_json",
+ "tinytemplate",
+ "walkdir",
+]
+
+[[package]]
+name = "criterion-plot"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1"
+dependencies = [
+ "cast",
+ "itertools 0.10.5",
+]
+
+[[package]]
+name = "criterion-plot"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b1bcc0dc7dfae599d84ad0b1a55f80cde8af3725da8313b528da95ef783e338"
+dependencies = [
+ "cast",
+ "itertools 0.13.0",
+]
+
+[[package]]
+name = "critical-section"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b"
+
+[[package]]
+name = "crossbeam-deque"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
+dependencies = [
+ "crossbeam-epoch",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-epoch"
+version = "0.9.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
+
+[[package]]
+name = "crunchy"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
+
+[[package]]
+name = "crypto-common"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
+dependencies = [
+ "generic-array",
+ "typenum",
+]
+
+[[package]]
+name = "ctor"
+version = "0.1.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096"
+dependencies = [
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "ctor"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501"
+dependencies = [
+ "quote",
+ "syn 2.0.106",
+]
+
+[[package]]
+name = "darling"
+version = "0.20.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee"
+dependencies = [
+ "darling_core",
+ "darling_macro",
+]
+
+[[package]]
+name = "darling_core"
+version = "0.20.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e"
+dependencies = [
+ "fnv",
+ "ident_case",
+ "proc-macro2",
+ "quote",
+ "strsim",
+ "syn 2.0.106",
+]
+
+[[package]]
+name = "darling_macro"
+version = "0.20.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
+dependencies = [
+ "darling_core",
+ "quote",
+ "syn 2.0.106",
+]
+
+[[package]]
+name = "derive_arbitrary"
+version = "1.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.106",
+]
+
+[[package]]
+name = "diff"
+version = "0.1.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
+
+[[package]]
+name = "digest"
+version = "0.10.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
+dependencies = [
+ "block-buffer",
+ "crypto-common",
+]
+
+[[package]]
+name = "either"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
+
+[[package]]
+name = "embedded-io"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced"
+
+[[package]]
+name = "embedded-io"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d"
+
+[[package]]
+name = "ensure-cov"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33753185802e107b8fa907192af1f0eca13b1fb33327a59266d650fef29b2b4e"
+
+[[package]]
+name = "enum-as-inner"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c9720bba047d567ffc8a3cba48bf19126600e249ab7f128e9233e6376976a116"
+dependencies = [
+ "heck 0.4.1",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "enum-as-inner"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc"
+dependencies = [
+ "heck 0.5.0",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.106",
+]
+
+[[package]]
+name = "enum_dispatch"
+version = "0.3.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd"
+dependencies = [
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.106",
+]
+
+[[package]]
+name = "equivalent"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
+
+[[package]]
+name = "errno"
+version = "0.3.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad"
+dependencies = [
+ "libc",
+ "windows-sys 0.60.2",
+]
+
+[[package]]
+name = "fastrand"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
+
+[[package]]
+name = "find-msvc-tools"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fd99930f64d146689264c637b5af2f0233a933bef0d8570e2526bf9e083192d"
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "fractional_index"
+version = "2.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce01fad85c73f947e62a39152c7b0f785af7f44859da36e421c42a3023bcbe3a"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
+
+[[package]]
+name = "futures-executor"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-task"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
+
+[[package]]
+name = "futures-util"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "pin-project-lite",
+ "pin-utils",
+ "slab",
+]
+
+[[package]]
+name = "generator"
+version = "0.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "605183a538e3e2a9c1038635cc5c2d194e2ee8fd0d1b66b8349fad7dbacce5a2"
+dependencies = [
+ "cc",
+ "cfg-if",
+ "libc",
+ "log",
+ "rustversion",
+ "windows",
+]
+
+[[package]]
+name = "generic-array"
+version = "0.14.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2"
+dependencies = [
+ "typenum",
+ "version_check",
+]
+
+[[package]]
+name = "generic-btree"
+version = "0.10.7"
+dependencies = [
+ "arbitrary",
+ "arref",
+ "heapless 0.9.1",
+ "itertools 0.11.0",
+ "loro-thunderdome",
+ "proc-macro2",
+ "rand 0.8.5",
+ "rustc-hash",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "libc",
+ "wasi 0.11.1+wasi-snapshot-preview1",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "r-efi",
+ "wasi 0.14.4+wasi-0.2.4",
+]
+
+[[package]]
+name = "gimli"
+version = "0.31.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
+
+[[package]]
+name = "half"
+version = "2.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9"
+dependencies = [
+ "cfg-if",
+ "crunchy",
+]
+
+[[package]]
+name = "hash32"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67"
+dependencies = [
+ "byteorder",
+]
+
+[[package]]
+name = "hash32"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606"
+dependencies = [
+ "byteorder",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
+
+[[package]]
+name = "hashbrown"
+version = "0.15.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
+
+[[package]]
+name = "heapless"
+version = "0.7.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f"
+dependencies = [
+ "atomic-polyfill",
+ "hash32 0.2.1",
+ "rustc_version",
+ "serde",
+ "spin",
+ "stable_deref_trait",
+]
+
+[[package]]
+name = "heapless"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad"
+dependencies = [
+ "hash32 0.3.1",
+ "stable_deref_trait",
+]
+
+[[package]]
+name = "heapless"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1edcd5a338e64688fbdcb7531a846cfd3476a54784dcb918a0844682bc7ada5"
+dependencies = [
+ "hash32 0.3.1",
+ "stable_deref_trait",
+]
+
+[[package]]
+name = "heck"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
+
+[[package]]
+name = "heck"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
+
+[[package]]
+name = "hermit-abi"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "hermit-abi"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
+
+[[package]]
+name = "ident_case"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
+
+[[package]]
+name = "im"
+version = "15.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0acd33ff0285af998aaf9b57342af478078f53492322fafc47450e09397e0e9"
+dependencies = [
+ "bitmaps",
+ "rand_core 0.6.4",
+ "rand_xoshiro",
+ "serde",
+ "sized-chunks",
+ "typenum",
+ "version_check",
+]
+
+[[package]]
+name = "indexmap"
+version = "1.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
+dependencies = [
+ "autocfg",
+ "hashbrown 0.12.3",
+]
+
+[[package]]
+name = "is-terminal"
+version = "0.4.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9"
+dependencies = [
+ "hermit-abi 0.5.2",
+ "libc",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "itertools"
+version = "0.10.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itertools"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itertools"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itertools"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
+
+[[package]]
+name = "jobserver"
+version = "0.1.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"
+dependencies = [
+ "getrandom 0.3.3",
+ "libc",
+]
+
+[[package]]
+name = "js-sys"
+version = "0.3.77"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
+dependencies = [
+ "once_cell",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
+
+[[package]]
+name = "leb128"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67"
+
+[[package]]
+name = "libc"
+version = "0.2.175"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
+
+[[package]]
+name = "lock_api"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
+dependencies = [
+ "scopeguard",
+]
+
+[[package]]
+name = "log"
+version = "0.4.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
+
+[[package]]
+name = "loom"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca"
+dependencies = [
+ "cfg-if",
+ "generator",
+ "scoped-tls",
+ "serde",
+ "serde_json",
+ "tracing",
+ "tracing-subscriber",
+]
+
+[[package]]
+name = "loro"
+version = "1.13.7"
+dependencies = [
+ "anyhow",
+ "base64",
+ "ctor 0.2.9",
+ "enum-as-inner 0.6.1",
+ "generic-btree",
+ "loom",
+ "loro-common",
+ "loro-delta",
+ "loro-internal",
+ "loro-kv-store",
+ "pretty_assertions",
+ "rand 0.8.5",
+ "rustc-hash",
+ "serde_json",
+ "serial_test",
+ "tracing",
+]
+
+[[package]]
+name = "loro-common"
+version = "1.13.1"
+dependencies = [
+ "arbitrary",
+ "enum-as-inner 0.6.1",
+ "js-sys",
+ "leb128",
+ "loro-rle",
+ "nonmax",
+ "rustc-hash",
+ "serde",
+ "serde_columnar",
+ "serde_json",
+ "thiserror 1.0.69",
+ "tracing",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "loro-delta"
+version = "1.13.0"
+dependencies = [
+ "arrayvec",
+ "color-backtrace",
+ "criterion 0.4.0",
+ "ctor 0.2.9",
+ "enum-as-inner 0.5.1",
+ "generic-btree",
+ "heapless 0.8.0",
+ "rand 0.8.5",
+ "tracing",
+ "tracing-subscriber",
+]
+
+[[package]]
+name = "loro-internal"
+version = "1.13.7"
+dependencies = [
+ "append-only-bytes",
+ "arbitrary",
+ "arref",
+ "bytes",
+ "criterion 0.7.0",
+ "ctor 0.1.26",
+ "either",
+ "ensure-cov",
+ "enum-as-inner 0.6.1",
+ "enum_dispatch",
+ "generic-btree",
+ "getrandom 0.2.16",
+ "im",
+ "itertools 0.12.1",
+ "js-sys",
+ "leb128",
+ "loom",
+ "loro-common",
+ "loro-delta",
+ "loro-kv-store",
+ "loro-rle",
+ "loro_fractional_index",
+ "md5",
+ "miniz_oxide 0.7.4",
+ "nonmax",
+ "num",
+ "num-traits",
+ "once_cell",
+ "parking_lot",
+ "pest",
+ "pest_derive",
+ "postcard",
+ "pretty_assertions",
+ "rand 0.8.5",
+ "rustc-hash",
+ "serde",
+ "serde_columnar",
+ "serde_json",
+ "smallvec",
+ "tabled",
+ "thiserror 1.0.69",
+ "thread_local",
+ "tracing",
+ "wasm-bindgen",
+ "xxhash-rust",
+ "zstd",
+]
+
+[[package]]
+name = "loro-kv-store"
+version = "1.13.7"
+dependencies = [
+ "bytes",
+ "ctor 0.2.9",
+ "ensure-cov",
+ "loro-common",
+ "lz4_flex",
+ "once_cell",
+ "quick_cache",
+ "rand 0.8.5",
+ "rustc-hash",
+ "tracing",
+ "xxhash-rust",
+]
+
+[[package]]
+name = "loro-rle"
+version = "1.6.0"
+dependencies = [
+ "append-only-bytes",
+ "ctor 0.1.26",
+ "num",
+ "proptest",
+ "rand 0.8.5",
+ "smallvec",
+ "smartstring",
+ "static_assertions",
+]
+
+[[package]]
+name = "loro-thunderdome"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f3d053a135388e6b1df14e8af1212af5064746e9b87a06a345a7a779ee9695a"
+
+[[package]]
+name = "loro_fractional_index"
+version = "1.13.0"
+dependencies = [
+ "criterion 0.5.1",
+ "fractional_index",
+ "once_cell",
+ "rand 0.8.5",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "lz4_flex"
+version = "0.11.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08ab2867e3eeeca90e844d1940eab391c9dc5228783db2ed999acbc0a9ed375a"
+dependencies = [
+ "twox-hash",
+]
+
+[[package]]
+name = "matchers"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9"
+dependencies = [
+ "regex-automata",
+]
+
+[[package]]
+name = "md5"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771"
+
+[[package]]
+name = "memchr"
+version = "2.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
+
+[[package]]
+name = "miniz_oxide"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08"
+dependencies = [
+ "adler",
+]
+
+[[package]]
+name = "miniz_oxide"
+version = "0.8.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
+dependencies = [
+ "adler2",
+]
+
+[[package]]
+name = "nonmax"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "610a5acd306ec67f907abe5567859a3c693fb9886eb1f012ab8f2a47bef3db51"
+
+[[package]]
+name = "nu-ansi-term"
+version = "0.50.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399"
+dependencies = [
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "num"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23"
+dependencies = [
+ "num-bigint",
+ "num-complex",
+ "num-integer",
+ "num-iter",
+ "num-rational",
+ "num-traits",
+]
+
+[[package]]
+name = "num-bigint"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
+dependencies = [
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-complex"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "num-integer"
+version = "0.1.46"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "num-iter"
+version = "0.1.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf"
+dependencies = [
+ "autocfg",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-rational"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824"
+dependencies = [
+ "num-bigint",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "object"
+version = "0.36.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.21.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
+
+[[package]]
+name = "oorandom"
+version = "11.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e"
+
+[[package]]
+name = "os_str_bytes"
+version = "6.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1"
+
+[[package]]
+name = "papergrid"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1526bb6aa9f10ec339fb10360f22c57edf81d5678d0278e93bc12a47ffbe4b01"
+dependencies = [
+ "bytecount",
+ "fnv",
+ "unicode-width",
+]
+
+[[package]]
+name = "parking_lot"
+version = "0.12.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
+dependencies = [
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.9.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall",
+ "smallvec",
+ "windows-link 0.2.1",
+]
+
+[[package]]
+name = "pest"
+version = "2.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "989e7521a040efde50c3ab6bbadafbe15ab6dc042686926be59ac35d74607df4"
+dependencies = [
+ "memchr",
+ "ucd-trie",
+]
+
+[[package]]
+name = "pest_derive"
+version = "2.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "187da9a3030dbafabbbfb20cb323b976dc7b7ce91fcd84f2f74d6e31d378e2de"
+dependencies = [
+ "pest",
+ "pest_generator",
+]
+
+[[package]]
+name = "pest_generator"
+version = "2.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49b401d98f5757ebe97a26085998d6c0eecec4995cad6ab7fc30ffdf4b052843"
+dependencies = [
+ "pest",
+ "pest_meta",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.106",
+]
+
+[[package]]
+name = "pest_meta"
+version = "2.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72f27a2cfee9f9039c4d86faa5af122a0ac3851441a34865b8a043b46be0065a"
+dependencies = [
+ "pest",
+ "sha2",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
+
+[[package]]
+name = "pin-utils"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+
+[[package]]
+name = "pkg-config"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
+
+[[package]]
+name = "plotters"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747"
+dependencies = [
+ "num-traits",
+ "plotters-backend",
+ "plotters-svg",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "plotters-backend"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a"
+
+[[package]]
+name = "plotters-svg"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670"
+dependencies = [
+ "plotters-backend",
+]
+
+[[package]]
+name = "postcard"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24"
+dependencies = [
+ "cobs",
+ "embedded-io 0.4.0",
+ "embedded-io 0.6.1",
+ "heapless 0.7.17",
+ "serde",
+]
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
+dependencies = [
+ "zerocopy",
+]
+
+[[package]]
+name = "pretty_assertions"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d"
+dependencies = [
+ "diff",
+ "yansi",
+]
+
+[[package]]
+name = "proc-macro-error"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
+dependencies = [
+ "proc-macro-error-attr",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+ "version_check",
+]
+
+[[package]]
+name = "proc-macro-error-attr"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "version_check",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.101"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "proptest"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6fcdab19deb5195a31cf7726a210015ff1496ba1464fd42cb4f537b8b01b471f"
+dependencies = [
+ "bit-set",
+ "bit-vec",
+ "bitflags 2.9.4",
+ "lazy_static",
+ "num-traits",
+ "rand 0.9.2",
+ "rand_chacha 0.9.0",
+ "rand_xorshift",
+ "regex-syntax",
+ "rusty-fork",
+ "tempfile",
+ "unarray",
+]
+
+[[package]]
+name = "quick-error"
+version = "1.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
+
+[[package]]
+name = "quick_cache"
+version = "0.6.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ad6644cb07b7f3488b9f3d2fde3b4c0a7fa367cafefb39dff93a659f76eb786"
+dependencies = [
+ "ahash",
+ "equivalent",
+ "hashbrown 0.15.5",
+ "parking_lot",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "r-efi"
+version = "5.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
+
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "libc",
+ "rand_chacha 0.3.1",
+ "rand_core 0.6.4",
+]
+
+[[package]]
+name = "rand"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
+dependencies = [
+ "rand_chacha 0.9.0",
+ "rand_core 0.9.3",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core 0.6.4",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
+dependencies = [
+ "ppv-lite86",
+ "rand_core 0.9.3",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+dependencies = [
+ "getrandom 0.2.16",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
+dependencies = [
+ "getrandom 0.3.3",
+]
+
+[[package]]
+name = "rand_xorshift"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a"
+dependencies = [
+ "rand_core 0.9.3",
+]
+
+[[package]]
+name = "rand_xoshiro"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa"
+dependencies = [
+ "rand_core 0.6.4",
+]
+
+[[package]]
+name = "rayon"
+version = "1.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f"
+dependencies = [
+ "either",
+ "rayon-core",
+]
+
+[[package]]
+name = "rayon-core"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
+dependencies = [
+ "crossbeam-deque",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.5.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77"
+dependencies = [
+ "bitflags 2.9.4",
+]
+
+[[package]]
+name = "regex"
+version = "1.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001"
+
+[[package]]
+name = "rustc-demangle"
+version = "0.1.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace"
+
+[[package]]
+name = "rustc-hash"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
+
+[[package]]
+name = "rustc_version"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
+dependencies = [
+ "semver",
+]
+
+[[package]]
+name = "rustix"
+version = "1.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8"
+dependencies = [
+ "bitflags 2.9.4",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys 0.60.2",
+]
+
+[[package]]
+name = "rustversion"
+version = "1.0.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
+
+[[package]]
+name = "rusty-fork"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f"
+dependencies = [
+ "fnv",
+ "quick-error",
+ "tempfile",
+ "wait-timeout",
+]
+
+[[package]]
+name = "ryu"
+version = "1.0.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
+
+[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "scc"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "46e6f046b7fef48e2660c57ed794263155d713de679057f2d0c169bfc6e756cc"
+dependencies = [
+ "sdd",
+]
+
+[[package]]
+name = "scoped-tls"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
+
+[[package]]
+name = "scopeguard"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+
+[[package]]
+name = "sdd"
+version = "3.0.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "490dcfcbfef26be6800d11870ff2df8774fa6e86d047e3e8c8a76b25655e41ca"
+
+[[package]]
+name = "semver"
+version = "1.0.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0"
+
+[[package]]
+name = "serde"
+version = "1.0.226"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0dca6411025b24b60bfa7ec1fe1f8e710ac09782dca409ee8237ba74b51295fd"
+dependencies = [
+ "serde_core",
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_columnar"
+version = "0.3.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a16e404f17b16d0273460350e29b02d76ba0d70f34afdc9a4fa034c97d6c6eb"
+dependencies = [
+ "itertools 0.11.0",
+ "postcard",
+ "serde",
+ "serde_columnar_derive",
+ "thiserror 1.0.69",
+]
+
+[[package]]
+name = "serde_columnar_derive"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "45958fce4903f67e871fbf15ac78e289269b21ebd357d6fecacdba233629112e"
+dependencies = [
+ "darling",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.106",
+]
+
+[[package]]
+name = "serde_core"
+version = "1.0.226"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba2ba63999edb9dac981fb34b3e5c0d111a69b0924e253ed29d83f7c99e966a4"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.226"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8db53ae22f34573731bafa1db20f04027b2d25e02d8205921b569171699cdb33"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.106",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.143"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a"
+dependencies = [
+ "itoa",
+ "memchr",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "serial_test"
+version = "3.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d0b343e184fc3b7bb44dff0705fffcf4b3756ba6aff420dddd8b24ca145e555"
+dependencies = [
+ "futures-executor",
+ "futures-util",
+ "log",
+ "once_cell",
+ "parking_lot",
+ "scc",
+ "serial_test_derive",
+]
+
+[[package]]
+name = "serial_test_derive"
+version = "3.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f50427f258fb77356e4cd4aa0e87e2bd2c66dbcee41dc405282cae2bfc26c83"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.106",
+]
+
+[[package]]
+name = "sha2"
+version = "0.10.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
+[[package]]
+name = "sharded-slab"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
+dependencies = [
+ "lazy_static",
+]
+
+[[package]]
+name = "shlex"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
+
+[[package]]
+name = "sized-chunks"
+version = "0.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16d69225bde7a69b235da73377861095455d298f2b970996eec25ddbb42b3d1e"
+dependencies = [
+ "bitmaps",
+ "typenum",
+]
+
+[[package]]
+name = "slab"
+version = "0.4.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589"
+
+[[package]]
+name = "smallvec"
+version = "1.15.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "smartstring"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29"
+dependencies = [
+ "autocfg",
+ "static_assertions",
+ "version_check",
+]
+
+[[package]]
+name = "spin"
+version = "0.9.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
+dependencies = [
+ "lock_api",
+]
+
+[[package]]
+name = "stable_deref_trait"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
+
+[[package]]
+name = "static_assertions"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+
+[[package]]
+name = "strsim"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
+
+[[package]]
+name = "syn"
+version = "1.0.109"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.106"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "tabled"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56c3ee73732ffceaea7b8f6b719ce3bb17f253fa27461ffeaf568ebd0cdb4b85"
+dependencies = [
+ "papergrid",
+ "tabled_derive",
+ "unicode-width",
+]
+
+[[package]]
+name = "tabled_derive"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "beca1b4eaceb4f2755df858b88d9b9315b7ccfd1ffd0d7a48a52602301f01a57"
+dependencies = [
+ "heck 0.4.1",
+ "proc-macro-error",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "tempfile"
+version = "3.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "15b61f8f20e3a6f7e0649d825294eaf317edce30f82cf6026e7e4cb9222a7d1e"
+dependencies = [
+ "fastrand",
+ "getrandom 0.3.3",
+ "once_cell",
+ "rustix",
+ "windows-sys 0.60.2",
+]
+
+[[package]]
+name = "termcolor"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "textwrap"
+version = "0.16.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057"
+
+[[package]]
+name = "thiserror"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
+dependencies = [
+ "thiserror-impl 1.0.69",
+]
+
+[[package]]
+name = "thiserror"
+version = "2.0.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0"
+dependencies = [
+ "thiserror-impl 2.0.16",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.106",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "2.0.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.106",
+]
+
+[[package]]
+name = "thread_local"
+version = "1.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "tinytemplate"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc"
+dependencies = [
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "tracing"
+version = "0.1.41"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
+dependencies = [
+ "pin-project-lite",
+ "tracing-attributes",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-attributes"
+version = "0.1.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.106",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678"
+dependencies = [
+ "once_cell",
+ "valuable",
+]
+
+[[package]]
+name = "tracing-log"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
+dependencies = [
+ "log",
+ "once_cell",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-subscriber"
+version = "0.3.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5"
+dependencies = [
+ "matchers",
+ "nu-ansi-term",
+ "once_cell",
+ "regex-automata",
+ "sharded-slab",
+ "smallvec",
+ "thread_local",
+ "tracing",
+ "tracing-core",
+ "tracing-log",
+]
+
+[[package]]
+name = "twox-hash"
+version = "2.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ea3136b675547379c4bd395ca6b938e5ad3c3d20fad76e7fe85f9e0d011419c"
+
+[[package]]
+name = "typenum"
+version = "1.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
+
+[[package]]
+name = "ucd-trie"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971"
+
+[[package]]
+name = "unarray"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
+
+[[package]]
+name = "unicode-width"
+version = "0.1.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
+
+[[package]]
+name = "valuable"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
+
+[[package]]
+name = "version_check"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
+
+[[package]]
+name = "wait-timeout"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "walkdir"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
+dependencies = [
+ "same-file",
+ "winapi-util",
+]
+
+[[package]]
+name = "wasi"
+version = "0.11.1+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
+
+[[package]]
+name = "wasi"
+version = "0.14.4+wasi-0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "88a5f4a424faf49c3c2c344f166f0662341d470ea185e939657aaff130f0ec4a"
+dependencies = [
+ "wit-bindgen",
+]
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+ "rustversion",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
+dependencies = [
+ "bumpalo",
+ "log",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.106",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.106",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "web-sys"
+version = "0.3.77"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0978bf7171b3d90bac376700cb56d606feb40f251a475a5d6634613564460b22"
+dependencies = [
+ "windows-sys 0.60.2",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "windows"
+version = "0.61.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893"
+dependencies = [
+ "windows-collections",
+ "windows-core",
+ "windows-future",
+ "windows-link 0.1.3",
+ "windows-numerics",
+]
+
+[[package]]
+name = "windows-collections"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8"
+dependencies = [
+ "windows-core",
+]
+
+[[package]]
+name = "windows-core"
+version = "0.61.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
+dependencies = [
+ "windows-implement",
+ "windows-interface",
+ "windows-link 0.1.3",
+ "windows-result",
+ "windows-strings",
+]
+
+[[package]]
+name = "windows-future"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e"
+dependencies = [
+ "windows-core",
+ "windows-link 0.1.3",
+ "windows-threading",
+]
+
+[[package]]
+name = "windows-implement"
+version = "0.60.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.106",
+]
+
+[[package]]
+name = "windows-interface"
+version = "0.59.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.106",
+]
+
+[[package]]
+name = "windows-link"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
+
+[[package]]
+name = "windows-link"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
+
+[[package]]
+name = "windows-numerics"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1"
+dependencies = [
+ "windows-core",
+ "windows-link 0.1.3",
+]
+
+[[package]]
+name = "windows-result"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
+dependencies = [
+ "windows-link 0.1.3",
+]
+
+[[package]]
+name = "windows-strings"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
+dependencies = [
+ "windows-link 0.1.3",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+dependencies = [
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.59.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
+dependencies = [
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.60.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
+dependencies = [
+ "windows-targets 0.53.3",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
+dependencies = [
+ "windows_aarch64_gnullvm 0.52.6",
+ "windows_aarch64_msvc 0.52.6",
+ "windows_i686_gnu 0.52.6",
+ "windows_i686_gnullvm 0.52.6",
+ "windows_i686_msvc 0.52.6",
+ "windows_x86_64_gnu 0.52.6",
+ "windows_x86_64_gnullvm 0.52.6",
+ "windows_x86_64_msvc 0.52.6",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.53.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91"
+dependencies = [
+ "windows-link 0.1.3",
+ "windows_aarch64_gnullvm 0.53.0",
+ "windows_aarch64_msvc 0.53.0",
+ "windows_i686_gnu 0.53.0",
+ "windows_i686_gnullvm 0.53.0",
+ "windows_i686_msvc 0.53.0",
+ "windows_x86_64_gnu 0.53.0",
+ "windows_x86_64_gnullvm 0.53.0",
+ "windows_x86_64_msvc 0.53.0",
+]
+
+[[package]]
+name = "windows-threading"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6"
+dependencies = [
+ "windows-link 0.1.3",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
+
+[[package]]
+name = "wit-bindgen"
+version = "0.45.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c573471f125075647d03df72e026074b7203790d41351cd6edc96f46bcccd36"
+
+[[package]]
+name = "xxhash-rust"
+version = "0.8.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3"
+
+[[package]]
+name = "yansi"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
+
+[[package]]
+name = "zerocopy"
+version = "0.8.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c"
+dependencies = [
+ "zerocopy-derive",
+]
+
+[[package]]
+name = "zerocopy-derive"
+version = "0.8.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.106",
+]
+
+[[package]]
+name = "zstd"
+version = "0.13.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a"
+dependencies = [
+ "zstd-safe",
+]
+
+[[package]]
+name = "zstd-safe"
+version = "7.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d"
+dependencies = [
+ "zstd-sys",
+]
+
+[[package]]
+name = "zstd-sys"
+version = "2.0.16+zstd.1.5.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748"
+dependencies = [
+ "cc",
+ "pkg-config",
+]
diff --git a/3rdparty/loro/Cargo.toml b/3rdparty/loro/Cargo.toml
new file mode 100644
index 0000000000..92988f52f0
--- /dev/null
+++ b/3rdparty/loro/Cargo.toml
@@ -0,0 +1,33 @@
+[workspace]
+members = [
+ "crates/loro",
+ "crates/rle",
+ "crates/loro-common",
+ "crates/loro-internal",
+ "crates/fractional_index",
+ "crates/delta",
+ "crates/kv-store",
+ "crates/generic-btree",
+]
+resolver = "2"
+
+# Use the in-tree fork of generic-btree (loro-dev maintains it). This redirects
+# every `generic-btree` dependency in the graph to the workspace crate.
+[patch.crates-io]
+generic-btree = { path = "crates/generic-btree" }
+
+[workspace.dependencies]
+enum_dispatch = "0.3.11"
+enum-as-inner = "0.6.0"
+rustc-hash = "2.1.1"
+tracing = { version = "0.1" }
+serde_columnar = { version = "0.3.14", default-features = false }
+serde_json = "1.0"
+thiserror = "1"
+smallvec = { version = "1.8.0", features = ["serde"] }
+itertools = "0.12.1"
+serde = "1"
+bytes = "1"
+once_cell = "1.18.0"
+xxhash-rust = { version = "0.8.12", features = ["xxh32"] }
+ensure-cov = "0.1.0"
diff --git a/3rdparty/loro/LICENSE b/3rdparty/loro/LICENSE
new file mode 100644
index 0000000000..bffe7781be
--- /dev/null
+++ b/3rdparty/loro/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2023 Loro
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/3rdparty/loro/README.md b/3rdparty/loro/README.md
new file mode 100644
index 0000000000..9c40381489
--- /dev/null
+++ b/3rdparty/loro/README.md
@@ -0,0 +1,169 @@
+
+
+
+
+
+
+
+
+
+ Make your JSON data collaborative and version-controlled 🦜
+
+
+
+
+
+
+ Documentation
+
+ |
+
+ Getting Started
+
+ |
+
+ Rust Doc
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+https://github.com/loro-dev/loro/assets/18425020/fe246c47-a120-44b3-91d4-1e7232a5b4ac
+
+
+ ✨ Loro 1.0 is out! Read the announcement.
+
+
+Loro is a [CRDTs(Conflict-free Replicated Data Types)](https://crdt.tech/) library that makes building [local-first][local-first] and collaborative apps easier. You can now use it in Rust, JS (via WASM), and Swift.
+
+# Features
+
+**Features Provided by CRDTs**
+
+- P2P Synchronization
+- Automatic Merging
+- Local Availability
+- Scalability
+- Delta Updates
+
+**Supported CRDT Algorithms**
+
+- 📝 Text Editing with [Fugue]
+- 📙 [Rich Text CRDT](https://loro.dev/blog/loro-richtext)
+- 🌲 [Moveable Tree](https://loro.dev/docs/tutorial/tree)
+- 🚗 [Moveable List](https://loro.dev/docs/tutorial/list)
+- 🗺️ [Last-Write-Wins Map](https://loro.dev/docs/tutorial/map)
+- 🤝 Mergeable map-key children via `ensureMergeable*` / `ensure_mergeable_*` for lazy child container creation
+
+**Advanced Features in Loro**
+
+- 🚀 [Fast Document Loading](https://loro.dev/blog/v1.0)
+- ⏱️ Fast [Time Travel](https://loro.dev/docs/tutorial/time_travel) Through History
+- 🏛️ [Version Control with Real-Time Collaboration](https://loro.dev/blog/v1.0#version-control)
+- 📦 [Shallow Snapshot](https://loro.dev/docs/advanced/shallow_snapshot) that Works like Git Shallow Clone
+
+https://github.com/user-attachments/assets/68e0017a-4987-4f71-b2cf-4ed28a210987
+
+> In this example, we demonstrate importing an entire Loro codebase into a Loro-powered
+> version controller, preserving the complete Git DAG history while enabling fast version switching.
+
+# Example
+
+[](https://stackblitz.com/edit/loro-basic-test?file=test%2Floro-sync.test.ts)
+
+```ts
+import { expect, test } from "vitest";
+import { LoroDoc, LoroList } from "loro-crdt";
+
+test("sync example", () => {
+ // Sync two docs with two rounds of exchanges
+
+ // Initialize document A
+ const docA = new LoroDoc();
+ const listA: LoroList = docA.getList("list");
+ listA.insert(0, "A");
+ listA.insert(1, "B");
+ listA.insert(2, "C");
+
+ // Export all updates from docA
+ const bytes: Uint8Array = docA.export({ mode: "update" });
+
+ // Simulate sending `bytes` across the network to another peer, B
+
+ const docB = new LoroDoc();
+ // Peer B imports the updates from A
+ docB.import(bytes);
+
+ // B's state matches A's state
+ expect(docB.toJSON()).toStrictEqual({
+ list: ["A", "B", "C"],
+ });
+
+ // Get the current version of docB
+ const version = docB.oplogVersion();
+
+ // Simulate editing at B: delete item 'B'
+ const listB: LoroList = docB.getList("list");
+ listB.delete(1, 1);
+
+ // Export the updates from B since the last sync point
+ const bytesB: Uint8Array = docB.export({ mode: "update", from: version });
+
+ // Simulate sending `bytesB` back across the network to A
+
+ // A imports the updates from B
+ docA.import(bytesB);
+
+ // A has the same state as B
+ expect(docA.toJSON()).toStrictEqual({
+ list: ["A", "C"],
+ });
+});
+```
+
+# DevTools
+
+## Loro Inspector
+
+You can use the [Loro Inspector](https://inspector.loro.dev) to inspect the state and history of a Loro document.
+
+https://github.com/user-attachments/assets/ceeb7450-80ce-42f2-aef4-2e08fa2d1f1b
+
+# Bindings
+
+You can find bindings for other programming languages in [loro-ffi](https://github.com/loro-dev/loro-ffi).
+
+# Blog
+
+- [Loro 1.0](https://loro.dev/blog/v1.0)
+- [Movable tree CRDTs and Loro's implementation](https://loro.dev/blog/movable-tree)
+- [Introduction to Loro's Rich Text CRDT](https://loro.dev/blog/loro-richtext)
+- [Loro: Reimagine State Management with CRDTs](https://loro.dev/blog/loro-now-open-source)
+
+# Sponsors
+
+[](https://github.com/sponsors/loro-dev)
+
+# Credits
+
+Loro draws inspiration from the innovative work of the following projects and individuals:
+
+- [Diamond-types](https://github.com/josephg/diamond-types): The [Event Graph Walker (Eg-walker)](https://loro.dev/docs/advanced/event_graph_walker) algorithm from @josephg has been adapted to reduce the computation and space usage of CRDTs.
+- [Automerge](https://github.com/automerge/automerge): Their use of columnar encoding for CRDTs has informed our strategies for efficient data encoding.
+- [Yjs](https://github.com/yjs/yjs): We have incorporated a similar algorithm for effectively merging collaborative editing operations, thanks to their pioneering work.
+- [Matthew Weidner](https://mattweidner.com/): His work on the [Fugue](https://arxiv.org/abs/2305.00583) algorithm has been invaluable, enhancing our text editing capabilities.
+- [Martin Kleppmann](https://martin.kleppmann.com/): His work on CRDTs has significantly influenced our comprehension of the field.
+
+[local-first]: https://www.inkandswitch.com/local-first/
+[Fugue]: https://arxiv.org/abs/2305.00583
diff --git a/3rdparty/loro/crates/delta/Cargo.toml b/3rdparty/loro/crates/delta/Cargo.toml
new file mode 100644
index 0000000000..df79eaadf4
--- /dev/null
+++ b/3rdparty/loro/crates/delta/Cargo.toml
@@ -0,0 +1,30 @@
+[package]
+name = "loro-delta"
+version = "1.13.0"
+edition = "2021"
+license = "MIT"
+description = "Loro internal library. It's used as a delta between two versions of text or lists. It's also an efficient Rope."
+homepage = "https://loro.dev"
+repository = "https://github.com/loro-dev/loro/"
+authors = ["Zixuan Chen", "Liang Zhao"]
+categories = ["data-structures"]
+keywords = ["rope"]
+
+[dependencies]
+arrayvec = "0.7.4"
+generic-btree = { version = "^0.10.7" }
+heapless = "0.8.0"
+enum-as-inner = "0.5.1"
+
+[dev-dependencies]
+tracing = "0.1.40"
+criterion = { version = "0.4", features = ["html_reports"] }
+rand = { version = "0.8.5" }
+tracing-subscriber = "0.3.18"
+color-backtrace = "0.6.1"
+ctor = "0.2"
+
+
+[[bench]]
+name = "rope"
+harness = false
diff --git a/3rdparty/loro/crates/delta/LICENSE b/3rdparty/loro/crates/delta/LICENSE
new file mode 100644
index 0000000000..afe68a4136
--- /dev/null
+++ b/3rdparty/loro/crates/delta/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2024 Loro
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/3rdparty/loro/crates/delta/fuzz/.gitignore b/3rdparty/loro/crates/delta/fuzz/.gitignore
new file mode 100644
index 0000000000..1a45eee776
--- /dev/null
+++ b/3rdparty/loro/crates/delta/fuzz/.gitignore
@@ -0,0 +1,4 @@
+target
+corpus
+artifacts
+coverage
diff --git a/3rdparty/loro/crates/delta/fuzz/Cargo.lock b/3rdparty/loro/crates/delta/fuzz/Cargo.lock
new file mode 100644
index 0000000000..0e11c3881d
--- /dev/null
+++ b/3rdparty/loro/crates/delta/fuzz/Cargo.lock
@@ -0,0 +1,1002 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "addr2line"
+version = "0.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb"
+dependencies = [
+ "gimli",
+]
+
+[[package]]
+name = "adler"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
+
+[[package]]
+name = "android-tzdata"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
+
+[[package]]
+name = "android_system_properties"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "arbitrary"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110"
+dependencies = [
+ "derive_arbitrary",
+]
+
+[[package]]
+name = "arrayvec"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
+
+[[package]]
+name = "arref"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2ccd462b64c3c72f1be8305905a85d85403d768e8690c9b8bd3b9009a5761679"
+
+[[package]]
+name = "atomic-polyfill"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4"
+dependencies = [
+ "critical-section",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
+
+[[package]]
+name = "backtrace"
+version = "0.3.71"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d"
+dependencies = [
+ "addr2line",
+ "cc",
+ "cfg-if",
+ "libc",
+ "miniz_oxide",
+ "object",
+ "rustc-demangle",
+]
+
+[[package]]
+name = "bumpalo"
+version = "3.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
+
+[[package]]
+name = "byteorder"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
+
+[[package]]
+name = "cc"
+version = "1.0.98"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41c270e7540d725e65ac7f1b212ac8ce349719624d7bcff99f8e2e488e8cf03f"
+dependencies = [
+ "jobserver",
+ "libc",
+ "once_cell",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "chrono"
+version = "0.4.38"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401"
+dependencies = [
+ "android-tzdata",
+ "iana-time-zone",
+ "js-sys",
+ "num-traits",
+ "wasm-bindgen",
+ "windows-targets 0.52.5",
+]
+
+[[package]]
+name = "color-backtrace"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "150fd80a270c0671379f388c8204deb6a746bb4eac8a6c03fe2460b2c0127ea0"
+dependencies = [
+ "backtrace",
+ "termcolor",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
+
+[[package]]
+name = "critical-section"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7059fff8937831a9ae6f0fe4d658ffabf58f2ca96aa9dec1c889f936f705f216"
+
+[[package]]
+name = "ctor"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "edb49164822f3ee45b17acd4a208cfc1251410cf0cad9a833234c9890774dd9f"
+dependencies = [
+ "quote",
+ "syn 2.0.65",
+]
+
+[[package]]
+name = "derive_arbitrary"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.65",
+]
+
+[[package]]
+name = "dev-utils"
+version = "0.1.0"
+dependencies = [
+ "chrono",
+ "color-backtrace",
+ "rand",
+ "tracing",
+ "tracing-chrome",
+ "tracing-subscriber",
+ "tracing-tree",
+]
+
+[[package]]
+name = "either"
+version = "1.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b"
+
+[[package]]
+name = "enum-as-inner"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c9720bba047d567ffc8a3cba48bf19126600e249ab7f128e9233e6376976a116"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "fxhash"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
+dependencies = [
+ "byteorder",
+]
+
+[[package]]
+name = "generic-btree"
+version = "0.10.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "210507e6dec78bb1304e52a174bd99efdd83894219bf20d656a066a0ce2fedc5"
+dependencies = [
+ "arref",
+ "fxhash",
+ "heapless 0.7.17",
+ "itertools",
+ "loro-thunderdome",
+ "proc-macro2",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "gimli"
+version = "0.28.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
+
+[[package]]
+name = "hash32"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67"
+dependencies = [
+ "byteorder",
+]
+
+[[package]]
+name = "hash32"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606"
+dependencies = [
+ "byteorder",
+]
+
+[[package]]
+name = "heapless"
+version = "0.7.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f"
+dependencies = [
+ "atomic-polyfill",
+ "hash32 0.2.1",
+ "rustc_version",
+ "spin",
+ "stable_deref_trait",
+]
+
+[[package]]
+name = "heapless"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad"
+dependencies = [
+ "hash32 0.3.1",
+ "stable_deref_trait",
+]
+
+[[package]]
+name = "heck"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
+
+[[package]]
+name = "iana-time-zone"
+version = "0.1.60"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141"
+dependencies = [
+ "android_system_properties",
+ "core-foundation-sys",
+ "iana-time-zone-haiku",
+ "js-sys",
+ "wasm-bindgen",
+ "windows-core",
+]
+
+[[package]]
+name = "iana-time-zone-haiku"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "itertools"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
+
+[[package]]
+name = "jobserver"
+version = "0.1.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "js-sys"
+version = "0.3.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d"
+dependencies = [
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "libc"
+version = "0.2.155"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
+
+[[package]]
+name = "libfuzzer-sys"
+version = "0.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a96cfd5557eb82f2b83fed4955246c988d331975a002961b07c81584d107e7f7"
+dependencies = [
+ "arbitrary",
+ "cc",
+ "once_cell",
+]
+
+[[package]]
+name = "lock_api"
+version = "0.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
+dependencies = [
+ "autocfg",
+ "scopeguard",
+]
+
+[[package]]
+name = "log"
+version = "0.4.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
+
+[[package]]
+name = "loro-delta"
+version = "0.5.1"
+dependencies = [
+ "arrayvec",
+ "enum-as-inner",
+ "generic-btree",
+ "heapless 0.8.0",
+ "tracing",
+]
+
+[[package]]
+name = "loro-delta-fuzz"
+version = "0.0.0"
+dependencies = [
+ "arbitrary",
+ "ctor",
+ "dev-utils",
+ "libfuzzer-sys",
+ "loro-delta",
+ "tracing",
+]
+
+[[package]]
+name = "loro-thunderdome"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f3d053a135388e6b1df14e8af1212af5064746e9b87a06a345a7a779ee9695a"
+
+[[package]]
+name = "memchr"
+version = "2.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"
+
+[[package]]
+name = "miniz_oxide"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87dfd01fe195c66b572b37921ad8803d010623c0aca821bea2302239d155cdae"
+dependencies = [
+ "adler",
+]
+
+[[package]]
+name = "nu-ansi-term"
+version = "0.46.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
+dependencies = [
+ "overload",
+ "winapi",
+]
+
+[[package]]
+name = "nu-ansi-term"
+version = "0.49.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c073d3c1930d0751774acf49e66653acecb416c3a54c6ec095a9b11caddb5a68"
+dependencies = [
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "object"
+version = "0.32.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
+
+[[package]]
+name = "overload"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02"
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b33eb56c327dec362a9e55b3ad14f9d2f0904fb5a5b03b513ab5465399e9f43"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "libc",
+ "rand_chacha",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "rustc-demangle"
+version = "0.1.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
+
+[[package]]
+name = "rustc_version"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
+dependencies = [
+ "semver",
+]
+
+[[package]]
+name = "ryu"
+version = "1.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
+
+[[package]]
+name = "scopeguard"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+
+[[package]]
+name = "semver"
+version = "1.0.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
+
+[[package]]
+name = "serde"
+version = "1.0.202"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "226b61a0d411b2ba5ff6d7f73a476ac4f8bb900373459cd00fab8512828ba395"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.202"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6048858004bcff69094cd972ed40a32500f153bd3be9f716b2eed2e8217c4838"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.65",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.117"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3"
+dependencies = [
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "sharded-slab"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
+dependencies = [
+ "lazy_static",
+]
+
+[[package]]
+name = "smallvec"
+version = "1.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
+
+[[package]]
+name = "spin"
+version = "0.9.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
+dependencies = [
+ "lock_api",
+]
+
+[[package]]
+name = "stable_deref_trait"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
+
+[[package]]
+name = "syn"
+version = "1.0.109"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.65"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2863d96a84c6439701d7a38f9de935ec562c8832cc55d1dde0f513b52fad106"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "termcolor"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "thread_local"
+version = "1.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+]
+
+[[package]]
+name = "tracing"
+version = "0.1.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
+dependencies = [
+ "pin-project-lite",
+ "tracing-attributes",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-attributes"
+version = "0.1.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.65",
+]
+
+[[package]]
+name = "tracing-chrome"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf0a738ed5d6450a9fb96e86a23ad808de2b727fd1394585da5cdd6788ffe724"
+dependencies = [
+ "serde_json",
+ "tracing-core",
+ "tracing-subscriber",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
+dependencies = [
+ "once_cell",
+ "valuable",
+]
+
+[[package]]
+name = "tracing-log"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
+dependencies = [
+ "log",
+ "once_cell",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-subscriber"
+version = "0.3.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b"
+dependencies = [
+ "nu-ansi-term 0.46.0",
+ "sharded-slab",
+ "smallvec",
+ "thread_local",
+ "tracing-core",
+ "tracing-log",
+]
+
+[[package]]
+name = "tracing-tree"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "65139ecd2c3f6484c3b99bc01c77afe21e95473630747c7aca525e78b0666675"
+dependencies = [
+ "nu-ansi-term 0.49.0",
+ "tracing-core",
+ "tracing-log",
+ "tracing-subscriber",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+
+[[package]]
+name = "valuable"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
+
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8"
+dependencies = [
+ "cfg-if",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da"
+dependencies = [
+ "bumpalo",
+ "log",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.65",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.65",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b"
+dependencies = [
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "windows-core"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
+dependencies = [
+ "windows-targets 0.52.5",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
+dependencies = [
+ "windows-targets 0.48.5",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+dependencies = [
+ "windows-targets 0.52.5",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
+dependencies = [
+ "windows_aarch64_gnullvm 0.48.5",
+ "windows_aarch64_msvc 0.48.5",
+ "windows_i686_gnu 0.48.5",
+ "windows_i686_msvc 0.48.5",
+ "windows_x86_64_gnu 0.48.5",
+ "windows_x86_64_gnullvm 0.48.5",
+ "windows_x86_64_msvc 0.48.5",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb"
+dependencies = [
+ "windows_aarch64_gnullvm 0.52.5",
+ "windows_aarch64_msvc 0.52.5",
+ "windows_i686_gnu 0.52.5",
+ "windows_i686_gnullvm",
+ "windows_i686_msvc 0.52.5",
+ "windows_x86_64_gnu 0.52.5",
+ "windows_x86_64_gnullvm 0.52.5",
+ "windows_x86_64_msvc 0.52.5",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
diff --git a/3rdparty/loro/crates/delta/fuzz/Cargo.toml b/3rdparty/loro/crates/delta/fuzz/Cargo.toml
new file mode 100644
index 0000000000..32dbe20fe0
--- /dev/null
+++ b/3rdparty/loro/crates/delta/fuzz/Cargo.toml
@@ -0,0 +1,31 @@
+[package]
+name = "loro-delta-fuzz"
+version = "0.0.0"
+publish = false
+edition = "2021"
+
+[package.metadata]
+cargo-fuzz = true
+
+[dependencies]
+arbitrary = { version = "1.3.2", features = ["derive"] }
+libfuzzer-sys = "0.4"
+dev-utils = { path = "../../dev-utils" }
+tracing = "0.1.40"
+ctor = "0.2.8"
+
+[dependencies.loro-delta]
+path = ".."
+
+# Prevent this from interfering with workspaces
+[workspace]
+members = ["."]
+
+[profile.release]
+debug = 1
+
+[[bin]]
+name = "ot"
+path = "fuzz_targets/ot.rs"
+test = false
+doc = false
diff --git a/3rdparty/loro/crates/delta/fuzz/fuzz_targets/ot.rs b/3rdparty/loro/crates/delta/fuzz/fuzz_targets/ot.rs
new file mode 100644
index 0000000000..5764478b99
--- /dev/null
+++ b/3rdparty/loro/crates/delta/fuzz/fuzz_targets/ot.rs
@@ -0,0 +1,6 @@
+#![no_main]
+
+use libfuzzer_sys::fuzz_target;
+use loro_delta_fuzz::{run, Op};
+
+fuzz_target!(|ops: Vec| run(ops, 5));
diff --git a/3rdparty/loro/crates/delta/fuzz/src/lib.rs b/3rdparty/loro/crates/delta/fuzz/src/lib.rs
new file mode 100644
index 0000000000..b0a8bc8a98
--- /dev/null
+++ b/3rdparty/loro/crates/delta/fuzz/src/lib.rs
@@ -0,0 +1,159 @@
+use arbitrary::Arbitrary;
+use loro_delta::{
+ text_delta::{TextChunk, TextDelta},
+ DeltaItem,
+};
+use tracing::{debug_span, instrument, trace};
+
+#[derive(Debug, Arbitrary)]
+pub enum Op {
+ Insert { site: u8, pos: u16, text: u16 },
+ Delete { site: u8, pos: u16, len: u16 },
+ Sync { site: u8 },
+}
+
+pub struct Actor {
+ rope: TextDelta,
+ last_sync_version: usize,
+ pending: TextDelta,
+}
+
+pub struct Manager {
+ server: TextDelta,
+ versions: Vec,
+ actors: Vec,
+}
+
+#[instrument(skip(m))]
+fn sync(m: &mut Manager, site: usize) {
+ let actor = &mut m.actors[site];
+ let mut server_ops = TextDelta::new();
+ for t in &m.versions[actor.last_sync_version..] {
+ server_ops.compose(t);
+ }
+
+ let client_to_apply = server_ops.transform(&actor.pending, true);
+
+ let client_ops = std::mem::take(&mut actor.pending);
+
+ let server_to_apply = client_ops.transform(&server_ops, false);
+
+ actor.rope.compose(&client_to_apply);
+ m.server.compose(&server_to_apply);
+ m.versions.push(server_to_apply);
+ actor.last_sync_version = m.versions.len();
+}
+
+pub fn run(mut ops: Vec, site_num: usize) {
+ let mut m = Manager {
+ server: TextDelta::new(),
+ versions: vec![],
+ actors: vec![],
+ };
+ for _ in 0..site_num {
+ m.actors.push(Actor {
+ rope: TextDelta::new(),
+ last_sync_version: 0,
+ pending: TextDelta::new(),
+ })
+ }
+
+ for op in &mut ops {
+ match op {
+ Op::Insert { site, pos, text } => {
+ *site = ((*site as usize) % site_num) as u8;
+ let actor = &mut m.actors[*site as usize];
+ let len = actor.rope.len();
+ *pos = ((*pos as usize) % (len + 1)) as u16;
+ let pos = *pos as usize;
+
+ actor.rope.insert_str(pos, text.to_string().as_str());
+ if actor.pending.len() < pos {
+ actor.pending.push_retain(pos, ());
+ }
+ actor.pending.insert_values(
+ pos,
+ TextChunk::from_long_str(text.to_string().as_str()).map(|chunk| {
+ DeltaItem::Replace {
+ value: chunk,
+ attr: Default::default(),
+ delete: 0,
+ }
+ }),
+ );
+ }
+ Op::Delete {
+ site,
+ pos,
+ len: del_len,
+ } => {
+ *site = ((*site as usize) % site_num) as u8;
+ let actor = &mut m.actors[*site as usize];
+ let len = actor.rope.len();
+ if len == 0 {
+ continue;
+ }
+ *pos = ((*pos as usize) % len) as u16;
+ let pos = *pos as usize;
+ *del_len = ((*del_len as usize) % len) as u16;
+ let del_len = *del_len as usize;
+ let mut del = TextDelta::new();
+ del.push_retain(pos, ()).push_delete(del_len);
+ actor.rope.compose(&del);
+ actor.pending.compose(&del);
+ }
+ Op::Sync { site } => {
+ *site = ((*site as usize) % site_num) as u8;
+ let site = *site as usize;
+ sync(&mut m, site);
+ }
+ }
+ }
+
+ debug_span!("Round 1").in_scope(|| {
+ for i in 0..site_num {
+ sync(&mut m, i);
+ }
+ });
+ debug_span!("Round 2").in_scope(|| {
+ for i in 0..site_num {
+ sync(&mut m, i);
+ }
+ });
+
+ let server_str = m.server.try_to_string().unwrap();
+ for i in 0..site_num {
+ let actor = &m.actors[i];
+ let rope_str = actor.rope.try_to_string().unwrap();
+ assert_eq!(rope_str, server_str, "site {} ops={:#?}", i, &ops);
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::Op::*;
+ use super::*;
+
+ #[ctor::ctor]
+ fn init() {
+ dev_utils::setup_test_log();
+ }
+
+ #[test]
+ fn test_run() {
+ let ops = vec![
+ Insert {
+ site: 1,
+ pos: 0,
+ text: 65535,
+ },
+ Sync { site: 1 },
+ Insert {
+ site: 1,
+ pos: 5,
+ text: 0,
+ },
+ ];
+ run(ops, 2);
+ }
+}
diff --git a/3rdparty/loro/crates/delta/src/array_vec.rs b/3rdparty/loro/crates/delta/src/array_vec.rs
new file mode 100644
index 0000000000..c5c485e68b
--- /dev/null
+++ b/3rdparty/loro/crates/delta/src/array_vec.rs
@@ -0,0 +1,280 @@
+use std::fmt::Debug;
+use std::ops::{Deref, DerefMut};
+
+use generic_btree::rle::{HasLength, Mergeable, Sliceable, TryInsert};
+use heapless::Vec;
+
+use crate::delta_trait::{DeltaAttr, DeltaValue};
+use crate::{DeltaRope, DeltaRopeBuilder};
+
+#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
+#[repr(transparent)]
+pub struct ArrayVec {
+ vec: Vec,
+}
+
+impl ArrayVec {
+ pub fn new() -> Self {
+ Self { vec: Vec::new() }
+ }
+
+ pub fn insert_many(&mut self, pos: usize, values: Self) -> Result<(), Self> {
+ let self_len = self.len();
+ let values_len = values.len();
+ let Some(new_len) = self_len.checked_add(values_len) else {
+ return Err(values);
+ };
+
+ if pos > self_len || C < new_len {
+ return Err(values);
+ }
+
+ // SAFETY: We have the ownership of the values
+ unsafe {
+ let ptr_start = self.vec.as_mut_ptr().add(pos);
+ ptr_start.copy_to(ptr_start.add(values_len), self_len - pos);
+ ptr_start.copy_from_nonoverlapping(values.as_ptr(), values_len);
+ self.vec.set_len(new_len);
+ }
+
+ // This will not cause memory leak because the vec is on the stack
+ // And we already take the ownership of the elements inside the vec
+ std::mem::forget(values.vec);
+ Ok(())
+ }
+
+ pub fn from_many(mut iter: impl Iterator- ) -> impl Iterator
- {
+ let mut new = Self::new();
+ std::iter::from_fn(move || {
+ let mut v = iter.next();
+ while let Some(iv) = v {
+ if new.vec.push(iv).is_err() {
+ unreachable!()
+ }
+
+ if new.vec.is_full() {
+ break;
+ }
+
+ v = iter.next();
+ }
+
+ if new.is_empty() {
+ return None;
+ }
+
+ Some(std::mem::take(&mut new))
+ })
+ }
+
+ // The type system doesn't allow us to use unexported types in trait's associated types
+ // So we don't impl the trait for this
+ #[allow(clippy::should_implement_trait)]
+ pub fn into_iter(self) -> impl Iterator
- {
+ self.vec.into_iter()
+ }
+}
+
+impl DeltaRope, Attr> {
+ pub fn from_many(iter: impl Iterator
- ) -> Self {
+ let mut rope = DeltaRope::new();
+ rope.insert_values(
+ 0,
+ ArrayVec::from_many(iter).map(|x| crate::DeltaItem::Replace {
+ value: x,
+ attr: Default::default(),
+ delete: 0,
+ }),
+ );
+
+ rope
+ }
+}
+
+impl DeltaRopeBuilder, Attr> {
+ pub fn insert_many(mut self, value_iter: impl IntoIterator
- , attr: Attr) -> Self {
+ let iter = ArrayVec::from_many(value_iter.into_iter());
+ for value in iter {
+ self = self.insert(value, attr.clone());
+ }
+ self
+ }
+}
+
+impl Default for ArrayVec {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl Deref for ArrayVec {
+ type Target = Vec;
+
+ fn deref(&self) -> &Self::Target {
+ &self.vec
+ }
+}
+
+impl DerefMut for ArrayVec {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.vec
+ }
+}
+
+impl HasLength for ArrayVec {
+ fn rle_len(&self) -> usize {
+ self.len()
+ }
+}
+
+impl Sliceable for ArrayVec {
+ fn _slice(&self, range: std::ops::Range) -> Self {
+ Self {
+ vec: Vec::from_slice(&self.vec.as_slice()[range]).unwrap(),
+ }
+ }
+
+ fn split(&mut self, pos: usize) -> Self {
+ let right = self.slice(pos..);
+ self.vec.truncate(pos);
+ right
+ }
+}
+
+impl Mergeable for ArrayVec {
+ fn can_merge(&self, rhs: &Self) -> bool {
+ C >= self.len() + rhs.len()
+ }
+
+ fn merge_right(&mut self, rhs: &Self) {
+ self.extend_from_slice(rhs.as_slice()).unwrap();
+ }
+
+ fn merge_left(&mut self, left: &Self) {
+ match self.insert_many(0, left.clone()) {
+ Ok(_) => {}
+ Err(_) => unreachable!(),
+ }
+ }
+}
+
+impl TryInsert for ArrayVec {
+ fn try_insert(&mut self, pos: usize, elem: Self) -> Result<(), Self>
+ where
+ Self: Sized,
+ {
+ self.insert_many(pos, elem)
+ }
+}
+
+impl DeltaValue for ArrayVec where T: Clone + Debug {}
+
+impl From<[T; A]> for ArrayVec
+where
+ T: Clone,
+{
+ fn from(array: [T; A]) -> Self {
+ let vec = Vec::from_slice(&array).unwrap();
+ ArrayVec { vec }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use std::sync::Arc;
+
+ use super::*;
+
+ #[test]
+ fn test_insert() {
+ let mut array_vec: ArrayVec, 8> =
+ ArrayVec::from([Arc::new(1), Arc::new(2), Arc::new(3)]);
+ let b = ArrayVec::from([Arc::new(1), Arc::new(2), Arc::new(3)]);
+ array_vec.insert_many(1, b).unwrap();
+ }
+
+ #[cfg(miri)]
+ #[test]
+ fn miri_insert_many_rejects_out_of_bounds_position_before_pointer_arithmetic() {
+ let mut array_vec: ArrayVec = ArrayVec::from([1, 2, 3]);
+ let values = ArrayVec::from([4]);
+
+ assert!(array_vec.insert_many(usize::MAX, values).is_err());
+ assert_eq!(array_vec.as_slice(), &[1, 2, 3]);
+ }
+
+ #[test]
+ fn test_slice() {
+ let array_vec: ArrayVec = ArrayVec::from([1, 2, 3, 4, 5]);
+ let sliced = array_vec._slice(1..3);
+ assert_eq!(sliced.as_slice(), &[2, 3]);
+ }
+
+ #[test]
+ fn test_split() {
+ let mut array_vec: ArrayVec = ArrayVec::from([1, 2, 3, 4, 5]);
+ let right = array_vec.split(2);
+ assert_eq!(array_vec.as_slice(), &[1, 2]);
+ assert_eq!(right.as_slice(), &[3, 4, 5]);
+ }
+
+ #[test]
+ fn test_can_merge() {
+ let array_vec1: ArrayVec = ArrayVec::from([1, 2, 3]);
+ let array_vec2: ArrayVec = ArrayVec::from([4, 5, 6]);
+ assert!(array_vec1.can_merge(&array_vec2));
+ }
+
+ #[test]
+ fn test_merge_right() {
+ let mut array_vec1: ArrayVec = ArrayVec::from([1, 2, 3]);
+ let array_vec2: ArrayVec = ArrayVec::from([4, 5, 6]);
+ array_vec1.merge_right(&array_vec2);
+ assert_eq!(array_vec1.as_slice(), &[1, 2, 3, 4, 5, 6]);
+ }
+
+ #[test]
+ fn test_merge_left() {
+ let mut array_vec1: ArrayVec = ArrayVec::from([4, 5, 6]);
+ let array_vec2: ArrayVec = ArrayVec::from([1, 2, 3]);
+ array_vec1.merge_left(&array_vec2);
+ assert_eq!(array_vec1.as_slice(), &[1, 2, 3, 4, 5, 6]);
+ }
+
+ #[test]
+ fn test_try_insert() {
+ let mut array_vec: ArrayVec = ArrayVec::from([1, 2, 5]);
+ let result = array_vec.try_insert(2, ArrayVec::from([3, 4]));
+ assert!(result.is_ok());
+ assert_eq!(array_vec.as_slice(), &[1, 2, 3, 4, 5]);
+ }
+
+ #[test]
+ fn test_merge_fail() {
+ let array_vec1: ArrayVec = ArrayVec::from([1, 2, 3, 4, 5]);
+ let array_vec2: ArrayVec = ArrayVec::from([6, 7, 8]);
+ let result = array_vec1.can_merge(&array_vec2);
+ assert!(!result);
+ }
+
+ #[test]
+ fn test_try_insert_fail() {
+ let mut array_vec: ArrayVec = ArrayVec::from([1, 2, 4, 5, 6]);
+ let result = array_vec.try_insert(2, ArrayVec::from([3]));
+ assert!(result.is_err());
+ }
+
+ #[test]
+ fn test_insert_many_fail() {
+ let mut array_vec: ArrayVec = ArrayVec::from([1, 2, 3]);
+ let result = array_vec.insert_many(1, ArrayVec::from([4, 5, 6]));
+ assert!(result.is_err());
+ }
+
+ #[test]
+ #[should_panic]
+ fn merge_right_overflow() {
+ let mut array_vec: ArrayVec = ArrayVec::from([1, 2, 3]);
+ array_vec.merge_right(&ArrayVec::from([4, 5, 6]));
+ }
+}
diff --git a/3rdparty/loro/crates/delta/src/delta_item.rs b/3rdparty/loro/crates/delta/src/delta_item.rs
new file mode 100644
index 0000000000..b64e3149e7
--- /dev/null
+++ b/3rdparty/loro/crates/delta/src/delta_item.rs
@@ -0,0 +1,374 @@
+use std::ops::RangeBounds;
+
+use super::*;
+use generic_btree::rle::{CanRemove, TryInsert};
+
+impl DeltaItem {
+ /// Including the delete length
+ pub fn delta_len(&self) -> usize {
+ match self {
+ DeltaItem::Retain { len, .. } => *len,
+ DeltaItem::Replace {
+ value,
+ attr: _,
+ delete,
+ } => value.rle_len() + delete,
+ }
+ }
+
+ /// The real length of the item in the delta, excluding the delete length
+ pub fn data_len(&self) -> usize {
+ match self {
+ DeltaItem::Retain { len, .. } => *len,
+ DeltaItem::Replace { value, .. } => value.rle_len(),
+ }
+ }
+
+ pub fn new_insert(value: V, attr: Attr) -> Self {
+ DeltaItem::Replace {
+ value,
+ attr,
+ delete: 0,
+ }
+ }
+}
+
+impl DeltaItem {
+ pub fn new_delete(len: usize) -> Self {
+ DeltaItem::Replace {
+ value: Default::default(),
+ attr: Default::default(),
+ delete: len,
+ }
+ }
+}
+
+impl HasLength for DeltaItem {
+ /// This would treat the len of the Delete as 0
+ fn rle_len(&self) -> usize {
+ self.delta_len()
+ }
+}
+
+impl Mergeable for DeltaItem {
+ fn can_merge(&self, rhs: &Self) -> bool {
+ match (self, rhs) {
+ (DeltaItem::Retain { attr: attr1, .. }, DeltaItem::Retain { attr: attr2, .. }) => {
+ attr1 == attr2
+ }
+ (
+ DeltaItem::Replace {
+ value: value1,
+ attr: attr1,
+ delete: _del1,
+ },
+ DeltaItem::Replace {
+ value: value2,
+ attr: attr2,
+ delete: _del2,
+ },
+ ) => value1.can_merge(value2) && attr1 == attr2,
+ _ => false,
+ }
+ }
+
+ fn merge_right(&mut self, rhs: &Self) {
+ match (self, rhs) {
+ (DeltaItem::Retain { len: len1, .. }, DeltaItem::Retain { len: len2, .. }) => {
+ *len1 += len2
+ }
+ (
+ DeltaItem::Replace {
+ value: value1,
+ delete: del1,
+ ..
+ },
+ DeltaItem::Replace {
+ value: value2,
+ delete: del2,
+ ..
+ },
+ ) => {
+ value1.merge_right(value2);
+ *del1 += *del2;
+ }
+ _ => unreachable!(),
+ }
+ }
+
+ fn merge_left(&mut self, left: &Self) {
+ match (self, left) {
+ (DeltaItem::Retain { len: len1, .. }, DeltaItem::Retain { len: len2, .. }) => {
+ *len1 += len2
+ }
+ (
+ DeltaItem::Replace {
+ value: value1,
+ delete: del1,
+ ..
+ },
+ DeltaItem::Replace {
+ value: value2,
+ delete: del2,
+ ..
+ },
+ ) => {
+ value1.merge_left(value2);
+ *del1 += del2;
+ }
+ _ => unreachable!(),
+ }
+ }
+}
+
+impl Sliceable for DeltaItem {
+ fn _slice(&self, range: std::ops::Range) -> Self {
+ assert!(range.end <= self.rle_len());
+ match self {
+ DeltaItem::Retain { len, attr } => {
+ assert!(range.end <= *len);
+ DeltaItem::Retain {
+ len: range.len(),
+ attr: attr.clone(),
+ }
+ }
+ DeltaItem::Replace {
+ value,
+ attr,
+ delete,
+ } => {
+ if range.end <= value.rle_len() {
+ let value = value._slice(range.clone());
+ DeltaItem::Replace {
+ value,
+ attr: attr.clone(),
+ delete: 0,
+ }
+ } else if range.start >= value.rle_len() {
+ debug_assert!(range.end <= delete + value.rle_len());
+ debug_assert!(range.len() <= *delete);
+ DeltaItem::new_delete(range.len())
+ } else {
+ let delete_len = range.end - value.rle_len();
+ debug_assert!(delete_len <= *delete);
+ let value = value._slice(range.start..value.rle_len());
+ DeltaItem::Replace {
+ delete: delete_len,
+ value,
+ attr: attr.clone(),
+ }
+ }
+ }
+ }
+ }
+
+ /// slice in-place
+ #[inline(always)]
+ fn slice_(&mut self, range: impl RangeBounds) {
+ *self = self.slice(range);
+ }
+
+ fn split(&mut self, pos: usize) -> Self {
+ match self {
+ DeltaItem::Retain { len, attr } => {
+ let right_len = *len - pos;
+ *len = pos;
+ DeltaItem::Retain {
+ len: right_len,
+ attr: attr.clone(),
+ }
+ }
+ DeltaItem::Replace {
+ value,
+ attr,
+ delete,
+ } => {
+ if pos < value.rle_len() {
+ let right = value.split(pos);
+ let right_delete = *delete;
+ *delete = 0;
+ DeltaItem::Replace {
+ value: right,
+ attr: attr.clone(),
+ delete: right_delete,
+ }
+ } else {
+ let right_len = value.rle_len() + *delete - pos;
+ let right = DeltaItem::new_delete(right_len);
+ *delete -= right_len;
+ right
+ }
+ }
+ }
+ }
+
+ /// Update the slice in the given range.
+ /// This method may split `self` into two or three parts.
+ /// If so, it will make `self` the leftmost part and return the next split parts.
+ ///
+ /// # Example
+ ///
+ /// If `self.rle_len() == 10`, `self.update(1..5)` will split self into three parts and update the middle part.
+ /// It returns the middle and the right part.
+ fn update_with_split(
+ &mut self,
+ range: impl RangeBounds,
+ f: impl FnOnce(&mut Self),
+ ) -> (Option, Option) {
+ let start = match range.start_bound() {
+ std::ops::Bound::Included(x) => *x,
+ std::ops::Bound::Excluded(x) => x + 1,
+ std::ops::Bound::Unbounded => 0,
+ };
+
+ let end = match range.end_bound() {
+ std::ops::Bound::Included(x) => x + 1,
+ std::ops::Bound::Excluded(x) => *x,
+ std::ops::Bound::Unbounded => self.rle_len(),
+ };
+
+ match (start == 0, end == self.rle_len()) {
+ (true, true) => {
+ f(self);
+ (None, None)
+ }
+ (true, false) => {
+ let right = self.split(end);
+ f(self);
+ if self.can_merge(&right) {
+ self.merge_right(&right);
+ (None, None)
+ } else {
+ (Some(right), None)
+ }
+ }
+ (false, true) => {
+ let mut right = self.split(start);
+ f(&mut right);
+ if self.can_merge(&right) {
+ self.merge_right(&right);
+ (None, None)
+ } else {
+ (Some(right), None)
+ }
+ }
+ (false, false) => {
+ let right = self.split(end);
+ let mut middle = self.split(start);
+ f(&mut middle);
+ if middle.can_remove() {
+ if self.can_merge(&right) {
+ self.merge_right(&right);
+ (None, None)
+ } else {
+ (Some(right), None)
+ }
+ } else if middle.can_merge(&right) {
+ middle.merge_right(&right);
+ if self.can_merge(&middle) {
+ self.merge_right(&middle);
+ (None, None)
+ } else {
+ (Some(middle), None)
+ }
+ } else if self.can_merge(&middle) {
+ self.merge_right(&middle);
+ (Some(right), None)
+ } else {
+ (Some(middle), Some(right))
+ }
+ }
+ }
+ }
+}
+
+impl TryInsert for DeltaItem {
+ fn try_insert(&mut self, pos: usize, elem: Self) -> Result<(), Self>
+ where
+ Self: Sized,
+ {
+ match (self, elem) {
+ (
+ DeltaItem::Retain { len, attr },
+ DeltaItem::Retain {
+ len: len_b,
+ attr: attr_b,
+ },
+ ) => {
+ if attr == &attr_b {
+ *len += len_b;
+ Ok(())
+ } else {
+ Err(DeltaItem::Retain {
+ len: len_b,
+ attr: attr_b,
+ })
+ }
+ }
+ (
+ DeltaItem::Replace {
+ value: l_value,
+ attr: l_attr,
+ delete: l_delete,
+ },
+ DeltaItem::Replace {
+ value: r_value,
+ attr: r_attr,
+ delete: r_delete,
+ },
+ ) => {
+ if l_value.rle_len() == 0 && r_value.rle_len() == 0 {
+ *l_delete += r_delete;
+ return Ok(());
+ }
+
+ if l_attr == &r_attr {
+ match l_value.try_insert(pos, r_value) {
+ Ok(_) => {
+ *l_delete += r_delete;
+ return Ok(());
+ }
+ Err(r_value) => {
+ return Err(DeltaItem::Replace {
+ value: r_value,
+ attr: r_attr,
+ delete: r_delete,
+ })
+ }
+ }
+ }
+
+ Err(DeltaItem::Replace {
+ value: r_value,
+ attr: r_attr,
+ delete: r_delete,
+ })
+ }
+ (_, a) => Err(a),
+ }
+ }
+}
+
+impl CanRemove for DeltaItem {
+ fn can_remove(&self) -> bool {
+ match self {
+ DeltaItem::Retain { len, .. } => *len == 0,
+ DeltaItem::Replace {
+ value,
+ attr: _,
+ delete,
+ } => value.rle_len() == 0 && *delete == 0,
+ }
+ }
+}
+
+impl Default for DeltaItem {
+ fn default() -> Self {
+ DeltaItem::Retain {
+ len: 0,
+ attr: Default::default(),
+ }
+ }
+}
+
+impl DeltaValue for DeltaItem {}
diff --git a/3rdparty/loro/crates/delta/src/delta_rope.rs b/3rdparty/loro/crates/delta/src/delta_rope.rs
new file mode 100644
index 0000000000..66e9d2683f
--- /dev/null
+++ b/3rdparty/loro/crates/delta/src/delta_rope.rs
@@ -0,0 +1,580 @@
+use std::{fmt::Debug, ops::Range};
+
+use generic_btree::{rle::Sliceable, Cursor};
+
+use crate::{
+ delta_rope::rle_tree::LengthFinder,
+ delta_trait::{DeltaAttr, DeltaValue},
+ DeltaItem, DeltaRope, DeltaRopeBuilder,
+};
+
+use self::rle_tree::Len;
+
+use super::iter::Iter;
+
+mod compose;
+pub(crate) mod rle_tree;
+
+impl DeltaRope {
+ pub fn new() -> Self {
+ Self {
+ tree: Default::default(),
+ }
+ }
+
+ pub fn first(&self) -> Option<&DeltaItem> {
+ let leaf = self.tree.first_leaf()?;
+ self.tree.get_elem(leaf)
+ }
+
+ pub fn iter(&self) -> impl Iterator
- > {
+ self.tree.iter()
+ }
+
+ /// Returns the length of the delta rope (insertions + retains).
+ pub fn len(&self) -> usize {
+ self.tree.root_cache().data_len as usize
+ }
+
+ /// Returns the length of the delta rope (deletions + retains).
+ pub fn old_len(&self) -> usize {
+ self.tree.root_cache().delta_len as usize
+ }
+
+ pub fn is_empty(&self) -> bool {
+ self.tree.is_empty()
+ }
+
+ pub fn push_insert(&mut self, v: V, attr: Attr) -> &mut Self {
+ if v.rle_len() == 0 {
+ return self;
+ }
+
+ let Some(leaf) = self.tree.last_leaf() else {
+ self.tree.push(DeltaItem::Replace {
+ value: v,
+ attr,
+ delete: 0,
+ });
+ return self;
+ };
+ let mut inserted = false;
+ self.tree.update_leaf(leaf, |item| {
+ if let DeltaItem::Replace {
+ value,
+ attr: a,
+ delete: _,
+ } = item
+ {
+ if value.can_merge(&v) && a == &attr {
+ value.merge_right(&v);
+ inserted = true;
+ return (true, None, None);
+ }
+ }
+ (false, None, None)
+ });
+
+ if !inserted {
+ self.tree.push(DeltaItem::Replace {
+ value: v,
+ attr,
+ delete: 0,
+ });
+ }
+
+ self
+ }
+
+ pub fn push_retain(&mut self, retain: usize, attr: Attr) -> &mut Self {
+ if retain == 0 {
+ return self;
+ }
+
+ let Some(leaf) = self.tree.last_leaf() else {
+ self.tree.push(DeltaItem::Retain { len: retain, attr });
+ return self;
+ };
+
+ let mut inserted = false;
+ self.tree.update_leaf(leaf, |item| {
+ if let DeltaItem::Retain { len, attr: a } = item {
+ if a == &attr {
+ *len += retain;
+ inserted = true;
+ return (true, None, None);
+ }
+ }
+ (false, None, None)
+ });
+
+ if !inserted {
+ self.tree.push(DeltaItem::Retain { len: retain, attr });
+ }
+
+ self
+ }
+
+ pub fn push_replace(&mut self, value: V, attr: Attr, delete: usize) -> &mut Self {
+ if value.rle_len() == 0 && delete == 0 {
+ return self;
+ }
+
+ let Some(leaf) = self.tree.last_leaf() else {
+ self.tree.push(DeltaItem::Replace {
+ value,
+ attr,
+ delete,
+ });
+ return self;
+ };
+
+ let mut inserted = false;
+ self.tree.update_leaf(leaf, |item| {
+ if let DeltaItem::Replace {
+ value: v,
+ attr: a,
+ delete: d,
+ } = item
+ {
+ if a == &attr && v.can_merge(&value) {
+ v.merge_right(&value);
+ *d += delete;
+ inserted = true;
+ return (true, None, None);
+ }
+ }
+ (false, None, None)
+ });
+
+ if !inserted {
+ self.tree.push(DeltaItem::Replace {
+ value,
+ attr,
+ delete,
+ });
+ }
+
+ self
+ }
+
+ pub fn push_delete(&mut self, len: usize) -> &mut Self {
+ if len == 0 {
+ return self;
+ }
+
+ let Some(leaf) = self.tree.last_leaf() else {
+ self.tree.push(DeltaItem::Replace {
+ value: Default::default(),
+ attr: Default::default(),
+ delete: len,
+ });
+ return self;
+ };
+
+ let mut inserted = false;
+ self.tree.update_leaf(leaf, |item| {
+ if let DeltaItem::Replace {
+ value: _,
+ attr: _,
+ delete,
+ } = item
+ {
+ *delete += len;
+ inserted = true;
+ return (true, None, None);
+ }
+ (false, None, None)
+ });
+
+ if !inserted {
+ self.tree.push(DeltaItem::new_delete(len));
+ }
+
+ self
+ }
+
+ pub fn push(&mut self, item: DeltaItem) -> &mut Self {
+ match item {
+ DeltaItem::Retain { len, attr } => self.push_retain(len, attr),
+ DeltaItem::Replace {
+ value,
+ attr,
+ delete,
+ } => self.push_replace(value, attr, delete),
+ }
+ }
+
+ /// Returns an iterator that can iterate over the delta rope with a custom length.
+ ///
+ /// It's more controllable compared to the default iterator.
+ ///
+ /// - Iterating over the delta rope with a custom length.
+ /// - You can peek the next item.
+ ///
+ /// It's useful to implement algorithms related to Delta
+ pub fn iter_with_len(&self) -> Iter<'_, V, Attr> {
+ Iter::new(self)
+ }
+
+ pub fn chop(&mut self) {
+ let mut last_leaf = self.tree.last_leaf();
+ while let Some(last_leaf_idx) = last_leaf {
+ let elem = self.tree.get_elem(last_leaf_idx).unwrap();
+ match elem {
+ DeltaItem::Retain { len: _, attr } if attr.attr_is_empty() => {
+ self.tree.remove_leaf(Cursor {
+ leaf: last_leaf_idx,
+ offset: 0,
+ });
+ last_leaf = self.tree.last_leaf();
+ }
+ _ => return,
+ }
+ }
+ }
+
+ pub fn transform(&self, other: &Self, left_prior: bool) -> Self {
+ let mut this_iter = self.iter_with_len();
+ let mut other_iter = other.iter_with_len();
+ let mut transformed_delta = DeltaRope::new();
+
+ while this_iter.peek().is_some() || other_iter.peek().is_some() {
+ if this_iter.peek_is_insert() && (left_prior || !other_iter.peek_is_insert()) {
+ let insert_length;
+ match this_iter.peek().unwrap() {
+ DeltaItem::Replace { value, attr, .. } => {
+ insert_length = value.rle_len();
+ transformed_delta.push_insert(value.clone(), attr.clone());
+ }
+ DeltaItem::Retain { .. } => unreachable!(),
+ }
+ this_iter.next_with(insert_length).unwrap();
+ } else if other_iter.peek_is_insert() {
+ let insert_length = other_iter.peek_insert_length();
+ transformed_delta.push_retain(insert_length, Default::default());
+ other_iter.next_with(insert_length).unwrap();
+ } else {
+ // It's now either retains or deletes
+ let length = this_iter.peek_length().min(other_iter.peek_length());
+ let this_op_peek = this_iter.peek().cloned();
+ let other_op_peek = other_iter.peek().cloned();
+ let _ = this_iter.next_with(length);
+ let _ = other_iter.next_with(length);
+ if other_op_peek.map(|x| x.is_delete()).unwrap_or(false) {
+ // It makes our deletes or retains redundant
+ continue;
+ } else if this_op_peek
+ .as_ref()
+ .map(|x| x.is_delete())
+ .unwrap_or(false)
+ {
+ transformed_delta.push_delete(length);
+ } else {
+ transformed_delta.push_retain(
+ length,
+ this_op_peek
+ .map(|x| x.into_retain().unwrap().1)
+ .unwrap_or_default(),
+ );
+ // FIXME: transform the attributes
+ }
+ }
+ }
+
+ transformed_delta.chop();
+ transformed_delta
+ }
+
+ /// Transforms operation `self` against another operation `other` in such a way that the
+ /// impact of `other` is effectively included in `self`.
+ pub fn transform_(&mut self, other: &Self, left_prior: bool) {
+ *self = self.transform(other, left_prior);
+ }
+
+ /// Transforms the position based on self
+ pub fn transform_pos(&self, mut pos: usize, left_prior: bool) -> usize {
+ let mut index = 0;
+ let mut iter = self.iter_with_len();
+ while iter.peek().is_some() {
+ if iter.peek_is_retain() {
+ let DeltaItem::Retain { len, attr: _ } = iter.next().unwrap() else {
+ unreachable!()
+ };
+ index += len;
+ if index > pos || (index == pos && !left_prior) {
+ return pos;
+ }
+ } else if iter.peek_is_insert() {
+ if index == pos && !left_prior {
+ return pos;
+ }
+
+ let insert_len;
+ match iter.peek().unwrap() {
+ DeltaItem::Replace { value, .. } => {
+ insert_len = value.rle_len();
+ }
+ _ => {
+ unreachable!()
+ }
+ }
+
+ pos += insert_len;
+ index += insert_len;
+ iter.next_with(insert_len).unwrap();
+ } else {
+ // Delete
+ match iter.next().unwrap() {
+ DeltaItem::Replace { delete, .. } => {
+ pos = pos.saturating_sub(delete);
+ if pos < index {
+ pos = index;
+ return pos;
+ }
+ }
+ DeltaItem::Retain { .. } => unreachable!(),
+ }
+ }
+ }
+
+ pos
+ }
+}
+
+impl PartialEq for DeltaRope {
+ fn eq(&self, other: &Self) -> bool {
+ if self.len() != other.len() {
+ return false;
+ }
+
+ let mut a = self.iter_with_len();
+ let mut b = other.iter_with_len();
+ while let (Some(x), Some(y)) = (a.peek(), b.peek()) {
+ let len = x.delta_len().min(y.delta_len());
+ match (x, y) {
+ (
+ DeltaItem::Replace {
+ value: va,
+ attr: attr_a,
+ delete: d_a,
+ },
+ DeltaItem::Replace {
+ value: vb,
+ attr: attr_b,
+ delete: d_b,
+ },
+ ) => {
+ if attr_a != attr_b {
+ return false;
+ }
+
+ let va_empty = va.rle_len() == 0;
+ let vb_empty = vb.rle_len() == 0;
+ if vb_empty || va_empty {
+ // both deletions
+ let min_del_len = (*d_a).min(*d_b);
+ if min_del_len == 0 {
+ return false;
+ }
+
+ a.next_with_del(min_del_len).unwrap();
+ b.next_with_del(min_del_len).unwrap();
+ } else {
+ let len = (va.rle_len()).min(vb.rle_len());
+ let va_slice = va.slice(..len);
+ let vb_slice = vb.slice(..len);
+ if va_slice != vb_slice {
+ return false;
+ }
+
+ a.next_with(len).unwrap();
+ b.next_with(len).unwrap();
+ }
+ }
+ (DeltaItem::Retain { attr, .. }, DeltaItem::Retain { attr: b_attr, .. })
+ if *attr == *b_attr =>
+ {
+ a.next_with(len).unwrap();
+ b.next_with(len).unwrap();
+ }
+ _ => return false,
+ }
+ }
+
+ a.peek().is_none() && b.peek().is_none()
+ }
+}
+
+impl Default for DeltaRope {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl DeltaRope {
+ pub fn insert_values(
+ &mut self,
+ pos: usize,
+ values: impl IntoIterator
- >,
+ ) {
+ if self.is_empty() {
+ for value in values {
+ self.tree.push(value.clone());
+ }
+ return;
+ }
+
+ let pos = self.tree.query::(&pos).unwrap();
+ // This would crash if values's number is large
+ self.tree
+ .insert_many_by_cursor(Some(pos.cursor), values.into_iter());
+ }
+
+ pub fn insert_value(&mut self, pos: usize, value: V, attr: Attr) {
+ if self.len() < pos {
+ self.push_retain(pos - self.len(), Default::default());
+ }
+
+ if pos == self.len() {
+ self.tree.push(DeltaItem::Replace {
+ value,
+ attr,
+ delete: 0,
+ });
+ return;
+ }
+
+ let pos = self.tree.query::(&pos).unwrap();
+ self.tree.insert_by_path(
+ pos.cursor,
+ DeltaItem::Replace {
+ value,
+ attr,
+ delete: 0,
+ },
+ );
+ }
+
+ fn update_attr_in_range(&mut self, range: Range, attr: &Attr) {
+ if range.start == range.end || self.is_empty() {
+ return;
+ }
+
+ let from = self.tree.query::(&range.start).unwrap();
+ let to = self.tree.query::(&range.end).unwrap();
+ self.tree.update(from.cursor..to.cursor, &mut |item| {
+ match item {
+ DeltaItem::Retain { attr: a, .. } => {
+ a.compose(attr);
+ }
+ DeltaItem::Replace { attr: a, .. } => a.compose(attr),
+ }
+
+ None
+ });
+ }
+}
+
+impl DeltaRopeBuilder {
+ pub fn new() -> Self {
+ Self { items: vec![] }
+ }
+
+ pub fn insert(mut self, v: V, attr: Attr) -> Self {
+ if v.rle_len() == 0 {
+ return self;
+ }
+
+ if let Some(DeltaItem::Replace { value, attr: a, .. }) = self.items.last_mut() {
+ if value.can_merge(&v) && a == &attr {
+ value.merge_right(&v);
+ return self;
+ }
+ }
+
+ self.items.push(DeltaItem::Replace {
+ value: v,
+ attr,
+ delete: 0,
+ });
+ self
+ }
+
+ pub fn retain(mut self, retain: usize, attr: Attr) -> Self {
+ if retain == 0 {
+ return self;
+ }
+
+ if let Some(DeltaItem::Retain { len, attr: a }) = self.items.last_mut() {
+ if *a == attr {
+ *len += retain;
+ return self;
+ }
+ }
+
+ self.items.push(DeltaItem::Retain { len: retain, attr });
+ self
+ }
+
+ pub fn delete(mut self, len: usize) -> Self {
+ if len == 0 {
+ return self;
+ }
+
+ if let Some(DeltaItem::Replace { delete, .. }) = self.items.last_mut() {
+ *delete += len;
+ return self;
+ }
+
+ self.items.push(DeltaItem::new_delete(len));
+ self
+ }
+
+ pub fn replace(mut self, value: V, attr: Attr, delete: usize) -> Self {
+ if delete == 0 && value.rle_len() == 0 {
+ return self;
+ }
+
+ if let Some(DeltaItem::Replace {
+ value: last_value,
+ attr: last_attr,
+ delete: last_delete,
+ }) = self.items.last_mut()
+ {
+ if last_value.can_merge(&value) && &attr == last_attr {
+ last_value.merge_right(&value);
+ *last_delete += delete;
+ return self;
+ }
+ }
+
+ self.items.push(DeltaItem::Replace {
+ value,
+ attr,
+ delete,
+ });
+ self
+ }
+
+ pub fn build(self) -> DeltaRope {
+ let mut rope = DeltaRope::new();
+ for item in self.items {
+ rope.tree.push(item);
+ }
+
+ rope
+ }
+}
+
+impl Default for DeltaRopeBuilder {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl Debug for DeltaRope {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.debug_list().entries(self.iter()).finish()
+ }
+}
diff --git a/3rdparty/loro/crates/delta/src/delta_rope/compose.rs b/3rdparty/loro/crates/delta/src/delta_rope/compose.rs
new file mode 100644
index 0000000000..7cf0967076
--- /dev/null
+++ b/3rdparty/loro/crates/delta/src/delta_rope/compose.rs
@@ -0,0 +1,221 @@
+use super::*;
+
+struct DeltaReplace<'a, V, Attr> {
+ value: &'a V,
+ attr: &'a Attr,
+ delete: usize,
+}
+
+impl DeltaRope {
+ pub fn compose(&mut self, other: &Self) {
+ // TODO: Need to implement a slow mode that is guaranteed to be correct, then we can fuzz on it
+ if self.is_empty() {
+ *self = other.clone();
+ return;
+ }
+
+ // trace!("Composing {:#?}\n{:#?}", &self, &other);
+ let mut index = 0;
+
+ let mut push_rest = false;
+ for item in other.iter() {
+ if index >= self.len() {
+ self.push_retain(index - self.len(), Attr::default());
+ push_rest = true;
+ }
+
+ if push_rest {
+ self.push(item.clone());
+ continue;
+ }
+
+ match item {
+ DeltaItem::Retain { len, attr } => {
+ if self.len() < index + len {
+ self.push_retain(index + len - self.len(), Default::default());
+ }
+ if !attr.attr_is_empty() {
+ self.update_attr_in_range(index..index + len, attr);
+ }
+ index += len;
+ }
+ DeltaItem::Replace {
+ value: this_value,
+ attr: this_attr,
+ delete,
+ } => {
+ self._compose_replace(
+ DeltaReplace {
+ value: this_value,
+ attr: this_attr,
+ delete: *delete,
+ },
+ &mut index,
+ );
+ }
+ }
+ }
+
+ // trace!("Composed {:#?}", &self);
+ }
+
+ pub fn delete(&mut self, mut index: usize, len: usize) {
+ self._compose_replace(
+ DeltaReplace {
+ value: &Default::default(),
+ attr: &Default::default(),
+ delete: len,
+ },
+ &mut index,
+ );
+ }
+
+ fn _compose_replace(
+ &mut self,
+ delta_replace_item @ DeltaReplace {
+ value: this_value,
+ attr: this_attr,
+ delete,
+ }: DeltaReplace,
+ index: &mut usize,
+ ) {
+ let mut should_insert = this_value.rle_len() > 0;
+ let mut left_del_len = delete;
+ if *index > self.len() {
+ self.push_retain(*index - self.len(), Attr::default());
+ }
+
+ if delete > 0 && *index != self.len() {
+ let range = *index..(*index + left_del_len).min(self.len());
+ let from = self.tree.query::(&range.start).unwrap();
+ let to = self.tree.query::(&range.end).unwrap();
+ if from.cursor.leaf == to.cursor.leaf {
+ self._replace_on_single_leaf(from, to, left_del_len, delta_replace_item);
+ should_insert = false;
+ left_del_len = 0;
+ } else {
+ self._replace_batch_leaves(from, to, &mut left_del_len);
+ }
+ }
+
+ if left_del_len > 0 || should_insert {
+ let replace = DeltaItem::Replace {
+ value: if should_insert {
+ this_value.clone()
+ } else {
+ Default::default()
+ },
+ attr: if should_insert {
+ this_attr.clone()
+ } else {
+ Default::default()
+ },
+ delete: left_del_len,
+ };
+ self.insert_values(*index, [replace]);
+ }
+
+ *index += this_value.rle_len();
+ }
+
+ fn _replace_batch_leaves(
+ &mut self,
+ from: generic_btree::QueryResult,
+ to: generic_btree::QueryResult,
+ left_del_len: &mut usize,
+ ) {
+ self.tree.update(from.cursor..to.cursor, &mut |item| {
+ // This method will split the leaf node before calling this closure.
+ // So it's guaranteed that the item is contained in the range.
+ if *left_del_len == 0 {
+ return None;
+ }
+
+ match item {
+ DeltaItem::Retain { len, .. } => {
+ assert!(*len <= *left_del_len);
+ *left_del_len -= *len;
+ let diff = -(*len as isize);
+ *item = DeltaItem::Replace {
+ delete: *len,
+ value: Default::default(),
+ attr: Default::default(),
+ };
+ Some(Len {
+ data_len: diff,
+ delta_len: 0,
+ })
+ }
+ DeltaItem::Replace { value, attr, .. } => {
+ if *left_del_len >= value.rle_len() {
+ let diff = value.rle_len() as isize;
+ *left_del_len -= value.rle_len();
+ *value = Default::default();
+ *attr = Default::default();
+ Some(Len {
+ data_len: -diff,
+ delta_len: -diff,
+ })
+ } else {
+ unreachable!()
+ }
+ }
+ }
+ });
+ }
+
+ fn _replace_on_single_leaf(
+ &mut self,
+ from: generic_btree::QueryResult,
+ to: generic_btree::QueryResult,
+ left_del_len: usize,
+ DeltaReplace {
+ value: this_value,
+ attr: this_attr,
+ delete: _,
+ }: DeltaReplace,
+ ) {
+ self.tree.update_leaf(from.cursor.leaf, |item| match item {
+ DeltaItem::Retain {
+ len: retain_len, ..
+ } => {
+ let start = from.cursor.offset;
+ let end = to.cursor.offset;
+ debug_assert!(end <= *retain_len);
+ let (l, r) = item.update_with_split(start..end, |item| {
+ *item = DeltaItem::Replace {
+ delete: left_del_len,
+ value: this_value.clone(),
+ attr: this_attr.clone(),
+ };
+ });
+
+ (true, l, r)
+ }
+ DeltaItem::Replace { value, delete, .. } => {
+ let start = from.cursor.offset;
+ let end = to.cursor.offset;
+ let value_len = value.rle_len();
+ let value_start = start.min(value_len);
+ let value_end = value_len.min(end);
+ {
+ // We need to remove the part of value that is between start and end.
+ // If the range is out of the bounds of the value, we record extra deletions
+ // on the `delete` field of this item.
+ let left = left_del_len.saturating_sub(value_end - value_start);
+ *delete += left;
+ }
+
+ let (l, r) = item.update_with_split(value_start..value_end, |item| {
+ *item = DeltaItem::Replace {
+ value: this_value.clone(),
+ attr: this_attr.clone(),
+ delete: 0,
+ };
+ });
+
+ (true, l, r)
+ }
+ });
+ }
+}
diff --git a/3rdparty/loro/crates/delta/src/delta_rope/rle_tree.rs b/3rdparty/loro/crates/delta/src/delta_rope/rle_tree.rs
new file mode 100644
index 0000000000..5dcd6b9ceb
--- /dev/null
+++ b/3rdparty/loro/crates/delta/src/delta_rope/rle_tree.rs
@@ -0,0 +1,199 @@
+use std::{
+ fmt::Debug,
+ iter::Sum,
+ ops::{Add, AddAssign, Sub},
+};
+
+use generic_btree::{rle::CanRemove, ArenaIndex, BTreeTrait, Child, FindResult, Query};
+
+use crate::{
+ delta_trait::{DeltaAttr, DeltaValue},
+ DeltaItem,
+};
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
+pub(crate) struct Len {
+ /// The length of insertions + retains
+ pub data_len: isize,
+ /// The length of deletions + retains + insertions
+ pub delta_len: isize,
+}
+
+impl CanRemove for Len {
+ fn can_remove(&self) -> bool {
+ self.data_len == 0 && self.delta_len == 0
+ }
+}
+
+impl Add for Len {
+ type Output = Self;
+
+ fn add(self, rhs: Self) -> Self {
+ Self {
+ data_len: self.data_len + rhs.data_len,
+ delta_len: self.delta_len + rhs.delta_len,
+ }
+ }
+}
+
+impl AddAssign for Len {
+ fn add_assign(&mut self, rhs: Self) {
+ self.data_len += rhs.data_len;
+ self.delta_len += rhs.delta_len;
+ }
+}
+
+impl Sub for Len {
+ type Output = Self;
+
+ fn sub(self, rhs: Self) -> Self {
+ Self {
+ data_len: self.data_len - rhs.data_len,
+ delta_len: self.delta_len - rhs.delta_len,
+ }
+ }
+}
+
+impl Sum for Len {
+ fn sum>(iter: I) -> Self {
+ iter.fold(
+ Self {
+ data_len: 0,
+ delta_len: 0,
+ },
+ |acc, x| acc + x,
+ )
+ }
+}
+
+pub(crate) struct DeltaTreeTrait {
+ _phantom: std::marker::PhantomData<(V, Attr)>,
+}
+
+impl BTreeTrait for DeltaTreeTrait {
+ type Elem = DeltaItem;
+
+ type Cache = Len;
+
+ type CacheDiff = Len;
+
+ fn calc_cache_internal(
+ cache: &mut Self::Cache,
+ caches: &[generic_btree::Child],
+ ) -> Self::CacheDiff {
+ let new = caches.iter().map(|c| c.cache).sum();
+ let diff = new - *cache;
+ *cache = new;
+ diff
+ }
+
+ fn apply_cache_diff(cache: &mut Self::Cache, diff: &Self::CacheDiff) {
+ *cache += *diff;
+ }
+
+ fn merge_cache_diff(diff1: &mut Self::CacheDiff, diff2: &Self::CacheDiff) {
+ *diff1 += *diff2;
+ }
+
+ fn get_elem_cache(elem: &Self::Elem) -> Self::Cache {
+ match elem {
+ DeltaItem::Retain { len, attr: _ } => Len {
+ data_len: *len as isize,
+ delta_len: *len as isize,
+ },
+ DeltaItem::Replace {
+ value,
+ attr: _,
+ delete,
+ } => Len {
+ data_len: value.rle_len() as isize,
+ delta_len: *delete as isize + value.rle_len() as isize,
+ },
+ }
+ }
+
+ fn new_cache_to_diff(cache: &Self::Cache) -> Self::CacheDiff {
+ *cache
+ }
+
+ fn sub_cache(cache_lhs: &Self::Cache, cache_rhs: &Self::Cache) -> Self::CacheDiff {
+ *cache_lhs - *cache_rhs
+ }
+}
+
+/// A generic length finder
+pub struct LengthFinder {
+ pub left: usize,
+ pub slot: u8,
+ pub parent: Option,
+}
+
+impl LengthFinder {
+ #[inline(always)]
+ pub fn new() -> Self {
+ Self {
+ left: 0,
+ slot: 0,
+ parent: None,
+ }
+ }
+}
+
+impl Default for LengthFinder {
+ #[inline(always)]
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl Query>
+ for LengthFinder
+{
+ type QueryArg = usize;
+
+ #[inline(always)]
+ fn init(target: &Self::QueryArg) -> Self {
+ Self {
+ left: *target,
+ slot: 0,
+ parent: None,
+ }
+ }
+
+ #[inline(always)]
+ fn find_node(
+ &mut self,
+ _: &Self::QueryArg,
+ child_caches: &[Child>],
+ ) -> FindResult {
+ let mut last_left = self.left;
+ let is_internal = matches!(child_caches.first().unwrap().arena, ArenaIndex::Internal(_));
+ for (i, cache) in child_caches.iter().enumerate() {
+ let len = cache.cache.data_len as usize;
+ if self.left >= len {
+ last_left = self.left;
+ self.left -= len;
+ } else {
+ if is_internal {
+ self.parent = Some(cache.arena);
+ } else {
+ self.slot = i as u8;
+ }
+ return FindResult::new_found(i, self.left);
+ }
+ }
+
+ self.left = last_left;
+ if is_internal {
+ self.parent = Some(child_caches.last().unwrap().arena);
+ } else {
+ self.slot = child_caches.len() as u8 - 1;
+ }
+ FindResult::new_missing(child_caches.len() - 1, last_left)
+ }
+
+ #[inline(always)]
+ fn confirm_elem(&mut self, _: &Self::QueryArg, elem: &DeltaItem) -> (usize, bool) {
+ (self.left, self.left < elem.data_len())
+ }
+}
diff --git a/3rdparty/loro/crates/delta/src/delta_trait.rs b/3rdparty/loro/crates/delta/src/delta_trait.rs
new file mode 100644
index 0000000000..cb0bca35fe
--- /dev/null
+++ b/3rdparty/loro/crates/delta/src/delta_trait.rs
@@ -0,0 +1,41 @@
+use generic_btree::rle::{HasLength, Mergeable, Sliceable, TryInsert};
+use std::hash::{BuildHasher, Hash};
+use std::{collections::HashMap, fmt::Debug};
+
+pub trait DeltaValue:
+ HasLength + Sliceable + Mergeable + TryInsert + Debug + Clone + Default
+{
+}
+
+pub trait DeltaAttr: Clone + PartialEq + Debug + Default {
+ fn compose(&mut self, other: &Self);
+ fn attr_is_empty(&self) -> bool;
+}
+
+mod implementations {
+ use super::*;
+
+ impl DeltaAttr for () {
+ fn compose(&mut self, _other: &Self) {}
+ fn attr_is_empty(&self) -> bool {
+ true
+ }
+ }
+
+ impl DeltaAttr for HashMap
+ where
+ K: Eq + Hash + Debug + Clone,
+ V: Debug + PartialEq + Clone,
+ S: BuildHasher + Default + Clone,
+ {
+ fn compose(&mut self, other: &Self) {
+ for (key, value) in other {
+ self.insert(key.clone(), value.clone());
+ }
+ }
+
+ fn attr_is_empty(&self) -> bool {
+ self.is_empty()
+ }
+ }
+}
diff --git a/3rdparty/loro/crates/delta/src/iter.rs b/3rdparty/loro/crates/delta/src/iter.rs
new file mode 100644
index 0000000000..faba92ba4f
--- /dev/null
+++ b/3rdparty/loro/crates/delta/src/iter.rs
@@ -0,0 +1,161 @@
+use generic_btree::{Cursor, LeafIndex};
+
+use crate::{
+ delta_trait::{DeltaAttr, DeltaValue},
+ DeltaItem, DeltaRope,
+};
+
+pub struct Iter<'a, V: DeltaValue, Attr: DeltaAttr> {
+ delta: &'a DeltaRope,
+ cursor: Option,
+ current: Option>,
+}
+
+impl<'a, V: DeltaValue, Attr: DeltaAttr> Iter<'a, V, Attr> {
+ pub fn new(delta: &'a DeltaRope) -> Self {
+ let leaf = delta.tree.first_leaf();
+ let mut current = None;
+ if let Some(leaf) = leaf {
+ current = delta.tree.get_elem(leaf).cloned();
+ }
+
+ Self {
+ delta,
+ cursor: leaf,
+ current,
+ }
+ }
+
+ pub fn peek(&self) -> Option<&'_ DeltaItem> {
+ self.current.as_ref()
+ }
+
+ pub fn peek_is_replace(&self) -> bool {
+ self.peek().map(|x| x.is_replace()).unwrap_or(false)
+ }
+
+ pub fn peek_is_insert(&self) -> bool {
+ self.peek().map(|x| x.is_insert()).unwrap_or(false)
+ }
+
+ pub fn peek_is_delete(&self) -> bool {
+ self.peek().map(|x| x.is_delete()).unwrap_or(false)
+ }
+
+ pub fn peek_is_retain(&self) -> bool {
+ self.peek().map(|x| x.is_retain()).unwrap_or(false)
+ }
+
+ pub fn peek_length(&self) -> usize {
+ self.peek().map(|x| x.delta_len()).unwrap_or(usize::MAX)
+ }
+
+ pub fn peek_insert_length(&self) -> usize {
+ self.peek()
+ .map(|x| match x {
+ DeltaItem::Retain { .. } => 0,
+ DeltaItem::Replace { value, .. } => value.rle_len(),
+ })
+ .unwrap_or(0)
+ }
+
+ pub fn next_with(&mut self, mut len: usize) -> Result<(), usize> {
+ while len > 0 {
+ let Some(current) = self.current.as_mut() else {
+ return Err(len);
+ };
+
+ if len >= current.delta_len() {
+ len -= current.delta_len();
+ self.cursor = self
+ .delta
+ .tree
+ .next_elem(Cursor {
+ leaf: self.cursor.unwrap(),
+ offset: 0,
+ })
+ .map(|x| x.leaf);
+ if let Some(leaf) = self.cursor {
+ self.current = self.delta.tree.get_elem(leaf).cloned();
+ } else {
+ self.current = None;
+ }
+ } else {
+ match current {
+ DeltaItem::Retain {
+ len: retain,
+ attr: _,
+ } => {
+ *retain -= len;
+ }
+ DeltaItem::Replace {
+ value,
+ attr: _,
+ delete,
+ } => {
+ if value.rle_len() > 0 {
+ value.slice_(len..);
+ } else {
+ *delete -= len;
+ }
+ }
+ }
+ len = 0;
+ }
+ }
+
+ Ok(())
+ }
+
+ /// Consume next `len` deletions in the current item
+ pub(crate) fn next_with_del(&mut self, mut len: usize) -> Result<(), usize> {
+ let Some(current) = self.current.as_mut() else {
+ return Err(len);
+ };
+
+ match current {
+ DeltaItem::Retain { .. } => return Err(len),
+ DeltaItem::Replace { delete, .. } => {
+ if *delete >= len {
+ *delete -= len;
+ len = 0;
+ } else {
+ len -= *delete;
+ *delete = 0;
+ }
+ }
+ }
+
+ if current.delta_len() == 0 {
+ self.next();
+ }
+
+ if len > 0 {
+ Err(len)
+ } else {
+ Ok(())
+ }
+ }
+}
+
+impl Iterator for Iter<'_, V, Attr> {
+ type Item = DeltaItem;
+
+ fn next(&mut self) -> Option {
+ self.cursor = self
+ .delta
+ .tree
+ .next_elem(Cursor {
+ leaf: self.cursor.unwrap(),
+ offset: 0,
+ })
+ .map(|x| x.leaf);
+ let old_current = std::mem::take(&mut self.current);
+ if let Some(c) = self.cursor {
+ self.current = self.delta.tree.get_elem(c).cloned();
+ } else {
+ self.current = None;
+ }
+ old_current
+ }
+}
diff --git a/3rdparty/loro/crates/delta/src/lib.rs b/3rdparty/loro/crates/delta/src/lib.rs
new file mode 100644
index 0000000000..eaec2c3ac5
--- /dev/null
+++ b/3rdparty/loro/crates/delta/src/lib.rs
@@ -0,0 +1,78 @@
+use delta_rope::rle_tree::DeltaTreeTrait;
+use delta_trait::{DeltaAttr, DeltaValue};
+use enum_as_inner::EnumAsInner;
+use generic_btree::{
+ rle::{HasLength, Mergeable, Sliceable},
+ BTree,
+};
+use std::fmt::Debug;
+
+pub mod array_vec;
+mod delta_item;
+mod delta_rope;
+pub mod delta_trait;
+pub mod iter;
+pub mod text_delta;
+pub mod utf16;
+
+/// A [DeltaRope] is a rope-like data structure that can be used to represent
+/// a sequence of [DeltaItem]. It has efficient operations for composing other
+/// [DeltaRope]s. It can also be used as a rope, where it only contains insertions.
+#[derive(Clone)]
+pub struct DeltaRope {
+ tree: BTree>,
+}
+
+pub struct DeltaRopeBuilder {
+ items: Vec>,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, EnumAsInner)]
+pub enum DeltaItem {
+ Retain {
+ len: usize,
+ attr: Attr,
+ },
+ /// This is the combined of a delete and an insert.
+ ///
+ /// They are two separate operations in the original Quill Delta format.
+ /// But the order of two neighboring delete and insert operations can be
+ /// swapped without changing the result. So Quill requires that the insert
+ /// always comes before the delete. So it creates room for invalid deltas
+ /// by the type system. Using Replace is a way to avoid this.
+ Replace {
+ value: V,
+ attr: Attr,
+ delete: usize,
+ },
+}
+
+impl DeltaItem {
+ fn is_insert(&self) -> bool {
+ match self {
+ DeltaItem::Retain { .. } => false,
+ DeltaItem::Replace { value, .. } => value.rle_len() > 0,
+ }
+ }
+
+ fn is_delete(&self) -> bool {
+ match self {
+ DeltaItem::Retain { .. } => false,
+ DeltaItem::Replace { value, delete, .. } => value.rle_len() == 0 && *delete > 0,
+ }
+ }
+
+ fn is_replace(&self) -> bool {
+ match self {
+ DeltaItem::Retain { .. } => false,
+ DeltaItem::Replace { .. } => true,
+ }
+ }
+
+ fn is_retain(&self) -> bool {
+ match self {
+ DeltaItem::Retain { .. } => true,
+ DeltaItem::Replace { .. } => false,
+ }
+ }
+}
diff --git a/3rdparty/loro/crates/delta/src/text_delta.rs b/3rdparty/loro/crates/delta/src/text_delta.rs
new file mode 100644
index 0000000000..48d0b62fe4
--- /dev/null
+++ b/3rdparty/loro/crates/delta/src/text_delta.rs
@@ -0,0 +1,191 @@
+use crate::{
+ delta_trait::{DeltaAttr, DeltaValue},
+ DeltaItem, DeltaRope,
+};
+use arrayvec::ArrayString;
+use generic_btree::rle::{HasLength, Mergeable, Sliceable, TryInsert};
+
+#[cfg(test)]
+const MAX_STRING_SIZE: usize = 8;
+#[cfg(not(test))]
+const MAX_STRING_SIZE: usize = 128;
+
+#[derive(Debug, Clone, PartialEq, Eq, Default)]
+pub struct TextChunk(ArrayString);
+pub type TextDelta = DeltaRope;
+
+impl TextDelta {
+ pub fn insert_str(&mut self, index: usize, s: &str) {
+ if s.is_empty() || index == self.len() {
+ self.push_str_insert(s);
+ return;
+ }
+
+ self.insert_values(
+ index,
+ TextChunk::from_long_str(s).map(|chunk| DeltaItem::Replace {
+ value: chunk,
+ attr: Default::default(),
+ delete: 0,
+ }),
+ );
+ }
+
+ pub fn push_str_insert(&mut self, s: &str) -> &mut Self {
+ self.push_str_insert_with_attr(s, Default::default())
+ }
+
+ pub fn push_str_insert_with_attr(&mut self, s: &str, attr: Attr) -> &mut Self {
+ if s.is_empty() {
+ return self;
+ }
+
+ if s.len() <= MAX_STRING_SIZE {
+ self.push_insert(TextChunk(ArrayString::from(s).unwrap()), attr);
+ return self;
+ }
+
+ let mut split_end = 128;
+ let mut split_start = 0;
+ while split_end != s.len() {
+ while !s.is_char_boundary(split_end) {
+ split_end -= 1;
+ }
+
+ let chunk = TextChunk(ArrayString::from(&s[split_start..split_end]).unwrap());
+ self.push_insert(chunk, attr.clone());
+ split_start = split_end;
+ split_end = (split_end + 128).min(s.len());
+ }
+
+ self
+ }
+
+ pub fn try_to_string(&self) -> Option {
+ let mut ans = String::with_capacity(self.len());
+ for item in self.iter() {
+ match item {
+ crate::DeltaItem::Retain { .. } => return None,
+ crate::DeltaItem::Replace { value, .. } => {
+ ans.push_str(&value.0);
+ }
+ }
+ }
+
+ Some(ans)
+ }
+}
+
+impl TextChunk {
+ pub(crate) fn try_insert(&mut self, pos: usize, s: &str) -> Result<(), ()> {
+ if self.0.len() + s.len() > MAX_STRING_SIZE {
+ return Err(());
+ }
+
+ assert!(self.0.is_char_boundary(pos));
+ let new_len = self.0.len() + s.len();
+ unsafe {
+ let ptr = self.0.as_mut_ptr().add(pos);
+ ptr.copy_to(ptr.add(s.len()), self.0.len() - pos);
+ ptr.copy_from_nonoverlapping(s.as_ptr(), s.len());
+ self.0.set_len(new_len);
+ }
+
+ Ok(())
+ }
+
+ pub fn try_from_str(s: &str) -> Option {
+ Some(TextChunk(ArrayString::from(s).ok()?))
+ }
+
+ pub fn from_long_str(s: &str) -> impl Iterator
- + '_ {
+ let mut text_iter = s.chars();
+ std::iter::from_fn(move || {
+ let mut chunk = Self::default();
+ for c in text_iter.by_ref() {
+ let mut bytes = [0, 0, 0, 0];
+ chunk.0.push_str(c.encode_utf8(&mut bytes));
+ if chunk.0.is_full() {
+ break;
+ }
+ }
+
+ if chunk.0.is_empty() {
+ return None;
+ }
+
+ Some(chunk)
+ })
+ }
+}
+
+impl HasLength for TextChunk {
+ fn rle_len(&self) -> usize {
+ self.0.len()
+ }
+}
+
+impl Mergeable for TextChunk {
+ fn can_merge(&self, rhs: &Self) -> bool {
+ MAX_STRING_SIZE >= self.0.len() + rhs.0.len()
+ }
+
+ fn merge_right(&mut self, rhs: &Self) {
+ self.0.push_str(&rhs.0)
+ }
+
+ fn merge_left(&mut self, left: &Self) {
+ assert!(self.can_merge(left), "TextChunk cannot merge over capacity");
+ let ptr = self.0.as_mut_ptr();
+ // Safety: `self.0` is a valid `ArrayString` and `left.0` is a valid `ArrayString`.
+ unsafe {
+ ptr.copy_to(ptr.add(left.0.len()), self.0.len());
+ ptr.copy_from_nonoverlapping(left.0.as_ptr(), left.0.len());
+ self.0.set_len(self.0.len() + left.0.len());
+ }
+ }
+}
+
+impl Sliceable for TextChunk {
+ fn _slice(&self, range: std::ops::Range) -> Self {
+ let mut new = ArrayString::new();
+ new.push_str(&self.0.as_str()[range]);
+ TextChunk(new)
+ }
+
+ fn split(&mut self, pos: usize) -> Self {
+ let mut right = ArrayString::new();
+ right.push_str(&self.0.as_str()[pos..]);
+ self.0.truncate(pos);
+ TextChunk(right)
+ }
+}
+
+impl TryInsert for TextChunk {
+ fn try_insert(&mut self, pos: usize, elem: Self) -> Result<(), Self>
+ where
+ Self: Sized,
+ {
+ match self.try_insert(pos, elem.0.as_str()) {
+ Ok(_) => Ok(()),
+ Err(_) => Err(elem),
+ }
+ }
+}
+
+impl DeltaValue for TextChunk {}
+
+#[cfg(all(test, miri))]
+mod miri_tests {
+ use super::TextChunk;
+ use generic_btree::rle::Mergeable;
+
+ #[test]
+ #[should_panic(expected = "TextChunk cannot merge over capacity")]
+ fn merge_left_rejects_over_capacity_before_raw_copy() {
+ let mut right = TextChunk::try_from_str("12345678").unwrap();
+ let left = TextChunk::try_from_str("9").unwrap();
+
+ right.merge_left(&left);
+ }
+}
diff --git a/3rdparty/loro/crates/delta/src/utf16.rs b/3rdparty/loro/crates/delta/src/utf16.rs
new file mode 100644
index 0000000000..72b3218f62
--- /dev/null
+++ b/3rdparty/loro/crates/delta/src/utf16.rs
@@ -0,0 +1,133 @@
+use generic_btree::rle::{HasLength, Mergeable, Sliceable, TryInsert};
+
+use crate::delta_trait::DeltaValue;
+
+#[derive(Debug, Clone)]
+pub struct AsUtf16Index> {
+ s: S,
+ utf16_len: usize,
+}
+
+impl> AsUtf16Index
{
+ pub fn new(s: S) -> Self {
+ let utf16_len = s
+ .as_ref()
+ .chars()
+ .fold(0, |prev, cur| prev + cur.len_utf16());
+ Self { s, utf16_len }
+ }
+
+ fn convert_utf16_to_utf8(&self, index: usize) -> usize {
+ if index == 0 {
+ return 0;
+ }
+
+ let s = self.s.as_ref();
+ if index == self.utf16_len {
+ return s.len();
+ }
+
+ if index > self.utf16_len {
+ panic!("Index out of bounds");
+ }
+
+ let mut utf16_index = 0;
+ for (i, c) in s.char_indices() {
+ if utf16_index >= index {
+ return i;
+ }
+ utf16_index += c.len_utf16();
+ }
+
+ unreachable!();
+ }
+
+ #[allow(unused)]
+ fn convert_utf8_to_utf16(&self, index: usize) -> usize {
+ if index == 0 {
+ return 0;
+ }
+
+ let s = self.s.as_ref();
+ if index == s.len() {
+ return self.utf16_len;
+ }
+
+ if index > s.len() {
+ panic!("Index out of bounds");
+ }
+
+ let mut utf16_index = 0;
+ for (i, c) in s.char_indices() {
+ if i >= index {
+ return utf16_index;
+ }
+ utf16_index += c.len_utf16();
+ }
+
+ unreachable!();
+ }
+}
+
+impl> HasLength for AsUtf16Index {
+ fn rle_len(&self) -> usize {
+ self.utf16_len
+ }
+}
+
+impl + Mergeable> Mergeable for AsUtf16Index {
+ fn can_merge(&self, rhs: &Self) -> bool {
+ self.s.can_merge(&rhs.s)
+ }
+
+ fn merge_right(&mut self, rhs: &Self) {
+ self.s.merge_right(&rhs.s);
+ self.utf16_len += rhs.utf16_len;
+ }
+
+ fn merge_left(&mut self, left: &Self) {
+ self.s.merge_left(&left.s);
+ self.utf16_len += left.utf16_len;
+ }
+}
+
+impl + Sliceable> Sliceable for AsUtf16Index {
+ fn _slice(&self, range: std::ops::Range) -> Self {
+ let start = self.convert_utf16_to_utf8(range.start);
+ let end = self.convert_utf16_to_utf8(range.end);
+ Self {
+ s: self.s._slice(start..end),
+ utf16_len: range.len(),
+ }
+ }
+}
+
+impl + Sliceable + TryInsert> TryInsert for AsUtf16Index {
+ fn try_insert(&mut self, pos: usize, elem: Self) -> Result<(), Self>
+ where
+ Self: Sized,
+ {
+ let start = self.convert_utf16_to_utf8(pos);
+ match self.s.try_insert(start, elem.s) {
+ Ok(()) => {
+ self.utf16_len += elem.utf16_len;
+ Ok(())
+ }
+ Err(e) => Err(Self {
+ s: e,
+ utf16_len: elem.utf16_len,
+ }),
+ }
+ }
+}
+
+impl + DeltaValue> Default for AsUtf16Index {
+ fn default() -> Self {
+ Self {
+ s: S::default(),
+ utf16_len: 0,
+ }
+ }
+}
+
+impl + DeltaValue> DeltaValue for AsUtf16Index {}
diff --git a/3rdparty/loro/crates/fractional_index/Cargo.toml b/3rdparty/loro/crates/fractional_index/Cargo.toml
new file mode 100644
index 0000000000..63ee685626
--- /dev/null
+++ b/3rdparty/loro/crates/fractional_index/Cargo.toml
@@ -0,0 +1,27 @@
+[package]
+name = "loro_fractional_index"
+version = "1.13.0"
+edition = "2021"
+license = "MIT"
+description = "Fraction index for Loro. Fork and modified from jamsocket/fractional_index"
+repository = "https://github.com/loro-dev/loro/"
+authors = ["Liang Zhao", "Zixuan Chen"]
+categories = []
+keywords = ["crdt", "local-first"]
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+serde = { workspace = true, features = ["derive", "rc"], optional = true }
+rand = { version = "^0.8" }
+once_cell = { workspace = true }
+
+[dev-dependencies]
+fraction_index = { version = "^2.0", package = "fractional_index" }
+criterion = "^0.5.0"
+serde_json = { workspace = true }
+
+
+[[bench]]
+name = "fractional_index"
+harness = false
diff --git a/3rdparty/loro/crates/fractional_index/LICENSE b/3rdparty/loro/crates/fractional_index/LICENSE
new file mode 100644
index 0000000000..3aff770ead
--- /dev/null
+++ b/3rdparty/loro/crates/fractional_index/LICENSE
@@ -0,0 +1,21 @@
+MIT License Copyright (c) 2024 Loro
+
+Permission is hereby granted, free of
+charge, to any person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use, copy, modify, merge,
+publish, distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to the
+following conditions:
+
+The above copyright notice and this permission notice
+(including the next paragraph) shall be included in all copies or substantial
+portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
+ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
+EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/3rdparty/loro/crates/fractional_index/README.md b/3rdparty/loro/crates/fractional_index/README.md
new file mode 100644
index 0000000000..d7f1490572
--- /dev/null
+++ b/3rdparty/loro/crates/fractional_index/README.md
@@ -0,0 +1,3 @@
+# Fractional Index
+
+This crate is forked from [drifting-in-space/fractional_index](https://github.com/drifting-in-space/fractional_index)
diff --git a/3rdparty/loro/crates/fractional_index/src/jitter.rs b/3rdparty/loro/crates/fractional_index/src/jitter.rs
new file mode 100644
index 0000000000..54ebd56020
--- /dev/null
+++ b/3rdparty/loro/crates/fractional_index/src/jitter.rs
@@ -0,0 +1,103 @@
+use std::sync::Arc;
+
+use crate::{new_after, new_before, new_between, FractionalIndex, TERMINATOR};
+use rand::Rng;
+
+impl FractionalIndex {
+ pub fn jitter_default(rng: &mut impl Rng, jitter: u8) -> Self {
+ Self::jitter(Vec::new(), rng, jitter)
+ }
+
+ pub fn new_jitter(
+ lower: Option<&FractionalIndex>,
+ upper: Option<&FractionalIndex>,
+ rng: &mut R,
+ jitter: u8,
+ ) -> Option {
+ match (lower, upper) {
+ (Some(lower), Some(upper)) => Self::new_between_jitter(lower, upper, rng, jitter),
+ (Some(lower), None) => Self::new_after_jitter(lower, rng, jitter).into(),
+ (None, Some(upper)) => Self::new_before_jitter(upper, rng, jitter).into(),
+ (None, None) => FractionalIndex::jitter_default(rng, jitter).into(),
+ }
+ }
+
+ fn jitter(mut bytes: Vec, rng: &mut R, jitter: u8) -> FractionalIndex {
+ bytes.push(TERMINATOR);
+ bytes.extend((0..jitter).map(|_| rng.gen::()));
+ FractionalIndex(Arc::new(bytes))
+ }
+
+ pub fn new_before_jitter(
+ FractionalIndex(bytes): &FractionalIndex,
+ rng: &mut R,
+ jitter: u8,
+ ) -> Self {
+ Self::jitter(new_before(bytes), rng, jitter)
+ }
+
+ pub fn new_after_jitter(
+ FractionalIndex(bytes): &FractionalIndex,
+ rng: &mut R,
+ jitter: u8,
+ ) -> Self {
+ Self::jitter(new_after(bytes), rng, jitter)
+ }
+
+ pub fn new_between_jitter(
+ FractionalIndex(left): &FractionalIndex,
+ FractionalIndex(right): &FractionalIndex,
+ rng: &mut R,
+ jitter: u8,
+ ) -> Option {
+ new_between(left, right, jitter as usize + 1).map(|x| Self::jitter(x, rng, jitter))
+ }
+
+ pub fn generate_n_evenly_jitter(
+ lower: Option<&FractionalIndex>,
+ upper: Option<&FractionalIndex>,
+ n: usize,
+ rng: &mut R,
+ jitter: u8,
+ ) -> Option> {
+ fn generate(
+ lower: Option<&FractionalIndex>,
+ upper: Option<&FractionalIndex>,
+ n: usize,
+ push: &mut impl FnMut(FractionalIndex),
+ rng: &mut impl Rng,
+ jitter: u8,
+ ) {
+ if n == 0 {
+ return;
+ }
+
+ let mid = n / 2;
+ let mid_ans = FractionalIndex::new_jitter(lower, upper, rng, jitter).unwrap();
+ if n == 1 {
+ push(mid_ans);
+ return;
+ }
+
+ generate(lower, Some(&mid_ans), mid, push, rng, jitter);
+ push(mid_ans.clone());
+ if n - mid - 1 == 0 {
+ return;
+ }
+ generate(Some(&mid_ans), upper, n - mid - 1, push, rng, jitter);
+ }
+
+ if n == 0 {
+ return Some(Vec::new());
+ }
+
+ match (lower, upper) {
+ (Some(a), Some(b)) if a >= b => return None,
+ _ => {}
+ }
+
+ let mut ans = Vec::with_capacity(n);
+ generate(lower, upper, n, &mut |v| ans.push(v), rng, jitter);
+ Some(ans)
+ }
+}
diff --git a/3rdparty/loro/crates/fractional_index/src/lib.rs b/3rdparty/loro/crates/fractional_index/src/lib.rs
new file mode 100644
index 0000000000..7673450874
--- /dev/null
+++ b/3rdparty/loro/crates/fractional_index/src/lib.rs
@@ -0,0 +1,225 @@
+use std::{
+ fmt::{Display, Write},
+ sync::Arc,
+};
+
+#[cfg(feature = "serde")]
+use serde::{Deserialize, Serialize};
+
+mod jitter;
+
+const TERMINATOR: u8 = 128;
+static DEFAULT_FRACTIONAL_INDEX: once_cell::sync::Lazy =
+ once_cell::sync::Lazy::new(|| FractionalIndex(Arc::new(vec![TERMINATOR])));
+
+#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+pub struct FractionalIndex(Arc>);
+
+impl Default for FractionalIndex {
+ fn default() -> Self {
+ DEFAULT_FRACTIONAL_INDEX.clone()
+ }
+}
+
+impl FractionalIndex {
+ fn from_vec_unterminated(mut bytes: Vec) -> Self {
+ bytes.push(TERMINATOR);
+ FractionalIndex(Arc::new(bytes))
+ }
+
+ pub fn from_bytes(bytes: Vec) -> Self {
+ FractionalIndex(Arc::new(bytes))
+ }
+
+ pub fn from_hex_string>(str: T) -> Self {
+ let s = str.as_ref();
+ let mut bytes = Vec::with_capacity(s.len() / 2);
+ for i in 0..s.len() / 2 {
+ let byte = u8::from_str_radix(&s[i * 2..i * 2 + 2], 16).unwrap();
+ bytes.push(byte);
+ }
+ FractionalIndex::from_bytes(bytes)
+ }
+
+ pub fn as_bytes(&self) -> &[u8] {
+ &self.0
+ }
+}
+
+pub(crate) fn new_before(bytes: &[u8]) -> Vec {
+ for i in 0..bytes.len() {
+ if bytes[i] > TERMINATOR {
+ return bytes[..i].into();
+ }
+ if bytes[i] > u8::MIN {
+ let mut ans: Vec = bytes[..=i].into();
+ ans[i] -= 1;
+ return ans;
+ }
+ }
+ unreachable!()
+}
+
+pub(crate) fn new_after(bytes: &[u8]) -> Vec {
+ for i in 0..bytes.len() {
+ if bytes[i] < TERMINATOR {
+ return bytes[0..i].into();
+ }
+ if bytes[i] < u8::MAX {
+ let mut ans: Vec = bytes[0..=i].into();
+ ans[i] += 1;
+ return ans;
+ }
+ }
+ unreachable!()
+}
+
+pub(crate) fn new_between(left: &[u8], right: &[u8], extra_capacity: usize) -> Option> {
+ let shorter_len = left.len().min(right.len()) - 1;
+ for i in 0..shorter_len {
+ let left_byte = left[i];
+ let right_byte = right[i];
+ let diff = i16::from(right_byte) - i16::from(left_byte);
+ if diff > 1 {
+ let mut ans: Vec = left[0..=i].into();
+ ans[i] += (right_byte - left_byte) / 2;
+ return ans.into();
+ }
+ if diff == 1 {
+ let (prefix, suffix) = left.split_at(i + 1);
+ let new_suffix = new_after(suffix);
+ let mut ans = Vec::with_capacity(prefix.len() + new_suffix.len() + extra_capacity);
+ ans.extend_from_slice(prefix);
+ ans.extend_from_slice(&new_suffix);
+ return ans.into();
+ }
+ if diff < 0 {
+ return None;
+ }
+ }
+
+ match left.len().cmp(&right.len()) {
+ std::cmp::Ordering::Less => {
+ let (prefix, suffix) = right.split_at(shorter_len + 1);
+ if prefix.last().unwrap() < &TERMINATOR {
+ return None;
+ }
+ let new_suffix = new_before(suffix);
+ let mut ans = Vec::with_capacity(new_suffix.len() + prefix.len() + extra_capacity);
+ ans.extend_from_slice(prefix);
+ ans.extend_from_slice(&new_suffix);
+ ans.into()
+ }
+ std::cmp::Ordering::Equal => None,
+ std::cmp::Ordering::Greater => {
+ let (prefix, suffix) = left.split_at(shorter_len + 1);
+ if prefix.last().unwrap() >= &TERMINATOR {
+ return None;
+ }
+ let new_suffix = new_after(suffix);
+ let mut ans = Vec::with_capacity(new_suffix.len() + prefix.len() + extra_capacity);
+ ans.extend_from_slice(prefix);
+ ans.extend_from_slice(&new_suffix);
+ ans.into()
+ }
+ }
+}
+impl FractionalIndex {
+ pub fn new(lower: Option<&FractionalIndex>, upper: Option<&FractionalIndex>) -> Option {
+ match (lower, upper) {
+ (Some(lower), Some(upper)) => Self::new_between(lower, upper),
+ (Some(lower), None) => Self::new_after(lower).into(),
+ (None, Some(upper)) => Self::new_before(upper).into(),
+ (None, None) => FractionalIndex::default().into(),
+ }
+ }
+
+ pub fn new_before(FractionalIndex(bytes): &FractionalIndex) -> Self {
+ FractionalIndex::from_vec_unterminated(new_before(bytes))
+ }
+
+ pub fn new_after(FractionalIndex(bytes): &FractionalIndex) -> Self {
+ FractionalIndex::from_vec_unterminated(new_after(bytes))
+ }
+
+ pub fn new_between(
+ FractionalIndex(left): &FractionalIndex,
+ FractionalIndex(right): &FractionalIndex,
+ ) -> Option {
+ new_between(left, right, 1).map(FractionalIndex::from_vec_unterminated)
+ }
+
+ pub fn generate_n_evenly(
+ lower: Option<&FractionalIndex>,
+ upper: Option<&FractionalIndex>,
+ n: usize,
+ ) -> Option> {
+ fn generate(
+ lower: Option<&FractionalIndex>,
+ upper: Option<&FractionalIndex>,
+ n: usize,
+ push: &mut impl FnMut(FractionalIndex),
+ ) {
+ if n == 0 {
+ return;
+ }
+
+ let mid = n / 2;
+ let mid_ans = FractionalIndex::new(lower, upper).unwrap();
+ if n == 1 {
+ push(mid_ans);
+ return;
+ }
+
+ generate(lower, Some(&mid_ans), mid, push);
+ push(mid_ans.clone());
+ if n - mid - 1 == 0 {
+ return;
+ }
+ generate(Some(&mid_ans), upper, n - mid - 1, push);
+ }
+
+ if n == 0 {
+ return Some(Vec::new());
+ }
+
+ match (lower, upper) {
+ (Some(a), Some(b)) if a >= b => return None,
+ _ => {}
+ }
+
+ let mut ans = Vec::with_capacity(n);
+ generate(lower, upper, n, &mut |v| ans.push(v));
+ Some(ans)
+ }
+}
+
+impl Display for FractionalIndex {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "{}", bytes_to_hex(&self.0))
+ }
+}
+
+pub fn bytes_to_hex(bytes: &[u8]) -> String {
+ bytes.iter().fold(String::new(), |mut output, b| {
+ let _ = write!(output, "{b:02X}");
+ output
+ })
+}
+
+#[cfg(test)]
+mod tests {
+ use super::FractionalIndex;
+
+ #[test]
+ fn new_between_handles_zero_shared_prefix() {
+ let lower = FractionalIndex::from_hex_string("0080");
+ let upper = FractionalIndex::from_hex_string("008180");
+ let index = FractionalIndex::new_between(&lower, &upper).unwrap();
+
+ assert!(lower < index);
+ assert!(index < upper);
+ assert_eq!(index.to_string(), "00817F80");
+ }
+}
diff --git a/3rdparty/loro/crates/generic-btree/Cargo.toml b/3rdparty/loro/crates/generic-btree/Cargo.toml
new file mode 100644
index 0000000000..e28744479d
--- /dev/null
+++ b/3rdparty/loro/crates/generic-btree/Cargo.toml
@@ -0,0 +1,35 @@
+[package]
+name = "generic-btree"
+version = "0.10.7"
+edition = "2021"
+authors = ["zxch3n "]
+description = "Generic BTree for versatile purposes"
+homepage = "https://github.com/loro-dev/generic-btree"
+documentation = "https://docs.rs/generic-btree"
+readme = "README.md"
+keywords = ["btree", "data-structure"]
+license = "MIT"
+repository = "https://github.com/loro-dev/generic-btree"
+
+# Vendored into the loro workspace (fork of crates.io generic-btree 0.10.7) so we
+# can evolve the b-tree (e.g. deferred cache propagation). Redirected from
+# crates.io via [patch.crates.io] in the root Cargo.toml.
+
+[features]
+test = []
+
+[lib]
+name = "generic_btree"
+path = "src/lib.rs"
+
+[dependencies]
+arref = "0.1.0"
+heapless = "0.9.1"
+itertools = "0.11.0"
+proc-macro2 = "1.0.67"
+rustc-hash = "2.1.1"
+thunderdome = { version = "0.6.2", package = "loro-thunderdome" }
+
+[dev-dependencies]
+arbitrary = { version = "1", features = ["derive"] }
+rand = "0.8.5"
diff --git a/3rdparty/loro/crates/generic-btree/README.md b/3rdparty/loro/crates/generic-btree/README.md
new file mode 100644
index 0000000000..593b12eaee
--- /dev/null
+++ b/3rdparty/loro/crates/generic-btree/README.md
@@ -0,0 +1,12 @@
+# Generic B-Tree
+
+It’s a pure safe BTree that can be used to build your own special-purpose btree
+data structure. It’s mainly developed to optimize the performance of Loro CRDT’s
+components.
+
+It can be used to build:
+
+- Rope
+- Run length encoding data structure
+- RangeMap that uses range as its key
+- BTreeSet & BTreeMap
diff --git a/3rdparty/loro/crates/generic-btree/src/generic_impl/gap_buffer.rs b/3rdparty/loro/crates/generic-btree/src/generic_impl/gap_buffer.rs
new file mode 100644
index 0000000000..b9ce6f040a
--- /dev/null
+++ b/3rdparty/loro/crates/generic-btree/src/generic_impl/gap_buffer.rs
@@ -0,0 +1,293 @@
+use std::ops::{Range, RangeBounds};
+
+use crate::rle::{CanRemove, HasLength, Mergeable, Sliceable, TryInsert};
+
+#[cfg(not(test))]
+pub const MAX_STRING_SIZE: usize = 128;
+#[cfg(test)]
+pub const MAX_STRING_SIZE: usize = 12;
+
+#[derive(Debug, Clone)]
+pub(super) struct GapBuffer {
+ buffer: [u8; MAX_STRING_SIZE],
+ gap_start: u16,
+ gap_len: u16,
+}
+
+impl GapBuffer {
+ pub fn new() -> Self {
+ Self {
+ buffer: [0; MAX_STRING_SIZE],
+ gap_start: 0,
+ gap_len: MAX_STRING_SIZE as u16,
+ }
+ }
+
+ pub fn shift_at(&mut self, index: usize) {
+ if index > self.len() {
+ panic!("index {} out of range len={}", index, self.len());
+ }
+
+ let gap_start = self.gap_start as usize;
+ let gap_end = (self.gap_start + self.gap_len) as usize;
+ match index.cmp(&gap_start) {
+ std::cmp::Ordering::Equal => {}
+ std::cmp::Ordering::Less => {
+ let gap_move = gap_start - index;
+ self.buffer
+ .copy_within(index..gap_start, gap_end - gap_move);
+ self.gap_start -= gap_move as u16;
+ }
+ std::cmp::Ordering::Greater => {
+ let gap_move = index - gap_start;
+ let move_end = self.buffer.len().min(gap_end + gap_move);
+ self.buffer.copy_within(gap_end..move_end, gap_start);
+ self.gap_start += gap_move as u16;
+ }
+ }
+ }
+
+ #[allow(unused)]
+ pub fn push(&mut self, value: u8) -> Result<(), ()> {
+ if self.gap_len == 0 {
+ return Err(());
+ }
+ self.buffer[self.gap_start as usize] = value;
+ self.gap_start += 1;
+ self.gap_len -= 1;
+ Ok(())
+ }
+
+ #[inline(always)]
+ pub fn push_bytes(&mut self, bytes: &[u8]) -> Result<(), ()> {
+ self.insert_bytes(self.len(), bytes)
+ }
+
+ pub fn insert_bytes(&mut self, index: usize, bytes: &[u8]) -> Result<(), ()> {
+ if (self.gap_len as usize) < bytes.len() {
+ return Err(());
+ }
+
+ self.shift_at(index);
+ self.buffer[index..index + bytes.len()].copy_from_slice(bytes);
+ self.gap_start += bytes.len() as u16;
+ self.gap_len -= bytes.len() as u16;
+ Ok(())
+ }
+
+ pub fn insert_bytes_pair(
+ &mut self,
+ index: usize,
+ (left, right): (&[u8], &[u8]),
+ ) -> Result<(), ()> {
+ let len = left.len() + right.len();
+ if (self.gap_len as usize) < len {
+ return Err(());
+ }
+
+ self.shift_at(index);
+ self.buffer[index..index + left.len()].copy_from_slice(left);
+ self.buffer[index + left.len()..index + len].copy_from_slice(right);
+ self.gap_start += len as u16;
+ self.gap_len -= len as u16;
+ Ok(())
+ }
+
+ pub fn delete(&mut self, range: impl RangeBounds) {
+ let mut start = match range.start_bound() {
+ std::ops::Bound::Included(x) => *x,
+ std::ops::Bound::Excluded(x) => x + 1,
+ std::ops::Bound::Unbounded => 0,
+ };
+ let mut end = match range.end_bound() {
+ std::ops::Bound::Included(x) => x + 1,
+ std::ops::Bound::Excluded(x) => *x,
+ std::ops::Bound::Unbounded => self.len(),
+ };
+
+ end = end.min(self.len());
+ start = start.min(self.len()).min(end);
+ if start == end {
+ return;
+ }
+
+ let len = end - start;
+ self.shift_at(end);
+ self.gap_start = start as u16;
+ self.gap_len += len as u16;
+ }
+
+ #[inline]
+ pub fn capacity(&self) -> usize {
+ self.buffer.len()
+ }
+
+ #[inline]
+ pub fn len(&self) -> usize {
+ self.buffer.len() - self.gap_len as usize
+ }
+
+ pub fn as_bytes(&self) -> (&[u8], &[u8]) {
+ (
+ &self.buffer[..self.gap_start as usize],
+ &self.buffer[(self.gap_start + self.gap_len) as usize..],
+ )
+ }
+
+ #[allow(unused)]
+ pub fn to_vec(&self) -> Vec {
+ let mut vec = Vec::with_capacity(self.len());
+ let (left, right) = self.as_bytes();
+ vec.extend_from_slice(left);
+ vec.extend_from_slice(right);
+ vec
+ }
+
+ pub(crate) fn from_str(elem: &str) -> impl Iterator- + '_ {
+ let mut i = 0;
+ let elem = elem.as_bytes();
+ std::iter::from_fn(move || {
+ if i >= elem.len() {
+ return None;
+ }
+
+ let mut gb = GapBuffer::new();
+ gb.push_bytes(&elem[i..(i + MAX_STRING_SIZE).min(elem.len())])
+ .unwrap();
+ i += MAX_STRING_SIZE;
+ Some(gb)
+ })
+ }
+}
+
+impl HasLength for GapBuffer {
+ fn rle_len(&self) -> usize {
+ self.len()
+ }
+}
+
+impl Sliceable for GapBuffer {
+ fn _slice(&self, range: Range) -> Self {
+ let mut gb = Self::new();
+ let start = range.start;
+ let end = range.end;
+
+ let (l, r) = self.as_bytes();
+ if start < l.len() {
+ gb.push_bytes(&l[start..end.min(l.len())]).unwrap();
+ }
+ if end > l.len() {
+ gb.push_bytes(&r[start.saturating_sub(l.len())..end.saturating_sub(l.len())])
+ .unwrap();
+ }
+
+ debug_assert_eq!(gb.len(), end - start);
+ gb
+ }
+
+ fn slice_(&mut self, range: impl RangeBounds)
+ where
+ Self: Sized,
+ {
+ let start = match range.start_bound() {
+ std::ops::Bound::Included(x) => *x,
+ std::ops::Bound::Excluded(x) => x + 1,
+ std::ops::Bound::Unbounded => 0,
+ };
+ let end = match range.end_bound() {
+ std::ops::Bound::Included(x) => x + 1,
+ std::ops::Bound::Excluded(x) => *x,
+ std::ops::Bound::Unbounded => self.len(),
+ };
+
+ self.delete(end..);
+ self.delete(..start);
+ debug_assert_eq!(self.len(), end - start);
+ }
+
+ fn split(&mut self, pos: usize) -> Self
+ where
+ Self: Sized,
+ {
+ self.shift_at(pos);
+ let right = self.as_bytes().1;
+ let mut r = Self::new();
+ r.push_bytes(right).unwrap();
+ self.gap_len = (self.capacity() - pos) as u16;
+ r
+ }
+}
+
+impl Mergeable for GapBuffer {
+ fn can_merge(&self, rhs: &Self) -> bool {
+ self.len() + rhs.len() <= MAX_STRING_SIZE
+ }
+
+ fn merge_right(&mut self, rhs: &Self) {
+ let pair = rhs.as_bytes();
+ self.insert_bytes_pair(self.len(), pair).unwrap();
+ }
+
+ fn merge_left(&mut self, left: &Self) {
+ let pair = left.as_bytes();
+ self.insert_bytes_pair(0, pair).unwrap();
+ }
+}
+
+impl TryInsert for GapBuffer {
+ fn try_insert(&mut self, pos: usize, elem: Self) -> Result<(), Self>
+ where
+ Self: Sized,
+ {
+ if self.len() + elem.len() > MAX_STRING_SIZE {
+ return Err(elem);
+ }
+
+ let pair = elem.as_bytes();
+ self.insert_bytes_pair(pos, pair).unwrap();
+ Ok(())
+ }
+}
+
+impl CanRemove for GapBuffer {
+ fn can_remove(&self) -> bool {
+ self.len() == 0
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ #[test]
+ fn basic() {
+ let mut gb: GapBuffer = GapBuffer::new();
+ gb.insert_bytes(0, &[3, 8]).unwrap();
+ assert_eq!(gb.to_vec(), vec![3, 8]);
+ gb.insert_bytes(1, &[4, 5, 6]).unwrap();
+ assert_eq!(gb.to_vec(), vec![3, 4, 5, 6, 8]);
+ assert_eq!(gb.len(), 5);
+ gb.insert_bytes(4, &[7]).unwrap();
+ assert_eq!(gb.to_vec(), vec![3, 4, 5, 6, 7, 8]);
+ gb.insert_bytes(0, &[1, 2, 9, 9]).unwrap();
+ assert_eq!(gb.to_vec(), vec![1, 2, 9, 9, 3, 4, 5, 6, 7, 8]);
+ gb.delete(2..4);
+ assert_eq!(gb.len(), 8);
+ let (left, right) = gb.as_bytes();
+ assert_eq!(left, &[1, 2]);
+ assert_eq!(right, &[3, 4, 5, 6, 7, 8]);
+ assert_eq!(gb.to_vec(), vec![1, 2, 3, 4, 5, 6, 7, 8])
+ }
+
+ #[test]
+ fn slice() {
+ let mut gb = GapBuffer::new();
+ gb.push_bytes(&[0, 1, 2, 3, 4, 5, 6, 7]).unwrap();
+ gb.shift_at(5);
+ let b = gb.slice(2..5);
+ assert_eq!(b.to_vec(), vec![2, 3, 4]);
+
+ gb.slice_(2..5);
+ assert_eq!(gb.to_vec(), vec![2, 3, 4]);
+ }
+}
diff --git a/3rdparty/loro/crates/generic-btree/src/generic_impl/len_finder.rs b/3rdparty/loro/crates/generic-btree/src/generic_impl/len_finder.rs
new file mode 100644
index 0000000000..1fe0d6f813
--- /dev/null
+++ b/3rdparty/loro/crates/generic-btree/src/generic_impl/len_finder.rs
@@ -0,0 +1,91 @@
+use std::fmt::Debug;
+
+use thunderdome::Index;
+
+use crate::rle::HasLength;
+use crate::{BTreeTrait, FindResult, Query};
+
+/// A generic length finder
+pub struct LengthFinder {
+ pub left: usize,
+ pub slot: u8,
+ pub parent: Option,
+}
+
+impl LengthFinder {
+ #[inline(always)]
+ pub fn new() -> Self {
+ Self {
+ left: 0,
+ slot: 0,
+ parent: None,
+ }
+ }
+}
+
+impl Default for LengthFinder {
+ #[inline(always)]
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+pub trait UseLengthFinder {
+ fn get_len(cache: &B::Cache) -> usize;
+}
+
+impl + UseLengthFinder> Query
+ for LengthFinder
+{
+ type QueryArg = usize;
+
+ #[inline(always)]
+ fn init(target: &Self::QueryArg) -> Self {
+ Self {
+ left: *target,
+ slot: 0,
+ parent: None,
+ }
+ }
+
+ #[inline(always)]
+ fn find_node(
+ &mut self,
+ _: &Self::QueryArg,
+ child_caches: &[crate::Child],
+ ) -> crate::FindResult {
+ let mut last_left = self.left;
+ let is_internal = child_caches.first().unwrap().is_internal();
+ for (i, cache) in child_caches.iter().enumerate() {
+ let len = B::get_len(&cache.cache);
+ if self.left >= len {
+ last_left = self.left;
+ self.left -= len;
+ } else {
+ if is_internal {
+ self.parent = Some(cache.arena.unwrap());
+ } else {
+ self.slot = i as u8;
+ }
+ return FindResult::new_found(i, self.left);
+ }
+ }
+
+ self.left = last_left;
+ if is_internal {
+ self.parent = Some(child_caches.last().unwrap().arena.unwrap());
+ } else {
+ self.slot = child_caches.len() as u8 - 1;
+ }
+ FindResult::new_missing(child_caches.len() - 1, last_left)
+ }
+
+ #[inline(always)]
+ fn confirm_elem(
+ &mut self,
+ _: &Self::QueryArg,
+ elem: &::Elem,
+ ) -> (usize, bool) {
+ (self.left, self.left < elem.rle_len())
+ }
+}
diff --git a/3rdparty/loro/crates/generic-btree/src/generic_impl/mod.rs b/3rdparty/loro/crates/generic-btree/src/generic_impl/mod.rs
new file mode 100644
index 0000000000..8272773b4e
--- /dev/null
+++ b/3rdparty/loro/crates/generic-btree/src/generic_impl/mod.rs
@@ -0,0 +1,7 @@
+mod gap_buffer;
+mod len_finder;
+mod ord;
+mod rope;
+pub use len_finder::{LengthFinder, UseLengthFinder};
+pub use ord::{OrdTreeMap, OrdTreeSet};
+pub use rope::Rope;
diff --git a/3rdparty/loro/crates/generic-btree/src/generic_impl/ord.rs b/3rdparty/loro/crates/generic-btree/src/generic_impl/ord.rs
new file mode 100644
index 0000000000..cd570fea08
--- /dev/null
+++ b/3rdparty/loro/crates/generic-btree/src/generic_impl/ord.rs
@@ -0,0 +1,408 @@
+use core::fmt::Debug;
+use std::cmp::Ordering;
+use std::ops::Range;
+
+use crate::rle::{CanRemove, HasLength, Mergeable, Sliceable, TryInsert};
+use crate::{BTree, BTreeTrait, FindResult, Query, SplitInfo};
+
+#[derive(Debug)]
+#[repr(transparent)]
+struct OrdTrait {
+ _phantom: core::marker::PhantomData<(Key, Value)>,
+}
+
+#[derive(Debug)]
+pub struct OrdTreeMap {
+ tree: BTree>,
+ len: usize,
+}
+
+#[derive(Debug)]
+pub struct OrdTreeSet(OrdTreeMap);
+
+impl OrdTreeMap {
+ #[inline(always)]
+ pub fn new() -> Self {
+ Self {
+ tree: BTree::new(),
+ len: 0,
+ }
+ }
+
+ #[inline(always)]
+ pub fn insert(&mut self, key: Key, value: Value) {
+ let Some(result) = self.tree.query::>(&key) else {
+ self.len += 1;
+ self.tree.push(Unmergeable((key, value)));
+ return;
+ };
+
+ if !result.found {
+ self.len += 1;
+ let tree = &mut self.tree;
+ let data = Unmergeable((key, value));
+ let index = result.leaf();
+ let leaf = tree.leaf_nodes.get_mut(index.0).unwrap();
+ let parent = leaf.parent();
+
+ let mut is_full = false;
+ // Try to merge
+ if result.cursor.offset == 0 && data.can_merge(&leaf.elem) {
+ leaf.elem.merge_left(&data);
+ } else if result.cursor.offset == leaf.elem.rle_len() && leaf.elem.can_merge(&data) {
+ leaf.elem.merge_right(&data);
+ } else {
+ // Insert new leaf node
+ let child = tree.alloc_leaf_child(data, parent.unwrap_internal());
+ let SplitInfo {
+ parent_idx: parent_index,
+ insert_slot: insert_index,
+ ..
+ } = tree.split_leaf_if_needed(result.cursor);
+ let parent = tree.in_nodes.get_mut(parent_index).unwrap();
+ parent.children.insert(insert_index, child).unwrap();
+ is_full = parent.is_full();
+ }
+
+ tree.recursive_update_cache(parent, false, None);
+ if is_full {
+ tree.split(parent);
+ }
+ } else {
+ let leaf = self.tree.get_elem_mut(result.leaf()).unwrap();
+ leaf.0 .1 = value;
+ }
+ }
+
+ #[inline(always)]
+ pub fn delete(&mut self, key: &Key) -> Option<(Key, Value)> {
+ let q = self.tree.query::>(key)?;
+ match self.tree.remove_leaf(q.cursor) {
+ Some(v) => {
+ self.len -= 1;
+ Some(v.0)
+ }
+ None => None,
+ }
+ }
+
+ #[inline(always)]
+ pub fn iter(&self) -> impl Iterator
- {
+ self.tree.iter().map(|x| &x.0)
+ }
+
+ #[inline(always)]
+ pub fn iter_key(&self) -> impl Iterator
- {
+ self.tree.iter().map(|x| &x.0 .0)
+ }
+
+ #[inline(always)]
+ pub fn len(&self) -> usize {
+ self.len
+ }
+
+ #[inline(always)]
+ pub fn is_empty(&self) -> bool {
+ self.len == 0
+ }
+
+ #[allow(unused)]
+ pub(crate) fn check(&self) {
+ self.tree.check()
+ }
+}
+
+impl OrdTreeSet {
+ #[inline(always)]
+ pub fn new() -> Self {
+ Self(OrdTreeMap::new())
+ }
+
+ #[inline(always)]
+ pub fn insert(&mut self, key: Key) {
+ self.0.insert(key, ());
+ }
+
+ #[inline(always)]
+ pub fn delete(&mut self, key: &Key) -> bool {
+ self.0.delete(key).is_some()
+ }
+
+ #[inline(always)]
+ pub fn iter(&self) -> impl Iterator
- {
+ self.0.iter_key()
+ }
+
+ pub fn len(&self) -> usize {
+ self.0.len
+ }
+
+ pub fn is_empty(&self) -> bool {
+ self.0.len == 0
+ }
+
+ #[allow(unused)]
+ fn check(&self) {
+ self.0.check()
+ }
+}
+
+impl Default for OrdTreeSet {
+ #[inline(always)]
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl Default
+ for OrdTreeMap
+{
+ #[inline(always)]
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl Default for OrdTrait {
+ #[inline(always)]
+ fn default() -> Self {
+ Self {
+ _phantom: Default::default(),
+ }
+ }
+}
+
+#[repr(transparent)]
+#[derive(Debug, Clone)]
+pub struct Unmergeable(T);
+
+impl HasLength for Unmergeable {
+ fn rle_len(&self) -> usize {
+ 1
+ }
+}
+
+impl Sliceable for Unmergeable {
+ fn _slice(&self, range: Range) -> Self {
+ if range.end - range.start != 1 {
+ panic!("Invalid range");
+ }
+
+ self.clone()
+ }
+}
+
+impl Mergeable for Unmergeable {
+ fn can_merge(&self, _rhs: &Self) -> bool {
+ false
+ }
+
+ fn merge_right(&mut self, _rhs: &Self) {
+ unreachable!()
+ }
+
+ fn merge_left(&mut self, _left: &Self) {
+ unreachable!()
+ }
+}
+
+impl TryInsert for Unmergeable {
+ fn try_insert(&mut self, _pos: usize, elem: Self) -> Result<(), Self> {
+ Err(elem)
+ }
+}
+
+impl CanRemove for Unmergeable {
+ fn can_remove(&self) -> bool {
+ false
+ }
+}
+
+impl BTreeTrait for OrdTrait {
+ type Elem = Unmergeable<(Key, Value)>;
+ type Cache = Option<(Key, Key)>;
+ type CacheDiff = ();
+ const USE_DIFF: bool = false;
+
+ #[inline(always)]
+ fn calc_cache_internal(cache: &mut Self::Cache, caches: &[crate::Child]) {
+ if caches.is_empty() {
+ return;
+ }
+
+ *cache = Some((
+ caches[0].cache.as_ref().unwrap().0.clone(),
+ caches[caches.len() - 1].cache.as_ref().unwrap().1.clone(),
+ ));
+ }
+
+ #[inline(always)]
+ fn apply_cache_diff(_: &mut Self::Cache, _: &Self::CacheDiff) {
+ unreachable!()
+ }
+
+ #[inline(always)]
+ fn merge_cache_diff(_: &mut Self::CacheDiff, _: &Self::CacheDiff) {}
+
+ #[inline(always)]
+ fn get_elem_cache(elem: &Self::Elem) -> Self::Cache {
+ Some((elem.0 .0.clone(), elem.0 .0.clone()))
+ }
+
+ #[inline(always)]
+ fn new_cache_to_diff(_: &Self::Cache) -> Self::CacheDiff {}
+
+ fn sub_cache(_: &Self::Cache, _: &Self::Cache) -> Self::CacheDiff {}
+}
+
+impl Query>
+ for OrdTrait
+{
+ type QueryArg = Key;
+
+ #[inline(always)]
+ fn init(_target: &Self::QueryArg) -> Self {
+ Self::default()
+ }
+
+ #[inline]
+ fn find_node(
+ &mut self,
+ target: &Self::QueryArg,
+ child_caches: &[crate::Child>],
+ ) -> crate::FindResult {
+ let result = child_caches.binary_search_by(|x| {
+ let (min, max) = x.cache.as_ref().unwrap();
+ if target < min {
+ core::cmp::Ordering::Greater
+ } else if target > max {
+ core::cmp::Ordering::Less
+ } else {
+ core::cmp::Ordering::Equal
+ }
+ });
+ match result {
+ Ok(i) => FindResult::new_found(i, 0),
+ Err(i) => FindResult::new_missing(
+ i.min(child_caches.len() - 1),
+ if i == child_caches.len() { 1 } else { 0 },
+ ),
+ }
+ }
+
+ #[inline(always)]
+ fn confirm_elem(
+ &mut self,
+ q: &Self::QueryArg,
+ elem: & as BTreeTrait>::Elem,
+ ) -> (usize, bool) {
+ match q.cmp(&elem.0 .0) {
+ Ordering::Less => (0, false),
+ Ordering::Equal => (0, true),
+ Ordering::Greater => (1, false),
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use std::cmp::Ordering;
+
+ use rand::{Rng, SeedableRng};
+
+ use crate::HeapVec;
+
+ use super::*;
+
+ #[test]
+ fn test() {
+ let mut tree: OrdTreeSet = OrdTreeSet::new();
+ let mut rng = rand::rngs::StdRng::seed_from_u64(123);
+ let mut data: HeapVec = (0..1000).map(|_| rng.gen()).collect();
+ for &value in data.iter() {
+ tree.insert(value);
+ }
+ data.sort_unstable();
+ assert_eq!(tree.iter().copied().collect::>(), data);
+ tree.check();
+ }
+
+ #[test]
+ fn test_delete() {
+ let mut tree: OrdTreeSet = OrdTreeSet::new();
+ tree.insert(12);
+ tree.delete(&12);
+ assert_eq!(tree.len(), 0);
+ }
+
+ #[test]
+ fn test_compare_pos() {
+ let mut tree: OrdTreeSet = OrdTreeSet::new();
+ for i in 0..100 {
+ tree.insert(i);
+ }
+
+ for i in 0..99 {
+ let a = tree.0.tree.query::>(&i).unwrap();
+ assert_eq!(
+ tree.0.tree.compare_pos(a.cursor(), a.cursor()),
+ Ordering::Equal
+ );
+ for j in i + 1..100 {
+ let b = tree.0.tree.query::>(&j).unwrap();
+ assert_eq!(
+ tree.0.tree.compare_pos(a.cursor(), b.cursor()),
+ Ordering::Less
+ );
+ assert_eq!(
+ tree.0.tree.compare_pos(b.cursor(), a.cursor()),
+ Ordering::Greater
+ );
+ }
+ }
+ }
+
+ mod move_event_test {
+
+ use super::*;
+
+ #[test]
+ fn test() {
+ let mut tree: OrdTreeMap = OrdTreeMap::new();
+ let mut rng = rand::rngs::StdRng::seed_from_u64(123);
+ let mut data: HeapVec = (0..1000).map(|_| rng.gen()).collect();
+ for &value in data.iter() {
+ tree.insert(value, 0);
+ }
+ for value in data.drain(0..100) {
+ tree.delete(&value);
+ }
+ for value in data.drain(0..800) {
+ tree.delete(&value);
+ }
+ tree.tree.check();
+ for _ in (0..100).rev() {
+ tree.delete(&data.pop().unwrap());
+ }
+ }
+ }
+
+ #[test]
+ #[ignore]
+ fn depth_test() {
+ let mut tree: OrdTreeSet = OrdTreeSet::new();
+ for i in 0..2_100_000 {
+ tree.insert(i as u64);
+ let m = (!i) + 1;
+ if (i & m) == i {
+ eprintln!(
+ "i={}, Depth={}, Avg Children={}",
+ i,
+ tree.0.tree.depth(),
+ tree.0.tree.internal_avg_children_num()
+ );
+ }
+ }
+ tree.check();
+ }
+}
diff --git a/3rdparty/loro/crates/generic-btree/src/generic_impl/rope.rs b/3rdparty/loro/crates/generic-btree/src/generic_impl/rope.rs
new file mode 100644
index 0000000000..e1c0c71a36
--- /dev/null
+++ b/3rdparty/loro/crates/generic-btree/src/generic_impl/rope.rs
@@ -0,0 +1,3442 @@
+extern crate alloc;
+
+use core::ops::RangeBounds;
+use std::assert_eq;
+use std::fmt::Display;
+
+use crate::generic_impl::gap_buffer::MAX_STRING_SIZE;
+use crate::rle::Sliceable;
+use crate::{BTree, BTreeTrait, LeafIndex, LengthFinder, QueryResult};
+
+use super::gap_buffer::GapBuffer;
+use super::len_finder::UseLengthFinder;
+
+#[derive(Debug)]
+struct RopeTrait;
+
+#[derive(Debug)]
+struct Cursor {
+ pos: usize,
+ leaf: LeafIndex,
+}
+
+// TODO: move Rope into a separate project
+#[derive(Debug)]
+pub struct Rope {
+ tree: BTree,
+ cursor: Option,
+}
+
+impl UseLengthFinder for RopeTrait {
+ #[inline(always)]
+ fn get_len(cache: &::Cache) -> usize {
+ *cache as usize
+ }
+}
+
+impl Rope {
+ #[inline(always)]
+ pub fn len(&self) -> usize {
+ self.tree.root_cache as usize
+ }
+
+ #[inline(always)]
+ pub fn is_empty(&self) -> bool {
+ self.tree.root_cache == 0
+ }
+
+ pub fn insert(&mut self, index: usize, elem: &str) {
+ if index > self.len() {
+ panic!("index {} out of range len={}", index, self.len());
+ }
+
+ if self.is_empty() {
+ for chunk in GapBuffer::from_str(elem) {
+ self.tree.push(chunk);
+ }
+ return;
+ }
+
+ if let Some(Cursor { pos, leaf }) = self.cursor {
+ if pos <= index {
+ let node = self.tree.leaf_nodes.get(leaf.0).unwrap();
+ if index <= pos + node.elem.len() {
+ let mut success = true;
+ let offset = index - pos;
+ let valid = self
+ .tree
+ .update_leaf(leaf, |leaf| {
+ if leaf.len() + elem.len() < MAX_STRING_SIZE {
+ leaf.insert_bytes(offset, elem.as_bytes()).unwrap();
+ (true, None, None)
+ } else {
+ let mut right = leaf.split(offset);
+ if leaf.len() + elem.len() < MAX_STRING_SIZE {
+ success = leaf.push_bytes(elem.as_bytes()).is_ok();
+ } else {
+ success = right.insert_bytes(0, elem.as_bytes()).is_ok();
+ }
+
+ (true, Some(right), None)
+ }
+ })
+ .0;
+
+ if !valid {
+ self.cursor = None;
+ }
+
+ if success {
+ return;
+ }
+ }
+ }
+ }
+
+ let (q, f) = self.tree.query_with_finder_return::(&index);
+ self.cursor = q.and_then(|q| {
+ if q.offset() == 0 {
+ if f.slot == 0 || f.parent.is_none() {
+ None
+ } else {
+ let node = self.tree.in_nodes.get(f.parent.unwrap()).unwrap();
+ let child = &node.children[f.slot as usize - 1];
+ Some(Cursor {
+ pos: index - child.cache as usize,
+ leaf: child.arena.unwrap().into(),
+ })
+ }
+ } else {
+ Some(Cursor {
+ pos: index - q.offset(),
+ leaf: q.leaf(),
+ })
+ }
+ });
+
+ self.tree
+ .insert_many_by_cursor(q.map(|x| x.cursor), GapBuffer::from_str(elem));
+ }
+
+ pub fn delete_range(&mut self, range: impl RangeBounds) {
+ if self.is_empty() {
+ return;
+ }
+
+ let start = match range.start_bound() {
+ core::ops::Bound::Included(x) => *x,
+ core::ops::Bound::Excluded(x) => *x + 1,
+ core::ops::Bound::Unbounded => 0,
+ };
+ let end = match range.end_bound() {
+ core::ops::Bound::Included(&x) => x + 1,
+ core::ops::Bound::Excluded(&x) => x,
+ core::ops::Bound::Unbounded => self.len(),
+ };
+ let end = end.min(self.len());
+ let start = start.min(end);
+ if start == end {
+ return;
+ }
+
+ if let Some(Cursor { pos, leaf }) = self.cursor {
+ if pos <= start {
+ let node = self.tree.leaf_nodes.get(leaf.0).unwrap();
+ if end <= pos + node.elem.len() {
+ let start_offset = start - pos;
+ let end_offset = end - pos;
+ let valid = self
+ .tree
+ .update_leaf(leaf, |leaf| {
+ leaf.delete(start_offset..end_offset);
+ (true, None, None)
+ })
+ .0;
+
+ if !valid {
+ self.cursor = None;
+ }
+
+ return;
+ }
+ }
+ }
+
+ if end - start == 1 {
+ let q = self
+ .tree
+ .update_leaf_by_search::