├── .github └── workflows │ ├── ci.yml │ └── doc.yml ├── .gitignore ├── .rustfmt.toml ├── CHANGELOG.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── icon.png ├── src ├── circular_buffer.rs ├── error.rs ├── io.rs ├── lazy.rs ├── lib.rs ├── slice.rs ├── splittable.rs ├── splittable │ ├── cloneable.rs │ ├── sequence.rs │ └── view.rs └── view.rs └── tests ├── circular_buffer.rs ├── io.rs ├── lazy.rs ├── object_safe.rs ├── sequence.rs └── slice.rs /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: [push] 4 | 5 | jobs: 6 | test: 7 | strategy: 8 | matrix: 9 | version: 10 | - 1.63.0 11 | - stable 12 | - beta 13 | - nightly 14 | os: 15 | - macos-latest 16 | - ubuntu-latest 17 | - windows-latest 18 | 19 | runs-on: ${{ matrix.os }} 20 | 21 | steps: 22 | - uses: actions/checkout@v1 23 | - name: Install toolchain 24 | uses: actions-rs/toolchain@v1 25 | with: 26 | toolchain: ${{ matrix.version }} 27 | override: true 28 | profile: minimal 29 | - name: Run tests 30 | shell: bash 31 | run: cargo test --verbose 32 | 33 | lint: 34 | strategy: 35 | matrix: 36 | version: 37 | - nightly 38 | os: 39 | - macos-latest 40 | - ubuntu-latest 41 | - windows-latest 42 | runs-on: ${{ matrix.os }} 43 | steps: 44 | - uses: actions/checkout@v1 45 | - name: Install toolchain 46 | uses: actions-rs/toolchain@v1 47 | with: 48 | toolchain: stable 49 | override: true 50 | profile: minimal 51 | components: clippy, rustfmt 52 | - name: Clippy lint 53 | run: cargo clippy --all-targets -- -D warnings 54 | - name: Check formatting 55 | run: cargo fmt -- --check 56 | #- name: Install Deadlinks 57 | # run: cargo install cargo-deadlinks 58 | #- name: Check Deadlinks 59 | # run: | 60 | # cargo doc 61 | # cargo deadlinks -v --check-http 62 | -------------------------------------------------------------------------------- /.github/workflows/doc.yml: -------------------------------------------------------------------------------- 1 | name: Documentation 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | release: 10 | name: Deploy Documentation 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Checkout Repository 15 | uses: actions/checkout@v1 16 | 17 | - name: Setup Rust 18 | run: | 19 | rustup update nightly --no-self-update 20 | rustup default nightly 21 | 22 | - name: Build Documentation 23 | run: cargo doc --no-deps 24 | env: 25 | RUSTDOCFLAGS: --cfg docsrs 26 | 27 | - name: Deploy Documentation 28 | uses: peaceiris/actions-gh-pages@v3 29 | with: 30 | github_token: ${{ secrets.GITHUB_TOKEN }} 31 | publish_branch: gh-pages 32 | publish_dir: ./target/doc 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | **/*.swp 4 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | edition = "2021" 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [Unreleased] 8 | 9 | ## [0.1.0] - 2022-01-04 10 | ### Added 11 | - Initial release 12 | 13 | [Unreleased]: https://github.com/calebzulawski/rivulet/compare/0.1.0...HEAD 14 | [0.1.0]: https://github.com/calebzulawski/rivulet/releases/tag/0.1.0 15 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.21.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler" 16 | version = "1.0.2" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 19 | 20 | [[package]] 21 | name = "autocfg" 22 | version = "1.1.0" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 25 | 26 | [[package]] 27 | name = "backtrace" 28 | version = "0.3.69" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" 31 | dependencies = [ 32 | "addr2line", 33 | "cc", 34 | "cfg-if 1.0.0", 35 | "libc", 36 | "miniz_oxide", 37 | "object", 38 | "rustc-demangle", 39 | ] 40 | 41 | [[package]] 42 | name = "cc" 43 | version = "1.0.83" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" 46 | dependencies = [ 47 | "libc", 48 | ] 49 | 50 | [[package]] 51 | name = "cfg-if" 52 | version = "0.1.10" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 55 | 56 | [[package]] 57 | name = "cfg-if" 58 | version = "1.0.0" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 61 | 62 | [[package]] 63 | name = "futures" 64 | version = "0.3.28" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" 67 | dependencies = [ 68 | "futures-channel", 69 | "futures-core", 70 | "futures-executor", 71 | "futures-io", 72 | "futures-sink", 73 | "futures-task", 74 | "futures-util", 75 | ] 76 | 77 | [[package]] 78 | name = "futures-channel" 79 | version = "0.3.28" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" 82 | dependencies = [ 83 | "futures-core", 84 | "futures-sink", 85 | ] 86 | 87 | [[package]] 88 | name = "futures-core" 89 | version = "0.3.28" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" 92 | 93 | [[package]] 94 | name = "futures-executor" 95 | version = "0.3.28" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" 98 | dependencies = [ 99 | "futures-core", 100 | "futures-task", 101 | "futures-util", 102 | ] 103 | 104 | [[package]] 105 | name = "futures-io" 106 | version = "0.3.28" 107 | source = "registry+https://github.com/rust-lang/crates.io-index" 108 | checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" 109 | 110 | [[package]] 111 | name = "futures-sink" 112 | version = "0.3.28" 113 | source = "registry+https://github.com/rust-lang/crates.io-index" 114 | checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" 115 | 116 | [[package]] 117 | name = "futures-task" 118 | version = "0.3.28" 119 | source = "registry+https://github.com/rust-lang/crates.io-index" 120 | checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" 121 | 122 | [[package]] 123 | name = "futures-util" 124 | version = "0.3.28" 125 | source = "registry+https://github.com/rust-lang/crates.io-index" 126 | checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" 127 | dependencies = [ 128 | "futures-channel", 129 | "futures-core", 130 | "futures-io", 131 | "futures-sink", 132 | "futures-task", 133 | "memchr", 134 | "pin-project-lite", 135 | "pin-utils", 136 | "slab", 137 | ] 138 | 139 | [[package]] 140 | name = "getrandom" 141 | version = "0.2.10" 142 | source = "registry+https://github.com/rust-lang/crates.io-index" 143 | checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" 144 | dependencies = [ 145 | "cfg-if 1.0.0", 146 | "libc", 147 | "wasi", 148 | ] 149 | 150 | [[package]] 151 | name = "gimli" 152 | version = "0.28.0" 153 | source = "registry+https://github.com/rust-lang/crates.io-index" 154 | checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" 155 | 156 | [[package]] 157 | name = "hermit-abi" 158 | version = "0.3.3" 159 | source = "registry+https://github.com/rust-lang/crates.io-index" 160 | checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" 161 | 162 | [[package]] 163 | name = "libc" 164 | version = "0.2.149" 165 | source = "registry+https://github.com/rust-lang/crates.io-index" 166 | checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" 167 | 168 | [[package]] 169 | name = "memchr" 170 | version = "2.6.4" 171 | source = "registry+https://github.com/rust-lang/crates.io-index" 172 | checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" 173 | 174 | [[package]] 175 | name = "miniz_oxide" 176 | version = "0.7.1" 177 | source = "registry+https://github.com/rust-lang/crates.io-index" 178 | checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" 179 | dependencies = [ 180 | "adler", 181 | ] 182 | 183 | [[package]] 184 | name = "num-integer" 185 | version = "0.1.45" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" 188 | dependencies = [ 189 | "autocfg", 190 | "num-traits", 191 | ] 192 | 193 | [[package]] 194 | name = "num-traits" 195 | version = "0.2.17" 196 | source = "registry+https://github.com/rust-lang/crates.io-index" 197 | checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" 198 | dependencies = [ 199 | "autocfg", 200 | ] 201 | 202 | [[package]] 203 | name = "num_cpus" 204 | version = "1.16.0" 205 | source = "registry+https://github.com/rust-lang/crates.io-index" 206 | checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" 207 | dependencies = [ 208 | "hermit-abi", 209 | "libc", 210 | ] 211 | 212 | [[package]] 213 | name = "object" 214 | version = "0.32.1" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" 217 | dependencies = [ 218 | "memchr", 219 | ] 220 | 221 | [[package]] 222 | name = "once_cell" 223 | version = "1.18.0" 224 | source = "registry+https://github.com/rust-lang/crates.io-index" 225 | checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" 226 | 227 | [[package]] 228 | name = "pin-project" 229 | version = "1.1.3" 230 | source = "registry+https://github.com/rust-lang/crates.io-index" 231 | checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" 232 | dependencies = [ 233 | "pin-project-internal", 234 | ] 235 | 236 | [[package]] 237 | name = "pin-project-internal" 238 | version = "1.1.3" 239 | source = "registry+https://github.com/rust-lang/crates.io-index" 240 | checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" 241 | dependencies = [ 242 | "proc-macro2", 243 | "quote", 244 | "syn", 245 | ] 246 | 247 | [[package]] 248 | name = "pin-project-lite" 249 | version = "0.2.13" 250 | source = "registry+https://github.com/rust-lang/crates.io-index" 251 | checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" 252 | 253 | [[package]] 254 | name = "pin-utils" 255 | version = "0.1.0" 256 | source = "registry+https://github.com/rust-lang/crates.io-index" 257 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 258 | 259 | [[package]] 260 | name = "ppv-lite86" 261 | version = "0.2.17" 262 | source = "registry+https://github.com/rust-lang/crates.io-index" 263 | checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" 264 | 265 | [[package]] 266 | name = "proc-macro2" 267 | version = "1.0.68" 268 | source = "registry+https://github.com/rust-lang/crates.io-index" 269 | checksum = "5b1106fec09662ec6dd98ccac0f81cef56984d0b49f75c92d8cbad76e20c005c" 270 | dependencies = [ 271 | "unicode-ident", 272 | ] 273 | 274 | [[package]] 275 | name = "quote" 276 | version = "1.0.33" 277 | source = "registry+https://github.com/rust-lang/crates.io-index" 278 | checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" 279 | dependencies = [ 280 | "proc-macro2", 281 | ] 282 | 283 | [[package]] 284 | name = "rand" 285 | version = "0.8.5" 286 | source = "registry+https://github.com/rust-lang/crates.io-index" 287 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 288 | dependencies = [ 289 | "libc", 290 | "rand_chacha", 291 | "rand_core", 292 | ] 293 | 294 | [[package]] 295 | name = "rand_chacha" 296 | version = "0.3.1" 297 | source = "registry+https://github.com/rust-lang/crates.io-index" 298 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 299 | dependencies = [ 300 | "ppv-lite86", 301 | "rand_core", 302 | ] 303 | 304 | [[package]] 305 | name = "rand_core" 306 | version = "0.6.4" 307 | source = "registry+https://github.com/rust-lang/crates.io-index" 308 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 309 | dependencies = [ 310 | "getrandom", 311 | ] 312 | 313 | [[package]] 314 | name = "rivulet" 315 | version = "0.1.0" 316 | dependencies = [ 317 | "futures", 318 | "num-integer", 319 | "once_cell", 320 | "pin-project", 321 | "rand", 322 | "seahash", 323 | "tokio", 324 | "vmap", 325 | ] 326 | 327 | [[package]] 328 | name = "rustc-demangle" 329 | version = "0.1.23" 330 | source = "registry+https://github.com/rust-lang/crates.io-index" 331 | checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" 332 | 333 | [[package]] 334 | name = "seahash" 335 | version = "4.1.0" 336 | source = "registry+https://github.com/rust-lang/crates.io-index" 337 | checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" 338 | 339 | [[package]] 340 | name = "slab" 341 | version = "0.4.9" 342 | source = "registry+https://github.com/rust-lang/crates.io-index" 343 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 344 | dependencies = [ 345 | "autocfg", 346 | ] 347 | 348 | [[package]] 349 | name = "syn" 350 | version = "2.0.38" 351 | source = "registry+https://github.com/rust-lang/crates.io-index" 352 | checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" 353 | dependencies = [ 354 | "proc-macro2", 355 | "quote", 356 | "unicode-ident", 357 | ] 358 | 359 | [[package]] 360 | name = "system_error" 361 | version = "0.1.1" 362 | source = "registry+https://github.com/rust-lang/crates.io-index" 363 | checksum = "3de669e17bff533e09de7f0bf4cd3a3fb2f448b1dda609cf374cdfd039ff8bde" 364 | dependencies = [ 365 | "cfg-if 0.1.10", 366 | ] 367 | 368 | [[package]] 369 | name = "tokio" 370 | version = "1.32.0" 371 | source = "registry+https://github.com/rust-lang/crates.io-index" 372 | checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9" 373 | dependencies = [ 374 | "backtrace", 375 | "num_cpus", 376 | "pin-project-lite", 377 | "tokio-macros", 378 | ] 379 | 380 | [[package]] 381 | name = "tokio-macros" 382 | version = "2.1.0" 383 | source = "registry+https://github.com/rust-lang/crates.io-index" 384 | checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" 385 | dependencies = [ 386 | "proc-macro2", 387 | "quote", 388 | "syn", 389 | ] 390 | 391 | [[package]] 392 | name = "unicode-ident" 393 | version = "1.0.12" 394 | source = "registry+https://github.com/rust-lang/crates.io-index" 395 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 396 | 397 | [[package]] 398 | name = "vmap" 399 | version = "0.5.1" 400 | source = "registry+https://github.com/rust-lang/crates.io-index" 401 | checksum = "3525e0897da33b9b246b03718dca1df8de4a26a2db73a479ea807f2b773f7856" 402 | dependencies = [ 403 | "libc", 404 | "system_error", 405 | "winapi", 406 | ] 407 | 408 | [[package]] 409 | name = "wasi" 410 | version = "0.11.0+wasi-snapshot-preview1" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 413 | 414 | [[package]] 415 | name = "winapi" 416 | version = "0.3.9" 417 | source = "registry+https://github.com/rust-lang/crates.io-index" 418 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 419 | dependencies = [ 420 | "winapi-i686-pc-windows-gnu", 421 | "winapi-x86_64-pc-windows-gnu", 422 | ] 423 | 424 | [[package]] 425 | name = "winapi-i686-pc-windows-gnu" 426 | version = "0.4.0" 427 | source = "registry+https://github.com/rust-lang/crates.io-index" 428 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 429 | 430 | [[package]] 431 | name = "winapi-x86_64-pc-windows-gnu" 432 | version = "0.4.0" 433 | source = "registry+https://github.com/rust-lang/crates.io-index" 434 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 435 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rivulet" 3 | version = "0.1.0" 4 | authors = ["Caleb Zulawski "] 5 | license = "MIT OR Apache-2.0" 6 | description = "Asynchronous contiguous-memory streams" 7 | repository = "https://github.com/calebzulawski/rivulet" 8 | categories = ["asynchronous", "memory-management"] 9 | readme = "README.md" 10 | edition = "2021" 11 | 12 | [features] 13 | default = ["std"] 14 | std = ["num-integer", "vmap"] 15 | 16 | [dependencies] 17 | pin-project = "1" 18 | futures = { version = "0.3", default-features = false, features = ["executor"] } 19 | num-integer = { version = "0.1", default-features = false, optional = true } 20 | vmap = { version = "0.5", optional = true } 21 | once_cell = "1" 22 | 23 | [dev-dependencies] 24 | seahash = "4" 25 | rand = { version = "0.8", features = ["small_rng"] } 26 | tokio = { version = "1", default-features = false, features = ["sync", "rt-multi-thread", "macros"] } 27 | 28 | [package.metadata.docs.rs] 29 | all-features = true 30 | rustdoc-args = ["--cfg", "docsrs"] 31 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright 2020 Caleb Zulawski 2 | 3 | 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: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | 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. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | iconRivulet 2 | ======= 3 | [![Build Status](https://github.com/calebzulawski/rivulet/workflows/Build/badge.svg?branch=master)](https://github.com/calebzulawski/rivulet/actions) 4 | ![Rustc Version 1.63+](https://img.shields.io/badge/rustc-1.63+-lightgray.svg) 5 | [![License](https://img.shields.io/crates/l/rivulet)](https://crates.io/crates/rivulet) 6 | [![Crates.io](https://img.shields.io/crates/v/rivulet)](https://crates.io/crates/rivulet) 7 | [![Rust Documentation](https://img.shields.io/badge/api-rustdoc-blue.svg)](https://docs.rs/rivulet) 8 | 9 | Rivulet is a library for creating asynchronous pipelines of contiguous data. 10 | 11 | Main features, at a glance: 12 | 13 | * **Asynchronous**: Pipeline components are `async`-aware, allowing more control over task priority and data backpressure. 14 | * **Contiguous views**: Data is always contiguous, accessible by a single slice. 15 | * **Modular**: Pipelines can be combined and reused using common interfaces. 16 | 17 | ## License 18 | Rivulet is distributed under the terms of both the MIT license and the Apache License (Version 2.0). 19 | 20 | See [LICENSE-APACHE](LICENSE-APACHE) and [LICENSE-MIT](LICENSE-MIT) for details. 21 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calebzulawski/rivulet/8be66848f8d27403a5f934eebe96feb7090ac69e/icon.png -------------------------------------------------------------------------------- /src/circular_buffer.rs: -------------------------------------------------------------------------------- 1 | #![cfg(all(feature = "std"))] 2 | #![cfg_attr(docsrs, doc(cfg(all(feature = "std"))))] 3 | //! An asynchronous copy-free circular buffer. 4 | //! 5 | //! This buffer is optimized for contiguous memory segments and never copies data to other regions 6 | //! of the buffer. 7 | use crate::{ 8 | error::GrantOverflow, 9 | splittable::{SplittableViewImpl, SplittableViewImplMut}, 10 | View, ViewMut, 11 | }; 12 | use futures::task::AtomicWaker; 13 | use num_integer::{div_ceil, lcm}; 14 | use std::{ 15 | convert::TryInto, 16 | mem::{size_of, MaybeUninit}, 17 | pin::Pin, 18 | sync::{ 19 | atomic::{AtomicBool, AtomicU64, Ordering}, 20 | Arc, Mutex, 21 | }, 22 | task::{Context, Poll, Waker}, 23 | }; 24 | 25 | struct UnsafeCircularBuffer { 26 | ptr: *mut T, 27 | size: usize, 28 | } 29 | 30 | unsafe impl Send for UnsafeCircularBuffer where T: Send {} 31 | unsafe impl Sync for UnsafeCircularBuffer where T: Send {} 32 | 33 | impl Drop for UnsafeCircularBuffer { 34 | fn drop(&mut self) { 35 | // Safety: the underlying storage is always initialized upon construction, and is safe to 36 | // drop and unmap. 37 | unsafe { 38 | for i in 0..self.size { 39 | std::ptr::drop_in_place(self.ptr.add(i)); 40 | } 41 | vmap::os::unmap_ring(self.ptr as *mut u8, self.size * size_of::()).unwrap(); 42 | } 43 | } 44 | } 45 | 46 | impl UnsafeCircularBuffer { 47 | pub fn new(minimum_size: usize) -> Self { 48 | // Determine the smallest buffer larger than minimum_size that is both a multiple of the 49 | // allocation size and the type size. 50 | let size_bytes = { 51 | let granularity = lcm(vmap::allocation_size(), size_of::()); 52 | div_ceil(minimum_size * size_of::(), granularity) 53 | .checked_mul(granularity) 54 | .unwrap() 55 | }; 56 | let size = size_bytes / size_of::(); 57 | 58 | // Initialize the buffer memory 59 | // Safety: `map_ring` returns an uninitialized slice. 60 | let ptr = unsafe { 61 | let ptr = vmap::os::map_ring(size_bytes).unwrap() as *mut T; 62 | for v in std::slice::from_raw_parts_mut(ptr as *mut MaybeUninit, size) { 63 | v.as_mut_ptr().write(T::default()); 64 | } 65 | ptr 66 | }; 67 | 68 | Self { ptr, size } 69 | } 70 | } 71 | 72 | impl UnsafeCircularBuffer { 73 | pub fn len(&self) -> usize { 74 | self.size 75 | } 76 | 77 | // Only safe if you can guarantee no mutable references to this range 78 | pub unsafe fn range(&self, index: u64, len: usize) -> &[T] { 79 | debug_assert!(len <= self.len()); 80 | let buf_len: u64 = self.len().try_into().unwrap(); 81 | let offset = index % buf_len; 82 | std::slice::from_raw_parts(self.ptr.add(offset.try_into().unwrap()), len) 83 | } 84 | 85 | // Only safe if you can guarantee no other references to the same range 86 | #[allow(clippy::mut_from_ref)] 87 | pub unsafe fn range_mut(&self, index: u64, len: usize) -> &mut [T] { 88 | debug_assert!(len <= self.len()); 89 | let buf_len: u64 = self.len().try_into().unwrap(); 90 | let offset = index % buf_len; 91 | std::slice::from_raw_parts_mut(self.ptr.add(offset.try_into().unwrap()), len) 92 | } 93 | } 94 | 95 | /// Shared state 96 | struct State { 97 | buffer: UnsafeCircularBuffer, 98 | closed: AtomicBool, // true if the stream is closed 99 | head: AtomicU64, // start index of written data 100 | tail: AtomicU64, // start index of unwritten data 101 | write_waker: AtomicWaker, // waker waited on by the writer 102 | read_waker: Mutex>>, // wake readers when new data is available 103 | } 104 | 105 | impl State { 106 | fn new(minimum_size: usize) -> Self { 107 | // The +1 ensures there's room for a marker element (to indicate the difference between 108 | // empty and full 109 | Self { 110 | buffer: UnsafeCircularBuffer::new(minimum_size + 1), 111 | closed: AtomicBool::new(false), 112 | head: AtomicU64::new(0), 113 | tail: AtomicU64::new(0), 114 | write_waker: AtomicWaker::new(), 115 | read_waker: Mutex::new(None), 116 | } 117 | } 118 | } 119 | 120 | impl State { 121 | fn readable_len(&self, start: u64) -> usize { 122 | (self.tail.load(Ordering::Relaxed) - start) 123 | .try_into() 124 | .unwrap() 125 | } 126 | 127 | fn writeable_len(&self) -> usize { 128 | self.buffer.len() - self.readable_len(self.head.load(Ordering::Relaxed)) 129 | } 130 | } 131 | 132 | /// The writer of a circular buffer. 133 | /// 134 | /// Writes made to this become available at the associated [`Source`]. 135 | pub struct Sink { 136 | state: Arc>, 137 | tail: u64, 138 | available: usize, 139 | read_waker: Option>, 140 | } 141 | 142 | impl Sink { 143 | fn new(state: Arc>) -> Self { 144 | Self { 145 | state, 146 | tail: 0, 147 | available: 0, 148 | read_waker: None, 149 | } 150 | } 151 | 152 | fn wake_readers(&mut self) { 153 | if self.read_waker.is_none() { 154 | let mut lock = self 155 | .state 156 | .read_waker 157 | .lock() 158 | .expect("another thread panicked"); 159 | std::mem::swap(&mut *lock, &mut self.read_waker); 160 | } 161 | if let Some(read_waker) = self.read_waker.as_ref() { 162 | read_waker() 163 | } 164 | } 165 | } 166 | 167 | impl Drop for Sink { 168 | fn drop(&mut self) { 169 | self.state.closed.store(true, Ordering::Relaxed); 170 | self.wake_readers(); // waiting readers can exit without sufficient data 171 | } 172 | } 173 | 174 | impl View for Sink { 175 | type Item = T; 176 | type Error = GrantOverflow; 177 | 178 | fn view(&self) -> &[T] { 179 | // Safety: this region is owned exclusively by the writer. 180 | unsafe { self.state.buffer.range(self.tail, self.available) } 181 | } 182 | 183 | fn poll_grant( 184 | mut self: Pin<&mut Self>, 185 | cx: &mut Context, 186 | count: usize, 187 | ) -> Poll> { 188 | if count > self.state.buffer.len() { 189 | return Poll::Ready(Err(GrantOverflow(self.state.buffer.len()))); 190 | } 191 | 192 | if self.available >= count { 193 | return Poll::Ready(Ok(())); 194 | } 195 | 196 | // Perform double-checking on the amount of available data 197 | // The first check is efficient, but may spuriously fail. 198 | // The second check occurs after the `acquire` produced by registering the waker. 199 | let available = self.state.writeable_len(); 200 | if available >= count { 201 | self.available = available; 202 | Poll::Ready(Ok(())) 203 | } else { 204 | self.state.write_waker.register(cx.waker()); 205 | let available = self.state.writeable_len(); 206 | if available >= count || self.state.closed.load(Ordering::Relaxed) { 207 | self.available = available; 208 | Poll::Ready(Ok(())) 209 | } else { 210 | Poll::Pending 211 | } 212 | } 213 | } 214 | 215 | fn release(&mut self, count: usize) { 216 | if count == 0 { 217 | return; 218 | } 219 | 220 | assert!( 221 | count <= self.available, 222 | "attempted to release more than current grant" 223 | ); 224 | 225 | // Advance the buffer 226 | self.available -= count; 227 | let count: u64 = count.try_into().unwrap(); 228 | self.tail += count; 229 | self.state.tail.store(self.tail, Ordering::Relaxed); 230 | self.wake_readers(); 231 | } 232 | } 233 | 234 | impl ViewMut for Sink { 235 | fn view_mut(&mut self) -> &mut [T] { 236 | // Safety: this region is owned exclusively by the writer. 237 | unsafe { self.state.buffer.range_mut(self.tail, self.available) } 238 | } 239 | } 240 | 241 | /// The reader of a circular buffer. 242 | /// 243 | /// Writes made to the associated [`Sink`] are made available to this. 244 | pub struct Source { 245 | state: Arc>, 246 | } 247 | 248 | impl Source { 249 | fn new(state: Arc>) -> Self { 250 | Self { state } 251 | } 252 | } 253 | 254 | impl Drop for Source { 255 | fn drop(&mut self) { 256 | self.state.closed.store(true, Ordering::Relaxed); 257 | self.state.write_waker.wake(); 258 | } 259 | } 260 | 261 | unsafe impl SplittableViewImpl for Source { 262 | type Item = T; 263 | type Error = GrantOverflow; 264 | 265 | unsafe fn set_reader_waker(&self, waker: impl Fn() + Send + Sync + 'static) { 266 | let mut lock = self 267 | .state 268 | .read_waker 269 | .lock() 270 | .expect("another thread panicked"); 271 | assert!(lock.is_none(), "reader waker already set!"); 272 | *lock = Some(Box::new(waker)); 273 | } 274 | 275 | unsafe fn set_head(&self, index: u64) { 276 | self.state.head.store(index, Ordering::Relaxed); 277 | self.state.write_waker.wake(); 278 | } 279 | 280 | unsafe fn compare_set_head(&self, index: u64) { 281 | // only set the head if it's greater than the current head 282 | let mut current = self.state.head.load(Ordering::Relaxed); 283 | if index > current { 284 | while let Err(previous) = self.state.head.compare_exchange_weak( 285 | current, 286 | index, 287 | Ordering::Relaxed, 288 | Ordering::Relaxed, 289 | ) { 290 | if index > previous { 291 | current = previous 292 | } else { 293 | break; 294 | } 295 | } 296 | } 297 | self.state.write_waker.wake(); 298 | } 299 | 300 | fn poll_available( 301 | self: Pin<&Self>, 302 | cx: &mut Context, 303 | register_wakeup: impl Fn(&Waker), 304 | index: u64, 305 | len: usize, 306 | ) -> Poll> { 307 | let max_len = self.state.buffer.len(); 308 | if len > max_len { 309 | return Poll::Ready(Err(GrantOverflow(max_len))); 310 | } 311 | 312 | // Perform double-checking on the amount of available data 313 | // The first check is efficient, but may spuriously fail. 314 | // The second check occurs after the `acquire` produced by registering the waker. 315 | let available = self.state.readable_len(index); 316 | if available >= len { 317 | Poll::Ready(Ok(available)) 318 | } else { 319 | register_wakeup(cx.waker()); 320 | let available = self.state.readable_len(index); 321 | if available >= len || self.state.closed.load(Ordering::Relaxed) { 322 | Poll::Ready(Ok(available)) 323 | } else { 324 | Poll::Pending 325 | } 326 | } 327 | } 328 | 329 | unsafe fn view(&self, index: u64, len: usize) -> &[Self::Item] { 330 | self.state.buffer.range(index, len) 331 | } 332 | } 333 | 334 | unsafe impl SplittableViewImplMut for Source { 335 | unsafe fn view_mut(&self, index: u64, len: usize) -> &mut [Self::Item] { 336 | self.state.buffer.range_mut(index, len) 337 | } 338 | } 339 | 340 | /// Create a circular buffer that can hold at least `min_size` elements. 341 | /// 342 | /// # Panics 343 | /// Panics if `min_size` is 0. 344 | pub fn circular_buffer( 345 | min_size: usize, 346 | ) -> (Sink, Source) { 347 | assert!(min_size > 0, "`min_size` must be greater than 0"); 348 | 349 | let state = Arc::new(State::new(min_size)); 350 | 351 | (Sink::new(state.clone()), Source::new(state)) 352 | } 353 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | //! Errors produced by streams. 2 | 3 | /// Error produced when a request is too large to grant. 4 | /// 5 | /// Contains the maximum allowable grant. 6 | #[derive(Copy, Clone, Debug)] 7 | pub struct GrantOverflow(pub usize); 8 | 9 | impl core::fmt::Display for GrantOverflow { 10 | fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> { 11 | write!(f, "request exceeded maximum possible grant of `{}`", self.0) 12 | } 13 | } 14 | 15 | #[cfg(feature = "std")] 16 | impl std::error::Error for GrantOverflow { 17 | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { 18 | None 19 | } 20 | } 21 | 22 | #[cfg(feature = "std")] 23 | impl std::convert::From for std::io::Error { 24 | fn from(e: GrantOverflow) -> Self { 25 | Self::new(std::io::ErrorKind::InvalidInput, e) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/io.rs: -------------------------------------------------------------------------------- 1 | #![cfg(all(feature = "std"))] 2 | #![cfg_attr(docsrs, doc(cfg(all(feature = "std"))))] 3 | //! Utilities for working with [`std::io`]. 4 | 5 | use crate::{View, ViewMut}; 6 | use futures::io::{AsyncBufRead, AsyncRead, AsyncWrite}; 7 | use std::{ 8 | io::{BufRead, Read, Write}, 9 | pin::Pin, 10 | task::{Context, Poll}, 11 | }; 12 | 13 | /// Implements [`std::io::Read`] for a [`View`]. 14 | #[derive(Copy, Clone, Debug)] 15 | pub struct Reader(T) 16 | where 17 | T: View; 18 | 19 | impl Reader 20 | where 21 | T: View, 22 | { 23 | /// Create a new `Reader` 24 | pub fn new(view: T) -> Self { 25 | Self(view) 26 | } 27 | 28 | /// Return the original [`View`] 29 | pub fn into_inner(self) -> T { 30 | self.0 31 | } 32 | } 33 | 34 | impl Read for Reader 35 | where 36 | T: View, 37 | std::io::Error: From, 38 | { 39 | fn read(&mut self, buf: &mut [u8]) -> std::io::Result { 40 | let len = if buf.len() <= self.0.view().len() { 41 | buf.len() 42 | } else { 43 | self.0.blocking_grant(1)?; 44 | buf.len().min(self.0.view().len()) 45 | }; 46 | buf[..len].copy_from_slice(&self.0.view()[..len]); 47 | self.0.release(len); 48 | Ok(len) 49 | } 50 | } 51 | 52 | impl BufRead for Reader 53 | where 54 | T: View, 55 | std::io::Error: From, 56 | { 57 | fn fill_buf(&mut self) -> std::io::Result<&[u8]> { 58 | self.0.blocking_grant(1)?; 59 | Ok(self.0.view()) 60 | } 61 | 62 | fn consume(&mut self, amt: usize) { 63 | self.0.release(amt); 64 | } 65 | } 66 | 67 | /// Implements `futures::io::AsyncRead` for a [`View`]. 68 | #[derive(Copy, Clone, Debug)] 69 | pub struct AsyncReader 70 | where 71 | T: View, 72 | { 73 | view: T, 74 | len: usize, 75 | } 76 | 77 | impl AsyncReader 78 | where 79 | T: View, 80 | { 81 | /// Create a new `AsyncReader` 82 | pub fn new(view: T) -> Self { 83 | Self { view, len: 0 } 84 | } 85 | 86 | /// Return the original [`View`] 87 | pub fn into_inner(self) -> T { 88 | self.view 89 | } 90 | } 91 | 92 | impl AsyncRead for AsyncReader 93 | where 94 | T: View, 95 | std::io::Error: From, 96 | { 97 | fn poll_read( 98 | mut self: Pin<&mut Self>, 99 | cx: &mut Context<'_>, 100 | buf: &mut [u8], 101 | ) -> Poll> { 102 | if self.len == 0 { 103 | self.len = if buf.len() <= self.view.view().len() { 104 | buf.len() 105 | } else { 106 | futures::ready!(Pin::new(&mut self.view).poll_grant(cx, 1))?; 107 | buf.len().min(self.view.view().len()) 108 | }; 109 | buf[..self.len].copy_from_slice(&self.view.view()[..self.len]); 110 | } 111 | let len = self.len; 112 | self.view.release(len); 113 | Poll::Ready(Ok(std::mem::take(&mut self.len))) // set len to 0 114 | } 115 | } 116 | 117 | impl AsyncBufRead for AsyncReader 118 | where 119 | T: View, 120 | std::io::Error: From, 121 | { 122 | fn poll_fill_buf<'a>( 123 | mut self: Pin<&'a mut Self>, 124 | cx: &mut Context<'_>, 125 | ) -> Poll> { 126 | futures::ready!(Pin::new(&mut self.view).poll_grant(cx, 1))?; 127 | Poll::Ready(Ok(Pin::into_inner(self).view.view())) 128 | } 129 | 130 | fn consume(mut self: Pin<&mut Self>, amt: usize) { 131 | self.view.release(amt); 132 | } 133 | } 134 | 135 | /// Implements [`std::io::Write`] for a [`ViewMut`]. 136 | #[derive(Copy, Clone, Debug)] 137 | pub struct Writer(T) 138 | where 139 | T: ViewMut; 140 | 141 | impl Writer 142 | where 143 | T: ViewMut, 144 | { 145 | /// Create a new `Writer` 146 | pub fn new(sink: T) -> Self { 147 | Self(sink) 148 | } 149 | 150 | /// Return the original [`ViewMut`] 151 | pub fn into_inner(self) -> T { 152 | self.0 153 | } 154 | } 155 | 156 | impl Write for Writer 157 | where 158 | T: ViewMut, 159 | std::io::Error: From, 160 | { 161 | fn write(&mut self, buf: &[u8]) -> std::io::Result { 162 | let len = if buf.len() <= self.0.view().len() { 163 | buf.len() 164 | } else { 165 | self.0.blocking_grant(1)?; 166 | buf.len().min(self.0.view().len()) 167 | }; 168 | self.0.view_mut()[..len].copy_from_slice(&buf[..len]); 169 | self.0.release(len); 170 | Ok(len) 171 | } 172 | 173 | fn flush(&mut self) -> std::io::Result<()> { 174 | Ok(()) 175 | } 176 | } 177 | 178 | /// Implements `futures::io::AsyncWrite` for a [`ViewMut`]. 179 | #[derive(Copy, Clone, Debug)] 180 | pub struct AsyncWriter 181 | where 182 | T: ViewMut, 183 | { 184 | sink: T, 185 | len: usize, 186 | } 187 | 188 | impl AsyncWriter 189 | where 190 | T: ViewMut, 191 | { 192 | /// Create a new `AsyncWriter` 193 | pub fn new(sink: T) -> Self { 194 | Self { sink, len: 0 } 195 | } 196 | 197 | /// Return the original [`ViewMut`] 198 | pub fn into_inner(self) -> T { 199 | self.sink 200 | } 201 | } 202 | 203 | impl AsyncWrite for AsyncWriter 204 | where 205 | T: ViewMut, 206 | std::io::Error: From, 207 | { 208 | fn poll_write( 209 | mut self: Pin<&mut Self>, 210 | cx: &mut Context<'_>, 211 | buf: &[u8], 212 | ) -> Poll> { 213 | if self.len == 0 { 214 | self.len = if buf.len() <= self.sink.view().len() { 215 | buf.len() 216 | } else { 217 | futures::ready!(Pin::new(&mut self.sink).poll_grant(cx, 1))?; 218 | buf.len().min(self.sink.view().len()) 219 | }; 220 | let len = self.len; 221 | self.sink.view_mut()[..len].copy_from_slice(&buf[..len]); 222 | } 223 | let len = self.len; 224 | self.sink.release(len); 225 | Poll::Ready(Ok(std::mem::take(&mut self.len))) // set to 0 226 | } 227 | 228 | fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { 229 | Poll::Ready(Ok(())) 230 | } 231 | 232 | fn poll_close(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { 233 | Poll::Ready(Ok(())) 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /src/lazy.rs: -------------------------------------------------------------------------------- 1 | //! Lazy-initialized streams. 2 | 3 | use crate::{View, ViewMut}; 4 | use core::{ 5 | pin::Pin, 6 | sync::atomic::AtomicBool, 7 | task::{Context, Poll}, 8 | }; 9 | use pin_project::pin_project; 10 | 11 | /// A lazy-initialized view. 12 | /// 13 | /// The view is only initialized when polled for a grant. 14 | #[pin_project] 15 | #[derive(Copy, Clone, Debug, Hash)] 16 | pub struct Lazy { 17 | view: Option, 18 | init: Option, 19 | } 20 | 21 | impl Lazy { 22 | /// Create a new lazy view. 23 | pub fn new(init: F) -> Self { 24 | Self { 25 | view: None, 26 | init: Some(init), 27 | } 28 | } 29 | 30 | /// Return the inner type, if it has been initialized. 31 | pub fn into_inner(self) -> Option { 32 | self.view 33 | } 34 | } 35 | 36 | #[cfg(feature = "std")] 37 | #[cfg_attr(docsrs, doc(cfg(all(feature = "std"))))] 38 | impl Lazy V>> { 39 | /// Create a new lazy view with a boxed initialization function. 40 | pub fn new_boxed(init: impl FnOnce() -> V + 'static) -> Self { 41 | Self::new(Box::new(init)) 42 | } 43 | } 44 | 45 | impl View for Lazy 46 | where 47 | V: View, 48 | F: FnOnce() -> V, 49 | { 50 | type Item = V::Item; 51 | type Error = V::Error; 52 | 53 | fn view(&self) -> &[Self::Item] { 54 | if let Some(view) = self.view.as_ref() { 55 | view.view() 56 | } else { 57 | &[] 58 | } 59 | } 60 | 61 | fn poll_grant( 62 | self: Pin<&mut Self>, 63 | cx: &mut Context, 64 | count: usize, 65 | ) -> Poll> { 66 | if count > 0 { 67 | let this = self.project(); 68 | if this.view.is_none() { 69 | this.view.get_or_insert(this.init.take().unwrap()()); 70 | } 71 | Pin::new(this.view.as_mut().unwrap()).poll_grant(cx, count) 72 | } else { 73 | Poll::Ready(Ok(())) 74 | } 75 | } 76 | 77 | fn release(&mut self, count: usize) { 78 | if count > 0 { 79 | self.view 80 | .as_mut() 81 | .expect("attempted to release greater than grant") 82 | .release(count) 83 | } 84 | } 85 | } 86 | 87 | impl ViewMut for Lazy 88 | where 89 | V: ViewMut, 90 | F: FnOnce() -> V, 91 | { 92 | fn view_mut(&mut self) -> &mut [Self::Item] { 93 | if let Some(view) = self.view.as_mut() { 94 | view.view_mut() 95 | } else { 96 | &mut [] 97 | } 98 | } 99 | } 100 | 101 | #[cfg(feature = "std")] 102 | #[cfg_attr(docsrs, doc(cfg(all(feature = "std"))))] 103 | mod channel { 104 | use super::*; 105 | use core::marker::PhantomData; 106 | use std::sync::{atomic::Ordering, Arc, Mutex}; 107 | 108 | struct LazyChannelImpl { 109 | ready: AtomicBool, 110 | source: Mutex>, 111 | init: Mutex>, 112 | _sink: PhantomData, 113 | } 114 | 115 | impl LazyChannelImpl 116 | where 117 | F: FnOnce() -> (Sink, Source), 118 | { 119 | fn new(f: F) -> Self { 120 | Self { 121 | ready: AtomicBool::new(false), 122 | source: Mutex::new(None), 123 | init: Mutex::new(Some(f)), 124 | _sink: PhantomData, 125 | } 126 | } 127 | 128 | fn take_sink(&self) -> Sink { 129 | let init = self.init.lock().unwrap().take().unwrap(); 130 | let (sink, source) = init(); 131 | self.source.lock().unwrap().replace(source); 132 | self.ready.store(true, Ordering::Release); 133 | sink 134 | } 135 | 136 | fn try_take_source(&self) -> Option { 137 | if self.ready.load(Ordering::Acquire) { 138 | self.source.lock().unwrap().take() 139 | } else { 140 | None 141 | } 142 | } 143 | } 144 | 145 | /// A sink created by [`lazy_channel`]. 146 | #[pin_project] 147 | #[cfg_attr(docsrs, doc(cfg(all(feature = "std"))))] 148 | pub struct LazyChannelSink { 149 | view: Option, 150 | shared: Arc>, 151 | } 152 | 153 | impl View for LazyChannelSink 154 | where 155 | Sink: crate::View, 156 | F: FnOnce() -> (Sink, Source), 157 | { 158 | type Item = Sink::Item; 159 | type Error = Sink::Error; 160 | 161 | fn view(&self) -> &[Self::Item] { 162 | if let Some(view) = self.view.as_ref() { 163 | view.view() 164 | } else { 165 | &[] 166 | } 167 | } 168 | 169 | fn poll_grant( 170 | self: Pin<&mut Self>, 171 | cx: &mut Context, 172 | count: usize, 173 | ) -> Poll> { 174 | if count > 0 { 175 | let this = self.project(); 176 | if this.view.is_none() { 177 | this.view.get_or_insert(this.shared.take_sink()); 178 | } 179 | Pin::new(this.view.as_mut().unwrap()).poll_grant(cx, count) 180 | } else { 181 | Poll::Ready(Ok(())) 182 | } 183 | } 184 | 185 | fn release(&mut self, count: usize) { 186 | if count > 0 { 187 | self.view 188 | .as_mut() 189 | .expect("attempted to release greater than grant") 190 | .release(count) 191 | } 192 | } 193 | } 194 | 195 | impl ViewMut for LazyChannelSink 196 | where 197 | Sink: ViewMut, 198 | F: FnOnce() -> (Sink, Source), 199 | { 200 | fn view_mut(&mut self) -> &mut [Self::Item] { 201 | if let Some(view) = self.view.as_mut() { 202 | view.view_mut() 203 | } else { 204 | &mut [] 205 | } 206 | } 207 | } 208 | 209 | /// A source created by [`lazy_channel`]. 210 | #[pin_project] 211 | #[cfg_attr(docsrs, doc(cfg(all(feature = "std"))))] 212 | pub struct LazyChannelSource { 213 | view: Option, 214 | shared: Arc>, 215 | } 216 | 217 | impl View for LazyChannelSource 218 | where 219 | Source: View, 220 | F: FnOnce() -> (Sink, Source), 221 | { 222 | type Item = Source::Item; 223 | type Error = Source::Error; 224 | 225 | fn view(&self) -> &[Self::Item] { 226 | if let Some(view) = self.view.as_ref() { 227 | view.view() 228 | } else { 229 | &[] 230 | } 231 | } 232 | 233 | fn poll_grant( 234 | self: Pin<&mut Self>, 235 | cx: &mut Context, 236 | count: usize, 237 | ) -> Poll> { 238 | if count > 0 { 239 | let this = self.project(); 240 | if this.view.is_none() { 241 | if let Some(source) = this.shared.try_take_source() { 242 | this.view.get_or_insert(source); 243 | } 244 | } 245 | Pin::new(this.view.as_mut().unwrap()).poll_grant(cx, count) 246 | } else { 247 | Poll::Ready(Ok(())) 248 | } 249 | } 250 | 251 | fn release(&mut self, count: usize) { 252 | if count > 0 { 253 | self.view 254 | .as_mut() 255 | .expect("attempted to release greater than grant") 256 | .release(count) 257 | } 258 | } 259 | } 260 | 261 | impl ViewMut for LazyChannelSource 262 | where 263 | Source: ViewMut, 264 | F: FnOnce() -> (Sink, Source), 265 | { 266 | fn view_mut(&mut self) -> &mut [Self::Item] { 267 | if let Some(view) = self.view.as_mut() { 268 | view.view_mut() 269 | } else { 270 | &mut [] 271 | } 272 | } 273 | } 274 | 275 | /// Create a lazy-initialized channel. 276 | /// 277 | /// The channel is only initialized when first writing to the sink. 278 | #[cfg_attr(docsrs, doc(cfg(all(feature = "std"))))] 279 | pub fn lazy_channel( 280 | f: F, 281 | ) -> ( 282 | LazyChannelSink, 283 | LazyChannelSource, 284 | ) 285 | where 286 | F: FnOnce() -> (Sink, Source) + 'static, 287 | Sink: 'static, 288 | Source: 'static, 289 | { 290 | let shared = Arc::new(LazyChannelImpl::new(f)); 291 | 292 | ( 293 | LazyChannelSink { 294 | view: None, 295 | shared: shared.clone(), 296 | }, 297 | LazyChannelSource { view: None, shared }, 298 | ) 299 | } 300 | } 301 | 302 | #[cfg(feature = "std")] 303 | pub use channel::*; 304 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(docsrs, feature(doc_cfg))] 2 | #![cfg_attr(not(feature = "std"), no_std)] 3 | #![deny(missing_docs)] 4 | //! Rivulet is a library for creating asynchronous pipelines of contiguous data. 5 | //! 6 | //! Main features, at a glance: 7 | //! 8 | //! * **Asynchronous**: Pipeline components are `async`-aware, allowing more control over task priority and data backpressure. 9 | //! * **Contiguous views**: Data is always contiguous, accessible by a single slice. 10 | //! * **Modular**: Pipelines can be combined and reused using common interfaces. 11 | //! 12 | //! # Getting started 13 | //! 14 | //! The most basic interface is a [`View`]. 15 | //! Streams of data in `rivulet` are like slices, but you can't access the entire slice at once. 16 | //! A [`View`] is sliding window over the stream, requiring only a small portion of the stream to 17 | //! be in memory. 18 | //! 19 | //! A [`SplittableView`] is a special view that can split into multiple, simultaneously available views for use with multiple readers and writers. 20 | //! 21 | //! This crate provides a few stream implementations, but the most notable is the [`mod@circular_buffer`], which is optimized for asynchronous contiguous data access. 22 | //! 23 | //! # Example 24 | //! 25 | //! Let's create a simple averaging downsampler pipeline. 26 | //! 27 | //! ``` 28 | //! use rivulet::{View, ViewMut}; 29 | //! use futures::future::TryFutureExt; 30 | //! 31 | //! /// This function reads samples from the source, averages them, 32 | //! /// and writes the average to the sink. 33 | //! async fn downsample( 34 | //! mut source: impl View, 35 | //! mut sink: impl ViewMut, 36 | //! factor: usize 37 | //! ) -> Result<(), &'static str> { 38 | //! loop { 39 | //! // Wait for the input and output to be available. 40 | //! tokio::try_join!( 41 | //! source.grant(factor).map_err(|_| "we got an input error!"), 42 | //! sink.grant(1).map_err(|_| "we got an output error!"), 43 | //! )?; 44 | //! 45 | //! // Each view could be longer (if extra data is available) 46 | //! // or shorter (if the stream closed) 47 | //! let input = source.view(); 48 | //! let output = sink.view_mut(); 49 | //! if input.len() < factor || output.is_empty() { 50 | //! break Ok(()) 51 | //! } 52 | //! 53 | //! // Average the values 54 | //! output[0] = input[..factor].iter().sum::() / factor as f32; 55 | //! 56 | //! // We've written all our data, so release our current views 57 | //! // and advance the streams 58 | //! source.release(factor); 59 | //! sink.release(1); 60 | //! } 61 | //! } 62 | //! ``` 63 | 64 | pub mod circular_buffer; 65 | pub mod error; 66 | pub mod io; 67 | pub mod lazy; 68 | pub mod slice; 69 | pub mod splittable; 70 | pub mod view; 71 | 72 | pub use circular_buffer::circular_buffer; 73 | pub use splittable::SplittableView; 74 | pub use view::{View, ViewMut}; 75 | -------------------------------------------------------------------------------- /src/slice.rs: -------------------------------------------------------------------------------- 1 | //! Utilities for working with slices. 2 | 3 | use crate::splittable::{SplittableViewImpl, SplittableViewImplMut}; 4 | use core::{ 5 | convert::Infallible, 6 | convert::TryInto, 7 | marker::PhantomData, 8 | pin::Pin, 9 | slice, 10 | task::{Context, Poll, Waker}, 11 | }; 12 | 13 | /// Treats a slice as a stream. 14 | pub struct Slice<'a, T>(&'a [T]); 15 | 16 | impl<'a, T> Slice<'a, T> { 17 | /// Create a stream from a slice. 18 | pub fn new(slice: &'a [T]) -> Self { 19 | Self(slice) 20 | } 21 | 22 | /// Return the original slice. 23 | pub fn into_inner(self) -> &'a [T] { 24 | self.0 25 | } 26 | } 27 | 28 | unsafe impl<'a, T> SplittableViewImpl for Slice<'a, T> { 29 | type Item = T; 30 | type Error = Infallible; 31 | 32 | unsafe fn set_reader_waker(&self, _: impl Fn() + Send + Sync + 'static) {} 33 | 34 | unsafe fn set_head(&self, _: u64) {} 35 | 36 | unsafe fn compare_set_head(&self, _: u64) {} 37 | 38 | fn poll_available( 39 | self: Pin<&Self>, 40 | _cx: &mut Context, 41 | _register_wakeup: impl FnOnce(&Waker), 42 | index: u64, 43 | _len: usize, 44 | ) -> Poll> { 45 | let index: usize = index.try_into().unwrap(); 46 | let len = self.0.as_ref().len(); 47 | Poll::Ready(Ok(len - index.min(len))) 48 | } 49 | 50 | unsafe fn view(&self, index: u64, len: usize) -> &[Self::Item] { 51 | let index = index.try_into().unwrap(); 52 | &self.0[index..index + len] 53 | } 54 | } 55 | 56 | /// Treats a mutable slice as a stream. 57 | pub struct SliceMut<'a, T> { 58 | ptr: *mut T, 59 | len: usize, 60 | phantom_data: PhantomData<&'a mut [T]>, 61 | } 62 | 63 | impl<'a, T> SliceMut<'a, T> { 64 | /// Create a stream from a slice. 65 | pub fn new(slice: &'a mut [T]) -> Self { 66 | let ptr = slice.as_mut_ptr(); 67 | let len = slice.len(); 68 | Self { 69 | ptr, 70 | len, 71 | phantom_data: PhantomData, 72 | } 73 | } 74 | 75 | /// Return the original slice. 76 | pub fn into_inner(self) -> &'a mut [T] { 77 | // Safety: reconstruct the original slice 78 | unsafe { slice::from_raw_parts_mut(self.ptr, self.len) } 79 | } 80 | } 81 | 82 | unsafe impl<'a, T> SplittableViewImpl for SliceMut<'a, T> { 83 | type Item = T; 84 | type Error = Infallible; 85 | 86 | unsafe fn set_reader_waker(&self, _: impl Fn() + Send + Sync + 'static) {} 87 | 88 | unsafe fn set_head(&self, _: u64) {} 89 | 90 | unsafe fn compare_set_head(&self, _: u64) {} 91 | 92 | fn poll_available( 93 | self: Pin<&Self>, 94 | _cx: &mut Context, 95 | _register_wakeup: impl FnOnce(&Waker), 96 | index: u64, 97 | _len: usize, 98 | ) -> Poll> { 99 | let index: usize = index.try_into().unwrap(); 100 | Poll::Ready(Ok(self.len - index.min(self.len))) 101 | } 102 | 103 | unsafe fn view(&self, index: u64, len: usize) -> &[Self::Item] { 104 | slice::from_raw_parts(self.ptr.add(index.try_into().unwrap()) as *const T, len) 105 | } 106 | } 107 | 108 | unsafe impl<'a, T> SplittableViewImplMut for SliceMut<'a, T> { 109 | unsafe fn view_mut(&self, index: u64, len: usize) -> &mut [Self::Item] { 110 | slice::from_raw_parts_mut(self.ptr.add(index.try_into().unwrap()), len) 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/splittable.rs: -------------------------------------------------------------------------------- 1 | //! Streams that can be split into multiple views. 2 | 3 | use core::{ 4 | pin::Pin, 5 | task::{Context, Poll, Waker}, 6 | }; 7 | 8 | mod view; 9 | pub use view::View; 10 | 11 | mod cloneable; 12 | pub use cloneable::Cloneable; 13 | 14 | mod sequence; 15 | use sequence::make_sequence; 16 | pub use sequence::{First, Second}; 17 | 18 | /// The implementation behind [`SplittableView`]. 19 | /// 20 | /// Unless you are manually implementing a view, you should use [`SplittableView`] directly. 21 | /// 22 | /// # Safety 23 | /// The implementation must satisfy the various interior mutability conditions specified in each method. 24 | pub unsafe trait SplittableViewImpl: Sized + Unpin { 25 | /// The streamed type. 26 | type Item; 27 | 28 | /// The error produced by [`poll_available`](`Self::poll_available`). 29 | type Error: core::fmt::Debug; 30 | 31 | /// Set the reader waking function. 32 | /// 33 | /// This should only be called once, after creation. 34 | /// 35 | /// # Safety 36 | /// Only set the waker if you have unique ownership of this. 37 | unsafe fn set_reader_waker(&self, waker: impl Fn() + Send + Sync + 'static); 38 | 39 | /// Set the earliest position retained in the stream. 40 | /// 41 | /// # Panics 42 | /// May panic if the provided an index earlier than the current head. 43 | /// 44 | /// # Safety 45 | /// Only set the head if you have unique ownership of this and will not attempt to read any 46 | /// data earlier than `index`. 47 | unsafe fn set_head(&self, index: u64); 48 | 49 | /// Set the earliest position retained in the stream. 50 | /// 51 | /// This function, unlike [`set_head`](`Self::set_head`), is synchronized between threads. 52 | /// If the provided head is less than the current head, the current head remains unchanged. 53 | /// 54 | /// # Safety 55 | /// See [`set_head`](`Self::set_head`). 56 | unsafe fn compare_set_head(&self, index: u64); 57 | 58 | /// Suspends the current task until `len` samples starting at `index` are available, returning 59 | /// the available length. 60 | /// 61 | /// If the stream is closed, the returned available length may be less than `len`. 62 | fn poll_available( 63 | self: Pin<&Self>, 64 | cx: &mut Context, 65 | register_wakeup: impl Fn(&Waker), 66 | index: u64, 67 | len: usize, 68 | ) -> Poll>; 69 | 70 | /// Obtain a view into the stream. 71 | /// 72 | /// # Safety 73 | /// The parameters must be within an available window as returned by 74 | /// [`poll_available`](`Self::poll_available`). 75 | unsafe fn view(&self, index: u64, len: usize) -> &[Self::Item]; 76 | } 77 | 78 | /// The implementation behind [`SplittableViewMut`]. 79 | /// 80 | /// Unless you are manually implementing a view, you should use [`SplittableViewMut`] directly. 81 | /// 82 | /// # Safety 83 | /// The implementation must satisfy the various interior mutability conditions specified in each method. 84 | pub unsafe trait SplittableViewImplMut: SplittableViewImpl { 85 | /// Obtain a mutable view into the stream. 86 | /// 87 | /// # Safety 88 | /// * The parameters must be within an available window as returned by 89 | /// [`poll_available`](`SplittableViewImpl::poll_available`). 90 | /// * The view must not overlap with any other view of this stream. 91 | /// 92 | /// Note that this function produces a mutable reference from a regular reference. 93 | /// Implementations of this function must take care to correctly implement interior 94 | /// mutability (such as using `UnsafeCell`). 95 | #[allow(clippy::mut_from_ref)] 96 | unsafe fn view_mut(&self, index: u64, len: usize) -> &mut [Self::Item]; 97 | } 98 | 99 | /// A view that can be split for use with multiple readers. 100 | pub trait SplittableView: SplittableViewImpl { 101 | /// Create a view for a single reader. 102 | fn into_view(self) -> View { 103 | View::new(self) 104 | } 105 | 106 | /// Create a view that implements `Clone`. 107 | fn into_cloneable_view(self) -> Cloneable { 108 | Cloneable::new(self) 109 | } 110 | 111 | /// Split this view into two sequential views, such that data released by `First` becomes 112 | /// accessible to `Second`. 113 | fn sequence(self) -> (First, Second) { 114 | make_sequence(self) 115 | } 116 | } 117 | 118 | /// A mutable view that can be split for use with multiple readers. 119 | pub trait SplittableViewMut: SplittableView + SplittableViewImplMut {} 120 | 121 | impl SplittableView for T where T: SplittableViewImpl {} 122 | impl SplittableViewMut for T where T: SplittableView + SplittableViewImplMut {} 123 | -------------------------------------------------------------------------------- /src/splittable/cloneable.rs: -------------------------------------------------------------------------------- 1 | use super::SplittableView; 2 | use futures::task::AtomicWaker; 3 | use std::{ 4 | convert::TryInto, 5 | pin::Pin, 6 | sync::{ 7 | atomic::{AtomicU64, Ordering}, 8 | Arc, RwLock, 9 | }, 10 | task::{Context, Poll}, 11 | }; 12 | 13 | struct Reader { 14 | waker: AtomicWaker, 15 | head: AtomicU64, 16 | } 17 | 18 | pub(crate) struct Waker(RwLock>>); 19 | 20 | impl Waker { 21 | /// Create a new waker with one reader 22 | pub(crate) fn new() -> Self { 23 | let reader = Reader { 24 | waker: AtomicWaker::new(), 25 | head: AtomicU64::new(0), 26 | }; 27 | Self(RwLock::new(vec![Arc::new(reader)])) 28 | } 29 | 30 | /// Wake all readers 31 | pub(crate) fn wake(&self) { 32 | let lock = self.0.read().expect("another thread panicked"); 33 | for reader in lock.iter() { 34 | reader.waker.wake(); 35 | } 36 | } 37 | 38 | /// Copy the specified reader 39 | fn insert(&self, reader: &Arc) -> Arc { 40 | let reader = Arc::new(Reader { 41 | waker: AtomicWaker::new(), 42 | head: AtomicU64::new(reader.head.load(Ordering::Relaxed)), 43 | }); 44 | let mut lock = self.0.write().expect("another thread panicked"); 45 | lock.push(reader.clone()); 46 | reader 47 | } 48 | 49 | /// Removes the specified reader 50 | fn remove(&self, reader: &Arc) { 51 | let mut lock = self.0.write().expect("another thread panicked"); 52 | lock.retain(|test_reader| !Arc::ptr_eq(test_reader, reader)); 53 | } 54 | 55 | /// Return the earliest head between all threads 56 | fn earliest_head(&self) -> u64 { 57 | let lock = self.0.read().expect("another thread panicked"); 58 | lock.iter() 59 | .map(|reader| reader.head.load(Ordering::Relaxed)) 60 | .min() 61 | .unwrap() 62 | } 63 | } 64 | 65 | /// A view returned by 66 | /// [`SplittableView::into_cloneable_view`](`super::SplittableView::into_cloneable_view`). 67 | /// 68 | /// This view may be cloned to be used with other readers. The cloned view is initialized with 69 | /// the same view of the stream. 70 | pub struct Cloneable 71 | where 72 | T: SplittableView, 73 | { 74 | splittable: Pin>, 75 | this_reader: Arc, 76 | waker: Arc, 77 | head: u64, 78 | len: usize, 79 | } 80 | 81 | impl Cloneable 82 | where 83 | T: SplittableView, 84 | { 85 | pub(crate) fn new(splittable: T) -> Self { 86 | let waker = Arc::new(Waker::new()); 87 | // Safety: we have unique ownership of `splittable` 88 | let splittable = unsafe { 89 | let waker = waker.clone(); 90 | splittable.set_reader_waker(move || waker.wake()); 91 | Arc::pin(splittable) 92 | }; 93 | let this_reader = waker.0.read().expect("another thread panicked")[0].clone(); 94 | Self { 95 | splittable, 96 | this_reader, 97 | waker, 98 | head: 0, 99 | len: 0, 100 | } 101 | } 102 | } 103 | 104 | impl Clone for Cloneable 105 | where 106 | T: SplittableView, 107 | { 108 | fn clone(&self) -> Self { 109 | let this_reader = self.waker.insert(&self.this_reader); 110 | Self { 111 | splittable: self.splittable.clone(), 112 | this_reader, 113 | waker: self.waker.clone(), 114 | head: self.head, 115 | len: self.len, 116 | } 117 | } 118 | } 119 | 120 | impl Drop for Cloneable 121 | where 122 | T: SplittableView, 123 | { 124 | fn drop(&mut self) { 125 | self.waker.remove(&self.this_reader); 126 | } 127 | } 128 | 129 | impl crate::View for Cloneable 130 | where 131 | T: SplittableView, 132 | { 133 | type Item = T::Item; 134 | type Error = T::Error; 135 | 136 | fn view(&self) -> &[Self::Item] { 137 | // Safety: there are no mutable views 138 | unsafe { self.splittable.view(self.head, self.len) } 139 | } 140 | 141 | fn poll_grant( 142 | mut self: Pin<&mut Self>, 143 | cx: &mut Context, 144 | count: usize, 145 | ) -> Poll> { 146 | match self.splittable.as_ref().poll_available( 147 | cx, 148 | |waker| self.this_reader.waker.register(waker), 149 | self.head, 150 | count, 151 | ) { 152 | Poll::Ready(Ok(len)) => { 153 | self.len = len; 154 | Poll::Ready(Ok(())) 155 | } 156 | Poll::Ready(Err(e)) => Poll::Ready(Err(e)), 157 | Poll::Pending => Poll::Pending, 158 | } 159 | } 160 | 161 | fn release(&mut self, count: usize) { 162 | self.len -= count; 163 | let count: u64 = count.try_into().unwrap(); 164 | self.head += count; 165 | self.this_reader.head.store(self.head, Ordering::Relaxed); 166 | 167 | // Safety: we never read earlier than this head value with this reader 168 | unsafe { 169 | self.splittable 170 | .as_ref() 171 | .compare_set_head(self.waker.earliest_head()); 172 | } 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /src/splittable/sequence.rs: -------------------------------------------------------------------------------- 1 | use crate::splittable::{ 2 | SplittableView, SplittableViewImpl, SplittableViewImplMut, SplittableViewMut, 3 | }; 4 | use once_cell::sync::OnceCell; 5 | use std::{ 6 | convert::TryInto, 7 | pin::Pin, 8 | sync::{ 9 | atomic::{AtomicBool, AtomicU64, Ordering}, 10 | Arc, Mutex, 11 | }, 12 | task::{Context, Poll, Waker}, 13 | }; 14 | 15 | pub(super) fn make_sequence(splittable: T) -> (First, Second) 16 | where 17 | T: SplittableView, 18 | { 19 | let shared = Arc::new(Shared { 20 | splittable, 21 | head: AtomicU64::new(0), 22 | closed: AtomicBool::new(false), 23 | waker: Mutex::new(None), 24 | }); 25 | 26 | ( 27 | First { 28 | shared: shared.clone(), 29 | waker: OnceCell::new(), 30 | }, 31 | Second { shared }, 32 | ) 33 | } 34 | 35 | struct Shared 36 | where 37 | T: SplittableView, 38 | { 39 | splittable: T, 40 | head: AtomicU64, 41 | closed: AtomicBool, 42 | waker: Mutex>>, 43 | } 44 | 45 | /// The first `SplittableView` produced by [`sequence`](`crate::SplittableView::sequence`). 46 | pub struct First 47 | where 48 | T: SplittableView, 49 | { 50 | shared: Arc>, 51 | waker: OnceCell>, 52 | } 53 | 54 | impl Drop for First 55 | where 56 | T: SplittableView, 57 | { 58 | fn drop(&mut self) { 59 | self.shared.closed.store(true, Ordering::Relaxed); 60 | self.wake_second() 61 | } 62 | } 63 | 64 | impl First 65 | where 66 | T: SplittableView, 67 | { 68 | fn wake_second(&self) { 69 | if let Ok(waker) = self.waker.get_or_try_init(|| { 70 | let mut lock = self.shared.waker.lock().expect("another thread panicked"); 71 | lock.take().ok_or(()) 72 | }) { 73 | waker() 74 | } 75 | } 76 | } 77 | 78 | unsafe impl SplittableViewImpl for First 79 | where 80 | T: SplittableView, 81 | { 82 | type Item = T::Item; 83 | type Error = T::Error; 84 | 85 | unsafe fn set_reader_waker(&self, waker: impl Fn() + Send + Sync + 'static) { 86 | self.shared.splittable.set_reader_waker(waker); 87 | } 88 | 89 | unsafe fn set_head(&self, index: u64) { 90 | if self.shared.closed.load(Ordering::Relaxed) { 91 | // This may overlap with a drop of `Second`, so always use `compare_set_head`. 92 | self.shared.splittable.compare_set_head(index); 93 | } else { 94 | self.shared.head.store(index, Ordering::Relaxed); 95 | self.wake_second(); 96 | } 97 | } 98 | 99 | unsafe fn compare_set_head(&self, index: u64) { 100 | if self.shared.closed.load(Ordering::Relaxed) { 101 | self.shared.splittable.compare_set_head(index); 102 | } else { 103 | // only set the head if it's greater than the current head 104 | let mut current = self.shared.head.load(Ordering::Relaxed); 105 | if index > current { 106 | while let Err(previous) = self.shared.head.compare_exchange_weak( 107 | current, 108 | index, 109 | Ordering::Relaxed, 110 | Ordering::Relaxed, 111 | ) { 112 | if index > previous { 113 | current = previous 114 | } else { 115 | break; 116 | } 117 | } 118 | } 119 | self.wake_second() 120 | } 121 | } 122 | 123 | fn poll_available( 124 | self: Pin<&Self>, 125 | cx: &mut Context, 126 | register_wakeup: impl Fn(&Waker), 127 | index: u64, 128 | len: usize, 129 | ) -> Poll> { 130 | Pin::new(&self.shared.splittable).poll_available(cx, register_wakeup, index, len) 131 | } 132 | 133 | unsafe fn view(&self, index: u64, len: usize) -> &[Self::Item] { 134 | self.shared.splittable.view(index, len) 135 | } 136 | } 137 | 138 | unsafe impl SplittableViewImplMut for First 139 | where 140 | T: SplittableViewMut, 141 | { 142 | unsafe fn view_mut(&self, index: u64, len: usize) -> &mut [Self::Item] { 143 | self.shared.splittable.view_mut(index, len) 144 | } 145 | } 146 | 147 | /// The second `SplittableView` produced by [`sequence`](`crate::SplittableView::sequence`). 148 | pub struct Second 149 | where 150 | T: SplittableView, 151 | { 152 | shared: Arc>, 153 | } 154 | 155 | impl Drop for Second 156 | where 157 | T: SplittableView, 158 | { 159 | fn drop(&mut self) { 160 | self.shared.closed.store(true, Ordering::Relaxed); 161 | 162 | // Safety: this view is done with `head` so we can drop up to it. 163 | // We must use `compare_set_head` since this may overlap with an advance on `First` that 164 | // happens after `closed` is set but before setting the head occurs. 165 | unsafe { 166 | self.shared 167 | .splittable 168 | .compare_set_head(self.shared.head.load(Ordering::Relaxed)); 169 | } 170 | } 171 | } 172 | 173 | impl Second 174 | where 175 | T: SplittableView, 176 | { 177 | fn readable_len(&self, start: u64) -> usize { 178 | (self.shared.head.load(Ordering::Relaxed) - start) 179 | .try_into() 180 | .unwrap() 181 | } 182 | } 183 | 184 | unsafe impl SplittableViewImpl for Second 185 | where 186 | T: SplittableView, 187 | { 188 | type Item = T::Item; 189 | type Error = T::Error; 190 | 191 | unsafe fn set_reader_waker(&self, waker: impl Fn() + Send + Sync + 'static) { 192 | let mut lock = self.shared.waker.lock().expect("another thread panicked!"); 193 | *lock = Some(Box::new(waker)); 194 | } 195 | 196 | unsafe fn set_head(&self, index: u64) { 197 | self.shared.splittable.set_head(index); 198 | } 199 | 200 | unsafe fn compare_set_head(&self, index: u64) { 201 | self.shared.splittable.compare_set_head(index); 202 | } 203 | 204 | fn poll_available( 205 | self: Pin<&Self>, 206 | cx: &mut Context, 207 | register_wakeup: impl Fn(&Waker), 208 | index: u64, 209 | len: usize, 210 | ) -> Poll> { 211 | // Perform double-checking on the amount of available data 212 | // The first check is efficient, but may spuriously fail. 213 | // The second check occurs after the `acquire` produced by registering the waker. 214 | let available = self.readable_len(index); 215 | if available >= len { 216 | Poll::Ready(Ok(available)) 217 | } else { 218 | register_wakeup(cx.waker()); 219 | let available = self.readable_len(index); 220 | if available >= len || self.shared.closed.load(Ordering::Relaxed) { 221 | Poll::Ready(Ok(available)) 222 | } else { 223 | Poll::Pending 224 | } 225 | } 226 | } 227 | 228 | unsafe fn view(&self, index: u64, len: usize) -> &[Self::Item] { 229 | self.shared.splittable.view(index, len) 230 | } 231 | } 232 | 233 | unsafe impl SplittableViewImplMut for Second 234 | where 235 | T: SplittableViewMut, 236 | { 237 | unsafe fn view_mut(&self, index: u64, len: usize) -> &mut [Self::Item] { 238 | self.shared.splittable.view_mut(index, len) 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /src/splittable/view.rs: -------------------------------------------------------------------------------- 1 | use super::{SplittableView, SplittableViewMut}; 2 | use futures::task::AtomicWaker; 3 | use std::{ 4 | convert::TryInto, 5 | pin::Pin, 6 | sync::Arc, 7 | task::{Context, Poll}, 8 | }; 9 | 10 | /// A view returned by [`SplittableView::into_view`](`super::SplittableView::into_view`). 11 | pub struct View 12 | where 13 | T: SplittableView, 14 | { 15 | splittable: T, 16 | waker: Arc, 17 | head: u64, 18 | len: usize, 19 | } 20 | 21 | impl View 22 | where 23 | T: SplittableView, 24 | { 25 | pub(crate) fn new(splittable: T) -> Self { 26 | let waker = Arc::new(AtomicWaker::new()); 27 | // Safety: we have unique ownership of this 28 | unsafe { 29 | let waker = waker.clone(); 30 | splittable.set_reader_waker(move || waker.wake()); 31 | } 32 | Self { 33 | splittable, 34 | waker, 35 | head: 0, 36 | len: 0, 37 | } 38 | } 39 | } 40 | 41 | impl crate::View for View 42 | where 43 | T: SplittableView, 44 | { 45 | type Item = T::Item; 46 | type Error = T::Error; 47 | 48 | fn view(&self) -> &[Self::Item] { 49 | // Safety: we have unique ownership of the view, so this doesn't overlap with any other views 50 | unsafe { self.splittable.view(self.head, self.len) } 51 | } 52 | 53 | fn poll_grant( 54 | mut self: Pin<&mut Self>, 55 | cx: &mut Context, 56 | count: usize, 57 | ) -> Poll> { 58 | match Pin::new(&self.splittable).poll_available( 59 | cx, 60 | |waker| self.waker.register(waker), 61 | self.head, 62 | count, 63 | ) { 64 | Poll::Ready(Ok(len)) => { 65 | self.len = len; 66 | Poll::Ready(Ok(())) 67 | } 68 | Poll::Ready(Err(e)) => Poll::Ready(Err(e)), 69 | Poll::Pending => Poll::Pending, 70 | } 71 | } 72 | 73 | fn release(&mut self, count: usize) { 74 | assert!( 75 | count <= self.len, 76 | "attempted to release more than current grant" 77 | ); 78 | 79 | self.len -= count; 80 | let count: u64 = count.try_into().unwrap(); 81 | self.head += count; 82 | // Safety: we never read earlier than this head value 83 | unsafe { 84 | self.splittable.set_head(self.head); 85 | } 86 | } 87 | } 88 | 89 | impl crate::ViewMut for View 90 | where 91 | T: SplittableViewMut, 92 | { 93 | fn view_mut(&mut self) -> &mut [Self::Item] { 94 | // Safety: we have unique ownership of the view, so this doesn't overlap with any other views 95 | unsafe { self.splittable.view_mut(self.head, self.len) } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/view.rs: -------------------------------------------------------------------------------- 1 | //! Views into asynchronous streams. 2 | 3 | use core::{ 4 | future::Future, 5 | pin::Pin, 6 | task::{Context, Poll}, 7 | }; 8 | use pin_project::pin_project; 9 | 10 | /// Future produced by [`View::grant`]. 11 | pub struct Grant<'a, T> { 12 | handle: &'a mut T, 13 | count: usize, 14 | } 15 | 16 | impl<'a, T> Future for Grant<'a, T> 17 | where 18 | T: View, 19 | { 20 | type Output = Result<(), T::Error>; 21 | 22 | fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll { 23 | let count = self.count; 24 | Pin::new(&mut self.handle).poll_grant(cx, count) 25 | } 26 | } 27 | 28 | /// Obtain views into asynchronous contiguous-memory streams. 29 | pub trait View: Unpin { 30 | /// The streamed type. 31 | type Item; 32 | 33 | /// The error produced by [`poll_grant`](`Self::poll_grant`). 34 | type Error: core::fmt::Debug; 35 | 36 | /// Obtain the current view of the stream. 37 | /// 38 | /// This view is obtained by successfully polling [`poll_grant`](`Self::poll_grant`) and 39 | /// advanced by calling [`release`](`Self::release`). 40 | /// 41 | /// If this slice is smaller than last successful grant request, the end of the stream has been 42 | /// reached and no additional values will be provided. 43 | fn view(&self) -> &[Self::Item]; 44 | 45 | /// Attempt to obtain a view of at least `count` elements. 46 | /// 47 | /// If the request exceeds the maximum possible grant (if there is one), an error should be returned. 48 | fn poll_grant( 49 | self: Pin<&mut Self>, 50 | cx: &mut Context, 51 | count: usize, 52 | ) -> Poll>; 53 | 54 | /// Attempt to advance past the first `count` elements in the current view. 55 | /// 56 | /// # Panics 57 | /// If the request exceeds the current grant, this function should panic. 58 | fn release(&mut self, count: usize); 59 | 60 | /// Create a future that obtains a view of at least `count` elements. 61 | /// 62 | /// See [`poll_grant`](`Self::poll_grant`). 63 | fn grant(&mut self, count: usize) -> Grant<'_, Self> 64 | where 65 | Self: Sized, 66 | { 67 | Grant { 68 | handle: self, 69 | count, 70 | } 71 | } 72 | 73 | /// Obtains a view of at least `count` elements, blocking the current thread. 74 | /// 75 | /// See [`poll_grant`](`View::poll_grant`). 76 | fn blocking_grant(&mut self, count: usize) -> Result<(), Self::Error> 77 | where 78 | Self: Sized, 79 | { 80 | futures::executor::block_on(self.grant(count)) 81 | } 82 | 83 | /// Maps this view to a new view producing error `E`. 84 | fn map_error(self, f: F) -> MapError 85 | where 86 | Self: Sized, 87 | F: Fn(Self::Error) -> E, 88 | { 89 | MapError { 90 | view: self, 91 | map: f, 92 | _error: core::marker::PhantomData, 93 | } 94 | } 95 | } 96 | 97 | impl View for &mut S { 98 | type Item = S::Item; 99 | type Error = S::Error; 100 | 101 | fn view(&self) -> &[Self::Item] { 102 | View::view(*self) 103 | } 104 | 105 | fn poll_grant( 106 | mut self: Pin<&mut Self>, 107 | cx: &mut Context, 108 | count: usize, 109 | ) -> Poll> { 110 | S::poll_grant(Pin::new(&mut **self), cx, count) 111 | } 112 | 113 | fn release(&mut self, count: usize) { 114 | S::release(self, count) 115 | } 116 | } 117 | 118 | /// Obtain mutable views into asynchronous contiguous-memory mutable streams. 119 | pub trait ViewMut: View { 120 | /// Obtain the current mutable view of the stream. 121 | /// 122 | /// Identical semantics to [`view`](trait.View.html#tymethod.view), but returns a mutable 123 | /// slice. 124 | fn view_mut(&mut self) -> &mut [Self::Item]; 125 | } 126 | 127 | impl ViewMut for &mut S { 128 | fn view_mut(&mut self) -> &mut [Self::Item] { 129 | ViewMut::view_mut(*self) 130 | } 131 | } 132 | 133 | /// An error-mapped view produced by [`View::map_error`]. 134 | #[pin_project] 135 | #[derive(Debug)] 136 | pub struct MapError { 137 | #[pin] 138 | view: V, 139 | map: F, 140 | _error: core::marker::PhantomData, 141 | } 142 | 143 | impl MapError { 144 | /// Return the original view. 145 | pub fn into_inner(self) -> V { 146 | self.view 147 | } 148 | } 149 | 150 | impl View for MapError 151 | where 152 | V: View, 153 | E: core::fmt::Debug, 154 | F: Fn(V::Error) -> E, 155 | { 156 | type Item = V::Item; 157 | type Error = E; 158 | 159 | fn view(&self) -> &[Self::Item] { 160 | self.view.view() 161 | } 162 | 163 | fn poll_grant( 164 | self: Pin<&mut Self>, 165 | cx: &mut Context, 166 | count: usize, 167 | ) -> Poll> { 168 | let pinned = self.project(); 169 | let f = pinned.map; 170 | pinned.view.poll_grant(cx, count).map(|r| r.map_err(f)) 171 | } 172 | 173 | fn release(&mut self, count: usize) { 174 | self.view.release(count) 175 | } 176 | } 177 | 178 | impl ViewMut for MapError 179 | where 180 | V: ViewMut, 181 | E: core::fmt::Debug, 182 | F: Fn(V::Error) -> E, 183 | { 184 | fn view_mut(&mut self) -> &mut [Self::Item] { 185 | self.view.view_mut() 186 | } 187 | } 188 | 189 | impl Copy for MapError 190 | where 191 | V: Copy, 192 | F: Copy, 193 | { 194 | } 195 | 196 | impl Clone for MapError 197 | where 198 | V: Clone, 199 | F: Clone, 200 | { 201 | fn clone(&self) -> Self { 202 | Self { 203 | view: self.view.clone(), 204 | map: self.map.clone(), 205 | _error: core::marker::PhantomData, 206 | } 207 | } 208 | } 209 | 210 | impl core::hash::Hash for MapError 211 | where 212 | V: core::hash::Hash, 213 | F: core::hash::Hash, 214 | { 215 | fn hash(&self, state: &mut H) 216 | where 217 | H: core::hash::Hasher, 218 | { 219 | self.view.hash(state); 220 | self.map.hash(state); 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /tests/circular_buffer.rs: -------------------------------------------------------------------------------- 1 | use rand::{rngs::SmallRng, Rng, SeedableRng}; 2 | use rivulet::{circular_buffer, SplittableView, View, ViewMut}; 3 | use std::hash::Hasher; 4 | 5 | static BUFFER_SIZE: usize = 4096; 6 | 7 | async fn write + Send>(mut sink: T, block: usize, count: usize) -> u64 { 8 | let mut hasher = seahash::SeaHasher::new(); 9 | let mut rng = SmallRng::from_entropy(); 10 | for _ in 0..count { 11 | sink.grant(block).await.unwrap(); 12 | for value in &mut sink.view_mut()[..block] { 13 | *value = rng.gen(); 14 | hasher.write_i64(*value); 15 | } 16 | sink.release(block); 17 | } 18 | hasher.finish() 19 | } 20 | 21 | async fn read + Send>(mut source: T) -> u64 { 22 | let mut hasher = seahash::SeaHasher::new(); 23 | let mut rng = SmallRng::from_entropy(); 24 | loop { 25 | let count = rng.gen_range(1..BUFFER_SIZE); 26 | source.grant(count).await.unwrap(); 27 | if source.view().is_empty() { 28 | break hasher.finish(); 29 | } 30 | for value in source.view() { 31 | hasher.write_i64(*value); 32 | } 33 | let released = source.view().len(); 34 | source.release(released); 35 | } 36 | } 37 | 38 | #[tokio::test] 39 | async fn single_reader_buffer_integrity() { 40 | let (sink, source) = circular_buffer::(BUFFER_SIZE); 41 | 42 | let write_hash = tokio::spawn(write(sink, 500, 400)); 43 | let read_hash = tokio::spawn(read(source.into_view())); 44 | 45 | let (write_hash, read_hash) = futures::future::join(write_hash, read_hash).await; 46 | assert_eq!(write_hash.unwrap(), read_hash.unwrap()); 47 | } 48 | 49 | #[tokio::test] 50 | async fn multiple_reader_buffer_integrity() { 51 | let (sink, source) = circular_buffer::(BUFFER_SIZE); 52 | let source = source.into_cloneable_view(); 53 | 54 | let write_hash = tokio::spawn(write(sink, 500, 400)); 55 | let read_hashes = (0..10) 56 | .map(|_| tokio::spawn(read(source.clone()))) 57 | .collect::>(); 58 | std::mem::drop(source); // remaining reference doesn't get used, so drop it 59 | 60 | let (write_hash, read_hashes) = 61 | futures::future::join(write_hash, futures::future::join_all(read_hashes)).await; 62 | for read_hash in read_hashes { 63 | assert_eq!(write_hash.as_ref().unwrap(), read_hash.as_ref().unwrap()); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /tests/io.rs: -------------------------------------------------------------------------------- 1 | use rand::{rngs::SmallRng, Rng, SeedableRng}; 2 | use rivulet::{circular_buffer, SplittableView}; 3 | use std::hash::{Hash, Hasher}; 4 | 5 | #[tokio::test] 6 | async fn async_reader_writer() { 7 | use futures::io::{AsyncReadExt, AsyncWriteExt}; 8 | 9 | let (sink, source) = circular_buffer(4096); 10 | let mut write = rivulet::io::AsyncWriter::new(sink); 11 | let mut read = rivulet::io::AsyncReader::new(source.into_view()); 12 | 13 | let sent = tokio::spawn(async move { 14 | let mut rng = SmallRng::from_entropy(); 15 | let values: Vec = (0..1_000_000).map(|_| rng.gen()).collect(); 16 | write.write_all(&values).await.unwrap(); 17 | let mut hasher = seahash::SeaHasher::new(); 18 | values.hash(&mut hasher); 19 | hasher.finish() 20 | }); 21 | 22 | let received = tokio::spawn(async move { 23 | let mut values = Vec::new(); 24 | read.read_to_end(&mut values).await.unwrap(); 25 | let mut hasher = seahash::SeaHasher::new(); 26 | values.hash(&mut hasher); 27 | hasher.finish() 28 | }); 29 | 30 | let (sent, received) = futures::future::join(sent, received).await; 31 | assert_eq!(sent.unwrap(), received.unwrap()); 32 | } 33 | 34 | #[tokio::test] 35 | async fn async_bufreader_writer() { 36 | use futures::io::{AsyncBufReadExt, AsyncWriteExt}; 37 | 38 | let (sink, source) = circular_buffer(4096); 39 | let mut write = rivulet::io::AsyncWriter::new(sink); 40 | let mut read = rivulet::io::AsyncReader::new(source.into_view()); 41 | 42 | let sent = tokio::spawn(async move { 43 | let mut rng = SmallRng::from_entropy(); 44 | let values: Vec = (0..1_000_000).map(|_| rng.gen()).collect(); 45 | write.write_all(&values).await.unwrap(); 46 | let mut hasher = seahash::SeaHasher::new(); 47 | values.hash(&mut hasher); 48 | hasher.finish() 49 | }); 50 | 51 | let received = tokio::spawn(async move { 52 | let mut values = Vec::new(); 53 | while read.read_until(0, &mut values).await.unwrap() != 0 {} 54 | let mut hasher = seahash::SeaHasher::new(); 55 | values.hash(&mut hasher); 56 | hasher.finish() 57 | }); 58 | 59 | let (sent, received) = futures::future::join(sent, received).await; 60 | assert_eq!(sent.unwrap(), received.unwrap()); 61 | } 62 | 63 | #[test] 64 | fn sync_reader_writer() { 65 | use std::io::{Read, Write}; 66 | 67 | let (sink, source) = circular_buffer(4096); 68 | let mut write = rivulet::io::Writer::new(sink); 69 | let mut read = rivulet::io::Reader::new(source.into_view()); 70 | 71 | let sent = std::thread::spawn(move || { 72 | let mut rng = SmallRng::from_entropy(); 73 | let values: Vec = (0..1_000_000).map(|_| rng.gen()).collect(); 74 | write.write_all(&values).unwrap(); 75 | let mut hasher = seahash::SeaHasher::new(); 76 | values.hash(&mut hasher); 77 | hasher.finish() 78 | }); 79 | 80 | let received = std::thread::spawn(move || { 81 | let mut values = Vec::new(); 82 | read.read_to_end(&mut values).unwrap(); 83 | let mut hasher = seahash::SeaHasher::new(); 84 | values.hash(&mut hasher); 85 | hasher.finish() 86 | }); 87 | 88 | let sent = sent.join().unwrap(); 89 | let received = received.join().unwrap(); 90 | assert_eq!(sent, received); 91 | } 92 | 93 | #[test] 94 | fn sync_bufreader_writer() { 95 | use std::io::{BufRead, Write}; 96 | 97 | let (sink, source) = circular_buffer(4096); 98 | let mut write = rivulet::io::Writer::new(sink); 99 | let mut read = rivulet::io::Reader::new(source.into_view()); 100 | 101 | let sent = std::thread::spawn(move || { 102 | let mut rng = SmallRng::from_entropy(); 103 | let values: Vec = (0..1_000_000).map(|_| rng.gen()).collect(); 104 | write.write_all(&values).unwrap(); 105 | let mut hasher = seahash::SeaHasher::new(); 106 | values.hash(&mut hasher); 107 | hasher.finish() 108 | }); 109 | 110 | let received = std::thread::spawn(move || { 111 | let mut values = Vec::new(); 112 | while read.read_until(0, &mut values).unwrap() != 0 {} 113 | let mut hasher = seahash::SeaHasher::new(); 114 | values.hash(&mut hasher); 115 | hasher.finish() 116 | }); 117 | 118 | let sent = sent.join().unwrap(); 119 | let received = received.join().unwrap(); 120 | assert_eq!(sent, received); 121 | } 122 | -------------------------------------------------------------------------------- /tests/lazy.rs: -------------------------------------------------------------------------------- 1 | use rand::{rngs::SmallRng, Rng, SeedableRng}; 2 | use rivulet::{circular_buffer, lazy, SplittableView, View, ViewMut}; 3 | use std::hash::Hasher; 4 | 5 | static BUFFER_SIZE: usize = 4096; 6 | 7 | async fn write + Send + Unpin>( 8 | mut sink: T, 9 | block: usize, 10 | count: usize, 11 | ) -> u64 { 12 | let mut hasher = seahash::SeaHasher::new(); 13 | let mut rng = SmallRng::from_entropy(); 14 | for _ in 0..count { 15 | sink.grant(block).await.unwrap(); 16 | for value in &mut sink.view_mut()[..block] { 17 | *value = rng.gen(); 18 | hasher.write_i64(*value); 19 | } 20 | sink.release(block); 21 | } 22 | hasher.finish() 23 | } 24 | 25 | async fn read + Send + Unpin>(mut source: T) -> u64 { 26 | let mut hasher = seahash::SeaHasher::new(); 27 | let mut rng = SmallRng::from_entropy(); 28 | loop { 29 | let count = rng.gen_range(1..BUFFER_SIZE); 30 | source.grant(count).await.unwrap(); 31 | if source.view().is_empty() { 32 | break hasher.finish(); 33 | } 34 | for value in source.view() { 35 | hasher.write_i64(*value); 36 | } 37 | let released = source.view().len(); 38 | source.release(released); 39 | } 40 | } 41 | 42 | #[tokio::test] 43 | async fn lazy_view() { 44 | let (sink, source) = circular_buffer::(BUFFER_SIZE); 45 | 46 | let write_hash = tokio::spawn(write(lazy::Lazy::new(|| sink), 500, 400)); 47 | let read_hash = tokio::spawn(read(lazy::Lazy::new(|| source.into_view()))); 48 | 49 | let (write_hash, read_hash) = futures::future::join(write_hash, read_hash).await; 50 | assert_eq!(write_hash.unwrap(), read_hash.unwrap()); 51 | } 52 | 53 | #[tokio::test] 54 | async fn lazy_channel() { 55 | let (sink, source) = lazy::lazy_channel(|| { 56 | let (sink, source) = circular_buffer::(BUFFER_SIZE); 57 | (sink, source.into_view()) 58 | }); 59 | 60 | let write_hash = tokio::spawn(write(sink, 500, 400)); 61 | let read_hash = tokio::spawn(read(source)); 62 | 63 | let (write_hash, read_hash) = futures::future::join(write_hash, read_hash).await; 64 | assert_eq!(write_hash.unwrap(), read_hash.unwrap()); 65 | } 66 | -------------------------------------------------------------------------------- /tests/object_safe.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused)] 2 | 3 | fn view_is_object_safe() -> Option<&'static mut dyn rivulet::View> { 4 | None 5 | } 6 | 7 | fn viewmut_is_object_safe() -> Option<&'static mut dyn rivulet::ViewMut> { 8 | None 9 | } 10 | -------------------------------------------------------------------------------- /tests/sequence.rs: -------------------------------------------------------------------------------- 1 | use rand::{rngs::SmallRng, Rng, SeedableRng}; 2 | use rivulet::{circular_buffer, SplittableView, View, ViewMut}; 3 | use std::hash::Hasher; 4 | 5 | static BUFFER_SIZE: usize = 4096; 6 | 7 | async fn write + Send>(mut sink: T, block: usize, count: usize) -> u64 { 8 | let mut hasher = seahash::SeaHasher::new(); 9 | let mut rng = SmallRng::from_entropy(); 10 | for _ in 0..count { 11 | sink.grant(block).await.unwrap(); 12 | for value in &mut sink.view_mut()[..block] { 13 | *value = rng.gen(); 14 | hasher.write_i64(*value); 15 | } 16 | sink.release(block); 17 | } 18 | hasher.finish() 19 | } 20 | 21 | async fn process + Send>(mut view: T) { 22 | let mut rng = SmallRng::from_entropy(); 23 | loop { 24 | let count = rng.gen_range(1..BUFFER_SIZE / 2); 25 | view.grant(count).await.unwrap(); 26 | if view.view().is_empty() { 27 | break; 28 | } 29 | for value in view.view_mut() { 30 | *value *= -1; 31 | } 32 | view.release(view.view().len()); 33 | } 34 | } 35 | 36 | async fn read + Send>(mut source: T, processed: bool) -> u64 { 37 | let mut hasher = seahash::SeaHasher::new(); 38 | let mut rng = SmallRng::from_entropy(); 39 | let factor = if processed { -1 } else { 1 }; 40 | loop { 41 | let count = rng.gen_range(1..BUFFER_SIZE / 2); 42 | source.grant(count).await.unwrap(); 43 | if source.view().is_empty() { 44 | break hasher.finish(); 45 | } 46 | for value in source.view() { 47 | hasher.write_i64(factor * *value); 48 | } 49 | source.release(source.view().len()); 50 | } 51 | } 52 | 53 | #[tokio::test] 54 | async fn sequence_single_reader() { 55 | let (sink, source) = circular_buffer::(BUFFER_SIZE); 56 | let (first, second) = source.sequence(); 57 | 58 | let write_hash = tokio::spawn(write(sink, 500, 400)); 59 | let process = tokio::spawn(process(first.into_view())); 60 | let read_hash = tokio::spawn(read(second.into_view(), true)); 61 | 62 | let (write_hash, read_hash, process) = tokio::join!(write_hash, read_hash, process); 63 | 64 | process.unwrap(); 65 | assert_eq!(write_hash.unwrap(), read_hash.unwrap()); 66 | } 67 | 68 | #[tokio::test] 69 | async fn sequence_multiple_reader() { 70 | let (sink, source) = circular_buffer::(BUFFER_SIZE); 71 | let (first, second) = source.sequence(); 72 | 73 | let second = second.into_cloneable_view(); 74 | 75 | let write_hash = tokio::spawn(write(sink, 500, 400)); 76 | let process = tokio::spawn(process(first.into_view())); 77 | let read_hashes = (0..10) 78 | .map(|_| tokio::spawn(read(second.clone(), true))) 79 | .collect::>(); 80 | std::mem::drop(second); // remaining reference doesn't get used, so drop it 81 | 82 | let (write_hash, read_hashes, process) = 83 | tokio::join!(write_hash, futures::future::join_all(read_hashes), process); 84 | 85 | process.unwrap(); 86 | for read_hash in read_hashes { 87 | assert_eq!(write_hash.as_ref().unwrap(), read_hash.as_ref().unwrap()); 88 | } 89 | } 90 | 91 | #[tokio::test] 92 | async fn sequence_drop_first() { 93 | let (mut sink, source) = circular_buffer::(BUFFER_SIZE); 94 | let (_, second) = source.sequence(); 95 | let mut second = second.into_view(); 96 | 97 | // Add a bit of data. 98 | sink.grant(1).await.unwrap(); 99 | sink.release(1); 100 | 101 | // The stream is closed, so this should return an empty view. 102 | second.grant(100).await.unwrap(); 103 | assert_eq!(second.view(), &[]); 104 | } 105 | 106 | #[tokio::test] 107 | async fn sequence_drop_second() { 108 | let (sink, source) = circular_buffer::(BUFFER_SIZE); 109 | let (first, _) = source.sequence(); 110 | 111 | let write_hash = tokio::spawn(write(sink, 500, 400)); 112 | let read_hash = tokio::spawn(read(first.into_view(), false)); 113 | 114 | let (write_hash, read_hash) = tokio::join!(write_hash, read_hash); 115 | 116 | assert_eq!(write_hash.unwrap(), read_hash.unwrap()); 117 | } 118 | -------------------------------------------------------------------------------- /tests/slice.rs: -------------------------------------------------------------------------------- 1 | use rivulet::{ 2 | slice::{Slice, SliceMut}, 3 | SplittableView, View, ViewMut, 4 | }; 5 | 6 | #[test] 7 | fn slice() { 8 | let storage: Vec = (0..100).collect(); 9 | let mut stream = Slice::new(&storage).into_view(); 10 | 11 | // There should be an immediate view available 12 | stream.blocking_grant(0).unwrap(); 13 | let view = stream.view(); 14 | assert_eq!(view.len(), 100); 15 | assert_eq!(view[0], 0); 16 | 17 | // Request a grant, nothing should change 18 | stream.blocking_grant(10).unwrap(); 19 | let view = stream.view(); 20 | assert_eq!(view.len(), 100); 21 | assert_eq!(view[0], 0); 22 | 23 | // Release a few items and change a value 24 | stream.release(5); 25 | let view = stream.view(); 26 | assert_eq!(view.len(), 95); 27 | assert_eq!(view[0], 5); 28 | 29 | // Do a giant (but useless) grant request and release the remaining items except one 30 | stream.blocking_grant(1000).unwrap(); 31 | stream.release(94); 32 | let view = stream.view(); 33 | assert_eq!(view.len(), 1); 34 | assert_eq!(view[0], 99); 35 | 36 | // Release the last item 37 | stream.release(1); 38 | let view = stream.view(); 39 | assert!(view.is_empty()); 40 | } 41 | 42 | #[test] 43 | fn slice_mut() { 44 | let mut storage: Vec = (0..100).collect(); 45 | let mut stream = SliceMut::new(&mut storage).into_view(); 46 | 47 | // There should be an immediate view available 48 | stream.blocking_grant(0).unwrap(); 49 | let view = stream.view(); 50 | assert_eq!(view.len(), 100); 51 | assert_eq!(view[0], 0); 52 | 53 | // Request a grant, nothing should change 54 | stream.blocking_grant(10).unwrap(); 55 | let view = stream.view(); 56 | assert_eq!(view.len(), 100); 57 | assert_eq!(view[0], 0); 58 | 59 | // Release a few items and change a value 60 | stream.release(5); 61 | let view = stream.view_mut(); 62 | assert_eq!(view.len(), 95); 63 | assert_eq!(view[0], 5); 64 | view[0] = 101; 65 | 66 | // Do a giant (but useless) grant request and release the remaining items except one 67 | stream.blocking_grant(1000).unwrap(); 68 | stream.release(94); 69 | let view = stream.view(); 70 | assert_eq!(view.len(), 1); 71 | assert_eq!(view[0], 99); 72 | 73 | // Release the last item 74 | stream.release(1); 75 | let view = stream.view(); 76 | assert!(view.is_empty()); 77 | 78 | // Check that the storage contains the original with the modification 79 | let expected = { 80 | let mut expected: Vec = (0..100).collect(); 81 | expected[5] = 101; 82 | expected 83 | }; 84 | assert_eq!(storage, expected); 85 | } 86 | 87 | #[test] 88 | #[should_panic] 89 | fn bad_release() { 90 | let storage: Vec = (0..100).collect(); 91 | let mut stream = Slice::new(&storage).into_view(); 92 | stream.release(101); 93 | } 94 | --------------------------------------------------------------------------------