├── .clippy.toml ├── .github ├── FUNDING.yml └── workflows │ └── ci.yml ├── .gitignore ├── .rustfmt.toml ├── Cargo.toml ├── LICENSE ├── README.md ├── eyeball-im-util ├── CHANGELOG.md ├── Cargo.toml ├── src │ ├── lib.rs │ ├── vector.rs │ └── vector │ │ ├── filter.rs │ │ ├── head.rs │ │ ├── ops.rs │ │ ├── skip.rs │ │ ├── sort.rs │ │ ├── tail.rs │ │ └── traits.rs └── tests │ └── it │ ├── filter.rs │ ├── filter_map.rs │ ├── head.rs │ ├── main.rs │ ├── skip.rs │ ├── sort.rs │ ├── sort_by.rs │ ├── sort_by_key.rs │ └── tail.rs ├── eyeball-im ├── CHANGELOG.md ├── Cargo.toml ├── src │ ├── lib.rs │ ├── reusable_box.rs │ ├── vector.rs │ └── vector │ │ ├── entry.rs │ │ ├── subscriber.rs │ │ └── transaction.rs └── tests │ └── it │ ├── apply.rs │ ├── batch.rs │ ├── entry.rs │ ├── main.rs │ ├── panic.rs │ └── serde.rs └── eyeball ├── CHANGELOG.md ├── Cargo.toml ├── README.md ├── benches └── set_a_lot.rs ├── src ├── lib.rs ├── lock.rs ├── read_guard.rs ├── shared.rs ├── state.rs ├── subscriber.rs ├── subscriber │ └── async_lock.rs └── unique.rs └── tests └── it ├── async_lock.rs ├── main.rs ├── shared.rs └── unique.rs /.clippy.toml: -------------------------------------------------------------------------------- 1 | avoid-breaking-exported-api = false 2 | -------------------------------------------------------------------------------- /.github/ FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [jplatte, Hywan] 2 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: [main] 7 | pull_request: 8 | branches: [main] 9 | 10 | env: 11 | CARGO_TERM_COLOR: always 12 | 13 | jobs: 14 | test: 15 | name: Run tests (Rust stable) 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - uses: actions/checkout@v4 20 | - uses: dtolnay/rust-toolchain@stable 21 | - uses: Swatinem/rust-cache@v2 22 | - run: cargo test --all-features 23 | 24 | docs: 25 | name: Check documentation (Rust stable) 26 | runs-on: ubuntu-latest 27 | 28 | steps: 29 | - uses: actions/checkout@v4 30 | - uses: dtolnay/rust-toolchain@stable 31 | - uses: Swatinem/rust-cache@v2 32 | - run: cargo doc --all-features 33 | env: 34 | RUSTDOCFLAGS: "-D warnings" 35 | 36 | formatting: 37 | name: Check Formatting 38 | runs-on: ubuntu-latest 39 | 40 | steps: 41 | - uses: actions/checkout@v4 42 | - uses: dtolnay/rust-toolchain@master 43 | with: 44 | toolchain: nightly 45 | components: rustfmt 46 | - run: cargo fmt -- --check 47 | 48 | test-miri: 49 | name: Run tests with miri (Rust nightly) 50 | runs-on: ubuntu-latest 51 | 52 | steps: 53 | - uses: actions/checkout@v4 54 | - uses: dtolnay/rust-toolchain@nightly 55 | with: 56 | components: miri 57 | - uses: Swatinem/rust-cache@v2 58 | - run: cargo miri test --all-features 59 | env: 60 | # Enable Tree Borrows, Stacked Borrows is weirdly restrictive 61 | # https://github.com/jneem/imbl/issues/59#issuecomment-1569746186 62 | MIRIFLAGS: "-Zmiri-tree-borrows" 63 | 64 | clippy: 65 | name: Run clippy (Rust nightly) 66 | runs-on: ubuntu-latest 67 | 68 | steps: 69 | - uses: actions/checkout@v4 70 | - uses: dtolnay/rust-toolchain@nightly 71 | with: 72 | components: clippy 73 | - uses: Swatinem/rust-cache@v2 74 | - run: cargo clippy --all-features -- -D warnings 75 | 76 | typos: 77 | name: Spell Check with Typos 78 | runs-on: ubuntu-latest 79 | 80 | steps: 81 | - uses: actions/checkout@v4 82 | - uses: crate-ci/typos@v1.29.4 83 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.vscode 2 | /target 3 | /Cargo.lock 4 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | format_code_in_doc_comments = true 2 | imports_granularity = "Crate" 3 | newline_style = "Unix" 4 | use_field_init_shorthand = true 5 | use_small_heuristics = "Max" 6 | wrap_comments = true 7 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["eyeball", "eyeball-im", "eyeball-im-util"] 3 | resolver = "2" 4 | 5 | [workspace.package] 6 | license = "MPL-2.0" 7 | repository = "https://github.com/jplatte/eyeball" 8 | categories = ["asynchronous", "gui"] 9 | keywords = ["async", "observable", "reactive"] 10 | 11 | [workspace.dependencies] 12 | assert_matches = "1.5.0" 13 | futures-core = "0.3.26" 14 | futures-util = { version = "0.3.26", default-features = false } 15 | imbl = "5.0.0" 16 | readlock = "0.1.5" 17 | stream_assert = "0.1.0" 18 | tokio = { version = "1.25.0", features = ["sync"] } 19 | tokio-util = "0.7.8" 20 | tracing = { version = "0.1.37", default-features = false, features = ["std"] } 21 | 22 | [workspace.lints.rust] 23 | rust_2018_idioms = { level = "warn", priority = -1 } 24 | missing_debug_implementations = "warn" 25 | missing_docs = "warn" 26 | semicolon_in_expressions_from_macros = "warn" 27 | unreachable_pub = "warn" 28 | unused_import_braces = "warn" 29 | unused_qualifications = "warn" 30 | 31 | [workspace.lints.clippy] 32 | branches_sharing_code = "warn" 33 | cloned_instead_of_copied = "warn" 34 | dbg_macro = "warn" 35 | empty_line_after_outer_attr = "warn" 36 | inefficient_to_string = "warn" 37 | macro_use_imports = "warn" 38 | map_flatten = "warn" 39 | mod_module_files = "warn" 40 | mut_mut = "warn" 41 | nonstandard_macro_braces = "warn" 42 | semicolon_if_nothing_returned = "warn" 43 | str_to_string = "warn" 44 | todo = "warn" 45 | unreadable_literal = "warn" 46 | unseparated_literal_suffix = "warn" 47 | wildcard_imports = "warn" 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # eyeball 👁️ 2 | 3 | > eyeball (verb) - synonym for observe 4 | 5 | Add observability to your Rust types! 6 | Currently, polling observables is only possibly in async Rust, 7 | but this constraint might be lifted in the future. 8 | 9 | This repository hosts the following crates: 10 | 11 | - [eyeball](./eyeball/) – Contains the basic `Observable` type and things related to that 12 | - [eyeball-im](./eyeball-im/) – Contains observable collections (currently only `ObservableVector`) 13 | - [eyeball-im-util](./eyeball-im-util/) – Contains additional utilities on top of `eyeball-im` 14 | 15 | Click on one of the links to find out more. 16 | 17 | ## License 18 | 19 | Both crates are distributed under the terms of the Mozilla Public License 2.0. 20 | You can find the license text in the [LICENSE](./LICENSE) file. 21 | -------------------------------------------------------------------------------- /eyeball-im-util/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.9.0 2 | 3 | - Upgrade `eyeball-im` dependency to 0.7 4 | 5 | # 0.8.1 6 | 7 | - Add the `Skip` adapter 8 | - Add the `Tail` adapter 9 | - Improve the documentation of `Head` by adding an example 10 | 11 | # 0.8.0 12 | 13 | - Rename `Limit` to `Head`, along with all the similar methods, like 14 | `VectorObserverExt::limit` which becomes `VectorObserverExt::head` and so on. 15 | - Upgrade `eyeball-im` dependency to 0.6 16 | 17 | # 0.7.0 18 | 19 | - Remove `Send` and `Sync` constraints from some traits and associated types 20 | 21 | # 0.6.0 22 | 23 | - Remove lifetime parameter from `SortBy` 24 | - Allow `SortBy::new` and `VectorObserverExt::sort_by` to accept a callable 25 | directly, instead of through a reference (references still work since `&F` 26 | also implements `Fn(X) -> Y` if `F` does) 27 | - Add `Sort`, `SortByKey` adapters and corresponding `VectorObserverExt` methods 28 | - Upgrade `imbl` dependency to version 3 29 | 30 | # 0.5.3 31 | 32 | - Add the `SortBy` adapter 33 | 34 | # 0.5.2 35 | 36 | - Optimize the implementation of the `Limit` adapter 37 | - Fix broken links in the documentation 38 | 39 | # 0.5.1 40 | 41 | - Fix a bug where the `Limit` adapter would fail to register interest in new 42 | items from its limit stream 43 | 44 | # 0.5.0 45 | 46 | - Move bounds to simplify documentation 47 | - Technically a breaking change, which is why this version is 0.5.0 48 | - Add `VectorSubscriberExt` and `VectorObserverExt` for fluent adapter 49 | construction 50 | 51 | # 0.4.0 52 | 53 | - Upgrade `eyeball-im` dependency to 0.4.0 54 | - Add `Limit` adapter for presenting a limited view of an underlying observable 55 | vector 56 | 57 | # 0.3.1 58 | 59 | - Fix a bug with `Filter` and `FilterMap` that was corrupting their internal 60 | state, leading to invalid output or panics, when the underlying stream 61 | produced a `VectorDiff::Insert` 62 | 63 | # 0.3.0 64 | 65 | - Upgrade `eyeball-im` dependency to 0.3.0 66 | - Make adapter types compatible with new batched streams 67 | - Remove `VectorExt` as it didn't fit in very well with the new stream types 68 | - Rename / move adapter types 69 | - `FilterVectorSubscriber` is now `vector::Filter` 70 | - `FilterMapVectorSubscriber` is now `vector::FilterMap` 71 | 72 | # 0.2.2 73 | 74 | This release only updates metadata for crates.io. 75 | 76 | # 0.2.1 77 | 78 | - Export `FilterMapVectorSubscriber` from the crate root 79 | 80 | # 0.2.0 81 | 82 | - Rename `subscribe_filtered` to `subscribe_filter` 83 | - Rename `FilteredVectorSubscriber` to `FilterVectorSubscriber` 84 | - Add `subscribe_filter_map` and `FilterMapVectorSubscriber` 85 | -------------------------------------------------------------------------------- /eyeball-im-util/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "eyeball-im-util" 3 | version = "0.9.0" 4 | edition = "2021" 5 | rust-version = "1.65.0" 6 | description = "Helpful utilities for `eyeball-im`." 7 | license.workspace = true 8 | repository.workspace = true 9 | categories.workspace = true 10 | keywords.workspace = true 11 | 12 | [package.metadata.docs.rs] 13 | all-features = true 14 | 15 | [dependencies] 16 | arrayvec = "0.7.4" 17 | eyeball-im = { version = "0.7.0", path = "../eyeball-im" } 18 | futures-core.workspace = true 19 | imbl.workspace = true 20 | pin-project-lite = "0.2.9" 21 | smallvec = { version = "1.11.2", features = ["const_generics", "const_new"] } 22 | 23 | [dev-dependencies] 24 | eyeball = { version = "0.8.6", path = "../eyeball" } 25 | futures-util.workspace = true 26 | stream_assert.workspace = true 27 | tokio = { workspace = true, features = ["macros", "rt"] } 28 | 29 | [lints] 30 | workspace = true 31 | -------------------------------------------------------------------------------- /eyeball-im-util/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Helpful utilities for [`eyeball-im`][eyeball_im]. 2 | 3 | pub mod vector; 4 | -------------------------------------------------------------------------------- /eyeball-im-util/src/vector.rs: -------------------------------------------------------------------------------- 1 | //! Utilities around [`ObservableVector`][eyeball_im::ObservableVector]. 2 | 3 | mod filter; 4 | mod head; 5 | mod ops; 6 | mod skip; 7 | mod sort; 8 | mod tail; 9 | mod traits; 10 | 11 | use eyeball_im::VectorDiff; 12 | use futures_core::Stream; 13 | 14 | use self::ops::{VectorDiffContainerFamilyMember, VectorDiffContainerOps}; 15 | pub use self::{ 16 | filter::{Filter, FilterMap}, 17 | head::{EmptyLimitStream, Head}, 18 | skip::{EmptyCountStream, Skip}, 19 | sort::{Sort, SortBy, SortByKey}, 20 | tail::Tail, 21 | traits::{ 22 | BatchedVectorSubscriber, VectorDiffContainer, VectorObserver, VectorObserverExt, 23 | VectorSubscriberExt, 24 | }, 25 | }; 26 | 27 | /// Type alias for extracting the element type from a stream of 28 | /// [`VectorDiffContainer`]s. 29 | pub type VectorDiffContainerStreamElement = 30 | <::Item as VectorDiffContainer>::Element; 31 | 32 | /// Type alias for extracting the stream item type after the element type was 33 | /// mapped to the given type `U`, from a stream of [`VectorDiffContainer`]s. 34 | pub type VectorDiffContainerStreamMappedItem = 35 | VectorDiffContainerFamilyMember, U>; 36 | 37 | /// Type alias for extracting the [`VectorDiffContainerFamily`] type from a 38 | /// stream of [`VectorDiffContainer`]s. 39 | /// 40 | /// [`VectorDiffContainerFamily`]: ops::VectorDiffContainerFamily 41 | type VectorDiffContainerStreamFamily = 42 | <::Item as VectorDiffContainerOps>>::Family; 43 | 44 | /// Type alias for a `VectorDiff` of `VectorDiffContainerStreamElement`s. 45 | type VectorDiffContainerDiff = VectorDiff>; 46 | 47 | /// Type alias for extracting the buffer type from a stream of 48 | /// [`VectorDiffContainer`]s' `HeadBuf`. 49 | type VectorDiffContainerStreamHeadBuf = 50 | <::Item as VectorDiffContainerOps>>::HeadBuf; 51 | 52 | /// Type alias for extracting the buffer type from a stream of 53 | /// [`VectorDiffContainer`]s' `TailBuf`. 54 | type VectorDiffContainerStreamTailBuf = 55 | <::Item as VectorDiffContainerOps>>::TailBuf; 56 | 57 | /// Type alias for extracting the buffer type from a stream of 58 | /// [`VectorDiffContainer`]s' `SkipBuf`. 59 | type VectorDiffContainerStreamSkipBuf = 60 | <::Item as VectorDiffContainerOps>>::SkipBuf; 61 | 62 | /// Type alias for extracting the buffer type from a stream of 63 | /// [`VectorDiffContainer`]s' `SortBuf`. 64 | type VectorDiffContainerStreamSortBuf = 65 | <::Item as VectorDiffContainerOps>>::SortBuf; 66 | -------------------------------------------------------------------------------- /eyeball-im-util/src/vector/filter.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::VecDeque, 3 | ops::Not, 4 | pin::Pin, 5 | task::{self, ready, Poll}, 6 | }; 7 | 8 | use eyeball_im::{Vector, VectorDiff}; 9 | use futures_core::Stream; 10 | use pin_project_lite::pin_project; 11 | 12 | use super::{ 13 | VectorDiffContainer, VectorDiffContainerDiff, VectorDiffContainerOps, 14 | VectorDiffContainerStreamElement, VectorDiffContainerStreamMappedItem, 15 | }; 16 | 17 | pin_project! { 18 | /// A [`VectorDiff`] stream adapter that presents a filtered view of the 19 | /// underlying [`ObservableVector`]s items. 20 | /// 21 | /// [`ObservableVector`]: eyeball_im::ObservableVector 22 | pub struct Filter { 23 | #[pin] 24 | inner: FilterImpl, 25 | filter: F, 26 | } 27 | } 28 | 29 | impl Filter 30 | where 31 | S: Stream, 32 | S::Item: VectorDiffContainer, 33 | F: Fn(&VectorDiffContainerStreamElement) -> bool, 34 | { 35 | /// Create a new `Filter` with the given (unfiltered) initial values, stream 36 | /// of `VectorDiff` updates for those values, and filter. 37 | pub fn new( 38 | mut values: Vector>, 39 | inner: S, 40 | filter: F, 41 | ) -> (Vector>, Self) { 42 | let original_len = values.len(); 43 | let mut filtered_indices = VecDeque::new(); 44 | 45 | let mut original_idx = 0; 46 | values.retain(|val| { 47 | let keep = filter(val); 48 | if keep { 49 | filtered_indices.push_back(original_idx); 50 | } 51 | original_idx += 1; 52 | keep 53 | }); 54 | 55 | let inner = FilterImpl { inner, filtered_indices, original_len }; 56 | (values, Self { inner, filter }) 57 | } 58 | } 59 | 60 | impl Stream for Filter 61 | where 62 | S: Stream, 63 | S::Item: VectorDiffContainer, 64 | F: Fn(&VectorDiffContainerStreamElement) -> bool, 65 | { 66 | type Item = S::Item; 67 | 68 | fn poll_next(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll> { 69 | let projected = self.project(); 70 | projected.inner.project().handle_diff_filter(&*projected.filter, cx) 71 | } 72 | } 73 | 74 | pin_project! { 75 | /// A [`VectorDiff`] stream adapter that presents a filter+mapped view of 76 | /// the underlying [`ObservableVector`]s items. 77 | /// 78 | /// [`ObservableVector`]: eyeball_im::ObservableVector 79 | pub struct FilterMap { 80 | #[pin] 81 | inner: FilterImpl, 82 | filter: F, 83 | } 84 | } 85 | 86 | impl FilterMap 87 | where 88 | S: Stream, 89 | S::Item: VectorDiffContainer, 90 | U: Clone, 91 | F: Fn(VectorDiffContainerStreamElement) -> Option, 92 | { 93 | /// Create a new `Filter` with the given (un-filter+mapped) initial values, 94 | /// stream of `VectorDiff` updates for those values, and filter. 95 | pub fn new( 96 | values: Vector>, 97 | inner: S, 98 | filter: F, 99 | ) -> (Vector, Self) { 100 | let original_len = values.len(); 101 | let (values, filtered_indices) = values 102 | .iter() 103 | .enumerate() 104 | .filter_map(|(original_idx, val)| { 105 | filter(val.clone()).map(|mapped| (mapped, original_idx)) 106 | }) 107 | .unzip(); 108 | 109 | let inner = FilterImpl { inner, filtered_indices, original_len }; 110 | (values, Self { inner, filter }) 111 | } 112 | } 113 | 114 | impl Stream for FilterMap 115 | where 116 | S: Stream, 117 | S::Item: VectorDiffContainer, 118 | U: Clone, 119 | F: Fn(VectorDiffContainerStreamElement) -> Option, 120 | { 121 | type Item = VectorDiffContainerStreamMappedItem; 122 | 123 | fn poll_next(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll> { 124 | let projected = self.project(); 125 | projected.inner.project().handle_diff_filter_map(&*projected.filter, cx) 126 | } 127 | } 128 | 129 | pin_project! { 130 | #[project = FilterImplProj] 131 | pub(super) struct FilterImpl { 132 | #[pin] 133 | inner: S, 134 | // Original indices of the elements the filter was applied to. 135 | // 136 | // For example, if the first element of this list is 1, that means the 137 | // first original element got filtered out so a set for index = 1 should 138 | // translate to a set for index = 0 on the filtered elements (if the 139 | // filter still matches after the set operation). 140 | filtered_indices: VecDeque, 141 | // Length of the original vector (before filter). 142 | original_len: usize, 143 | } 144 | } 145 | 146 | impl FilterImplProj<'_, S> 147 | where 148 | S: Stream, 149 | S::Item: VectorDiffContainer, 150 | { 151 | fn append_filter( 152 | &mut self, 153 | mut values: Vector>, 154 | f: &F, 155 | ) -> Option>> 156 | where 157 | F: Fn(&VectorDiffContainerStreamElement) -> bool, 158 | { 159 | let mut original_idx = *self.original_len; 160 | *self.original_len += values.len(); 161 | values.retain(|value| { 162 | let keep = f(value); 163 | if keep { 164 | self.filtered_indices.push_back(original_idx); 165 | } 166 | original_idx += 1; 167 | keep 168 | }); 169 | 170 | values.is_empty().not().then_some(values) 171 | } 172 | 173 | fn append_filter_map( 174 | &mut self, 175 | values: Vector>, 176 | f: &F, 177 | ) -> Option> 178 | where 179 | U: Clone, 180 | F: Fn(VectorDiffContainerStreamElement) -> Option, 181 | { 182 | let mut original_idx = *self.original_len; 183 | *self.original_len += values.len(); 184 | let mapped_values: Vector<_> = values 185 | .into_iter() 186 | .filter_map(|val| { 187 | let result = f(val).map(|mapped| { 188 | self.filtered_indices.push_back(original_idx); 189 | mapped 190 | }); 191 | original_idx += 1; 192 | result 193 | }) 194 | .collect(); 195 | 196 | mapped_values.is_empty().not().then_some(mapped_values) 197 | } 198 | 199 | fn handle_append_filter( 200 | &mut self, 201 | values: Vector>, 202 | f: &F, 203 | ) -> Option> 204 | where 205 | F: Fn(&VectorDiffContainerStreamElement) -> bool, 206 | { 207 | self.append_filter(values, f).map(|values| VectorDiff::Append { values }) 208 | } 209 | 210 | fn handle_append_filter_map( 211 | &mut self, 212 | values: Vector>, 213 | f: &F, 214 | ) -> Option> 215 | where 216 | U: Clone, 217 | F: Fn(VectorDiffContainerStreamElement) -> Option, 218 | { 219 | self.append_filter_map(values, f).map(|values| VectorDiff::Append { values }) 220 | } 221 | 222 | fn handle_clear(&mut self) -> Option> { 223 | self.filtered_indices.clear(); 224 | *self.original_len = 0; 225 | Some(VectorDiff::Clear) 226 | } 227 | 228 | fn handle_push_front( 229 | &mut self, 230 | value: VectorDiffContainerStreamElement, 231 | f: &F, 232 | ) -> Option> 233 | where 234 | U: Clone, 235 | F: Fn(VectorDiffContainerStreamElement) -> Option, 236 | { 237 | *self.original_len += 1; 238 | for idx in &mut *self.filtered_indices { 239 | *idx += 1; 240 | } 241 | 242 | f(value).map(|value| { 243 | self.filtered_indices.push_front(0); 244 | VectorDiff::PushFront { value } 245 | }) 246 | } 247 | 248 | fn handle_push_back( 249 | &mut self, 250 | value: VectorDiffContainerStreamElement, 251 | f: &F, 252 | ) -> Option> 253 | where 254 | U: Clone, 255 | F: Fn(VectorDiffContainerStreamElement) -> Option, 256 | { 257 | let original_idx = *self.original_len; 258 | *self.original_len += 1; 259 | f(value).map(|value| { 260 | self.filtered_indices.push_back(original_idx); 261 | VectorDiff::PushBack { value } 262 | }) 263 | } 264 | 265 | fn handle_pop_front(&mut self) -> Option> { 266 | *self.original_len -= 1; 267 | let result = self.filtered_indices.front().map_or(false, |&idx| idx == 0).then(|| { 268 | assert!(self.filtered_indices.pop_front().is_some()); 269 | VectorDiff::PopFront 270 | }); 271 | for idx in &mut *self.filtered_indices { 272 | *idx -= 1; 273 | } 274 | 275 | result 276 | } 277 | 278 | fn handle_pop_back(&mut self) -> Option> { 279 | *self.original_len -= 1; 280 | self.filtered_indices.back().map_or(false, |&idx| idx == *self.original_len).then(|| { 281 | assert!(self.filtered_indices.pop_back().is_some()); 282 | VectorDiff::PopBack 283 | }) 284 | } 285 | 286 | fn handle_insert( 287 | &mut self, 288 | index: usize, 289 | value: VectorDiffContainerStreamElement, 290 | f: &F, 291 | ) -> Option> 292 | where 293 | U: Clone, 294 | F: Fn(VectorDiffContainerStreamElement) -> Option, 295 | { 296 | *self.original_len += 1; 297 | let original_idx = index; 298 | let index = self.filtered_indices.partition_point(|&i| i < original_idx); 299 | for idx in self.filtered_indices.iter_mut().skip(index) { 300 | *idx += 1; 301 | } 302 | 303 | f(value).map(|value| { 304 | self.filtered_indices.insert(index, original_idx); 305 | VectorDiff::Insert { index, value } 306 | }) 307 | } 308 | 309 | fn handle_set( 310 | &mut self, 311 | index: usize, 312 | value: VectorDiffContainerStreamElement, 313 | f: &F, 314 | ) -> Option> 315 | where 316 | U: Clone, 317 | F: Fn(VectorDiffContainerStreamElement) -> Option, 318 | { 319 | let original_idx = index; 320 | let new_value = f(value); 321 | 322 | let index = self.filtered_indices.partition_point(|&i| i < original_idx); 323 | if self.filtered_indices.get(index).map_or(false, |&i| i == original_idx) { 324 | // The previous value matched the filter 325 | Some(if let Some(value) = new_value { 326 | VectorDiff::Set { index, value } 327 | } else { 328 | self.filtered_indices.remove(index); 329 | VectorDiff::Remove { index } 330 | }) 331 | } else { 332 | // The previous value didn't match the filter 333 | new_value.map(|value| { 334 | self.filtered_indices.insert(index, original_idx); 335 | VectorDiff::Insert { index, value } 336 | }) 337 | } 338 | } 339 | 340 | fn handle_remove(&mut self, index: usize) -> Option> { 341 | let original_idx = index; 342 | *self.original_len -= 1; 343 | 344 | let index = self.filtered_indices.partition_point(|&i| i < original_idx); 345 | let result = 346 | self.filtered_indices.get(index).map_or(false, |&i| i == original_idx).then(|| { 347 | // The value that was removed matched the filter 348 | self.filtered_indices.remove(index); 349 | VectorDiff::Remove { index } 350 | }); 351 | 352 | for idx in self.filtered_indices.iter_mut().skip(index) { 353 | *idx -= 1; 354 | } 355 | 356 | result 357 | } 358 | 359 | fn handle_truncate(&mut self, len: usize) -> Option> { 360 | *self.original_len = len; 361 | let new_filtered_len = self.filtered_indices.iter().take_while(|&&idx| idx < len).count(); 362 | (new_filtered_len < self.filtered_indices.len()).then(|| { 363 | self.filtered_indices.truncate(new_filtered_len); 364 | VectorDiff::Truncate { length: new_filtered_len } 365 | }) 366 | } 367 | 368 | fn handle_reset_filter( 369 | &mut self, 370 | values: Vector>, 371 | f: &F, 372 | ) -> Option> 373 | where 374 | F: Fn(&VectorDiffContainerStreamElement) -> bool, 375 | { 376 | self.filtered_indices.clear(); 377 | *self.original_len = 0; 378 | self.append_filter(values, f).map(|values| VectorDiff::Reset { values }) 379 | } 380 | 381 | fn handle_reset_filter_map( 382 | &mut self, 383 | values: Vector>, 384 | f: &F, 385 | ) -> Option> 386 | where 387 | U: Clone, 388 | F: Fn(VectorDiffContainerStreamElement) -> Option, 389 | { 390 | self.filtered_indices.clear(); 391 | *self.original_len = 0; 392 | self.append_filter_map(values, f).map(|values| VectorDiff::Reset { values }) 393 | } 394 | 395 | fn handle_diff_filter(&mut self, f: &F, cx: &mut task::Context<'_>) -> Poll> 396 | where 397 | F: Fn(&VectorDiffContainerStreamElement) -> bool, 398 | { 399 | // Transform filter function into filter_map function. 400 | let f2 = |value| f(&value).then_some(value); 401 | loop { 402 | let Some(diffs) = ready!(self.inner.as_mut().poll_next(cx)) else { 403 | return Poll::Ready(None); 404 | }; 405 | 406 | let result = diffs.filter_map(|diff| match diff { 407 | VectorDiff::Append { values } => self.handle_append_filter(values, f), 408 | VectorDiff::Clear => self.handle_clear(), 409 | VectorDiff::PushFront { value } => self.handle_push_front(value, &f2), 410 | VectorDiff::PushBack { value } => self.handle_push_back(value, &f2), 411 | VectorDiff::PopFront => self.handle_pop_front(), 412 | VectorDiff::PopBack => self.handle_pop_back(), 413 | VectorDiff::Insert { index, value } => self.handle_insert(index, value, &f2), 414 | VectorDiff::Set { index, value } => self.handle_set(index, value, &f2), 415 | VectorDiff::Remove { index } => self.handle_remove(index), 416 | VectorDiff::Truncate { length } => self.handle_truncate(length), 417 | VectorDiff::Reset { values } => self.handle_reset_filter(values, f), 418 | }); 419 | 420 | if let Some(diffs) = result { 421 | return Poll::Ready(Some(diffs)); 422 | } 423 | } 424 | } 425 | 426 | fn handle_diff_filter_map( 427 | &mut self, 428 | f: &F, 429 | cx: &mut task::Context<'_>, 430 | ) -> Poll>> 431 | where 432 | U: Clone, 433 | F: Fn(VectorDiffContainerStreamElement) -> Option, 434 | { 435 | loop { 436 | let Some(diffs) = ready!(self.inner.as_mut().poll_next(cx)) else { 437 | return Poll::Ready(None); 438 | }; 439 | 440 | let result = diffs.filter_map(|diff| match diff { 441 | VectorDiff::Append { values } => self.handle_append_filter_map(values, f), 442 | VectorDiff::Clear => self.handle_clear(), 443 | VectorDiff::PushFront { value } => self.handle_push_front(value, f), 444 | VectorDiff::PushBack { value } => self.handle_push_back(value, f), 445 | VectorDiff::PopFront => self.handle_pop_front(), 446 | VectorDiff::PopBack => self.handle_pop_back(), 447 | VectorDiff::Insert { index, value } => self.handle_insert(index, value, f), 448 | VectorDiff::Set { index, value } => self.handle_set(index, value, f), 449 | VectorDiff::Remove { index } => self.handle_remove(index), 450 | VectorDiff::Truncate { length } => self.handle_truncate(length), 451 | VectorDiff::Reset { values } => self.handle_reset_filter_map(values, f), 452 | }); 453 | 454 | if let Some(diffs) = result { 455 | return Poll::Ready(Some(diffs)); 456 | } 457 | } 458 | } 459 | } 460 | -------------------------------------------------------------------------------- /eyeball-im-util/src/vector/ops.rs: -------------------------------------------------------------------------------- 1 | use arrayvec::ArrayVec; 2 | use eyeball_im::VectorDiff; 3 | use smallvec::SmallVec; 4 | 5 | pub trait VectorDiffContainerOps: Sized { 6 | type Family: VectorDiffContainerFamily; 7 | type HeadBuf: Default; 8 | type TailBuf: Default; 9 | type SkipBuf: Default; 10 | type SortBuf: Default; 11 | 12 | fn from_item(vector_diff: VectorDiff) -> Self; 13 | 14 | fn filter_map( 15 | self, 16 | f: impl FnMut(VectorDiff) -> Option>, 17 | ) -> Option>; 18 | 19 | fn push_into_head_buf( 20 | self, 21 | buffer: &mut Self::HeadBuf, 22 | map_diffs: impl FnMut(VectorDiff) -> ArrayVec, 2>, 23 | ) -> Option; 24 | 25 | fn pop_from_head_buf(buffer: &mut Self::HeadBuf) -> Option; 26 | 27 | fn push_into_tail_buf( 28 | self, 29 | buffer: &mut Self::TailBuf, 30 | map_diffs: impl FnMut(VectorDiff) -> SmallVec<[VectorDiff; 2]>, 31 | ) -> Option; 32 | 33 | fn extend_tail_buf(diffs: Vec>, buffer: &mut Self::TailBuf) -> Option; 34 | 35 | fn pop_from_tail_buf(buffer: &mut Self::TailBuf) -> Option; 36 | 37 | fn push_into_skip_buf( 38 | self, 39 | buffer: &mut Self::SkipBuf, 40 | map_diffs: impl FnMut(VectorDiff) -> SmallVec<[VectorDiff; 2]>, 41 | ) -> Option; 42 | 43 | fn extend_skip_buf(diffs: Vec>, buffer: &mut Self::SkipBuf) -> Option; 44 | 45 | fn pop_from_skip_buf(buffer: &mut Self::SkipBuf) -> Option; 46 | 47 | fn push_into_sort_buf( 48 | self, 49 | buffer: &mut Self::SortBuf, 50 | map_diffs: impl FnMut(VectorDiff) -> SmallVec<[VectorDiff; 2]>, 51 | ) -> Option; 52 | 53 | fn pop_from_sort_buf(buffer: &mut Self::SortBuf) -> Option; 54 | } 55 | 56 | #[allow(unreachable_pub)] 57 | pub type VectorDiffContainerFamilyMember = ::Member; 58 | 59 | impl VectorDiffContainerOps for VectorDiff { 60 | type Family = VectorDiffFamily; 61 | type HeadBuf = Option>; 62 | type TailBuf = SmallVec<[VectorDiff; 2]>; 63 | type SkipBuf = SmallVec<[VectorDiff; 2]>; 64 | type SortBuf = SmallVec<[VectorDiff; 2]>; 65 | 66 | fn from_item(vector_diff: VectorDiff) -> Self { 67 | vector_diff 68 | } 69 | 70 | fn filter_map( 71 | self, 72 | mut f: impl FnMut(VectorDiff) -> Option>, 73 | ) -> Option> { 74 | f(self) 75 | } 76 | 77 | fn push_into_head_buf( 78 | self, 79 | buffer: &mut Self::HeadBuf, 80 | mut map_diffs: impl FnMut(VectorDiff) -> ArrayVec, 2>, 81 | ) -> Option { 82 | assert!(buffer.is_none(), "buffer must be None when calling push_into_head_buf"); 83 | 84 | let mut diffs = map_diffs(self); 85 | 86 | let last = diffs.pop(); 87 | if let Some(first) = diffs.pop() { 88 | *buffer = last; 89 | Some(first) 90 | } else { 91 | last 92 | } 93 | } 94 | 95 | fn pop_from_head_buf(buffer: &mut Self::HeadBuf) -> Option { 96 | buffer.take() 97 | } 98 | 99 | fn push_into_tail_buf( 100 | self, 101 | buffer: &mut Self::TailBuf, 102 | mut map_diffs: impl FnMut(VectorDiff) -> SmallVec<[VectorDiff; 2]>, 103 | ) -> Option { 104 | buffer.insert_many(0, map_diffs(self).into_iter().rev()); 105 | 106 | buffer.pop() 107 | } 108 | 109 | fn extend_tail_buf(diffs: Vec>, buffer: &mut Self::TailBuf) -> Option { 110 | // We cannot pop front on a `SmallVec`. We store all `diffs` in reverse order to 111 | // pop from it. 112 | buffer.insert_many(0, diffs.into_iter().rev()); 113 | 114 | buffer.pop() 115 | } 116 | 117 | fn pop_from_tail_buf(buffer: &mut Self::TailBuf) -> Option { 118 | buffer.pop() 119 | } 120 | 121 | fn push_into_skip_buf( 122 | self, 123 | buffer: &mut Self::SkipBuf, 124 | mut map_diffs: impl FnMut(VectorDiff) -> SmallVec<[VectorDiff; 2]>, 125 | ) -> Option { 126 | buffer.insert_many(0, map_diffs(self).into_iter().rev()); 127 | 128 | buffer.pop() 129 | } 130 | 131 | fn extend_skip_buf(diffs: Vec>, buffer: &mut Self::SkipBuf) -> Option { 132 | // We cannot pop front on a `SmallVec`. We store all `diffs` in reverse order to 133 | // pop from it. 134 | buffer.insert_many(0, diffs.into_iter().rev()); 135 | 136 | buffer.pop() 137 | } 138 | 139 | fn pop_from_skip_buf(buffer: &mut Self::SkipBuf) -> Option { 140 | buffer.pop() 141 | } 142 | 143 | fn push_into_sort_buf( 144 | self, 145 | buffer: &mut Self::SortBuf, 146 | mut map_diffs: impl FnMut(VectorDiff) -> SmallVec<[VectorDiff; 2]>, 147 | ) -> Option { 148 | assert!(buffer.is_empty(), "buffer must be empty when calling `push_into_sort_buf`"); 149 | 150 | let mut diffs = map_diffs(self); 151 | 152 | match diffs.len() { 153 | 0 => None, 154 | 1 => diffs.pop(), 155 | _ => { 156 | // We want the first element. We can't “pop front” on a `SmallVec`. 157 | // The idea is to reverse the `diffs` and to pop from it. 158 | diffs.reverse(); 159 | *buffer = diffs; 160 | 161 | buffer.pop() 162 | } 163 | } 164 | } 165 | 166 | fn pop_from_sort_buf(buffer: &mut Self::SortBuf) -> Option { 167 | buffer.pop() 168 | } 169 | } 170 | 171 | impl VectorDiffContainerOps for Vec> { 172 | type Family = VecVectorDiffFamily; 173 | type HeadBuf = (); 174 | type TailBuf = (); 175 | type SkipBuf = (); 176 | type SortBuf = (); 177 | 178 | fn from_item(vector_diff: VectorDiff) -> Self { 179 | vec![vector_diff] 180 | } 181 | 182 | fn filter_map( 183 | self, 184 | f: impl FnMut(VectorDiff) -> Option>, 185 | ) -> Option> { 186 | let res: Vec<_> = self.into_iter().filter_map(f).collect(); 187 | 188 | if res.is_empty() { 189 | None 190 | } else { 191 | Some(res) 192 | } 193 | } 194 | 195 | fn push_into_head_buf( 196 | self, 197 | _buffer: &mut Self::HeadBuf, 198 | map_diffs: impl FnMut(VectorDiff) -> ArrayVec, 2>, 199 | ) -> Option { 200 | let res: Vec<_> = self.into_iter().flat_map(map_diffs).collect(); 201 | 202 | if res.is_empty() { 203 | None 204 | } else { 205 | Some(res) 206 | } 207 | } 208 | 209 | fn pop_from_head_buf(_: &mut Self::HeadBuf) -> Option { 210 | None 211 | } 212 | 213 | fn push_into_tail_buf( 214 | self, 215 | _buffer: &mut Self::TailBuf, 216 | map_diffs: impl FnMut(VectorDiff) -> SmallVec<[VectorDiff; 2]>, 217 | ) -> Option { 218 | let res: Vec<_> = self.into_iter().flat_map(map_diffs).collect(); 219 | 220 | if res.is_empty() { 221 | None 222 | } else { 223 | Some(res) 224 | } 225 | } 226 | 227 | fn extend_tail_buf(diffs: Vec>, _buffer: &mut Self::TailBuf) -> Option { 228 | if diffs.is_empty() { 229 | None 230 | } else { 231 | Some(diffs) 232 | } 233 | } 234 | 235 | fn pop_from_tail_buf(_buffer: &mut Self::TailBuf) -> Option { 236 | None 237 | } 238 | 239 | fn push_into_skip_buf( 240 | self, 241 | _buffer: &mut Self::SkipBuf, 242 | map_diffs: impl FnMut(VectorDiff) -> SmallVec<[VectorDiff; 2]>, 243 | ) -> Option { 244 | let res: Vec<_> = self.into_iter().flat_map(map_diffs).collect(); 245 | 246 | if res.is_empty() { 247 | None 248 | } else { 249 | Some(res) 250 | } 251 | } 252 | 253 | fn extend_skip_buf(diffs: Vec>, _buffer: &mut Self::SkipBuf) -> Option { 254 | if diffs.is_empty() { 255 | None 256 | } else { 257 | Some(diffs) 258 | } 259 | } 260 | 261 | fn pop_from_skip_buf(_buffer: &mut Self::SkipBuf) -> Option { 262 | None 263 | } 264 | 265 | fn push_into_sort_buf( 266 | self, 267 | _buffer: &mut (), 268 | map_diffs: impl FnMut(VectorDiff) -> SmallVec<[VectorDiff; 2]>, 269 | ) -> Option { 270 | let res: Vec<_> = self.into_iter().flat_map(map_diffs).collect(); 271 | 272 | if res.is_empty() { 273 | None 274 | } else { 275 | Some(res) 276 | } 277 | } 278 | 279 | fn pop_from_sort_buf(_: &mut Self::HeadBuf) -> Option { 280 | None 281 | } 282 | } 283 | 284 | #[allow(unreachable_pub)] 285 | pub trait VectorDiffContainerFamily { 286 | type Member: VectorDiffContainerOps; 287 | } 288 | 289 | #[derive(Debug)] 290 | pub enum VectorDiffFamily {} 291 | 292 | impl VectorDiffContainerFamily for VectorDiffFamily { 293 | type Member = VectorDiff; 294 | } 295 | 296 | #[derive(Debug)] 297 | pub enum VecVectorDiffFamily {} 298 | 299 | impl VectorDiffContainerFamily for VecVectorDiffFamily { 300 | type Member = Vec>; 301 | } 302 | -------------------------------------------------------------------------------- /eyeball-im-util/src/vector/traits.rs: -------------------------------------------------------------------------------- 1 | //! Public traits. 2 | 3 | use std::cmp::Ordering; 4 | 5 | use eyeball_im::{ 6 | VectorDiff, VectorSubscriber, VectorSubscriberBatchedStream, VectorSubscriberStream, 7 | }; 8 | use futures_core::Stream; 9 | use imbl::Vector; 10 | 11 | use super::{ 12 | ops::{ 13 | VecVectorDiffFamily, VectorDiffContainerFamily, VectorDiffContainerOps, VectorDiffFamily, 14 | }, 15 | EmptyCountStream, EmptyLimitStream, Filter, FilterMap, Head, Skip, Sort, SortBy, SortByKey, 16 | Tail, 17 | }; 18 | 19 | /// Abstraction over stream items that the adapters in this module can deal 20 | /// with. 21 | pub trait VectorDiffContainer: 22 | VectorDiffContainerOps::Family> 23 | { 24 | /// The element type of the [`Vector`][imbl::Vector] that diffs are being 25 | /// handled for. 26 | type Element: Clone + 'static; 27 | 28 | #[doc(hidden)] 29 | type Family: VectorDiffContainerFamily = Self>; 30 | } 31 | 32 | impl VectorDiffContainer for VectorDiff { 33 | type Element = T; 34 | type Family = VectorDiffFamily; 35 | } 36 | 37 | impl VectorDiffContainer for Vec> { 38 | type Element = T; 39 | type Family = VecVectorDiffFamily; 40 | } 41 | 42 | /// Extension trait for [`VectorSubscriber`]. 43 | pub trait VectorSubscriberExt { 44 | /// Create a [`BatchedVectorSubscriber`] from `self`. 45 | fn batched(self) -> BatchedVectorSubscriber; 46 | } 47 | 48 | impl VectorSubscriberExt for VectorSubscriber { 49 | fn batched(self) -> BatchedVectorSubscriber { 50 | BatchedVectorSubscriber { inner: self } 51 | } 52 | } 53 | 54 | /// A wrapper around [`VectorSubscriber`] with a different [`VectorObserver`] 55 | /// impl. 56 | #[derive(Debug)] 57 | pub struct BatchedVectorSubscriber { 58 | inner: VectorSubscriber, 59 | } 60 | 61 | /// Abstraction over types that hold both a [`Vector`] and a stream of 62 | /// [`VectorDiff`] updates. 63 | /// 64 | /// See [`VectorObserverExt`] for operations available to implementers. 65 | pub trait VectorObserver: Sized { 66 | #[doc(hidden)] 67 | type Stream: Stream; 68 | 69 | #[doc(hidden)] 70 | fn into_parts(self) -> (Vector, Self::Stream); 71 | } 72 | 73 | impl VectorObserver for VectorSubscriber { 74 | type Stream = VectorSubscriberStream; 75 | 76 | fn into_parts(self) -> (Vector, Self::Stream) { 77 | self.into_values_and_stream() 78 | } 79 | } 80 | 81 | impl VectorObserver for BatchedVectorSubscriber { 82 | type Stream = VectorSubscriberBatchedStream; 83 | 84 | fn into_parts(self) -> (Vector, Self::Stream) { 85 | self.inner.into_values_and_batched_stream() 86 | } 87 | } 88 | 89 | impl VectorObserver for (Vector, S) 90 | where 91 | S: Stream, 92 | S::Item: VectorDiffContainer, 93 | { 94 | type Stream = S; 95 | 96 | fn into_parts(self) -> (Vector, Self::Stream) { 97 | self 98 | } 99 | } 100 | 101 | /// Convenience methods for [`VectorObserver`]s. 102 | /// 103 | /// See that trait for which types implement this. 104 | pub trait VectorObserverExt: VectorObserver 105 | where 106 | T: Clone + 'static, 107 | ::Item: VectorDiffContainer, 108 | { 109 | /// Filter the vector's values with the given function. 110 | fn filter(self, f: F) -> (Vector, Filter) 111 | where 112 | F: Fn(&T) -> bool, 113 | { 114 | let (items, stream) = self.into_parts(); 115 | Filter::new(items, stream, f) 116 | } 117 | 118 | /// Filter and map the vector's values with the given function. 119 | fn filter_map(self, f: F) -> (Vector, FilterMap) 120 | where 121 | U: Clone, 122 | F: Fn(T) -> Option, 123 | { 124 | let (items, stream) = self.into_parts(); 125 | FilterMap::new(items, stream, f) 126 | } 127 | 128 | /// Limit the observed values to the first `limit` values. 129 | /// 130 | /// See [`Head`] for more details. 131 | fn head(self, limit: usize) -> (Vector, Head) { 132 | let (items, stream) = self.into_parts(); 133 | Head::new(items, stream, limit) 134 | } 135 | 136 | /// Limit the first observed values to a number of values determined by the 137 | /// given stream. 138 | /// 139 | /// See [`Head`] for more details. 140 | fn dynamic_head(self, limit_stream: L) -> Head 141 | where 142 | L: Stream, 143 | { 144 | let (items, stream) = self.into_parts(); 145 | Head::dynamic(items, stream, limit_stream) 146 | } 147 | 148 | /// Limit the first observed values to `initial_limit` values initially, and 149 | /// update the limit with the value from the given stream. 150 | /// 151 | /// See [`Head`] for more details. 152 | fn dynamic_head_with_initial_value( 153 | self, 154 | initial_limit: usize, 155 | limit_stream: L, 156 | ) -> (Vector, Head) 157 | where 158 | L: Stream, 159 | { 160 | let (items, stream) = self.into_parts(); 161 | Head::dynamic_with_initial_limit(items, stream, initial_limit, limit_stream) 162 | } 163 | 164 | /// Limit the observed values to the last `limit` values. 165 | /// 166 | /// See [`Tail`] for more details. 167 | fn tail(self, limit: usize) -> (Vector, Tail) { 168 | let (items, stream) = self.into_parts(); 169 | Tail::new(items, stream, limit) 170 | } 171 | 172 | /// Limit the last observed values to a number of items determined by the 173 | /// given stream. 174 | /// 175 | /// See [`Tail`] for more details. 176 | fn dynamic_tail(self, limit_stream: L) -> Tail 177 | where 178 | L: Stream, 179 | { 180 | let (items, stream) = self.into_parts(); 181 | Tail::dynamic(items, stream, limit_stream) 182 | } 183 | 184 | /// Limit the last observed values to `initial_limit` items initially, and 185 | /// update the limit with the value from the given stream. 186 | /// 187 | /// See [`Tail`] for more details. 188 | fn dynamic_tail_with_initial_value( 189 | self, 190 | initial_limit: usize, 191 | limit_stream: L, 192 | ) -> (Vector, Tail) 193 | where 194 | L: Stream, 195 | { 196 | let (items, stream) = self.into_parts(); 197 | Tail::dynamic_with_initial_limit(items, stream, initial_limit, limit_stream) 198 | } 199 | 200 | /// Skip the first `count` observed values. 201 | /// 202 | /// See [`Skip`] for more details. 203 | fn skip(self, count: usize) -> (Vector, Skip) { 204 | let (items, stream) = self.into_parts(); 205 | Skip::new(items, stream, count) 206 | } 207 | 208 | /// Skip the first `count` observed values, where `count` is determined by 209 | /// the given stream. 210 | /// 211 | /// See [`Skip`] for more details. 212 | fn dynamic_skip(self, count_stream: C) -> Skip 213 | where 214 | C: Stream, 215 | { 216 | let (items, stream) = self.into_parts(); 217 | Skip::dynamic(items, stream, count_stream) 218 | } 219 | 220 | /// Skip the first `initial_count` observed values, and update the `count` 221 | /// with the values from the given stream. 222 | /// 223 | /// See [`Skip`] for more details. 224 | fn dynamic_skip_with_initial_count( 225 | self, 226 | initial_count: usize, 227 | count_stream: C, 228 | ) -> (Vector, Skip) 229 | where 230 | C: Stream, 231 | { 232 | let (items, stream) = self.into_parts(); 233 | Skip::dynamic_with_initial_count(items, stream, initial_count, count_stream) 234 | } 235 | 236 | /// Sort the observed values. 237 | /// 238 | /// See [`Sort`] for more details. 239 | fn sort(self) -> (Vector, Sort) 240 | where 241 | T: Ord, 242 | { 243 | let (items, stream) = self.into_parts(); 244 | Sort::new(items, stream) 245 | } 246 | 247 | /// Sort the observed values with the given comparison function. 248 | /// 249 | /// See [`SortBy`] for more details. 250 | fn sort_by(self, compare: F) -> (Vector, SortBy) 251 | where 252 | F: Fn(&T, &T) -> Ordering, 253 | { 254 | let (items, stream) = self.into_parts(); 255 | SortBy::new(items, stream, compare) 256 | } 257 | 258 | /// Sort the observed values with the given key function. 259 | /// 260 | /// See [`SortBy`] for more details. 261 | fn sort_by_key(self, key_fn: F) -> (Vector, SortByKey) 262 | where 263 | F: Fn(&T) -> K, 264 | K: Ord, 265 | { 266 | let (items, stream) = self.into_parts(); 267 | SortByKey::new(items, stream, key_fn) 268 | } 269 | } 270 | 271 | impl VectorObserverExt for O 272 | where 273 | T: Clone + 'static, 274 | O: VectorObserver, 275 | ::Item: VectorDiffContainer, 276 | { 277 | } 278 | -------------------------------------------------------------------------------- /eyeball-im-util/tests/it/filter.rs: -------------------------------------------------------------------------------- 1 | use eyeball_im::{ObservableVector, VectorDiff}; 2 | use eyeball_im_util::vector::VectorObserverExt; 3 | use imbl::vector; 4 | use stream_assert::{assert_closed, assert_next_eq, assert_pending}; 5 | 6 | #[test] 7 | fn append() { 8 | let mut ob: ObservableVector<&str> = ObservableVector::new(); 9 | let (_, mut sub) = ob.subscribe().filter(|s| s.len() < 8); 10 | 11 | ob.append(vector!["hello", "world"]); 12 | assert_next_eq!(sub, VectorDiff::Append { values: vector!["hello", "world"] }); 13 | 14 | ob.append(vector!["hello, world!"]); 15 | assert_pending!(sub); 16 | 17 | ob.append(vector!["goodbye"]); 18 | assert_next_eq!(sub, VectorDiff::Append { values: vector!["goodbye"] }); 19 | 20 | drop(ob); 21 | assert_closed!(sub); 22 | } 23 | 24 | #[test] 25 | fn append_clear() { 26 | let mut ob: ObservableVector = ObservableVector::new(); 27 | let (_, mut sub) = ob.subscribe().filter(|&i| i < 256); 28 | 29 | ob.append(vector![1024]); 30 | assert_pending!(sub); 31 | 32 | ob.append(vector![1, 2, 3]); 33 | assert_next_eq!(sub, VectorDiff::Append { values: vector![1, 2, 3] }); 34 | 35 | ob.clear(); 36 | assert_next_eq!(sub, VectorDiff::Clear); 37 | 38 | ob.append(vector![999, 256, 1234]); 39 | assert_pending!(sub); 40 | 41 | ob.append(vector![255, 127]); 42 | assert_next_eq!(sub, VectorDiff::Append { values: vector![255, 127] }); 43 | assert_pending!(sub); 44 | } 45 | 46 | #[test] 47 | fn buffering() { 48 | let mut ob: ObservableVector = ObservableVector::new(); 49 | let (_, mut sub) = ob.subscribe().filter(|&i| i < 256); 50 | 51 | ob.append(vector![1024]); 52 | ob.append(vector![1, 2, 3]); 53 | ob.clear(); 54 | ob.append(vector![255, 127]); 55 | ob.append(vector![999, 256, 1234]); 56 | 57 | assert_next_eq!(sub, VectorDiff::Append { values: vector![1, 2, 3] }); 58 | assert_next_eq!(sub, VectorDiff::Clear); 59 | assert_next_eq!(sub, VectorDiff::Append { values: vector![255, 127] }); 60 | assert_pending!(sub); 61 | } 62 | 63 | #[test] 64 | fn push_front() { 65 | let mut ob: ObservableVector = ObservableVector::new(); 66 | let (_, mut sub) = ob.subscribe().filter(|&i| i < 256); 67 | 68 | ob.push_front(1); 69 | assert_next_eq!(sub, VectorDiff::PushFront { value: 1 }); 70 | ob.push_front(1024); 71 | assert_pending!(sub); 72 | ob.push_front(2); 73 | assert_next_eq!(sub, VectorDiff::PushFront { value: 2 }); 74 | 75 | ob.clear(); 76 | ob.push_front(256); 77 | assert_next_eq!(sub, VectorDiff::Clear); 78 | assert_pending!(sub); 79 | ob.push_front(55); 80 | assert_next_eq!(sub, VectorDiff::PushFront { value: 55 }); 81 | } 82 | 83 | #[test] 84 | fn push_back() { 85 | let mut ob: ObservableVector = ObservableVector::new(); 86 | let (_, mut sub) = ob.subscribe().filter(|&i| i < 256); 87 | 88 | ob.push_back(1); 89 | assert_next_eq!(sub, VectorDiff::PushBack { value: 1 }); 90 | ob.push_back(1024); 91 | assert_pending!(sub); 92 | ob.push_back(2); 93 | assert_next_eq!(sub, VectorDiff::PushBack { value: 2 }); 94 | 95 | ob.clear(); 96 | ob.push_back(256); 97 | ob.push_back(55); 98 | assert_next_eq!(sub, VectorDiff::Clear); 99 | assert_next_eq!(sub, VectorDiff::PushBack { value: 55 }); 100 | } 101 | 102 | #[test] 103 | fn push_both() { 104 | let mut ob: ObservableVector = ObservableVector::new(); 105 | let (_, mut sub) = ob.subscribe().filter(|&i| i < 256); 106 | 107 | ob.push_back(1); 108 | ob.push_front(2); 109 | assert_next_eq!(sub, VectorDiff::PushBack { value: 1 }); 110 | assert_next_eq!(sub, VectorDiff::PushFront { value: 2 }); 111 | 112 | ob.push_front(777); 113 | ob.push_front(511); 114 | assert_pending!(sub); 115 | 116 | ob.push_front(4); 117 | ob.push_front(511); 118 | ob.push_back(123); 119 | assert_next_eq!(sub, VectorDiff::PushFront { value: 4 }); 120 | assert_next_eq!(sub, VectorDiff::PushBack { value: 123 }); 121 | } 122 | 123 | #[test] 124 | fn pop_front() { 125 | let mut ob: ObservableVector = ObservableVector::from(vector![1, 2, 3]); 126 | let (items, mut sub) = ob.subscribe().filter(|&i| i < 256); 127 | assert_eq!(items, vector![1, 2, 3]); 128 | 129 | ob.pop_front(); 130 | assert_next_eq!(sub, VectorDiff::PopFront); 131 | ob.pop_front(); 132 | ob.pop_front(); 133 | assert_next_eq!(sub, VectorDiff::PopFront); 134 | assert_next_eq!(sub, VectorDiff::PopFront); 135 | assert_pending!(sub); 136 | 137 | let mut ob: ObservableVector = ObservableVector::from(vector![1000, 2, 3000, 4]); 138 | let (items, mut sub) = ob.subscribe().filter(|&i| i < 256); 139 | assert_eq!(items, vector![2, 4]); 140 | 141 | ob.pop_front(); 142 | assert_pending!(sub); 143 | ob.pop_front(); 144 | assert_next_eq!(sub, VectorDiff::PopFront); 145 | ob.pop_front(); 146 | ob.pop_front(); 147 | assert_next_eq!(sub, VectorDiff::PopFront); 148 | assert_pending!(sub); 149 | } 150 | 151 | #[test] 152 | fn pop_back() { 153 | let mut ob: ObservableVector = ObservableVector::from(vector![1, 2, 3]); 154 | let (items, mut sub) = ob.subscribe().filter(|&i| i < 256); 155 | assert_eq!(items, vector![1, 2, 3]); 156 | 157 | ob.pop_back(); 158 | assert_next_eq!(sub, VectorDiff::PopBack); 159 | ob.pop_back(); 160 | ob.pop_back(); 161 | assert_next_eq!(sub, VectorDiff::PopBack); 162 | assert_next_eq!(sub, VectorDiff::PopBack); 163 | assert_pending!(sub); 164 | 165 | let mut ob: ObservableVector = ObservableVector::from(vector![1000, 2, 3000, 4]); 166 | let (items, mut sub) = ob.subscribe().filter(|&i| i < 256); 167 | assert_eq!(items, vector![2, 4]); 168 | 169 | ob.pop_back(); 170 | assert_next_eq!(sub, VectorDiff::PopBack); 171 | ob.pop_back(); 172 | assert_pending!(sub); 173 | ob.pop_back(); 174 | ob.pop_back(); 175 | assert_next_eq!(sub, VectorDiff::PopBack); 176 | assert_pending!(sub); 177 | } 178 | 179 | #[test] 180 | fn insert() { 181 | let mut ob: ObservableVector = ObservableVector::new(); 182 | let (_, mut sub) = ob.subscribe().filter(|&i| i < 256); 183 | 184 | ob.insert(0, 256); 185 | assert_pending!(sub); 186 | ob.insert(1, 123); 187 | assert_next_eq!(sub, VectorDiff::Insert { index: 0, value: 123 }); 188 | ob.insert(0, 234); 189 | assert_next_eq!(sub, VectorDiff::Insert { index: 0, value: 234 }); 190 | ob.insert(1, 1000); 191 | ob.insert(2, 255); 192 | assert_eq!(*ob, vector![234, 1000, 255, 256, 123]); 193 | assert_next_eq!(sub, VectorDiff::Insert { index: 1, value: 255 }); 194 | assert_pending!(sub); 195 | 196 | let mut ob: ObservableVector = 197 | ObservableVector::from(vector![1, 2000, 3000, 4000, 5000, 6]); 198 | let (_, mut sub) = ob.subscribe().filter(|&i| i < 256); 199 | ob.insert(3, 30); 200 | assert_next_eq!(sub, VectorDiff::Insert { index: 1, value: 30 }); 201 | assert_pending!(sub); 202 | ob.insert(4, 400); 203 | assert_pending!(sub); 204 | assert_eq!(*ob, vector![1, 2000, 3000, 30, 400, 4000, 5000, 6]); 205 | ob.insert(8, 80); 206 | assert_next_eq!(sub, VectorDiff::Insert { index: 3, value: 80 }); 207 | assert_pending!(sub); 208 | } 209 | 210 | #[test] 211 | fn set() { 212 | let mut ob: ObservableVector = 213 | ObservableVector::from(vector![0, 1000, 2000, 3000, 4, 5000]); 214 | let (_, mut sub) = ob.subscribe().filter(|&i| i < 256); 215 | 216 | ob.set(2, 200); 217 | assert_next_eq!(sub, VectorDiff::Insert { index: 1, value: 200 }); 218 | ob.set(2, 20); 219 | assert_next_eq!(sub, VectorDiff::Set { index: 1, value: 20 }); 220 | ob.set(0, 255); 221 | assert_next_eq!(sub, VectorDiff::Set { index: 0, value: 255 }); 222 | ob.set(4, 4000); 223 | assert_next_eq!(sub, VectorDiff::Remove { index: 2 }); 224 | ob.set(5, 50); 225 | assert_next_eq!(sub, VectorDiff::Insert { index: 2, value: 50 }); 226 | } 227 | 228 | #[test] 229 | fn remove() { 230 | let mut ob: ObservableVector = 231 | ObservableVector::from(vector![0, 1, 2000, 3000, 4000, 5, 6000]); 232 | let (_, mut sub) = ob.subscribe().filter(|&i| i < 256); 233 | 234 | ob.remove(3); // 3000 235 | assert_pending!(sub); 236 | ob.remove(3); // 4000 237 | assert_pending!(sub); 238 | ob.remove(3); // 5 239 | assert_next_eq!(sub, VectorDiff::Remove { index: 2 }); 240 | ob.remove(0); // 0 241 | assert_next_eq!(sub, VectorDiff::Remove { index: 0 }); 242 | ob.remove(2); // 6000 243 | assert_pending!(sub); 244 | ob.remove(1); // 2000 245 | assert_pending!(sub); 246 | ob.remove(0); // 2000 247 | assert_next_eq!(sub, VectorDiff::Remove { index: 0 }); 248 | } 249 | 250 | #[test] 251 | fn truncate_matching_prefix() { 252 | let mut ob: ObservableVector = ObservableVector::from(vector![5, 1, 10, -1, -2, -10]); 253 | let (_, mut sub) = ob.subscribe().filter(|&i| i > 0); 254 | 255 | ob.truncate(4); // remove some non-matching elements 256 | assert_pending!(sub); 257 | 258 | ob.truncate(3); // remove remaining non-matching element 259 | assert_pending!(sub); 260 | 261 | ob.truncate(1); // remove some matching elements 262 | assert_next_eq!(sub, VectorDiff::Truncate { length: 1 }); 263 | } 264 | 265 | #[test] 266 | fn truncate_matching_suffix() { 267 | let mut ob: ObservableVector = ObservableVector::from(vector![-1, -2, -10, 5, 1, 10]); 268 | let (_, mut sub) = ob.subscribe().filter(|&i| i > 0); 269 | 270 | ob.truncate(5); // remove one matching elements 271 | assert_next_eq!(sub, VectorDiff::Truncate { length: 2 }); 272 | 273 | ob.truncate(3); // remove remaining matching elements 274 | assert_next_eq!(sub, VectorDiff::Truncate { length: 0 }); 275 | 276 | ob.truncate(1); // remove some non-matching elements 277 | assert_pending!(sub); 278 | } 279 | 280 | #[test] 281 | fn truncate_complex() { 282 | let mut ob: ObservableVector = 283 | ObservableVector::from(vector![-17, 5, 1, -5, 10, -1, -2, 10]); 284 | let (_, mut sub) = ob.subscribe().filter(|&i| i > 0); 285 | 286 | ob.truncate(6); // remove non-matching, matching 287 | assert_next_eq!(sub, VectorDiff::Truncate { length: 3 }); 288 | 289 | ob.truncate(4); // remove matching, non-matching 290 | assert_next_eq!(sub, VectorDiff::Truncate { length: 2 }); 291 | 292 | ob.truncate(1); // remove 2 x matching, 1 x non-matching 293 | assert_next_eq!(sub, VectorDiff::Truncate { length: 0 }); 294 | 295 | ob.truncate(0); // remove last non-matching 296 | assert_pending!(sub); 297 | } 298 | 299 | #[test] 300 | fn reset() { 301 | let mut ob: ObservableVector = ObservableVector::with_capacity(1); 302 | let (_, mut sub) = ob.subscribe().filter(|&i| i < 256); 303 | 304 | ob.push_front(0); 305 | ob.append(vector![1000, 2, 3000, 4]); 306 | assert_next_eq!(sub, VectorDiff::Reset { values: vector![0, 2, 4] }); 307 | ob.remove(2); 308 | assert_next_eq!(sub, VectorDiff::Remove { index: 1 }); 309 | ob.remove(1); 310 | assert_pending!(sub); 311 | 312 | ob.pop_front(); 313 | ob.insert(2, 5); 314 | assert_next_eq!(sub, VectorDiff::Reset { values: vector![4, 5] }); 315 | ob.remove(2); 316 | assert_next_eq!(sub, VectorDiff::Remove { index: 1 }); 317 | ob.remove(1); 318 | assert_next_eq!(sub, VectorDiff::Remove { index: 0 }); 319 | ob.remove(0); 320 | assert_pending!(sub); 321 | } 322 | -------------------------------------------------------------------------------- /eyeball-im-util/tests/it/filter_map.rs: -------------------------------------------------------------------------------- 1 | use eyeball_im::{ObservableVector, VectorDiff}; 2 | use eyeball_im_util::vector::{VectorObserverExt, VectorSubscriberExt}; 3 | use imbl::vector; 4 | use stream_assert::{assert_next_eq, assert_pending}; 5 | 6 | #[test] 7 | fn insert_len_tracking() { 8 | let mut ob: ObservableVector = ObservableVector::new(); 9 | let (_, mut sub) = ob.subscribe().batched().filter_map(|i| u8::try_from(i).ok()); 10 | 11 | ob.insert(0, -1); 12 | assert_pending!(sub); 13 | 14 | ob.remove(0); 15 | assert_pending!(sub); 16 | } 17 | 18 | #[test] 19 | fn filter_map_batch() { 20 | let mut ob: ObservableVector = ObservableVector::new(); 21 | let (_, mut sub) = ob.subscribe().batched().filter_map(|i| u8::try_from(i).ok()); 22 | 23 | ob.append(vector![1024, -1]); 24 | assert_pending!(sub); 25 | 26 | let mut txn = ob.transaction(); 27 | txn.push_back(-1); 28 | txn.push_front(-2); 29 | txn.commit(); 30 | 31 | assert_pending!(sub); 32 | 33 | let mut txn = ob.transaction(); 34 | txn.push_back(1); 35 | assert_pending!(sub); 36 | txn.push_back(9999); 37 | txn.set(1, 2); 38 | assert_pending!(sub); 39 | txn.commit(); 40 | 41 | assert_next_eq!( 42 | sub, 43 | vec![VectorDiff::PushBack { value: 1 }, VectorDiff::Insert { index: 0, value: 2 }] 44 | ); 45 | } 46 | -------------------------------------------------------------------------------- /eyeball-im-util/tests/it/main.rs: -------------------------------------------------------------------------------- 1 | #![allow(missing_docs)] 2 | 3 | mod filter; 4 | mod filter_map; 5 | mod head; 6 | mod skip; 7 | mod sort; 8 | mod sort_by; 9 | mod sort_by_key; 10 | mod tail; 11 | -------------------------------------------------------------------------------- /eyeball-im-util/tests/it/sort.rs: -------------------------------------------------------------------------------- 1 | use eyeball_im::{ObservableVector, VectorDiff}; 2 | use eyeball_im_util::vector::VectorObserverExt; 3 | use imbl::vector; 4 | use stream_assert::{assert_closed, assert_next_eq, assert_pending}; 5 | 6 | #[test] 7 | fn new() { 8 | let ob = ObservableVector::::from(vector!['c', 'a', 'd', 'b']); 9 | let (values, mut sub) = ob.subscribe().sort(); 10 | 11 | assert_eq!(values, vector!['a', 'b', 'c', 'd']); 12 | assert_pending!(sub); 13 | 14 | drop(ob); 15 | assert_closed!(sub); 16 | } 17 | 18 | #[test] 19 | fn append() { 20 | let mut ob = ObservableVector::::new(); 21 | let (values, mut sub) = ob.subscribe().sort(); 22 | 23 | assert!(values.is_empty()); 24 | assert_pending!(sub); 25 | 26 | // Append on an empty vector. 27 | ob.append(vector!['d', 'a', 'e']); 28 | assert_next_eq!(sub, VectorDiff::Append { values: vector!['a', 'd', 'e'] }); 29 | 30 | // Append on an non-empty vector. 31 | ob.append(vector!['f', 'g', 'b']); 32 | assert_next_eq!(sub, VectorDiff::Insert { index: 1, value: 'b' }); 33 | assert_next_eq!(sub, VectorDiff::Append { values: vector!['f', 'g'] }); 34 | 35 | // Another append. 36 | // This time, it contains a duplicated new item + an insert + new items to be 37 | // appended. 38 | ob.append(vector!['i', 'h', 'c', 'j', 'a']); 39 | assert_next_eq!(sub, VectorDiff::PushFront { value: 'a' }); 40 | assert_next_eq!(sub, VectorDiff::Insert { index: 3, value: 'c' }); 41 | assert_next_eq!(sub, VectorDiff::Append { values: vector!['h', 'i', 'j'] }); 42 | 43 | // Another append. 44 | // This time, with two new items that are a duplication of the last item. 45 | ob.append(vector!['k', 'l', 'j', 'm', 'j']); 46 | assert_next_eq!(sub, VectorDiff::Append { values: vector!['j', 'j', 'k', 'l', 'm'] }); 47 | 48 | // Items in the vector have been appended and are not sorted. 49 | assert_eq!( 50 | *ob, 51 | vector!['d', 'a', 'e', 'f', 'g', 'b', 'i', 'h', 'c', 'j', 'a', 'k', 'l', 'j', 'm', 'j'] 52 | ); 53 | 54 | drop(ob); 55 | assert_closed!(sub); 56 | } 57 | 58 | #[test] 59 | fn clear() { 60 | let mut ob = ObservableVector::::new(); 61 | let (values, mut sub) = ob.subscribe().sort(); 62 | 63 | assert!(values.is_empty()); 64 | assert_pending!(sub); 65 | 66 | ob.append(vector!['b', 'a', 'c']); 67 | assert_next_eq!(sub, VectorDiff::Append { values: vector!['a', 'b', 'c'] }); 68 | 69 | assert_eq!(*ob, vector!['b', 'a', 'c']); 70 | 71 | // Let's clear it. 72 | ob.clear(); 73 | 74 | assert_next_eq!(sub, VectorDiff::Clear); 75 | 76 | // Items in the vector has been cleared out. 77 | assert!((*ob).is_empty()); 78 | 79 | drop(ob); 80 | assert_closed!(sub); 81 | } 82 | 83 | #[test] 84 | fn push_front() { 85 | let mut ob = ObservableVector::::new(); 86 | let (values, mut sub) = ob.subscribe().sort(); 87 | 88 | assert!(values.is_empty()); 89 | assert_pending!(sub); 90 | 91 | // Push front on an empty vector. 92 | ob.push_front('b'); 93 | assert_next_eq!(sub, VectorDiff::PushFront { value: 'b' }); 94 | 95 | // Push front on non-empty vector. 96 | // The new item should appear at position 0. 97 | ob.push_front('a'); 98 | assert_next_eq!(sub, VectorDiff::PushFront { value: 'a' }); 99 | 100 | // Another push front. 101 | // The new item should appear at last position. 102 | ob.push_front('d'); 103 | assert_next_eq!(sub, VectorDiff::PushBack { value: 'd' }); 104 | 105 | // Another push front. 106 | // The new item should appear in the middle. 107 | ob.push_front('c'); 108 | assert_next_eq!(sub, VectorDiff::Insert { index: 2, value: 'c' }); 109 | 110 | // Items in the vector have been pushed front and are not sorted. 111 | assert_eq!(*ob, vector!['c', 'd', 'a', 'b']); 112 | 113 | drop(ob); 114 | assert_closed!(sub); 115 | } 116 | 117 | #[test] 118 | fn push_back() { 119 | let mut ob = ObservableVector::::new(); 120 | let (values, mut sub) = ob.subscribe().sort(); 121 | 122 | assert!(values.is_empty()); 123 | assert_pending!(sub); 124 | 125 | // Push back on an empty vector. 126 | ob.push_back('b'); 127 | assert_next_eq!(sub, VectorDiff::PushFront { value: 'b' }); 128 | 129 | // Push back on non-empty vector. 130 | // The new item should appear at position 0. 131 | ob.push_back('a'); 132 | assert_next_eq!(sub, VectorDiff::PushFront { value: 'a' }); 133 | 134 | // Another push back. 135 | // The new item should appear at last position. 136 | ob.push_back('d'); 137 | assert_next_eq!(sub, VectorDiff::PushBack { value: 'd' }); 138 | 139 | // Another push back. 140 | // The new item should appear in the middle. 141 | ob.push_back('c'); 142 | assert_next_eq!(sub, VectorDiff::Insert { index: 2, value: 'c' }); 143 | 144 | // Items in the vector have been pushed back and are not sorted. 145 | assert_eq!(*ob, vector!['b', 'a', 'd', 'c']); 146 | 147 | drop(ob); 148 | assert_closed!(sub); 149 | } 150 | 151 | #[test] 152 | fn insert() { 153 | let mut ob = ObservableVector::::new(); 154 | let (values, mut sub) = ob.subscribe().sort(); 155 | 156 | assert!(values.is_empty()); 157 | assert_pending!(sub); 158 | 159 | // Insert on an empty vector. 160 | ob.insert(0, 'b'); 161 | assert_next_eq!(sub, VectorDiff::PushFront { value: 'b' }); 162 | 163 | // Insert on non-empty vector. 164 | // The new item should appear at position 0. 165 | ob.insert(1, 'a'); 166 | assert_next_eq!(sub, VectorDiff::PushFront { value: 'a' }); 167 | 168 | // Another insert. 169 | // The new item should appear at last position. 170 | ob.insert(1, 'd'); 171 | assert_next_eq!(sub, VectorDiff::PushBack { value: 'd' }); 172 | 173 | // Another insert. 174 | // The new item should appear at last position. 175 | ob.insert(1, 'e'); 176 | assert_next_eq!(sub, VectorDiff::PushBack { value: 'e' }); 177 | 178 | // Another insert. 179 | // The new item should appear in the middle. 180 | ob.insert(3, 'c'); 181 | assert_next_eq!(sub, VectorDiff::Insert { index: 2, value: 'c' }); 182 | 183 | // Items in the vector have been inserted and are not sorted. 184 | assert_eq!(*ob, vector!['b', 'e', 'd', 'c', 'a']); 185 | 186 | drop(ob); 187 | assert_closed!(sub); 188 | } 189 | 190 | #[test] 191 | fn pop_front() { 192 | let mut ob = ObservableVector::::new(); 193 | let (values, mut sub) = ob.subscribe().sort(); 194 | 195 | assert!(values.is_empty()); 196 | assert_pending!(sub); 197 | 198 | // Append a bunch of items. 199 | ob.append(vector!['e', 'b', 'a', 'd', 'c']); 200 | assert_next_eq!(sub, VectorDiff::Append { values: vector!['a', 'b', 'c', 'd', 'e'] }); 201 | 202 | // Pop front once. 203 | // `e` is at the last sorted position, so it generates a `VectorDiff::PopBack`. 204 | assert_eq!(ob.pop_front(), Some('e')); 205 | assert_next_eq!(sub, VectorDiff::PopBack); 206 | 207 | // Pop front again. 208 | // `b` is at the second sorted position, so it generates a `VectorDiff::Remove`. 209 | assert_eq!(ob.pop_front(), Some('b')); 210 | assert_next_eq!(sub, VectorDiff::Remove { index: 1 }); 211 | 212 | // Pop front again. 213 | // `a` is at the first sorted position, so it generates a 214 | // `VectorDiff::PopFront`. 215 | assert_eq!(ob.pop_front(), Some('a')); 216 | assert_next_eq!(sub, VectorDiff::PopFront); 217 | 218 | // Pop front again. 219 | // `d` is at the last sorted position, so it generates a `VectorDiff::PopBack`. 220 | assert_eq!(ob.pop_front(), Some('d')); 221 | assert_next_eq!(sub, VectorDiff::PopBack); 222 | 223 | // Pop front again. 224 | // `c` is at the first sorted position, so it generates a 225 | // `VectorDiff::PopFront`. 226 | assert_eq!(ob.pop_front(), Some('c')); 227 | assert_next_eq!(sub, VectorDiff::PopFront); 228 | 229 | assert!(ob.is_empty()); 230 | 231 | drop(ob); 232 | assert_closed!(sub); 233 | } 234 | 235 | #[test] 236 | fn pop_back() { 237 | let mut ob = ObservableVector::::new(); 238 | let (values, mut sub) = ob.subscribe().sort(); 239 | 240 | assert!(values.is_empty()); 241 | assert_pending!(sub); 242 | 243 | // Append a bunch of items. 244 | ob.append(vector!['e', 'b', 'a', 'd', 'c', 'f']); 245 | assert_next_eq!(sub, VectorDiff::Append { values: vector!['a', 'b', 'c', 'd', 'e', 'f'] }); 246 | 247 | // Pop back once. 248 | // `f` is at the last sorted position, so it generates a `VectorDiff::PopBack`. 249 | assert_eq!(ob.pop_back(), Some('f')); 250 | assert_next_eq!(sub, VectorDiff::PopBack); 251 | 252 | // Pop back again. 253 | // `c` is at the third sorted position, so it generates a `VectorDiff::Remove`. 254 | assert_eq!(ob.pop_back(), Some('c')); 255 | assert_next_eq!(sub, VectorDiff::Remove { index: 2 }); 256 | 257 | // Pop back again. 258 | // `d` is at the third sorted position, so it generates a `VectorDiff::Remove`. 259 | assert_eq!(ob.pop_back(), Some('d')); 260 | assert_next_eq!(sub, VectorDiff::Remove { index: 2 }); 261 | 262 | // Pop back again. 263 | // `a` is at the first sorted position, so it generates a 264 | // `VectorDiff::PopFront`. 265 | assert_eq!(ob.pop_back(), Some('a')); 266 | assert_next_eq!(sub, VectorDiff::PopFront); 267 | 268 | // Pop back again. 269 | // `b` is at the first sorted position, so it generates a 270 | // `VectorDiff::PopFront`. 271 | assert_eq!(ob.pop_back(), Some('b')); 272 | assert_next_eq!(sub, VectorDiff::PopFront); 273 | 274 | // Pop back again. 275 | // `e` is at the first sorted position, so it generates a 276 | // `VectorDiff::PopFront`. 277 | assert_eq!(ob.pop_back(), Some('e')); 278 | assert_next_eq!(sub, VectorDiff::PopFront); 279 | 280 | assert!(ob.is_empty()); 281 | 282 | drop(ob); 283 | assert_closed!(sub); 284 | } 285 | 286 | #[test] 287 | fn remove() { 288 | let mut ob = ObservableVector::::new(); 289 | let (values, mut sub) = ob.subscribe().sort(); 290 | 291 | assert!(values.is_empty()); 292 | assert_pending!(sub); 293 | 294 | // Append a bunch of items. 295 | ob.append(vector!['e', 'b', 'a', 'd', 'c']); 296 | assert_next_eq!(sub, VectorDiff::Append { values: vector!['a', 'b', 'c', 'd', 'e'] }); 297 | 298 | // Remove `a`. 299 | ob.remove(2); 300 | assert_next_eq!(sub, VectorDiff::PopFront); 301 | 302 | // Remove `e`. 303 | ob.remove(0); 304 | assert_next_eq!(sub, VectorDiff::PopBack); 305 | 306 | // Remove `c`. 307 | ob.remove(2); 308 | assert_next_eq!(sub, VectorDiff::Remove { index: 1 }); 309 | 310 | // Items in the vector have been removed and are not sorted. 311 | assert_eq!(*ob, vector!['b', 'd']); 312 | 313 | drop(ob); 314 | assert_closed!(sub); 315 | } 316 | 317 | #[test] 318 | fn set() { 319 | let mut ob = ObservableVector::::new(); 320 | let (values, mut sub) = ob.subscribe().sort(); 321 | 322 | assert!(values.is_empty()); 323 | assert_pending!(sub); 324 | 325 | // Append a bunch of items. 326 | ob.append(vector!['d', 'e', 'b', 'g']); 327 | assert_next_eq!(sub, VectorDiff::Append { values: vector!['b', 'd', 'e', 'g'] }); 328 | 329 | // Same value. 330 | ob.set(0, 'd'); 331 | assert_next_eq!(sub, VectorDiff::Set { index: 1, value: 'd' }); 332 | 333 | // Another value, that is sorted at the same sorted index: `d` is at the sorted 334 | // index 1, and `c` is at the sorted index 1 too. The `VectorDiff::Remove` + 335 | // `VectorDiff::Insert` are optimised as a single `VectorDiff::Set`. 336 | ob.set(0, 'c'); 337 | assert_next_eq!(sub, VectorDiff::Set { index: 1, value: 'c' }); 338 | 339 | // Another value, that is sorted at an adjacent sorted index: `c` is at the 340 | // sorted index 1, but `d` is at the sorted index 2. The `VectorDiff::Remove` + 341 | // `VectorDiff::Insert` are optimised as a single `VectorDiff::Set`. 342 | ob.set(0, 'd'); 343 | assert_next_eq!(sub, VectorDiff::Set { index: 1, value: 'd' }); 344 | 345 | // Another value, that is moved to the left. 346 | ob.set(0, 'a'); 347 | assert_next_eq!(sub, VectorDiff::Remove { index: 1 }); 348 | assert_next_eq!(sub, VectorDiff::Insert { index: 0, value: 'a' }); 349 | 350 | // Another value, that is moved to the right. 351 | ob.set(0, 'f'); 352 | assert_next_eq!(sub, VectorDiff::Remove { index: 0 }); 353 | assert_next_eq!(sub, VectorDiff::Insert { index: 2, value: 'f' }); 354 | 355 | // Another value, that is moved to the right-most position. 356 | ob.set(0, 'h'); 357 | assert_next_eq!(sub, VectorDiff::Remove { index: 2 }); 358 | assert_next_eq!(sub, VectorDiff::Insert { index: 3, value: 'h' }); 359 | 360 | // Same operation, at another index, just for fun. 361 | ob.set(2, 'f'); 362 | assert_next_eq!(sub, VectorDiff::Remove { index: 0 }); 363 | assert_next_eq!(sub, VectorDiff::Insert { index: 1, value: 'f' }); 364 | 365 | // Items in the vector have been updated and are not sorted. 366 | assert_eq!(*ob, vector!['h', 'e', 'f', 'g']); 367 | 368 | drop(ob); 369 | assert_closed!(sub); 370 | } 371 | 372 | #[test] 373 | fn truncate() { 374 | let mut ob = ObservableVector::::new(); 375 | let (values, mut sub) = ob.subscribe().sort(); 376 | 377 | assert!(values.is_empty()); 378 | assert_pending!(sub); 379 | 380 | // Append a bunch of items. 381 | ob.append(vector!['c', 'd', 'a']); 382 | assert_next_eq!(sub, VectorDiff::Append { values: vector!['a', 'c', 'd'] }); 383 | 384 | // Append other items. 385 | ob.append(vector!['b', 'e', 'f']); 386 | assert_next_eq!(sub, VectorDiff::Insert { index: 1, value: 'b' }); 387 | assert_next_eq!(sub, VectorDiff::Append { values: vector!['e', 'f'] }); 388 | 389 | // Truncate. 390 | ob.truncate(2); 391 | assert_next_eq!(sub, VectorDiff::Truncate { length: 2 }); 392 | 393 | // Items in the vector have been truncated and are not sorted. 394 | assert_eq!(*ob, vector!['c', 'd']); 395 | 396 | // Append other items. 397 | ob.append(vector!['b', 'x', 'y']); 398 | assert_next_eq!(sub, VectorDiff::PushFront { value: 'b' }); 399 | assert_next_eq!(sub, VectorDiff::Append { values: vector!['x', 'y'] }); 400 | 401 | drop(ob); 402 | assert_closed!(sub); 403 | } 404 | 405 | #[test] 406 | fn reset() { 407 | let mut ob = ObservableVector::::with_capacity(1); 408 | let (values, mut sub) = ob.subscribe().sort(); 409 | 410 | assert!(values.is_empty()); 411 | assert_pending!(sub); 412 | 413 | // Append a bunch of items. 414 | ob.append(vector!['c', 'd', 'a']); 415 | assert_next_eq!(sub, VectorDiff::Append { values: vector!['a', 'c', 'd'] }); 416 | 417 | // Push back a bunch of items 3 times, so that it overflows the capacity, and we 418 | // get a reset! 419 | ob.push_back('b'); 420 | ob.push_back('f'); 421 | assert_next_eq!(sub, VectorDiff::Reset { values: vector!['a', 'b', 'c', 'd', 'f'] }); 422 | 423 | // Items in the vector have been inserted and are not sorted. 424 | assert_eq!(*ob, vector!['c', 'd', 'a', 'b', 'f']); 425 | 426 | drop(ob); 427 | assert_closed!(sub); 428 | } 429 | -------------------------------------------------------------------------------- /eyeball-im-util/tests/it/sort_by.rs: -------------------------------------------------------------------------------- 1 | use eyeball_im::{ObservableVector, VectorDiff}; 2 | use eyeball_im_util::vector::VectorObserverExt; 3 | use imbl::vector; 4 | use std::cmp::Ordering; 5 | use stream_assert::{assert_closed, assert_next_eq, assert_pending}; 6 | 7 | /// Reversed sorting function. 8 | /// 9 | /// `sort_by(rev_cmp)` is equivalent to `sort_by_key(std::cmp::Reverse)` 10 | fn rev_cmp(left: &T, right: &T) -> Ordering 11 | where 12 | T: Ord, 13 | { 14 | right.cmp(left) 15 | } 16 | 17 | #[test] 18 | fn new() { 19 | let ob = ObservableVector::::from(vector!['c', 'a', 'd', 'b']); 20 | let (values, mut sub) = ob.subscribe().sort_by(rev_cmp); 21 | 22 | assert_eq!(values, vector!['d', 'c', 'b', 'a']); 23 | assert_pending!(sub); 24 | 25 | drop(ob); 26 | assert_closed!(sub); 27 | } 28 | 29 | #[test] 30 | fn append() { 31 | let mut ob = ObservableVector::::new(); 32 | let (values, mut sub) = ob.subscribe().sort_by(rev_cmp); 33 | 34 | assert!(values.is_empty()); 35 | assert_pending!(sub); 36 | 37 | // Append on an empty vector. 38 | ob.append(vector!['d', 'e', 'e']); 39 | assert_next_eq!(sub, VectorDiff::Append { values: vector!['e', 'e', 'd'] }); 40 | 41 | // Append on an non-empty vector. 42 | ob.append(vector!['f', 'g', 'c']); 43 | assert_next_eq!(sub, VectorDiff::PushFront { value: 'g' }); 44 | assert_next_eq!(sub, VectorDiff::Insert { index: 1, value: 'f' }); 45 | assert_next_eq!(sub, VectorDiff::Append { values: vector!['c'] }); 46 | 47 | // Another append. 48 | ob.append(vector!['i', 'h', 'j', 'a', 'b']); 49 | assert_next_eq!(sub, VectorDiff::PushFront { value: 'j' }); 50 | assert_next_eq!(sub, VectorDiff::Insert { index: 1, value: 'i' }); 51 | assert_next_eq!(sub, VectorDiff::Insert { index: 2, value: 'h' }); 52 | assert_next_eq!(sub, VectorDiff::Append { values: vector!['b', 'a'] }); 53 | 54 | // Items in the vector have been appended and are not sorted. 55 | assert_eq!(*ob, vector!['d', 'e', 'e', 'f', 'g', 'c', 'i', 'h', 'j', 'a', 'b']); 56 | 57 | drop(ob); 58 | assert_closed!(sub); 59 | } 60 | 61 | #[test] 62 | fn clear() { 63 | let mut ob = ObservableVector::::new(); 64 | let (values, mut sub) = ob.subscribe().sort_by(rev_cmp); 65 | 66 | assert!(values.is_empty()); 67 | assert_pending!(sub); 68 | 69 | ob.append(vector!['b', 'a', 'c']); 70 | assert_next_eq!(sub, VectorDiff::Append { values: vector!['c', 'b', 'a'] }); 71 | 72 | assert_eq!(*ob, vector!['b', 'a', 'c']); 73 | 74 | // Let's clear it. 75 | ob.clear(); 76 | 77 | assert_next_eq!(sub, VectorDiff::Clear); 78 | 79 | // Items in the vector has been cleared out. 80 | assert!((*ob).is_empty()); 81 | 82 | drop(ob); 83 | assert_closed!(sub); 84 | } 85 | 86 | #[test] 87 | fn push_front() { 88 | let mut ob = ObservableVector::::new(); 89 | let (values, mut sub) = ob.subscribe().sort_by(rev_cmp); 90 | 91 | assert!(values.is_empty()); 92 | assert_pending!(sub); 93 | 94 | // Push front on an empty vector. 95 | ob.push_front('b'); 96 | assert_next_eq!(sub, VectorDiff::PushFront { value: 'b' }); 97 | 98 | // Push front on non-empty vector. 99 | // The new item should appear at the end. 100 | ob.push_front('a'); 101 | assert_next_eq!(sub, VectorDiff::PushBack { value: 'a' }); 102 | 103 | // Another push front. 104 | // The new item should appear at the beginning. 105 | ob.push_front('d'); 106 | assert_next_eq!(sub, VectorDiff::PushFront { value: 'd' }); 107 | 108 | // Another push front. 109 | // The new item should appear in the middle. 110 | ob.push_front('c'); 111 | assert_next_eq!(sub, VectorDiff::Insert { index: 1, value: 'c' }); 112 | 113 | // Items in the vector have been pushed front and are not sorted. 114 | assert_eq!(*ob, vector!['c', 'd', 'a', 'b']); 115 | 116 | drop(ob); 117 | assert_closed!(sub); 118 | } 119 | 120 | #[test] 121 | fn push_back() { 122 | let mut ob = ObservableVector::::new(); 123 | let (values, mut sub) = ob.subscribe().sort_by(rev_cmp); 124 | 125 | assert!(values.is_empty()); 126 | assert_pending!(sub); 127 | 128 | // Push back on an empty vector. 129 | ob.push_back('b'); 130 | assert_next_eq!(sub, VectorDiff::PushFront { value: 'b' }); 131 | 132 | // Push back on non-empty vector. 133 | // The new item should appear at the end. 134 | ob.push_back('a'); 135 | assert_next_eq!(sub, VectorDiff::PushBack { value: 'a' }); 136 | 137 | // Another push back. 138 | // The new item should appear at the beginning. 139 | ob.push_back('d'); 140 | assert_next_eq!(sub, VectorDiff::PushFront { value: 'd' }); 141 | 142 | // Another push back. 143 | // The new item should appear in the middle. 144 | ob.push_back('c'); 145 | assert_next_eq!(sub, VectorDiff::Insert { index: 1, value: 'c' }); 146 | 147 | // Items in the vector have been pushed back and are not sorted. 148 | assert_eq!(*ob, vector!['b', 'a', 'd', 'c']); 149 | 150 | drop(ob); 151 | assert_closed!(sub); 152 | } 153 | 154 | #[test] 155 | fn insert() { 156 | let mut ob = ObservableVector::::new(); 157 | let (values, mut sub) = ob.subscribe().sort_by(rev_cmp); 158 | 159 | assert!(values.is_empty()); 160 | assert_pending!(sub); 161 | 162 | // Insert on an empty vector. 163 | ob.insert(0, 'b'); 164 | assert_next_eq!(sub, VectorDiff::PushFront { value: 'b' }); 165 | 166 | // Insert on non-empty vector. 167 | // The new item should appear at the end. 168 | ob.insert(1, 'a'); 169 | assert_next_eq!(sub, VectorDiff::PushBack { value: 'a' }); 170 | 171 | // Another insert. 172 | // The new item should appear at the beginning. 173 | ob.insert(1, 'd'); 174 | assert_next_eq!(sub, VectorDiff::PushFront { value: 'd' }); 175 | 176 | // Another insert. 177 | // The new item should appear at the beginning. 178 | ob.insert(1, 'e'); 179 | assert_next_eq!(sub, VectorDiff::PushFront { value: 'e' }); 180 | 181 | // Another insert. 182 | // The new item should appear in the middle. 183 | ob.insert(3, 'c'); 184 | assert_next_eq!(sub, VectorDiff::Insert { index: 2, value: 'c' }); 185 | 186 | // Items in the vector have been inserted and are not sorted. 187 | assert_eq!(*ob, vector!['b', 'e', 'd', 'c', 'a']); 188 | 189 | drop(ob); 190 | assert_closed!(sub); 191 | } 192 | 193 | #[test] 194 | fn pop_front() { 195 | let mut ob = ObservableVector::::new(); 196 | let (values, mut sub) = ob.subscribe().sort_by(rev_cmp); 197 | 198 | assert!(values.is_empty()); 199 | assert_pending!(sub); 200 | 201 | // Append a bunch of items. 202 | ob.append(vector!['e', 'b', 'a', 'd', 'c']); 203 | assert_next_eq!(sub, VectorDiff::Append { values: vector!['e', 'd', 'c', 'b', 'a'] }); 204 | 205 | // Pop front once. 206 | assert_eq!(ob.pop_front(), Some('e')); 207 | assert_next_eq!(sub, VectorDiff::PopFront); 208 | 209 | // Pop front again. 210 | assert_eq!(ob.pop_front(), Some('b')); 211 | assert_next_eq!(sub, VectorDiff::Remove { index: 2 }); 212 | 213 | // Pop front again. 214 | assert_eq!(ob.pop_front(), Some('a')); 215 | assert_next_eq!(sub, VectorDiff::PopBack); 216 | 217 | // Pop front again. 218 | assert_eq!(ob.pop_front(), Some('d')); 219 | assert_next_eq!(sub, VectorDiff::PopFront); 220 | 221 | // Pop front again. 222 | assert_eq!(ob.pop_front(), Some('c')); 223 | assert_next_eq!(sub, VectorDiff::PopFront); 224 | 225 | assert!(ob.is_empty()); 226 | 227 | drop(ob); 228 | assert_closed!(sub); 229 | } 230 | 231 | #[test] 232 | fn pop_back() { 233 | let mut ob = ObservableVector::::new(); 234 | let (values, mut sub) = ob.subscribe().sort_by(rev_cmp); 235 | 236 | assert!(values.is_empty()); 237 | assert_pending!(sub); 238 | 239 | // Append a bunch of items. 240 | ob.append(vector!['e', 'b', 'a', 'd', 'c', 'f']); 241 | assert_next_eq!(sub, VectorDiff::Append { values: vector!['f', 'e', 'd', 'c', 'b', 'a'] }); 242 | 243 | // Pop back once. 244 | // `f` is at the first sorted position, so it generates a 245 | // `VectorDiff::PopFront`. 246 | assert_eq!(ob.pop_back(), Some('f')); 247 | assert_next_eq!(sub, VectorDiff::PopFront); 248 | 249 | // Pop back again. 250 | assert_eq!(ob.pop_back(), Some('c')); 251 | assert_next_eq!(sub, VectorDiff::Remove { index: 2 }); 252 | 253 | // Pop back again. 254 | assert_eq!(ob.pop_back(), Some('d')); 255 | assert_next_eq!(sub, VectorDiff::Remove { index: 1 }); 256 | 257 | // Pop back again. 258 | assert_eq!(ob.pop_back(), Some('a')); 259 | assert_next_eq!(sub, VectorDiff::PopBack); 260 | 261 | // Pop back again. 262 | assert_eq!(ob.pop_back(), Some('b')); 263 | assert_next_eq!(sub, VectorDiff::PopBack); 264 | 265 | // Pop back again. 266 | assert_eq!(ob.pop_back(), Some('e')); 267 | assert_next_eq!(sub, VectorDiff::PopFront); 268 | 269 | assert!(ob.is_empty()); 270 | 271 | drop(ob); 272 | assert_closed!(sub); 273 | } 274 | 275 | #[test] 276 | fn remove() { 277 | let mut ob = ObservableVector::::new(); 278 | let (values, mut sub) = ob.subscribe().sort_by(rev_cmp); 279 | 280 | assert!(values.is_empty()); 281 | assert_pending!(sub); 282 | 283 | // Append a bunch of items. 284 | ob.append(vector!['e', 'b', 'a', 'd', 'c']); 285 | assert_next_eq!(sub, VectorDiff::Append { values: vector!['e', 'd', 'c', 'b', 'a'] }); 286 | 287 | // Remove `a`. 288 | ob.remove(2); 289 | assert_next_eq!(sub, VectorDiff::PopBack); 290 | 291 | // Remove `e`. 292 | ob.remove(0); 293 | assert_next_eq!(sub, VectorDiff::PopFront); 294 | 295 | // Remove `c`. 296 | ob.remove(2); 297 | assert_next_eq!(sub, VectorDiff::Remove { index: 1 }); 298 | 299 | // Items in the vector have been removed and are not sorted. 300 | assert_eq!(*ob, vector!['b', 'd']); 301 | 302 | drop(ob); 303 | assert_closed!(sub); 304 | } 305 | 306 | #[test] 307 | fn set() { 308 | let mut ob = ObservableVector::::new(); 309 | let (values, mut sub) = ob.subscribe().sort_by(rev_cmp); 310 | 311 | assert!(values.is_empty()); 312 | assert_pending!(sub); 313 | 314 | // Append a bunch of items. 315 | ob.append(vector!['d', 'e', 'b', 'g']); 316 | assert_next_eq!(sub, VectorDiff::Append { values: vector!['g', 'e', 'd', 'b'] }); 317 | 318 | // Same value. 319 | ob.set(0, 'd'); 320 | assert_next_eq!(sub, VectorDiff::Set { index: 2, value: 'd' }); 321 | 322 | // Different value but position stays the same. 323 | ob.set(0, 'c'); 324 | assert_next_eq!(sub, VectorDiff::Set { index: 2, value: 'c' }); 325 | 326 | // This time sorting moves the value. 327 | // Another value, that is moved to the right. 328 | ob.set(0, 'f'); 329 | assert_next_eq!(sub, VectorDiff::Remove { index: 2 }); 330 | assert_next_eq!(sub, VectorDiff::Insert { index: 1, value: 'f' }); 331 | 332 | // Same operation, at another index, just for fun. 333 | ob.set(2, 'f'); 334 | assert_next_eq!(sub, VectorDiff::Remove { index: 3 }); 335 | assert_next_eq!(sub, VectorDiff::Insert { index: 1, value: 'f' }); 336 | 337 | // Items in the vector have been updated and are not sorted. 338 | assert_eq!(*ob, vector!['f', 'e', 'f', 'g']); 339 | 340 | drop(ob); 341 | assert_closed!(sub); 342 | } 343 | 344 | #[test] 345 | fn truncate() { 346 | let mut ob = ObservableVector::::new(); 347 | let (values, mut sub) = ob.subscribe().sort_by(rev_cmp); 348 | 349 | assert!(values.is_empty()); 350 | assert_pending!(sub); 351 | 352 | // Append a bunch of items. 353 | ob.append(vector!['c', 'd', 'a']); 354 | assert_next_eq!(sub, VectorDiff::Append { values: vector!['d', 'c', 'a'] }); 355 | 356 | // Append other items. 357 | ob.append(vector!['b', 'e', 'f']); 358 | assert_next_eq!(sub, VectorDiff::PushFront { value: 'f' }); 359 | assert_next_eq!(sub, VectorDiff::Insert { index: 1, value: 'e' }); 360 | assert_next_eq!(sub, VectorDiff::Insert { index: 4, value: 'b' }); 361 | 362 | // Truncate. 363 | ob.truncate(2); 364 | assert_next_eq!(sub, VectorDiff::Truncate { length: 2 }); 365 | 366 | // Items in the vector have been truncated and are not sorted. 367 | assert_eq!(*ob, vector!['c', 'd']); 368 | 369 | // Append other items. 370 | ob.append(vector!['b', 'x', 'y']); 371 | assert_next_eq!(sub, VectorDiff::PushFront { value: 'y' }); 372 | assert_next_eq!(sub, VectorDiff::Insert { index: 1, value: 'x' }); 373 | assert_next_eq!(sub, VectorDiff::Append { values: vector!['b'] }); 374 | 375 | drop(ob); 376 | assert_closed!(sub); 377 | } 378 | 379 | #[test] 380 | fn reset() { 381 | let mut ob = ObservableVector::::with_capacity(1); 382 | let (values, mut sub) = ob.subscribe().sort_by(rev_cmp); 383 | 384 | assert!(values.is_empty()); 385 | assert_pending!(sub); 386 | 387 | // Append a bunch of items. 388 | ob.append(vector!['c', 'd', 'a']); 389 | assert_next_eq!(sub, VectorDiff::Append { values: vector!['d', 'c', 'a'] }); 390 | 391 | // Push back a bunch of items 3 times, so that it overflows the capacity, and we 392 | // get a reset! 393 | ob.push_back('b'); 394 | ob.push_back('f'); 395 | assert_next_eq!(sub, VectorDiff::Reset { values: vector!['f', 'd', 'c', 'b', 'a'] }); 396 | 397 | // Items in the vector have been inserted and are not sorted. 398 | assert_eq!(*ob, vector!['c', 'd', 'a', 'b', 'f']); 399 | 400 | drop(ob); 401 | assert_closed!(sub); 402 | } 403 | -------------------------------------------------------------------------------- /eyeball-im-util/tests/it/sort_by_key.rs: -------------------------------------------------------------------------------- 1 | use eyeball_im::{ObservableVector, VectorDiff}; 2 | use eyeball_im_util::vector::VectorObserverExt; 3 | use imbl::vector; 4 | use stream_assert::{assert_closed, assert_next_eq, assert_pending}; 5 | 6 | #[test] 7 | fn new() { 8 | let ob = ObservableVector::::from(vector!['c', 'a', 'd', 'b']); 9 | let (values, mut sub) = ob.subscribe().sort_by_key(|&x| x); 10 | 11 | assert_eq!(values, vector!['a', 'b', 'c', 'd']); 12 | assert_pending!(sub); 13 | 14 | drop(ob); 15 | assert_closed!(sub); 16 | } 17 | 18 | #[test] 19 | fn append() { 20 | let mut ob = ObservableVector::::new(); 21 | let (values, mut sub) = ob.subscribe().sort_by_key(|&x| x); 22 | 23 | assert!(values.is_empty()); 24 | assert_pending!(sub); 25 | 26 | // Append on an empty vector. 27 | ob.append(vector!['d', 'a', 'e']); 28 | assert_next_eq!(sub, VectorDiff::Append { values: vector!['a', 'd', 'e'] }); 29 | 30 | // Append on an non-empty vector. 31 | ob.append(vector!['f', 'g', 'b']); 32 | assert_next_eq!(sub, VectorDiff::Insert { index: 1, value: 'b' }); 33 | assert_next_eq!(sub, VectorDiff::Append { values: vector!['f', 'g'] }); 34 | 35 | // Another append. 36 | // This time, it contains a duplicated new item + an insert + new items to be 37 | // appended. 38 | ob.append(vector!['i', 'h', 'c', 'j', 'a']); 39 | assert_next_eq!(sub, VectorDiff::PushFront { value: 'a' }); 40 | assert_next_eq!(sub, VectorDiff::Insert { index: 3, value: 'c' }); 41 | assert_next_eq!(sub, VectorDiff::Append { values: vector!['h', 'i', 'j'] }); 42 | 43 | // Another append. 44 | // This time, with two new items that are a duplication of the last item. 45 | ob.append(vector!['k', 'l', 'j', 'm', 'j']); 46 | assert_next_eq!(sub, VectorDiff::Append { values: vector!['j', 'j', 'k', 'l', 'm'] }); 47 | 48 | // Items in the vector have been appended and are not sorted. 49 | assert_eq!( 50 | *ob, 51 | vector!['d', 'a', 'e', 'f', 'g', 'b', 'i', 'h', 'c', 'j', 'a', 'k', 'l', 'j', 'm', 'j'] 52 | ); 53 | 54 | drop(ob); 55 | assert_closed!(sub); 56 | } 57 | 58 | #[test] 59 | fn clear() { 60 | let mut ob = ObservableVector::::new(); 61 | let (values, mut sub) = ob.subscribe().sort_by_key(|&x| x); 62 | 63 | assert!(values.is_empty()); 64 | assert_pending!(sub); 65 | 66 | ob.append(vector!['b', 'a', 'c']); 67 | assert_next_eq!(sub, VectorDiff::Append { values: vector!['a', 'b', 'c'] }); 68 | 69 | assert_eq!(*ob, vector!['b', 'a', 'c']); 70 | 71 | // Let's clear it. 72 | ob.clear(); 73 | 74 | assert_next_eq!(sub, VectorDiff::Clear); 75 | 76 | // Items in the vector has been cleared out. 77 | assert!((*ob).is_empty()); 78 | 79 | drop(ob); 80 | assert_closed!(sub); 81 | } 82 | 83 | #[test] 84 | fn push_front() { 85 | let mut ob = ObservableVector::::new(); 86 | let (values, mut sub) = ob.subscribe().sort_by_key(|&x| x); 87 | 88 | assert!(values.is_empty()); 89 | assert_pending!(sub); 90 | 91 | // Push front on an empty vector. 92 | ob.push_front('b'); 93 | assert_next_eq!(sub, VectorDiff::PushFront { value: 'b' }); 94 | 95 | // Push front on non-empty vector. 96 | // The new item should appear at position 0. 97 | ob.push_front('a'); 98 | assert_next_eq!(sub, VectorDiff::PushFront { value: 'a' }); 99 | 100 | // Another push front. 101 | // The new item should appear at last position. 102 | ob.push_front('d'); 103 | assert_next_eq!(sub, VectorDiff::PushBack { value: 'd' }); 104 | 105 | // Another push front. 106 | // The new item should appear in the middle. 107 | ob.push_front('c'); 108 | assert_next_eq!(sub, VectorDiff::Insert { index: 2, value: 'c' }); 109 | 110 | // Items in the vector have been pushed front and are not sorted. 111 | assert_eq!(*ob, vector!['c', 'd', 'a', 'b']); 112 | 113 | drop(ob); 114 | assert_closed!(sub); 115 | } 116 | 117 | #[test] 118 | fn push_back() { 119 | let mut ob = ObservableVector::::new(); 120 | let (values, mut sub) = ob.subscribe().sort_by_key(|&x| x); 121 | 122 | assert!(values.is_empty()); 123 | assert_pending!(sub); 124 | 125 | // Push back on an empty vector. 126 | ob.push_back('b'); 127 | assert_next_eq!(sub, VectorDiff::PushFront { value: 'b' }); 128 | 129 | // Push back on non-empty vector. 130 | // The new item should appear at position 0. 131 | ob.push_back('a'); 132 | assert_next_eq!(sub, VectorDiff::PushFront { value: 'a' }); 133 | 134 | // Another push back. 135 | // The new item should appear at last position. 136 | ob.push_back('d'); 137 | assert_next_eq!(sub, VectorDiff::PushBack { value: 'd' }); 138 | 139 | // Another push back. 140 | // The new item should appear in the middle. 141 | ob.push_back('c'); 142 | assert_next_eq!(sub, VectorDiff::Insert { index: 2, value: 'c' }); 143 | 144 | // Items in the vector have been pushed back and are not sorted. 145 | assert_eq!(*ob, vector!['b', 'a', 'd', 'c']); 146 | 147 | drop(ob); 148 | assert_closed!(sub); 149 | } 150 | 151 | #[test] 152 | fn insert() { 153 | let mut ob = ObservableVector::::new(); 154 | let (values, mut sub) = ob.subscribe().sort_by_key(|&x| x); 155 | 156 | assert!(values.is_empty()); 157 | assert_pending!(sub); 158 | 159 | // Insert on an empty vector. 160 | ob.insert(0, 'b'); 161 | assert_next_eq!(sub, VectorDiff::PushFront { value: 'b' }); 162 | 163 | // Insert on non-empty vector. 164 | // The new item should appear at position 0. 165 | ob.insert(1, 'a'); 166 | assert_next_eq!(sub, VectorDiff::PushFront { value: 'a' }); 167 | 168 | // Another insert. 169 | // The new item should appear at last position. 170 | ob.insert(1, 'd'); 171 | assert_next_eq!(sub, VectorDiff::PushBack { value: 'd' }); 172 | 173 | // Another insert. 174 | // The new item should appear at last position. 175 | ob.insert(1, 'e'); 176 | assert_next_eq!(sub, VectorDiff::PushBack { value: 'e' }); 177 | 178 | // Another insert. 179 | // The new item should appear in the middle. 180 | ob.insert(3, 'c'); 181 | assert_next_eq!(sub, VectorDiff::Insert { index: 2, value: 'c' }); 182 | 183 | // Items in the vector have been inserted and are not sorted. 184 | assert_eq!(*ob, vector!['b', 'e', 'd', 'c', 'a']); 185 | 186 | drop(ob); 187 | assert_closed!(sub); 188 | } 189 | 190 | #[test] 191 | fn pop_front() { 192 | let mut ob = ObservableVector::::new(); 193 | let (values, mut sub) = ob.subscribe().sort_by_key(|&x| x); 194 | 195 | assert!(values.is_empty()); 196 | assert_pending!(sub); 197 | 198 | // Append a bunch of items. 199 | ob.append(vector!['e', 'b', 'a', 'd', 'c']); 200 | assert_next_eq!(sub, VectorDiff::Append { values: vector!['a', 'b', 'c', 'd', 'e'] }); 201 | 202 | // Pop front once. 203 | // `e` is at the last sorted position, so it generates a `VectorDiff::PopBack`. 204 | assert_eq!(ob.pop_front(), Some('e')); 205 | assert_next_eq!(sub, VectorDiff::PopBack); 206 | 207 | // Pop front again. 208 | // `b` is at the second sorted position, so it generates a `VectorDiff::Remove`. 209 | assert_eq!(ob.pop_front(), Some('b')); 210 | assert_next_eq!(sub, VectorDiff::Remove { index: 1 }); 211 | 212 | // Pop front again. 213 | // `a` is at the first sorted position, so it generates a 214 | // `VectorDiff::PopFront`. 215 | assert_eq!(ob.pop_front(), Some('a')); 216 | assert_next_eq!(sub, VectorDiff::PopFront); 217 | 218 | // Pop front again. 219 | // `d` is at the last sorted position, so it generates a `VectorDiff::PopBack`. 220 | assert_eq!(ob.pop_front(), Some('d')); 221 | assert_next_eq!(sub, VectorDiff::PopBack); 222 | 223 | // Pop front again. 224 | // `c` is at the first sorted position, so it generates a 225 | // `VectorDiff::PopFront`. 226 | assert_eq!(ob.pop_front(), Some('c')); 227 | assert_next_eq!(sub, VectorDiff::PopFront); 228 | 229 | assert!(ob.is_empty()); 230 | 231 | drop(ob); 232 | assert_closed!(sub); 233 | } 234 | 235 | #[test] 236 | fn pop_back() { 237 | let mut ob = ObservableVector::::new(); 238 | let (values, mut sub) = ob.subscribe().sort_by_key(|&x| x); 239 | 240 | assert!(values.is_empty()); 241 | assert_pending!(sub); 242 | 243 | // Append a bunch of items. 244 | ob.append(vector!['e', 'b', 'a', 'd', 'c', 'f']); 245 | assert_next_eq!(sub, VectorDiff::Append { values: vector!['a', 'b', 'c', 'd', 'e', 'f'] }); 246 | 247 | // Pop back once. 248 | // `f` is at the last sorted position, so it generates a `VectorDiff::PopBack`. 249 | assert_eq!(ob.pop_back(), Some('f')); 250 | assert_next_eq!(sub, VectorDiff::PopBack); 251 | 252 | // Pop back again. 253 | // `c` is at the third sorted position, so it generates a `VectorDiff::Remove`. 254 | assert_eq!(ob.pop_back(), Some('c')); 255 | assert_next_eq!(sub, VectorDiff::Remove { index: 2 }); 256 | 257 | // Pop back again. 258 | // `d` is at the third sorted position, so it generates a `VectorDiff::Remove`. 259 | assert_eq!(ob.pop_back(), Some('d')); 260 | assert_next_eq!(sub, VectorDiff::Remove { index: 2 }); 261 | 262 | // Pop back again. 263 | // `a` is at the first sorted position, so it generates a 264 | // `VectorDiff::PopFront`. 265 | assert_eq!(ob.pop_back(), Some('a')); 266 | assert_next_eq!(sub, VectorDiff::PopFront); 267 | 268 | // Pop back again. 269 | // `b` is at the first sorted position, so it generates a 270 | // `VectorDiff::PopFront`. 271 | assert_eq!(ob.pop_back(), Some('b')); 272 | assert_next_eq!(sub, VectorDiff::PopFront); 273 | 274 | // Pop back again. 275 | // `e` is at the first sorted position, so it generates a 276 | // `VectorDiff::PopFront`. 277 | assert_eq!(ob.pop_back(), Some('e')); 278 | assert_next_eq!(sub, VectorDiff::PopFront); 279 | 280 | assert!(ob.is_empty()); 281 | 282 | drop(ob); 283 | assert_closed!(sub); 284 | } 285 | 286 | #[test] 287 | fn remove() { 288 | let mut ob = ObservableVector::::new(); 289 | let (values, mut sub) = ob.subscribe().sort_by_key(|&x| x); 290 | 291 | assert!(values.is_empty()); 292 | assert_pending!(sub); 293 | 294 | // Append a bunch of items. 295 | ob.append(vector!['e', 'b', 'a', 'd', 'c']); 296 | assert_next_eq!(sub, VectorDiff::Append { values: vector!['a', 'b', 'c', 'd', 'e'] }); 297 | 298 | // Remove `a`. 299 | ob.remove(2); 300 | assert_next_eq!(sub, VectorDiff::PopFront); 301 | 302 | // Remove `e`. 303 | ob.remove(0); 304 | assert_next_eq!(sub, VectorDiff::PopBack); 305 | 306 | // Remove `c`. 307 | ob.remove(2); 308 | assert_next_eq!(sub, VectorDiff::Remove { index: 1 }); 309 | 310 | // Items in the vector have been removed and are not sorted. 311 | assert_eq!(*ob, vector!['b', 'd']); 312 | 313 | drop(ob); 314 | assert_closed!(sub); 315 | } 316 | 317 | #[test] 318 | fn set() { 319 | let mut ob = ObservableVector::::new(); 320 | let (values, mut sub) = ob.subscribe().sort_by_key(|&x| x); 321 | 322 | assert!(values.is_empty()); 323 | assert_pending!(sub); 324 | 325 | // Append a bunch of items. 326 | ob.append(vector!['d', 'e', 'b', 'g']); 327 | assert_next_eq!(sub, VectorDiff::Append { values: vector!['b', 'd', 'e', 'g'] }); 328 | 329 | // Same value. 330 | ob.set(0, 'd'); 331 | assert_next_eq!(sub, VectorDiff::Set { index: 1, value: 'd' }); 332 | 333 | // Another value, that is sorted at the same index. 334 | ob.set(0, 'c'); 335 | assert_next_eq!(sub, VectorDiff::Set { index: 1, value: 'c' }); 336 | 337 | // Another value, that is moved to the left. 338 | ob.set(0, 'a'); 339 | assert_next_eq!(sub, VectorDiff::Remove { index: 1 }); 340 | assert_next_eq!(sub, VectorDiff::Insert { index: 0, value: 'a' }); 341 | 342 | // Another value, that is moved to the right. 343 | ob.set(0, 'f'); 344 | assert_next_eq!(sub, VectorDiff::Remove { index: 0 }); 345 | assert_next_eq!(sub, VectorDiff::Insert { index: 2, value: 'f' }); 346 | 347 | // Another value, that is moved to the right-most position. 348 | ob.set(0, 'h'); 349 | assert_next_eq!(sub, VectorDiff::Remove { index: 2 }); 350 | assert_next_eq!(sub, VectorDiff::Insert { index: 3, value: 'h' }); 351 | 352 | // Same operation, at another index, just for fun. 353 | ob.set(2, 'f'); 354 | assert_next_eq!(sub, VectorDiff::Remove { index: 0 }); 355 | assert_next_eq!(sub, VectorDiff::Insert { index: 1, value: 'f' }); 356 | 357 | // Items in the vector have been updated and are not sorted. 358 | assert_eq!(*ob, vector!['h', 'e', 'f', 'g']); 359 | 360 | drop(ob); 361 | assert_closed!(sub); 362 | } 363 | 364 | #[test] 365 | fn truncate() { 366 | let mut ob = ObservableVector::::new(); 367 | let (values, mut sub) = ob.subscribe().sort_by_key(|&x| x); 368 | 369 | assert!(values.is_empty()); 370 | assert_pending!(sub); 371 | 372 | // Append a bunch of items. 373 | ob.append(vector!['c', 'd', 'a']); 374 | assert_next_eq!(sub, VectorDiff::Append { values: vector!['a', 'c', 'd'] }); 375 | 376 | // Append other items. 377 | ob.append(vector!['b', 'e', 'f']); 378 | assert_next_eq!(sub, VectorDiff::Insert { index: 1, value: 'b' }); 379 | assert_next_eq!(sub, VectorDiff::Append { values: vector!['e', 'f'] }); 380 | 381 | // Truncate. 382 | ob.truncate(2); 383 | assert_next_eq!(sub, VectorDiff::Truncate { length: 2 }); 384 | 385 | // Items in the vector have been truncated and are not sorted. 386 | assert_eq!(*ob, vector!['c', 'd']); 387 | 388 | // Append other items. 389 | ob.append(vector!['b', 'x', 'y']); 390 | assert_next_eq!(sub, VectorDiff::PushFront { value: 'b' }); 391 | assert_next_eq!(sub, VectorDiff::Append { values: vector!['x', 'y'] }); 392 | 393 | drop(ob); 394 | assert_closed!(sub); 395 | } 396 | 397 | #[test] 398 | fn reset() { 399 | let mut ob = ObservableVector::::with_capacity(1); 400 | let (values, mut sub) = ob.subscribe().sort_by_key(|&x| x); 401 | 402 | assert!(values.is_empty()); 403 | assert_pending!(sub); 404 | 405 | // Append a bunch of items. 406 | ob.append(vector!['c', 'd', 'a']); 407 | assert_next_eq!(sub, VectorDiff::Append { values: vector!['a', 'c', 'd'] }); 408 | 409 | // Push back a bunch of items 3 times, so that it overflows the capacity, and we 410 | // get a reset! 411 | ob.push_back('b'); 412 | ob.push_back('f'); 413 | assert_next_eq!(sub, VectorDiff::Reset { values: vector!['a', 'b', 'c', 'd', 'f'] }); 414 | 415 | // Items in the vector have been inserted and are not sorted. 416 | assert_eq!(*ob, vector!['c', 'd', 'a', 'b', 'f']); 417 | 418 | drop(ob); 419 | assert_closed!(sub); 420 | } 421 | -------------------------------------------------------------------------------- /eyeball-im/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.7.0 2 | 3 | - Upgrade `imbl` dependency to version 5 4 | 5 | # 0.6.1 6 | 7 | - Documentation improvements 8 | 9 | # 0.6.0 10 | 11 | - Upgrade `imbl` dependency to version 4 12 | 13 | # 0.5.1 14 | 15 | - Remove `Send` and `Sync` constraints for `VectorSubscriber` 16 | 17 | # 0.5.0 18 | 19 | - Upgrade `imbl` dependency to version 3 20 | 21 | # 0.4.3 22 | 23 | - Don't emit a diff to subscribers if `clear` is called on an already-empty 24 | `ObservableVector` 25 | - Add a `serde` feature 26 | - For now, this only gates a `Serialize` impl for `VectorDiff` 27 | - `Deserialize` might be added in the future 28 | 29 | # 0.4.2 30 | 31 | - Add `VectorDiff::apply` that applies a `VectorDiff` to a `Vector` 32 | 33 | # 0.4.1 34 | 35 | - Add extra methods to `VectorSubscriber`: `values`, `into_values_and_stream`, 36 | `into_values_and_batched_stream` 37 | 38 | # 0.4.0 39 | 40 | - Add `truncate` method to `ObservableVector`, `ObservableVectorTransaction` 41 | - Add `VectorDiff::Truncate { length: usize }` 42 | 43 | # 0.3.2 44 | 45 | - Fix transaction commit not working when there are no subscribers 46 | 47 | # 0.3.1 48 | 49 | - Fix logging for rollback-on-drop 50 | 51 | # 0.3.0 52 | 53 | - Add `ObservableVectorTransaction` for making multiple updates as one atomic 54 | unit (created via `observable_vector.transaction()`) 55 | - Remove `Stream` implementation from `VectorSubscriber` in favor of 56 | `.into_stream()` and `.into_batched_stream()` methods that return different 57 | stream types 58 | 59 | # 0.2.6 60 | 61 | This release only updates metadata for crates.io. 62 | 63 | # 0.2.5 64 | 65 | - Emit more tracing events when the `tracing` Cargo feature is enabled 66 | 67 | # 0.2.4 68 | 69 | - Add `ObservableVector::entries` 70 | 71 | # 0.2.3 72 | 73 | - Add `entry` and `for_each` methods to `ObservableVector` 74 | 75 | # 0.2.2 76 | 77 | - Update the lag handling in `VectorSubscriber` to yield a `VectorDiff::Reset` 78 | with the latest state, instead of the state N - 1 changes earlier (where N is 79 | the capacity of the internal buffer) 80 | 81 | # 0.2.1 82 | 83 | - Add `VectorDiff::map` 84 | 85 | # 0.2.0 86 | 87 | - Switch from the unmaintained `im` crate to the maintained fork `imbl` 88 | - Re-export the non-observable `Vector` type from the crate root 89 | -------------------------------------------------------------------------------- /eyeball-im/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "eyeball-im" 3 | version = "0.7.0" 4 | edition = "2021" 5 | rust-version = "1.64.0" 6 | description = "Observable collections based on the `im` crate." 7 | license.workspace = true 8 | repository.workspace = true 9 | categories.workspace = true 10 | keywords.workspace = true 11 | 12 | [package.metadata.docs.rs] 13 | all-features = true 14 | 15 | [dependencies] 16 | futures-core.workspace = true 17 | imbl.workspace = true 18 | serde = { version = "1.0", optional = true } 19 | tokio.workspace = true 20 | tracing = { workspace = true, optional = true } 21 | 22 | [dev-dependencies] 23 | serde_json = "1.0" 24 | stream_assert.workspace = true 25 | 26 | [features] 27 | default = [] 28 | # Enable this feature to implement `serde::Serialize` for `VectorDiff`. 29 | serde = ["dep:serde", "imbl/serde"] 30 | 31 | [lints] 32 | workspace = true 33 | -------------------------------------------------------------------------------- /eyeball-im/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Observable collections based on the `im` crate. 2 | //! 3 | //! Cargo features: 4 | //! 5 | //! - `tracing`: Emit [tracing] events when updates are sent out 6 | //! - `serde`: Enable the `Serialize` implementation for [`VectorDiff`] 7 | 8 | #![cfg_attr(docsrs, feature(doc_auto_cfg))] 9 | 10 | mod reusable_box; 11 | mod vector; 12 | 13 | pub use vector::{ 14 | ObservableVector, ObservableVectorEntries, ObservableVectorEntry, ObservableVectorTransaction, 15 | ObservableVectorTransactionEntries, ObservableVectorTransactionEntry, VectorDiff, 16 | VectorSubscriber, VectorSubscriberBatchedStream, VectorSubscriberStream, 17 | }; 18 | 19 | #[doc(no_inline)] 20 | pub use imbl::Vector; 21 | -------------------------------------------------------------------------------- /eyeball-im/src/reusable_box.rs: -------------------------------------------------------------------------------- 1 | // Copy-pasted from https://docs.rs/tokio-util/latest/src/tokio_util/sync/reusable_box.rs.html 2 | // Removed all `+ Send`s. 3 | 4 | use std::{ 5 | alloc::Layout, 6 | fmt, 7 | future::{self, Future}, 8 | mem::{self, ManuallyDrop}, 9 | pin::Pin, 10 | ptr, 11 | task::{Context, Poll}, 12 | }; 13 | 14 | /// A reusable `Pin + 'a>>`. 15 | /// 16 | /// This type lets you replace the future stored in the box without 17 | /// reallocating when the size and alignment permits this. 18 | pub(crate) struct ReusableBoxFuture<'a, T> { 19 | boxed: Pin + 'a>>, 20 | } 21 | 22 | impl<'a, T> ReusableBoxFuture<'a, T> { 23 | /// Create a new `ReusableBoxFuture` containing the provided future. 24 | pub(crate) fn new(future: F) -> Self 25 | where 26 | F: Future + 'a, 27 | { 28 | Self { boxed: Box::pin(future) } 29 | } 30 | 31 | /// Replace the future currently stored in this box. 32 | /// 33 | /// This reallocates if and only if the layout of the provided future is 34 | /// different from the layout of the currently stored future. 35 | pub(crate) fn set(&mut self, future: F) 36 | where 37 | F: Future + 'a, 38 | { 39 | if let Err(future) = self.try_set(future) { 40 | *self = Self::new(future); 41 | } 42 | } 43 | 44 | /// Replace the future currently stored in this box. 45 | /// 46 | /// This function never reallocates, but returns an error if the provided 47 | /// future has a different size or alignment from the currently stored 48 | /// future. 49 | pub(crate) fn try_set(&mut self, future: F) -> Result<(), F> 50 | where 51 | F: Future + 'a, 52 | { 53 | // If we try to inline the contents of this function, the type checker complains 54 | // because the bound `T: 'a` is not satisfied in the call to 55 | // `pending()`. But by putting it in an inner function that doesn't have 56 | // `T` as a generic parameter, we implicitly get the bound `F::Output: 57 | // 'a` transitively through `F: 'a`, allowing us to call `pending()`. 58 | #[inline(always)] 59 | fn real_try_set<'a, F>( 60 | this: &mut ReusableBoxFuture<'a, F::Output>, 61 | future: F, 62 | ) -> Result<(), F> 63 | where 64 | F: Future + 'a, 65 | { 66 | // future::Pending is a ZST so this never allocates. 67 | let boxed = mem::replace(&mut this.boxed, Box::pin(future::pending())); 68 | reuse_pin_box(boxed, future, |boxed| this.boxed = Pin::from(boxed)) 69 | } 70 | 71 | real_try_set(self, future) 72 | } 73 | 74 | /// Get a pinned reference to the underlying future. 75 | pub(crate) fn get_pin(&mut self) -> Pin<&mut (dyn Future)> { 76 | self.boxed.as_mut() 77 | } 78 | 79 | /// Poll the future stored inside this box. 80 | pub(crate) fn poll(&mut self, cx: &mut Context<'_>) -> Poll { 81 | self.get_pin().poll(cx) 82 | } 83 | } 84 | 85 | impl Future for ReusableBoxFuture<'_, T> { 86 | type Output = T; 87 | 88 | /// Poll the future stored inside this box. 89 | fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 90 | Pin::into_inner(self).get_pin().poll(cx) 91 | } 92 | } 93 | 94 | // The only method called on self.boxed is poll, which takes &mut self, so this 95 | // struct being Sync does not permit any invalid access to the Future, even if 96 | // the future is not Sync. 97 | unsafe impl Sync for ReusableBoxFuture<'_, T> {} 98 | 99 | impl fmt::Debug for ReusableBoxFuture<'_, T> { 100 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 101 | f.debug_struct("ReusableBoxFuture").finish() 102 | } 103 | } 104 | 105 | fn reuse_pin_box(boxed: Pin>, new_value: U, callback: F) -> Result 106 | where 107 | F: FnOnce(Box) -> O, 108 | { 109 | let layout = Layout::for_value::(&*boxed); 110 | if layout != Layout::new::() { 111 | return Err(new_value); 112 | } 113 | 114 | // SAFETY: We don't ever construct a non-pinned reference to the old `T` from 115 | // now on, and we always drop the `T`. 116 | let raw: *mut T = Box::into_raw(unsafe { Pin::into_inner_unchecked(boxed) }); 117 | 118 | // When dropping the old value panics, we still want to call `callback` — so 119 | // move the rest of the code into a guard type. 120 | let guard = CallOnDrop::new(|| { 121 | let raw: *mut U = raw.cast::(); 122 | unsafe { raw.write(new_value) }; 123 | 124 | // SAFETY: 125 | // - `T` and `U` have the same layout. 126 | // - `raw` comes from a `Box` that uses the same allocator as this one. 127 | // - `raw` points to a valid instance of `U` (we just wrote it in). 128 | let boxed = unsafe { Box::from_raw(raw) }; 129 | 130 | callback(boxed) 131 | }); 132 | 133 | // Drop the old value. 134 | unsafe { ptr::drop_in_place(raw) }; 135 | 136 | // Run the rest of the code. 137 | Ok(guard.call()) 138 | } 139 | 140 | struct CallOnDrop O> { 141 | f: ManuallyDrop, 142 | } 143 | 144 | impl O> CallOnDrop { 145 | fn new(f: F) -> Self { 146 | let f = ManuallyDrop::new(f); 147 | Self { f } 148 | } 149 | fn call(self) -> O { 150 | let mut this = ManuallyDrop::new(self); 151 | let f = unsafe { ManuallyDrop::take(&mut this.f) }; 152 | f() 153 | } 154 | } 155 | 156 | impl O> Drop for CallOnDrop { 157 | fn drop(&mut self) { 158 | let f = unsafe { ManuallyDrop::take(&mut self.f) }; 159 | f(); 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /eyeball-im/src/vector/entry.rs: -------------------------------------------------------------------------------- 1 | use std::{fmt, ops::Deref}; 2 | 3 | use super::ObservableVector; 4 | 5 | /// A handle to a single value in an [`ObservableVector`]. 6 | pub struct ObservableVectorEntry<'a, T> { 7 | inner: &'a mut ObservableVector, 8 | index: EntryIndex<'a>, 9 | } 10 | 11 | impl<'a, T> ObservableVectorEntry<'a, T> 12 | where 13 | T: Clone + 'static, 14 | { 15 | pub(super) fn new(inner: &'a mut ObservableVector, index: usize) -> Self { 16 | Self { inner, index: EntryIndex::Owned(index) } 17 | } 18 | 19 | fn new_borrowed(inner: &'a mut ObservableVector, index: &'a mut usize) -> Self { 20 | Self { inner, index: EntryIndex::Borrowed(index) } 21 | } 22 | 23 | /// Get the index of the element this `ObservableVectorEntry` refers to. 24 | pub fn index(this: &Self) -> usize { 25 | this.index.value() 26 | } 27 | 28 | /// Replace the given element, notify subscribers and return the previous 29 | /// element. 30 | pub fn set(this: &mut Self, value: T) -> T { 31 | this.inner.set(this.index.value(), value) 32 | } 33 | 34 | /// Remove the given element, notify subscribers and return the element. 35 | pub fn remove(mut this: Self) -> T { 36 | this.inner.remove(this.index.make_owned()) 37 | } 38 | } 39 | 40 | impl fmt::Debug for ObservableVectorEntry<'_, T> 41 | where 42 | T: fmt::Debug, 43 | { 44 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 45 | let index = self.index.value(); 46 | f.debug_struct("ObservableVectorEntry") 47 | .field("item", &self.inner[index]) 48 | .field("index", &index) 49 | .finish() 50 | } 51 | } 52 | 53 | impl Deref for ObservableVectorEntry<'_, T> { 54 | type Target = T; 55 | 56 | fn deref(&self) -> &Self::Target { 57 | &self.inner[self.index.value()] 58 | } 59 | } 60 | 61 | impl Drop for ObservableVectorEntry<'_, T> { 62 | fn drop(&mut self) { 63 | // If there is an association with an externally-stored index, that 64 | // index must be incremented on drop. This allows an external iterator 65 | // that produces ObservableVectorEntry items to advance conditionally. 66 | // 67 | // There are two cases this branch is not hit: 68 | // 69 | // - make_owned was previously called (used for removing the item and resuming 70 | // iteration with the same index) 71 | // - the ObservableVectorEntry was created with ObservableVector::entry, i.e. 72 | // it's not used for iteration at all 73 | if let EntryIndex::Borrowed(idx) = &mut self.index { 74 | **idx += 1; 75 | } 76 | } 77 | } 78 | 79 | pub(super) enum EntryIndex<'a> { 80 | Borrowed(&'a mut usize), 81 | Owned(usize), 82 | } 83 | 84 | impl EntryIndex<'_> { 85 | pub(super) fn value(&self) -> usize { 86 | match self { 87 | EntryIndex::Borrowed(idx) => **idx, 88 | EntryIndex::Owned(idx) => *idx, 89 | } 90 | } 91 | 92 | /// Remove the association with the externally-stored index, if any. 93 | /// 94 | /// Returns the index value for convenience. 95 | pub(super) fn make_owned(&mut self) -> usize { 96 | match self { 97 | EntryIndex::Borrowed(idx) => { 98 | let idx = **idx; 99 | *self = EntryIndex::Owned(idx); 100 | idx 101 | } 102 | EntryIndex::Owned(idx) => *idx, 103 | } 104 | } 105 | } 106 | 107 | /// An "iterator"¹ that yields entries into an [`ObservableVector`]. 108 | /// 109 | /// ¹ conceptually, though it does not implement `std::iterator::Iterator` 110 | #[derive(Debug)] 111 | pub struct ObservableVectorEntries<'a, T> { 112 | inner: &'a mut ObservableVector, 113 | index: usize, 114 | } 115 | 116 | impl<'a, T> ObservableVectorEntries<'a, T> 117 | where 118 | T: Clone + 'static, 119 | { 120 | pub(super) fn new(inner: &'a mut ObservableVector) -> Self { 121 | Self { inner, index: 0 } 122 | } 123 | 124 | /// Advance this iterator, yielding an `ObservableVectorEntry` for the next 125 | /// item in the vector, or `None` if all items have been visited. 126 | #[allow(clippy::should_implement_trait)] 127 | pub fn next(&mut self) -> Option> { 128 | if self.index < self.inner.len() { 129 | Some(ObservableVectorEntry::new_borrowed(self.inner, &mut self.index)) 130 | } else { 131 | None 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /eyeball-im/src/vector/subscriber.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fmt, 3 | hint::unreachable_unchecked, 4 | mem, 5 | pin::Pin, 6 | task::{ready, Context, Poll}, 7 | vec, 8 | }; 9 | 10 | use crate::reusable_box::ReusableBoxFuture; 11 | use futures_core::Stream; 12 | use imbl::Vector; 13 | use tokio::sync::broadcast::{ 14 | self, 15 | error::{RecvError, TryRecvError}, 16 | Receiver, 17 | }; 18 | #[cfg(feature = "tracing")] 19 | use tracing::info; 20 | 21 | use super::{BroadcastMessage, OneOrManyDiffs, VectorDiff}; 22 | 23 | /// A subscriber for updates of a [`Vector`]. 24 | #[derive(Debug)] 25 | pub struct VectorSubscriber { 26 | values: Vector, 27 | rx: Receiver>, 28 | } 29 | 30 | impl VectorSubscriber { 31 | pub(super) fn new(items: Vector, rx: Receiver>) -> Self { 32 | Self { values: items, rx } 33 | } 34 | 35 | /// Get the items the [`ObservableVector`][super::ObservableVector] 36 | /// contained when this subscriber was created. 37 | pub fn values(&self) -> Vector { 38 | self.values.clone() 39 | } 40 | 41 | /// Turn this `VectorSubcriber` into a stream of `VectorDiff`s. 42 | pub fn into_stream(self) -> VectorSubscriberStream { 43 | VectorSubscriberStream::new(ReusableBoxRecvFuture::new(self.rx)) 44 | } 45 | 46 | /// Turn this `VectorSubcriber` into a stream of `Vec`s. 47 | pub fn into_batched_stream(self) -> VectorSubscriberBatchedStream { 48 | VectorSubscriberBatchedStream::new(ReusableBoxRecvFuture::new(self.rx)) 49 | } 50 | 51 | /// Destructure this `VectorSubscriber` into the initial values and a stream 52 | /// of `VectorDiff`s. 53 | /// 54 | /// Semantically equivalent to calling `.values()` and `.into_stream()` 55 | /// separately, but guarantees that the values are not unnecessarily cloned. 56 | pub fn into_values_and_stream(self) -> (Vector, VectorSubscriberStream) { 57 | let Self { values, rx } = self; 58 | (values, VectorSubscriberStream::new(ReusableBoxRecvFuture::new(rx))) 59 | } 60 | 61 | /// Destructure this `VectorSubscriber` into the initial values and a stream 62 | /// of `Vec`s. 63 | /// 64 | /// Semantically equivalent to calling `.values()` and 65 | /// `.into_batched_stream()` separately, but guarantees that the values 66 | /// are not unnecessarily cloned. 67 | pub fn into_values_and_batched_stream(self) -> (Vector, VectorSubscriberBatchedStream) { 68 | let Self { values, rx } = self; 69 | (values, VectorSubscriberBatchedStream::new(ReusableBoxRecvFuture::new(rx))) 70 | } 71 | } 72 | 73 | /// A stream of `VectorDiff`s created from a [`VectorSubscriber`]. 74 | /// 75 | /// Use its [`Stream`] implementation to interact with it (futures-util and 76 | /// other futures-related crates have extension traits with convenience 77 | /// methods). 78 | #[derive(Debug)] 79 | pub struct VectorSubscriberStream { 80 | inner: ReusableBoxRecvFuture, 81 | state: VectorSubscriberStreamState, 82 | } 83 | 84 | impl VectorSubscriberStream { 85 | fn new(inner: ReusableBoxRecvFuture) -> Self { 86 | Self { inner, state: VectorSubscriberStreamState::Recv } 87 | } 88 | } 89 | 90 | #[derive(Debug)] 91 | enum VectorSubscriberStreamState { 92 | // Stream is waiting on a new message from the inner broadcast receiver. 93 | Recv, 94 | // Stream is yielding remaining items from a previous message with multiple 95 | // diffs. 96 | YieldBatch { iter: vec::IntoIter>, rx: Receiver> }, 97 | } 98 | 99 | // Not clear why this explicit impl is needed, but it's not unsafe so it is fine 100 | impl Unpin for VectorSubscriberStreamState {} 101 | 102 | impl Stream for VectorSubscriberStream { 103 | type Item = VectorDiff; 104 | 105 | fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 106 | match &mut self.state { 107 | VectorSubscriberStreamState::Recv => { 108 | let (result, mut rx) = ready!(self.inner.poll(cx)); 109 | 110 | let poll = match result { 111 | Ok(msg) => match msg.diffs { 112 | OneOrManyDiffs::One(diff) => Poll::Ready(Some(diff)), 113 | OneOrManyDiffs::Many(diffs) if diffs.is_empty() => { 114 | unreachable!("ObservableVectorTransaction never sends empty diffs") 115 | } 116 | OneOrManyDiffs::Many(mut diffs) if diffs.len() == 1 => { 117 | Poll::Ready(Some(diffs.pop().unwrap())) 118 | } 119 | OneOrManyDiffs::Many(diffs) => { 120 | let mut iter = diffs.into_iter(); 121 | let fst = iter.next().unwrap(); 122 | self.state = VectorSubscriberStreamState::YieldBatch { iter, rx }; 123 | return Poll::Ready(Some(fst)); 124 | } 125 | }, 126 | Err(RecvError::Closed) => Poll::Ready(None), 127 | Err(RecvError::Lagged(_)) => { 128 | Poll::Ready(handle_lag(&mut rx).map(|values| VectorDiff::Reset { values })) 129 | } 130 | }; 131 | 132 | self.inner.set(rx); 133 | poll 134 | } 135 | VectorSubscriberStreamState::YieldBatch { iter, .. } => { 136 | let diff = 137 | iter.next().expect("YieldBatch is never left empty when exiting poll_next"); 138 | 139 | if iter.len() == 0 { 140 | let old_state = 141 | mem::replace(&mut self.state, VectorSubscriberStreamState::Recv); 142 | let rx = match old_state { 143 | VectorSubscriberStreamState::YieldBatch { rx, .. } => rx, 144 | // Safety: We would not be in the outer branch otherwise 145 | _ => unsafe { unreachable_unchecked() }, 146 | }; 147 | 148 | self.inner.set(rx); 149 | } 150 | 151 | Poll::Ready(Some(diff)) 152 | } 153 | } 154 | } 155 | } 156 | 157 | /// A batched stream of `VectorDiff`s created from a [`VectorSubscriber`]. 158 | /// 159 | /// Use its [`Stream`] implementation to interact with it (futures-util and 160 | /// other futures-related crates have extension traits with convenience 161 | /// methods). 162 | #[derive(Debug)] 163 | pub struct VectorSubscriberBatchedStream { 164 | inner: ReusableBoxRecvFuture, 165 | } 166 | 167 | impl VectorSubscriberBatchedStream { 168 | fn new(inner: ReusableBoxRecvFuture) -> Self { 169 | Self { inner } 170 | } 171 | } 172 | 173 | impl Stream for VectorSubscriberBatchedStream { 174 | type Item = Vec>; 175 | 176 | fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 177 | fn append(target: &mut Vec>, source: OneOrManyDiffs) { 178 | match source { 179 | OneOrManyDiffs::One(diff) => target.push(diff), 180 | OneOrManyDiffs::Many(mut diffs) => target.append(&mut diffs), 181 | } 182 | } 183 | 184 | let (result, mut rx) = ready!(self.inner.poll(cx)); 185 | 186 | let poll = match result { 187 | Ok(msg) => { 188 | let mut batch = msg.diffs.into_vec(); 189 | loop { 190 | match rx.try_recv() { 191 | Ok(msg) => append(&mut batch, msg.diffs), 192 | Err(TryRecvError::Empty | TryRecvError::Closed) => { 193 | break Poll::Ready(Some(batch)); 194 | } 195 | Err(TryRecvError::Lagged(_)) => { 196 | break Poll::Ready( 197 | handle_lag(&mut rx) 198 | .map(|values| vec![VectorDiff::Reset { values }]), 199 | ); 200 | } 201 | } 202 | } 203 | } 204 | Err(RecvError::Closed) => Poll::Ready(None), 205 | Err(RecvError::Lagged(_)) => { 206 | Poll::Ready(handle_lag(&mut rx).map(|values| vec![VectorDiff::Reset { values }])) 207 | } 208 | }; 209 | 210 | self.inner.set(rx); 211 | poll 212 | } 213 | } 214 | 215 | fn handle_lag(rx: &mut Receiver>) -> Option> { 216 | let mut msg = None; 217 | loop { 218 | match rx.try_recv() { 219 | // There's a newer message in the receiver's buffer, use that for reset. 220 | Ok(m) => { 221 | msg = Some(m); 222 | } 223 | // Ideally we'd return a `VecDiff::Reset` with the last state before the 224 | // channel was closed here, but we have no way of obtaining the last state. 225 | Err(TryRecvError::Closed) => { 226 | #[cfg(feature = "tracing")] 227 | info!("Channel closed after lag, can't return last state"); 228 | return None; 229 | } 230 | // Lagged twice in a row, is this possible? If it is, it's fine to just 231 | // loop again and look at the next try_recv result. 232 | Err(TryRecvError::Lagged(_)) => {} 233 | Err(TryRecvError::Empty) => match msg { 234 | // We exhausted the internal buffer using try_recv, msg contains the 235 | // last message from it, which we use for the reset. 236 | Some(msg) => return Some(msg.state), 237 | // We exhausted the internal buffer using try_recv but there was no 238 | // message in it, even though we got TryRecvError::Lagged(_) before. 239 | None => unreachable!("got no new message via try_recv after lag"), 240 | }, 241 | } 242 | } 243 | } 244 | 245 | type SubscriberFutureReturn = (Result, Receiver); 246 | 247 | struct ReusableBoxRecvFuture { 248 | inner: ReusableBoxFuture<'static, SubscriberFutureReturn>>, 249 | } 250 | 251 | async fn make_recv_future(mut rx: Receiver) -> SubscriberFutureReturn { 252 | let result = rx.recv().await; 253 | (result, rx) 254 | } 255 | 256 | impl ReusableBoxRecvFuture 257 | where 258 | T: Clone + 'static, 259 | { 260 | fn set(&mut self, rx: Receiver>) { 261 | self.inner.set(make_recv_future(rx)); 262 | } 263 | 264 | fn poll(&mut self, cx: &mut Context<'_>) -> Poll>> { 265 | self.inner.poll(cx) 266 | } 267 | } 268 | 269 | impl ReusableBoxRecvFuture 270 | where 271 | T: Clone + 'static, 272 | { 273 | fn new(rx: Receiver>) -> Self { 274 | Self { inner: ReusableBoxFuture::new(make_recv_future(rx)) } 275 | } 276 | } 277 | 278 | fn assert_send(_val: T) {} 279 | #[allow(unused)] 280 | fn assert_make_future_send() { 281 | #[derive(Clone)] 282 | struct IsSend(*mut ()); 283 | unsafe impl Send for IsSend {} 284 | 285 | let (_sender, receiver): (_, Receiver) = broadcast::channel(1); 286 | 287 | assert_send(make_recv_future(receiver)); 288 | } 289 | // SAFETY: make_future is Send if T is, as proven by assert_make_future_send. 290 | unsafe impl Send for ReusableBoxRecvFuture {} 291 | 292 | impl fmt::Debug for ReusableBoxRecvFuture { 293 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 294 | f.debug_struct("ReusableBoxRecvFuture").finish() 295 | } 296 | } 297 | -------------------------------------------------------------------------------- /eyeball-im/src/vector/transaction.rs: -------------------------------------------------------------------------------- 1 | use std::{fmt, mem, ops}; 2 | 3 | use imbl::Vector; 4 | 5 | use crate::vector::OneOrManyDiffs; 6 | 7 | use super::{entry::EntryIndex, BroadcastMessage, ObservableVector, VectorDiff}; 8 | 9 | /// A transaction that allows making multiple updates to an `ObservableVector` 10 | /// as an atomic unit. 11 | /// 12 | /// For updates from the transaction to have affect, it has to be finalized with 13 | /// [`.commit()`](Self::commit). If the transaction is dropped without that 14 | /// method being called, the updates will be discarded. 15 | pub struct ObservableVectorTransaction<'o, T: Clone> { 16 | // The observable vector being modified, only modified on commit. 17 | inner: &'o mut ObservableVector, 18 | // A clone of the observable's values, what the methods operate on until commit. 19 | values: Vector, 20 | // The batched updates, to be sent to subscribers on commit. 21 | batch: Vec>, 22 | } 23 | 24 | impl<'o, T: Clone + 'static> ObservableVectorTransaction<'o, T> { 25 | pub(super) fn new(inner: &'o mut ObservableVector) -> Self { 26 | let values = inner.values.clone(); 27 | Self { inner, values, batch: Vec::new() } 28 | } 29 | 30 | /// Commit this transaction, persisting the changes and notifying 31 | /// subscribers. 32 | pub fn commit(mut self) { 33 | #[cfg(feature = "tracing")] 34 | tracing::debug!("commit"); 35 | 36 | self.inner.values = mem::take(&mut self.values); 37 | 38 | if self.batch.is_empty() { 39 | #[cfg(feature = "tracing")] 40 | tracing::trace!( 41 | target: "eyeball_im::vector::broadcast", 42 | "Skipping broadcast of empty list of diffs" 43 | ); 44 | } else { 45 | let diffs = OneOrManyDiffs::Many(mem::take(&mut self.batch)); 46 | let msg = BroadcastMessage { diffs, state: self.inner.values.clone() }; 47 | let _num_receivers = self.inner.sender.send(msg).unwrap_or(0); 48 | #[cfg(feature = "tracing")] 49 | tracing::debug!( 50 | target: "eyeball_im::vector::broadcast", 51 | "New observable value broadcast to {_num_receivers} receivers" 52 | ); 53 | } 54 | } 55 | 56 | /// Roll back all changes made using this transaction so far. 57 | /// 58 | /// Same as dropping the transaction and starting a new one, semantically. 59 | pub fn rollback(&mut self) { 60 | #[cfg(feature = "tracing")] 61 | tracing::debug!("rollback (explicit)"); 62 | 63 | self.values = self.inner.values.clone(); 64 | self.batch.clear(); 65 | } 66 | 67 | /// Append the given elements at the end of the `Vector` and notify 68 | /// subscribers. 69 | pub fn append(&mut self, values: Vector) { 70 | #[cfg(feature = "tracing")] 71 | tracing::debug!( 72 | target: "eyeball_im::vector::transaction::update", 73 | "append(len = {})", values.len() 74 | ); 75 | 76 | self.values.append(values.clone()); 77 | self.add_to_batch(VectorDiff::Append { values }); 78 | } 79 | 80 | /// Clear out all of the elements in this `Vector` and notify subscribers. 81 | pub fn clear(&mut self) { 82 | #[cfg(feature = "tracing")] 83 | tracing::debug!(target: "eyeball_im::vector::transaction::update", "clear"); 84 | 85 | self.values.clear(); 86 | self.batch.clear(); // All previous batched updates are irrelevant now 87 | self.add_to_batch(VectorDiff::Clear); 88 | } 89 | 90 | /// Add an element at the front of the list and notify subscribers. 91 | pub fn push_front(&mut self, value: T) { 92 | #[cfg(feature = "tracing")] 93 | tracing::debug!(target: "eyeball_im::vector::transaction::update", "push_front"); 94 | 95 | self.values.push_front(value.clone()); 96 | self.add_to_batch(VectorDiff::PushFront { value }); 97 | } 98 | 99 | /// Add an element at the back of the list and notify subscribers. 100 | pub fn push_back(&mut self, value: T) { 101 | #[cfg(feature = "tracing")] 102 | tracing::debug!(target: "eyeball_im::vector::transaction::update", "push_back"); 103 | 104 | self.values.push_back(value.clone()); 105 | self.add_to_batch(VectorDiff::PushBack { value }); 106 | } 107 | 108 | /// Remove the first element, notify subscribers and return the element. 109 | /// 110 | /// If there are no elements, subscribers will not be notified and this 111 | /// method will return `None`. 112 | pub fn pop_front(&mut self) -> Option { 113 | let value = self.values.pop_front(); 114 | if value.is_some() { 115 | #[cfg(feature = "tracing")] 116 | tracing::debug!(target: "eyeball_im::vector::transaction::update", "pop_front"); 117 | 118 | self.add_to_batch(VectorDiff::PopFront); 119 | } 120 | value 121 | } 122 | 123 | /// Remove the last element, notify subscribers and return the element. 124 | /// 125 | /// If there are no elements, subscribers will not be notified and this 126 | /// method will return `None`. 127 | pub fn pop_back(&mut self) -> Option { 128 | let value = self.values.pop_back(); 129 | if value.is_some() { 130 | #[cfg(feature = "tracing")] 131 | tracing::debug!(target: "eyeball_im::vector::transaction::update", "pop_back"); 132 | 133 | self.add_to_batch(VectorDiff::PopBack); 134 | } 135 | value 136 | } 137 | 138 | /// Insert an element at the given position and notify subscribers. 139 | /// 140 | /// # Panics 141 | /// 142 | /// Panics if `index > len`. 143 | #[track_caller] 144 | pub fn insert(&mut self, index: usize, value: T) { 145 | let len = self.values.len(); 146 | if index <= len { 147 | #[cfg(feature = "tracing")] 148 | tracing::debug!( 149 | target: "eyeball_im::vector::transaction::update", 150 | "insert(index = {index})" 151 | ); 152 | 153 | self.values.insert(index, value.clone()); 154 | self.add_to_batch(VectorDiff::Insert { index, value }); 155 | } else { 156 | panic!("index out of bounds: the length is {len} but the index is {index}"); 157 | } 158 | } 159 | 160 | /// Replace the element at the given position, notify subscribers and return 161 | /// the previous element at that position. 162 | /// 163 | /// # Panics 164 | /// 165 | /// Panics if `index >= len`. 166 | #[track_caller] 167 | pub fn set(&mut self, index: usize, value: T) -> T { 168 | let len = self.values.len(); 169 | if index < len { 170 | #[cfg(feature = "tracing")] 171 | tracing::debug!( 172 | target: "eyeball_im::vector::transaction::update", 173 | "set(index = {index})" 174 | ); 175 | 176 | let old_value = self.values.set(index, value.clone()); 177 | self.add_to_batch(VectorDiff::Set { index, value }); 178 | old_value 179 | } else { 180 | panic!("index out of bounds: the length is {len} but the index is {index}"); 181 | } 182 | } 183 | 184 | /// Remove the element at the given position, notify subscribers and return 185 | /// the element. 186 | /// 187 | /// # Panics 188 | /// 189 | /// Panics if `index >= len`. 190 | #[track_caller] 191 | pub fn remove(&mut self, index: usize) -> T { 192 | let len = self.values.len(); 193 | if index < len { 194 | #[cfg(feature = "tracing")] 195 | tracing::debug!( 196 | target: "eyeball_im::vector::transaction::update", 197 | "remove(index = {index})" 198 | ); 199 | 200 | let value = self.values.remove(index); 201 | self.add_to_batch(VectorDiff::Remove { index }); 202 | value 203 | } else { 204 | panic!("index out of bounds: the length is {len} but the index is {index}"); 205 | } 206 | } 207 | 208 | /// Truncate the vector to `len` elements and notify subscribers. 209 | /// 210 | /// Does nothing if `len` is greater or equal to the vector's current 211 | /// length. 212 | pub fn truncate(&mut self, len: usize) { 213 | if len < self.len() { 214 | #[cfg(feature = "tracing")] 215 | tracing::debug!(target: "eyeball_im::vector::update", "truncate(len = {len})"); 216 | 217 | self.values.truncate(len); 218 | self.add_to_batch(VectorDiff::Truncate { length: len }); 219 | } 220 | } 221 | 222 | /// Gets an entry for the given index through which only the element at that 223 | /// index alone can be updated or removed. 224 | /// 225 | /// # Panics 226 | /// 227 | /// Panics if `index >= len`. 228 | #[track_caller] 229 | pub fn entry(&mut self, index: usize) -> ObservableVectorTransactionEntry<'_, 'o, T> { 230 | let len = self.values.len(); 231 | if index < len { 232 | ObservableVectorTransactionEntry::new(self, index) 233 | } else { 234 | panic!("index out of bounds: the length is {len} but the index is {index}"); 235 | } 236 | } 237 | 238 | /// Call the given closure for every element in this `ObservableVector`, 239 | /// with an entry struct that allows updating or removing that element. 240 | /// 241 | /// Iteration happens in order, i.e. starting at index `0`. 242 | pub fn for_each(&mut self, mut f: impl FnMut(ObservableVectorTransactionEntry<'_, 'o, T>)) { 243 | let mut entries = self.entries(); 244 | while let Some(entry) = entries.next() { 245 | f(entry); 246 | } 247 | } 248 | 249 | /// Get an iterator over all the entries in this `ObservableVector`. 250 | /// 251 | /// This is a more flexible, but less convenient alternative to 252 | /// [`for_each`][Self::for_each]. If you don't need to use special control 253 | /// flow like `.await` or `break` when iterating, it's recommended to use 254 | /// that method instead. 255 | /// 256 | /// Because `std`'s `Iterator` trait does not allow iterator items to borrow 257 | /// from the iterator itself, the returned typed does not implement the 258 | /// `Iterator` trait and can thus not be used with a `for` loop. Instead, 259 | /// you have to call its `.next()` method directly, as in: 260 | /// 261 | /// ```rust 262 | /// # use eyeball_im::ObservableVector; 263 | /// # let mut ob = ObservableVector::::new(); 264 | /// let mut entries = ob.entries(); 265 | /// while let Some(entry) = entries.next() { 266 | /// // use entry 267 | /// } 268 | /// ``` 269 | pub fn entries(&mut self) -> ObservableVectorTransactionEntries<'_, 'o, T> { 270 | ObservableVectorTransactionEntries::new(self) 271 | } 272 | 273 | fn add_to_batch(&mut self, diff: VectorDiff) { 274 | if self.inner.sender.receiver_count() != 0 { 275 | self.batch.push(diff); 276 | } 277 | } 278 | } 279 | 280 | impl fmt::Debug for ObservableVectorTransaction<'_, T> 281 | where 282 | T: Clone + fmt::Debug, 283 | { 284 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 285 | f.debug_struct("ObservableVectorWriteGuard") 286 | .field("values", &self.values) 287 | .finish_non_exhaustive() 288 | } 289 | } 290 | 291 | // Note: No DerefMut because all mutating must go through inherent methods that 292 | // notify subscribers 293 | impl ops::Deref for ObservableVectorTransaction<'_, T> { 294 | type Target = Vector; 295 | 296 | fn deref(&self) -> &Self::Target { 297 | &self.values 298 | } 299 | } 300 | 301 | impl Drop for ObservableVectorTransaction<'_, T> { 302 | fn drop(&mut self) { 303 | #[cfg(feature = "tracing")] 304 | if !self.batch.is_empty() { 305 | tracing::debug!("rollback (drop)"); 306 | } 307 | } 308 | } 309 | 310 | /// A handle to a single value in an [`ObservableVector`], obtained from a 311 | /// transaction. 312 | pub struct ObservableVectorTransactionEntry<'a, 'o, T: Clone> { 313 | inner: &'a mut ObservableVectorTransaction<'o, T>, 314 | index: EntryIndex<'a>, 315 | } 316 | 317 | impl<'a, 'o, T> ObservableVectorTransactionEntry<'a, 'o, T> 318 | where 319 | T: Clone + 'static, 320 | { 321 | pub(super) fn new(inner: &'a mut ObservableVectorTransaction<'o, T>, index: usize) -> Self { 322 | Self { inner, index: EntryIndex::Owned(index) } 323 | } 324 | 325 | fn new_borrowed( 326 | inner: &'a mut ObservableVectorTransaction<'o, T>, 327 | index: &'a mut usize, 328 | ) -> Self { 329 | Self { inner, index: EntryIndex::Borrowed(index) } 330 | } 331 | 332 | /// Get the index of the element this `ObservableVectorEntry` refers to. 333 | pub fn index(this: &Self) -> usize { 334 | this.index.value() 335 | } 336 | 337 | /// Replace the given element, notify subscribers and return the previous 338 | /// element. 339 | pub fn set(this: &mut Self, value: T) -> T { 340 | this.inner.set(this.index.value(), value) 341 | } 342 | 343 | /// Remove the given element, notify subscribers and return the element. 344 | pub fn remove(mut this: Self) -> T { 345 | this.inner.remove(this.index.make_owned()) 346 | } 347 | } 348 | 349 | impl fmt::Debug for ObservableVectorTransactionEntry<'_, '_, T> 350 | where 351 | T: Clone + fmt::Debug, 352 | { 353 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 354 | let index = self.index.value(); 355 | f.debug_struct("ObservableVectorEntry") 356 | .field("item", &self.inner[index]) 357 | .field("index", &index) 358 | .finish() 359 | } 360 | } 361 | 362 | impl ops::Deref for ObservableVectorTransactionEntry<'_, '_, T> { 363 | type Target = T; 364 | 365 | fn deref(&self) -> &Self::Target { 366 | &self.inner[self.index.value()] 367 | } 368 | } 369 | 370 | impl Drop for ObservableVectorTransactionEntry<'_, '_, T> { 371 | fn drop(&mut self) { 372 | // If there is an association with an externally-stored index, that 373 | // index must be incremented on drop. This allows an external iterator 374 | // that produces ObservableVectorEntry items to advance conditionally. 375 | // 376 | // There are two cases this branch is not hit: 377 | // 378 | // - make_owned was previously called (used for removing the item and resuming 379 | // iteration with the same index) 380 | // - the ObservableVectorEntry was created with ObservableVector::entry, i.e. 381 | // it's not used for iteration at all 382 | if let EntryIndex::Borrowed(idx) = &mut self.index { 383 | **idx += 1; 384 | } 385 | } 386 | } 387 | 388 | /// An "iterator"¹ that yields entries into an [`ObservableVector`], obtained 389 | /// from a transaction. 390 | /// 391 | /// ¹ conceptually, though it does not implement `std::iterator::Iterator` 392 | #[derive(Debug)] 393 | pub struct ObservableVectorTransactionEntries<'a, 'o, T: Clone> { 394 | inner: &'a mut ObservableVectorTransaction<'o, T>, 395 | index: usize, 396 | } 397 | 398 | impl<'a, 'o, T> ObservableVectorTransactionEntries<'a, 'o, T> 399 | where 400 | T: Clone + 'static, 401 | { 402 | pub(super) fn new(inner: &'a mut ObservableVectorTransaction<'o, T>) -> Self { 403 | Self { inner, index: 0 } 404 | } 405 | 406 | /// Advance this iterator, yielding an `ObservableVectorEntry` for the next 407 | /// item in the vector, or `None` if all items have been visited. 408 | #[allow(clippy::should_implement_trait)] 409 | pub fn next(&mut self) -> Option> { 410 | if self.index < self.inner.len() { 411 | Some(ObservableVectorTransactionEntry::new_borrowed(self.inner, &mut self.index)) 412 | } else { 413 | None 414 | } 415 | } 416 | } 417 | -------------------------------------------------------------------------------- /eyeball-im/tests/it/apply.rs: -------------------------------------------------------------------------------- 1 | use imbl::vector; 2 | 3 | use eyeball_im::VectorDiff; 4 | 5 | #[test] 6 | fn reset_larger() { 7 | let mut vec = vector![1, 2, 3]; 8 | VectorDiff::Reset { values: vector![4, 5, 6, 7] }.apply(&mut vec); 9 | assert_eq!(vec, vector![4, 5, 6, 7]); 10 | } 11 | 12 | #[test] 13 | fn reset_same_size() { 14 | let mut vec = vector![1, 2, 3]; 15 | VectorDiff::Reset { values: vector![4, 5, 6] }.apply(&mut vec); 16 | assert_eq!(vec, vector![4, 5, 6]); 17 | } 18 | 19 | #[test] 20 | fn reset_smaller() { 21 | let mut vec = vector![1, 2, 3]; 22 | VectorDiff::Reset { values: vector![4, 5] }.apply(&mut vec); 23 | assert_eq!(vec, vector![4, 5]); 24 | } 25 | 26 | #[test] 27 | fn reset_clear() { 28 | let mut vec = vector![1, 2, 3]; 29 | VectorDiff::Reset { values: vector![] }.apply(&mut vec); 30 | assert_eq!(vec, vector![]); 31 | } 32 | -------------------------------------------------------------------------------- /eyeball-im/tests/it/batch.rs: -------------------------------------------------------------------------------- 1 | use imbl::vector; 2 | use stream_assert::{assert_next_eq, assert_pending}; 3 | 4 | use eyeball_im::{ObservableVector, VectorDiff}; 5 | 6 | #[test] 7 | fn lagging_batch_stream() { 8 | let mut ob = ObservableVector::new(); 9 | let mut st = ob.subscribe().into_batched_stream(); 10 | 11 | ob.push_back(0); 12 | ob.append(vector![1, 2]); 13 | ob.push_back(3); 14 | 15 | assert_next_eq!( 16 | st, 17 | vec![ 18 | VectorDiff::PushBack { value: 0 }, 19 | VectorDiff::Append { values: vector![1, 2] }, 20 | VectorDiff::PushBack { value: 3 }, 21 | ] 22 | ); 23 | } 24 | 25 | #[test] 26 | fn transaction() { 27 | let mut ob = ObservableVector::new(); 28 | let mut st = ob.subscribe().into_batched_stream(); 29 | let mut txn = ob.transaction(); 30 | 31 | txn.push_back(0); 32 | assert_pending!(st); 33 | 34 | txn.push_front(-1); 35 | assert_pending!(st); 36 | 37 | txn.commit(); 38 | assert_next_eq!( 39 | st, 40 | vec![VectorDiff::PushBack { value: 0 }, VectorDiff::PushFront { value: -1 }] 41 | ); 42 | 43 | let mut txn = ob.transaction(); 44 | txn.push_back(3); 45 | txn.clear(); 46 | txn.push_back(1); 47 | txn.commit(); 48 | 49 | assert_next_eq!(st, vec![VectorDiff::Clear, VectorDiff::PushBack { value: 1 }]); 50 | } 51 | -------------------------------------------------------------------------------- /eyeball-im/tests/it/entry.rs: -------------------------------------------------------------------------------- 1 | use imbl::vector; 2 | 3 | use eyeball_im::{ObservableVector, ObservableVectorEntry}; 4 | 5 | #[test] 6 | fn entry() { 7 | let mut ob: ObservableVector = ObservableVector::from(vector![1, 2]); 8 | ObservableVectorEntry::set(&mut ob.entry(1), 3); 9 | ObservableVectorEntry::remove(ob.entry(0)); 10 | 11 | assert_eq!(ob.into_inner(), vector![3]); 12 | } 13 | 14 | #[test] 15 | #[should_panic] 16 | fn entry_out_of_range() { 17 | let mut ob: ObservableVector = ObservableVector::new(); 18 | ob.entry(0); 19 | } 20 | 21 | #[test] 22 | fn entries() { 23 | let mut ob = ObservableVector::from(vector![1, 2, 3]); 24 | let mut entries = ob.entries(); 25 | while let Some(mut entry) = entries.next() { 26 | if ObservableVectorEntry::index(&entry) == 1 { 27 | break; 28 | } 29 | 30 | ObservableVectorEntry::set(&mut entry, 5); 31 | } 32 | 33 | assert_eq!(ob.into_inner(), vector![5, 2, 3]); 34 | } 35 | 36 | #[test] 37 | fn remove_entries() { 38 | let mut ob = ObservableVector::from(vector![1, 2, 3]); 39 | let mut entries = ob.entries(); 40 | while let Some(entry) = entries.next() { 41 | if ObservableVectorEntry::index(&entry) == 1 { 42 | unreachable!("index stays 0 is we remove all elements"); 43 | } 44 | 45 | ObservableVectorEntry::remove(entry); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /eyeball-im/tests/it/main.rs: -------------------------------------------------------------------------------- 1 | #![allow(missing_docs)] 2 | 3 | use imbl::{vector, Vector}; 4 | use stream_assert::{assert_closed, assert_next_eq, assert_pending}; 5 | 6 | use eyeball_im::{ObservableVector, ObservableVectorEntry, VectorDiff}; 7 | 8 | mod apply; 9 | mod batch; 10 | mod entry; 11 | mod panic; 12 | #[cfg(feature = "serde")] 13 | mod serde; 14 | 15 | #[test] 16 | fn lag() { 17 | let mut ob = ObservableVector::with_capacity(1); 18 | let mut rx1 = ob.subscribe().into_stream(); 19 | let mut rx2 = ob.subscribe().into_stream(); 20 | 21 | ob.push_back("hello".to_owned()); 22 | assert_next_eq!(rx1, VectorDiff::PushBack { value: "hello".to_owned() }); 23 | 24 | ob.push_back("world".to_owned()); 25 | assert_next_eq!(rx1, VectorDiff::PushBack { value: "world".to_owned() }); 26 | assert_next_eq!( 27 | rx2, 28 | VectorDiff::Reset { values: Vector::from_iter(["hello".to_owned(), "world".to_owned()]) } 29 | ); 30 | } 31 | 32 | #[test] 33 | fn lag2() { 34 | let mut ob: ObservableVector = ObservableVector::with_capacity(2); 35 | let mut sub = ob.subscribe().into_stream(); 36 | 37 | ob.push_back(0); 38 | ob.append(vector![1, 2]); 39 | ob.push_back(3); 40 | 41 | // Reset takes us immediately to the latest state, no updates afterwards 42 | // without modifying the vector again. 43 | assert_next_eq!(sub, VectorDiff::Reset { values: vector![0, 1, 2, 3] }); 44 | assert_pending!(sub); 45 | } 46 | 47 | #[test] 48 | fn truncate() { 49 | let mut ob: ObservableVector = ObservableVector::from(vector![1, 2]); 50 | let mut sub = ob.subscribe().into_stream(); 51 | 52 | ob.truncate(3); 53 | ob.truncate(2); 54 | assert_pending!(sub); 55 | assert_eq!(*ob, vector![1, 2]); 56 | 57 | ob.truncate(1); 58 | assert_next_eq!(sub, VectorDiff::Truncate { length: 1 }); 59 | assert_eq!(*ob, vector![1]); 60 | 61 | ob.truncate(0); 62 | assert_next_eq!(sub, VectorDiff::Truncate { length: 0 }); 63 | assert!(ob.is_empty()); 64 | } 65 | 66 | #[test] 67 | fn clear() { 68 | let mut ob: ObservableVector = ObservableVector::from(vector![1, 2]); 69 | let mut sub = ob.subscribe().into_stream(); 70 | assert_pending!(sub); 71 | 72 | ob.clear(); 73 | assert_next_eq!(sub, VectorDiff::Clear); 74 | assert!(ob.is_empty()); 75 | 76 | // Clearing again. The vector is empty now. We don't expect a 77 | // `VectorDiff::Clear`. 78 | ob.clear(); 79 | assert_pending!(sub); 80 | 81 | drop(ob); 82 | assert_closed!(sub); 83 | } 84 | 85 | #[test] 86 | fn for_each() { 87 | let mut ob: ObservableVector = ObservableVector::from(vector![0, 10, 1, 2, 4, 33, 5]); 88 | let mut sub = ob.subscribe().into_stream(); 89 | let mut saw_five = false; 90 | 91 | ob.for_each(|mut item| { 92 | if *item % 2 == 0 { 93 | let new_value = *item / 2; 94 | ObservableVectorEntry::set(&mut item, new_value); 95 | if *item == 0 { 96 | ObservableVectorEntry::remove(item); 97 | } 98 | } else if *item > 10 { 99 | ObservableVectorEntry::remove(item); 100 | } else if *item == 5 { 101 | // only possible because `for_each` accepts FnMut 102 | saw_five = true; 103 | } 104 | }); 105 | 106 | assert!(saw_five); 107 | assert_next_eq!(sub, VectorDiff::Set { index: 0, value: 0 }); 108 | assert_next_eq!(sub, VectorDiff::Remove { index: 0 }); 109 | assert_next_eq!(sub, VectorDiff::Set { index: 0, value: 5 }); 110 | assert_next_eq!(sub, VectorDiff::Set { index: 2, value: 1 }); 111 | assert_next_eq!(sub, VectorDiff::Set { index: 3, value: 2 }); 112 | assert_next_eq!(sub, VectorDiff::Remove { index: 4 }); 113 | assert_pending!(sub); 114 | } 115 | 116 | #[test] 117 | fn transaction() { 118 | let mut ob = ObservableVector::new(); 119 | let mut st = ob.subscribe().into_stream(); 120 | let mut txn = ob.transaction(); 121 | 122 | txn.push_back(0); 123 | assert_pending!(st); 124 | 125 | txn.push_front(-1); 126 | assert_pending!(st); 127 | 128 | txn.commit(); 129 | assert_next_eq!(st, VectorDiff::PushBack { value: 0 }); 130 | assert_next_eq!(st, VectorDiff::PushFront { value: -1 }); 131 | } 132 | 133 | #[test] 134 | fn transaction_rollback() { 135 | let mut ob = ObservableVector::new(); 136 | let mut st = ob.subscribe().into_stream(); 137 | 138 | let mut txn = ob.transaction(); 139 | txn.push_back(1); 140 | drop(txn); 141 | 142 | assert_pending!(st); 143 | 144 | let mut txn = ob.transaction(); 145 | txn.push_back(0); 146 | txn.rollback(); 147 | txn.insert(0, 123); 148 | txn.commit(); 149 | 150 | assert_next_eq!(st, VectorDiff::Insert { index: 0, value: 123 }); 151 | } 152 | 153 | #[test] 154 | fn transaction_no_subscribers() { 155 | let mut ob = ObservableVector::new(); 156 | let mut txn = ob.transaction(); 157 | txn.push_back(0); 158 | txn.rollback(); 159 | txn.insert(0, 123); 160 | txn.push_front(45); 161 | txn.commit(); 162 | 163 | assert_eq!(*ob, vector![45, 123]); 164 | } 165 | -------------------------------------------------------------------------------- /eyeball-im/tests/it/panic.rs: -------------------------------------------------------------------------------- 1 | use eyeball_im::{ObservableVector, ObservableVectorTransaction}; 2 | use imbl::vector; 3 | 4 | #[test] 5 | #[should_panic] 6 | fn zero_capacity() { 7 | let _ob: ObservableVector = ObservableVector::with_capacity(0); 8 | } 9 | 10 | #[test] 11 | #[should_panic] 12 | fn capacity_overflow() { 13 | let _ob: ObservableVector = ObservableVector::with_capacity(usize::MAX / 2); 14 | } 15 | 16 | #[test] 17 | #[should_panic] 18 | fn insert_out_of_range() { 19 | let mut ob: ObservableVector = ObservableVector::new(); 20 | ob.insert(1, -1); 21 | } 22 | 23 | #[test] 24 | #[should_panic] 25 | fn set_out_of_range() { 26 | let mut ob = ObservableVector::::new(); 27 | ob.append(vector![10, 20]); 28 | ob.set(2, 30); 29 | } 30 | 31 | #[test] 32 | #[should_panic] 33 | fn remove_out_of_range() { 34 | let mut ob: ObservableVector = ObservableVector::new(); 35 | ob.remove(0); 36 | } 37 | 38 | #[test] 39 | #[should_panic] 40 | fn transaction_insert_out_of_range() { 41 | let mut ob = ObservableVector::new(); 42 | let mut txn = ob.transaction(); 43 | txn.insert(1, 1); 44 | } 45 | 46 | #[test] 47 | #[should_panic] 48 | fn transaction_set_out_of_range() { 49 | let mut ob = ObservableVector::new(); 50 | let mut txn = ob.transaction(); 51 | txn.set(0, 1); 52 | } 53 | 54 | #[test] 55 | #[should_panic] 56 | fn transaction_remove_out_of_range() { 57 | let mut ob: ObservableVector = ObservableVector::new(); 58 | let mut txn = ob.transaction(); 59 | txn.remove(0); 60 | } 61 | 62 | #[test] 63 | #[should_panic] 64 | fn transaction_entry_out_of_range() { 65 | let mut ob = ObservableVector::new(); 66 | ob.append(vector![1]); 67 | let mut txn = ob.transaction(); 68 | txn.entry(1); 69 | } 70 | -------------------------------------------------------------------------------- /eyeball-im/tests/it/serde.rs: -------------------------------------------------------------------------------- 1 | use eyeball_im::VectorDiff; 2 | use imbl::vector; 3 | 4 | macro_rules! test { 5 | ($test_name:ident: $vector_diff:expr => $json:expr) => { 6 | #[test] 7 | fn $test_name() -> Result<(), Box> { 8 | let vector_diff: VectorDiff = $vector_diff; 9 | let json = serde_json::to_string(&vector_diff)?; 10 | 11 | assert_eq!(json, $json); 12 | 13 | Ok(()) 14 | } 15 | }; 16 | } 17 | 18 | test!(append: VectorDiff::Append { values: vector!['a', 'b'] } => r#"{"Append":{"values":["a","b"]}}"#); 19 | test!(clear: VectorDiff::Clear => r#"{"Clear":{}}"#); 20 | test!(push_front: VectorDiff::PushFront { value: 'a' } => r#"{"PushFront":{"value":"a"}}"#); 21 | test!(push_back: VectorDiff::PushBack { value: 'a' } => r#"{"PushBack":{"value":"a"}}"#); 22 | test!(pop_front: VectorDiff::PopFront => r#"{"PopFront":{}}"#); 23 | test!(pop_back: VectorDiff::PopBack => r#"{"PopBack":{}}"#); 24 | test!(insert: VectorDiff::Insert { index: 42, value: 'a' } => r#"{"Insert":{"index":42,"value":"a"}}"#); 25 | test!(set: VectorDiff::Set { index: 42, value: 'a' } => r#"{"Set":{"index":42,"value":"a"}}"#); 26 | test!(remove: VectorDiff::Remove { index: 42 } => r#"{"Remove":{"index":42}}"#); 27 | test!(truncate: VectorDiff::Truncate { length: 3 } => r#"{"Truncate":{"length":3}}"#); 28 | test!(reset: VectorDiff::Reset { values: vector!['a', 'b'] } => r#"{"Reset":{"values":["a","b"]}}"#); 29 | -------------------------------------------------------------------------------- /eyeball/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.8.8 2 | 3 | Documentation improvements. 4 | 5 | # 0.8.7 6 | 7 | - When the `tracing` feature is enabled, emit a debug log when the value of an 8 | observable was updated but no wakers were registered for it 9 | 10 | # 0.8.6 11 | 12 | This release only updates metadata for docs.rs. 13 | 14 | # 0.8.5 15 | 16 | This release only updates metadata for crates.io. 17 | 18 | # 0.8.4 19 | 20 | - Add `SharedObservable::{try_read, try_write}` 21 | 22 | # 0.8.3 23 | 24 | - Loosen bounds on `L` for `Observable`'s `Debug` implementation 25 | - The `Debug` impl was broken before since `SyncLock` and `AsyncLock` don't 26 | implement `Debug` 27 | 28 | # 0.8.2 29 | 30 | - Add `ObservableWriteGuard` to the crate root 31 | - It was previously part of the `shared` module and forgotten to be exported 32 | as part of 0.8.0 33 | 34 | # 0.8.1 35 | 36 | - Improve README reading experience on crates.io 37 | 38 | # 0.8.0 39 | 40 | - Rename / move the observable types: 41 | - `eyeball::unique::Observable` is now `eyeball::Observable` 42 | - `eyeball::shared::Observable` is now `eyeball::SharedObservable` 43 | - Add a new generic parameter to `SharedObservable` and `Subscriber`¹ that 44 | controls whether the internal lock is an async-aware one or not. It defaults 45 | to `SyncLock` which is the same behavior as before, but can be set to 46 | `AsyncLock` (created with `Observable::new_async`), if you want to lock the 47 | inner value for writing over `.await` points in async code. This means that 48 | most operations on the observable and its subscribers become `async`.\ 49 | ¹ also for `Observable`, but much less useful there 50 | 51 | # 0.7.0 52 | 53 | - Remove `shared::Observable::try_into_unique` 54 | - It wasn't working as documented. It might be added back later. Please open 55 | an issue if you want to have it back. 56 | - Add `shared::Observable::downgrade` and `shared::WeakObservable` 57 | - Rename `shared::Observable::ref_count` to `strong_count` 58 | 59 | # 0.6.0 60 | 61 | - Make `unique::Observable::subscriber_count` a regular associated function like 62 | all the others, not a method 63 | - Add `unique::Observable::into_shared` 64 | - Add `shared::Observable::try_into_unique` 65 | 66 | # 0.5.1 67 | 68 | - `Add shared::Observable::{observable_count, subscriber_count}` 69 | 70 | # 0.5.0 71 | 72 | - Remove `T: Clone` bound from `set_eq` 73 | - Merge `replace`s functionality of returning the previous value into `set` 74 | - Remove `update_eq`, `update_hash` 75 | - Rename `set_eq` to `set_if_not_eq` 76 | - Rename `set_hash` to `set_if_hash_not_eq` 77 | - Return the previous inner value if `set_if_not_eq` or `set_if_hash_not_eq` 78 | replaces it 79 | - Add `update_if` 80 | 81 | # 0.4.2 82 | 83 | - Add `unique::Observable::subscriber_count` 84 | - Add `shared::Observable::ref_count` 85 | 86 | # 0.4.1 87 | 88 | - Implement `Clone` for `Subscriber` 89 | - Add `Subscriber::reset` and `Subscriber::clone_reset` 90 | - Add `Observable::subscribe_reset` for both observable types 91 | 92 | # 0.4.0 93 | 94 | - Make `unique::Subscriber` and `shared::Subscriber` the same type 95 | - Same for `ObservableReadGuard` and other auxiliary types 96 | - The `unique` Cargo feature was removed, `readlock` is no longer an optional 97 | dependency 98 | 99 | # 0.3.2 100 | 101 | - Add `shared::Observable::get` 102 | 103 | # 0.3.1 104 | 105 | - Relax `&mut` methods to `&` in `shared::Observable` (copy-paste error) 106 | 107 | # 0.3.0 108 | 109 | - Move the existing `Observable` into a module called `unique`, to contrast it 110 | with the shared observable type 111 | - Remove `SharedObservable` (wrapper around an `Observable` with extra locking) 112 | - Add `shared::Observable`, which provides a similar API to the previous 113 | `SharedObservable`, in a more efficient and more obvious way 114 | - Add `#[clippy::has_significant_drop]` attribute to `SubscriberReadLock` so the 115 | [`clippy::significant_drop_in_scrutinee`] lint works with it 116 | - Rewrite the waking implementation to not rely on `tokio`'s broadcast channel. 117 | This improves compile time if you're not using tokio otherwise, and improves 118 | performance when there's a small number of subscribers. Expect performance for 119 | more than 4 subscribers to potentially regress, especially if it's many more. 120 | This case might be optimized in the future. 121 | 122 | [`clippy::significant_drop_in_scrutinee`]: https://rust-lang.github.io/rust-clippy/master/index.html#significant_drop_in_scrutinee 123 | 124 | # 0.2.0 125 | 126 | - Add more documentation 127 | - Move `SharedObservableBase` and `ObservableLock` out of the crate root 128 | - They are now accessible in `eyeball::shared` 129 | 130 | # 0.1.5 131 | 132 | - Add `Subscriber::{next, next_ref, get, read}` 133 | 134 | # 0.1.4 135 | 136 | - Add `SharedObservable` convenience API 137 | 138 | # 0.1.3 139 | 140 | - Allow non-`Send` and / or non-`'static` values 141 | 142 | # 0.1.2 143 | 144 | - Implement `Default` for `Observable` 145 | -------------------------------------------------------------------------------- /eyeball/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "eyeball" 3 | version = "0.8.8" 4 | edition = "2021" 5 | rust-version = "1.70.0" 6 | description = "Add observability to your Rust types!" 7 | license.workspace = true 8 | repository.workspace = true 9 | categories.workspace = true 10 | keywords.workspace = true 11 | 12 | [package.metadata.docs.rs] 13 | features = ["async-lock", "tracing"] 14 | 15 | [dependencies] 16 | futures-core.workspace = true 17 | readlock.workspace = true 18 | readlock-tokio = { version = "0.1.1", optional = true } 19 | tracing = { workspace = true, optional = true } 20 | tokio = { workspace = true, optional = true } 21 | tokio-util = { version = "0.7.8", optional = true } 22 | 23 | # for benchmarking 24 | divan = { version = "0.1.14", optional = true } 25 | 26 | [dev-dependencies] 27 | futures-executor = "0.3.30" 28 | futures-util.workspace = true 29 | macro_rules_attribute = "0.2.0" 30 | stream_assert.workspace = true 31 | tokio = { workspace = true, features = ["macros", "rt"] } 32 | 33 | [features] 34 | async-lock = ["dep:readlock-tokio", "dep:tokio", "dep:tokio-util"] 35 | tracing = ["dep:tracing"] 36 | 37 | __bench = ["dep:divan", "dep:tokio", "tokio?/rt-multi-thread"] 38 | 39 | [[bench]] 40 | name = "set_a_lot" 41 | harness = false 42 | required-features = ["__bench"] 43 | 44 | [lints] 45 | workspace = true 46 | -------------------------------------------------------------------------------- /eyeball/README.md: -------------------------------------------------------------------------------- 1 | # eyeball 2 | 3 | This crate implements a basic form of the [Observer pattern][] for Rust. 4 | It provides `Observable` as a type that semi-transparently wraps an inner 5 | value `T` and broadcasts changes to any associated `Subscriber`s. 6 | `Subscriber`s can currently only be polled for updates using `async` / `.await`, 7 | but this may change in the future. 8 | 9 | There is also `SharedObservable` as another variation which implements 10 | `Clone` but not `Deref`. It is more ergonomic and efficient than putting an 11 | `Observable` inside of `Arc>` for updating the value from multiple 12 | places in the code. 13 | 14 | Here is a quick walk-through: 15 | 16 | ```rust 17 | use eyeball::Observable; 18 | 19 | let mut observable = Observable::new("A".to_owned()); 20 | // Observable has no methods of its own, as those could conflict 21 | // with methods of the inner type, which it `Deref`erences to. 22 | let mut subscriber1 = Observable::subscribe(&observable); 23 | let mut subscriber2 = Observable::subscribe(&observable); 24 | 25 | // You can get the current value from a subscriber without waiting 26 | // for updates. 27 | assert_eq!(subscriber1.get(), "A"); 28 | 29 | Observable::set(&mut observable, "B".to_owned()); 30 | // `.next().await` will wait for the next update, then return the 31 | // new value. 32 | assert_eq!(subscriber1.next().await, Some("B".to_owned())); 33 | 34 | // If multiple updates have happened without the subscriber being 35 | // polled, the next poll will skip all but the latest. 36 | Observable::set(&mut observable, "C".to_owned()); 37 | assert_eq!(subscriber1.next().await, Some("C".to_owned())); 38 | assert_eq!(subscriber2.next().await, Some("C".to_owned())); 39 | 40 | // You can even obtain the value without cloning the value, by 41 | // using `.read()` (no waiting) or `.next_ref().await` (waits for 42 | // the next update). 43 | // If you restrict yourself to these methods, you can even use 44 | // `Observable` with inner types that don't implement the `Clone` 45 | // trait. 46 | // However, note that while a read guard returned by `.read()` or 47 | // `.next_ref().await` is alive, updating the observable is 48 | // blocked. 49 | Observable::set(&mut observable, "D".to_owned()); 50 | { 51 | let guard = subscriber1.next_ref().await.unwrap(); 52 | assert_eq!(*guard, "D"); 53 | } 54 | 55 | // The latest value is kept alive by subscribers when the 56 | // `Observable` is dropped. 57 | drop(observable); 58 | assert_eq!(subscriber1.get(), "D"); 59 | assert_eq!(*subscriber2.read(), "D"); 60 | ``` 61 | 62 | This library is currently optimized for low (0 - 4) numbers of subscribers. 63 | If you care about performance of a few dozens of subscribers, or are using 64 | hundreds of subscribers, please open an issue to discuss. 65 | 66 | For more details, see the documentation [on docs.rs][docs.rs]. 67 | 68 | [Observer pattern]: https://en.wikipedia.org/wiki/Observer_pattern 69 | [docs.rs]: https://docs.rs/eyeball/latest/eyeball/ 70 | -------------------------------------------------------------------------------- /eyeball/benches/set_a_lot.rs: -------------------------------------------------------------------------------- 1 | #![allow(missing_docs)] 2 | 3 | use divan::{black_box, main, Bencher}; 4 | 5 | use eyeball::Observable; 6 | use tokio::task::JoinSet; 7 | 8 | #[divan::bench] 9 | fn baseline(b: Bencher<'_, '_>) { 10 | b.with_inputs(|| Box::new([0; 256])).bench_refs(|x| { 11 | for i in 1..=256 { 12 | *x = black_box(Box::new([i; 256])); 13 | } 14 | }); 15 | } 16 | 17 | #[divan::bench] 18 | fn no_subscribers(b: Bencher<'_, '_>) { 19 | b.with_inputs(|| Observable::new(Box::new([0; 256]))).bench_refs(|ob| { 20 | for i in 1..=256 { 21 | Observable::set(ob, black_box(Box::new([i; 256]))); 22 | } 23 | }); 24 | } 25 | 26 | #[divan::bench(args = [1, 2, 4, 16, 64])] 27 | fn n_subscribers(b: Bencher<'_, '_>, n: usize) { 28 | b.with_inputs(|| { 29 | let tokio_rt = tokio::runtime::Builder::new_multi_thread() 30 | .enable_all() 31 | .build() 32 | .expect("Failed to build tokio runtime"); 33 | 34 | let ob = Observable::new(Box::new([0; 256])); 35 | let mut join_set = JoinSet::new(); 36 | for _ in 0..n { 37 | let mut subscriber = Observable::subscribe(&ob); 38 | tokio_rt.block_on(async { 39 | join_set.spawn(async move { 40 | while let Some(value) = subscriber.next().await { 41 | black_box(&value); 42 | } 43 | }); 44 | }); 45 | } 46 | (tokio_rt, ob, join_set) 47 | }) 48 | .bench_values(|(tokio_rt, mut ob, mut join_set)| { 49 | tokio_rt.block_on(async move { 50 | for i in 1..=256 { 51 | Observable::set(&mut ob, black_box(Box::new([i; 256]))); 52 | } 53 | drop(ob); 54 | 55 | // wait for all tasks to finish 56 | while join_set.join_next().await.is_some() {} 57 | }); 58 | }); 59 | } 60 | -------------------------------------------------------------------------------- /eyeball/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Add observability to your Rust types! 2 | //! 3 | //! This crate implements a basic form of the [Observer pattern][] for Rust. 4 | //! It provides [`Observable`] as a type that semi-transparently wraps an 5 | //! inner value `T` and broadcasts changes to any associated [`Subscriber`]s. 6 | //! `Subscriber`s can currently only be polled for updates using `async` / 7 | //! `.await`, but this may change in the future. 8 | //! 9 | //! There is also [`SharedObservable`] as another variation which 10 | //! implements [`Clone`] but not [`Deref`][std::ops::Deref]. It is more 11 | //! ergonomic and efficient than putting a `Observable` inside of 12 | //! `Arc>` for updating the value from multiple places in the code. 13 | //! 14 | //! Here is a quick walk-through: 15 | //! 16 | //! ``` 17 | //! use eyeball::Observable; 18 | //! 19 | //! # #[tokio::main(flavor = "current_thread")] 20 | //! # async fn main() { 21 | //! let mut observable = Observable::new("A".to_owned()); 22 | //! // Observable has no methods of its own, as those could conflict 23 | //! // with methods of the inner type, which it `Deref`erences to. 24 | //! let mut subscriber1 = Observable::subscribe(&observable); 25 | //! let mut subscriber2 = Observable::subscribe(&observable); 26 | //! 27 | //! // You can get the current value from a subscriber without waiting 28 | //! // for updates. 29 | //! assert_eq!(subscriber1.get(), "A"); 30 | //! 31 | //! Observable::set(&mut observable, "B".to_owned()); 32 | //! // `.next().await` will wait for the next update, then return the 33 | //! // new value. 34 | //! assert_eq!(subscriber1.next().await, Some("B".to_owned())); 35 | //! 36 | //! // If multiple updates have happened without the subscriber being 37 | //! // polled as is the case for subscriber2 here, the next poll will 38 | //! // skip all but the latest. 39 | //! Observable::set(&mut observable, "C".to_owned()); 40 | //! assert_eq!(subscriber1.next().await, Some("C".to_owned())); 41 | //! assert_eq!(subscriber2.next().await, Some("C".to_owned())); 42 | //! 43 | //! // You can even obtain the value without cloning the value, by 44 | //! // using `.read()` (no waiting) or `.next_ref().await` (waits for 45 | //! // the next update). 46 | //! // If you restrict yourself to these methods, you can even use 47 | //! // `Observable` with inner types that don't implement the `Clone` 48 | //! // trait. 49 | //! // However, note that while a read guard returned by `.read()` or 50 | //! // `.next_ref().await` is alive, updating the observable is 51 | //! // blocked. 52 | //! Observable::set(&mut observable, "D".to_owned()); 53 | //! { 54 | //! let guard = subscriber1.next_ref().await.unwrap(); 55 | //! assert_eq!(*guard, "D"); 56 | //! } 57 | //! 58 | //! // The latest value is kept alive by subscribers when the 59 | //! // `Observable` is dropped. 60 | //! drop(observable); 61 | //! assert_eq!(subscriber1.get(), "D"); 62 | //! assert_eq!(*subscriber2.read(), "D"); 63 | //! # } 64 | //! ``` 65 | //! 66 | //! Cargo features: 67 | //! 68 | //! - `tracing`: Emit [tracing] events when updates are sent out 69 | //! 70 | //! [Observer pattern]: https://en.wikipedia.org/wiki/Observer_pattern 71 | 72 | #![cfg_attr(docsrs, feature(doc_auto_cfg))] 73 | 74 | mod lock; 75 | mod read_guard; 76 | mod shared; 77 | mod state; 78 | pub mod subscriber; 79 | mod unique; 80 | 81 | #[cfg(feature = "async-lock")] 82 | #[doc(inline)] 83 | pub use self::lock::AsyncLock; 84 | #[doc(inline)] 85 | pub use self::{ 86 | lock::SyncLock, 87 | read_guard::ObservableReadGuard, 88 | shared::{ObservableWriteGuard, SharedObservable, WeakObservable}, 89 | subscriber::Subscriber, 90 | unique::Observable, 91 | }; 92 | -------------------------------------------------------------------------------- /eyeball/src/lock.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | ops::{Deref, DerefMut}, 3 | sync::Arc, 4 | }; 5 | 6 | use crate::state::ObservableState; 7 | 8 | pub trait Lock { 9 | type RwLock; 10 | type RwLockReadGuard<'a, T>: Deref 11 | where 12 | T: 'a; 13 | type RwLockWriteGuard<'a, T>: DerefMut 14 | where 15 | T: 'a; 16 | type Shared: Deref; 17 | type SharedReadGuard<'a, T>: Deref 18 | where 19 | T: 'a; 20 | type SubscriberState; 21 | 22 | fn new_rwlock(value: T) -> Self::RwLock; 23 | fn read_noblock(lock: &Self::RwLock) -> Self::RwLockReadGuard<'_, T>; 24 | 25 | fn new_shared(value: T) -> Self::Shared; 26 | fn shared_read_count(shared: &Self::Shared) -> usize; 27 | fn shared_into_inner(shared: Self::Shared) -> Arc>; 28 | } 29 | 30 | /// Marker type for using a synchronous lock for the inner value. 31 | #[allow(missing_debug_implementations)] 32 | pub enum SyncLock {} 33 | 34 | impl Lock for SyncLock { 35 | type RwLock = std::sync::RwLock; 36 | type RwLockReadGuard<'a, T> 37 | = std::sync::RwLockReadGuard<'a, T> 38 | where 39 | T: 'a; 40 | type RwLockWriteGuard<'a, T> 41 | = std::sync::RwLockWriteGuard<'a, T> 42 | where 43 | T: 'a; 44 | type Shared = readlock::Shared; 45 | type SharedReadGuard<'a, T> 46 | = readlock::SharedReadGuard<'a, T> 47 | where 48 | T: 'a; 49 | type SubscriberState = readlock::SharedReadLock>; 50 | 51 | fn new_rwlock(value: T) -> Self::RwLock { 52 | Self::RwLock::new(value) 53 | } 54 | fn read_noblock(lock: &Self::RwLock) -> Self::RwLockReadGuard<'_, T> { 55 | lock.try_read().unwrap() 56 | } 57 | 58 | fn new_shared(value: T) -> Self::Shared { 59 | Self::Shared::new(value) 60 | } 61 | fn shared_read_count(shared: &Self::Shared) -> usize { 62 | Self::Shared::read_count(shared) 63 | } 64 | fn shared_into_inner(shared: Self::Shared) -> Arc> { 65 | Self::Shared::into_inner(shared) 66 | } 67 | } 68 | 69 | /// Marker type for using an asynchronous lock for the inner value. 70 | #[cfg(feature = "async-lock")] 71 | #[allow(missing_debug_implementations)] 72 | pub enum AsyncLock {} 73 | 74 | #[cfg(feature = "async-lock")] 75 | impl Lock for AsyncLock { 76 | type RwLock = tokio::sync::RwLock; 77 | type RwLockReadGuard<'a, T> 78 | = tokio::sync::RwLockReadGuard<'a, T> 79 | where 80 | T: 'a; 81 | type RwLockWriteGuard<'a, T> 82 | = tokio::sync::RwLockWriteGuard<'a, T> 83 | where 84 | T: 'a; 85 | type Shared = readlock_tokio::Shared; 86 | type SharedReadGuard<'a, T> 87 | = readlock_tokio::SharedReadGuard<'a, T> 88 | where 89 | T: 'a; 90 | type SubscriberState = crate::subscriber::async_lock::AsyncSubscriberState; 91 | 92 | fn new_rwlock(value: T) -> Self::RwLock { 93 | Self::RwLock::new(value) 94 | } 95 | fn read_noblock(lock: &Self::RwLock) -> Self::RwLockReadGuard<'_, T> { 96 | lock.try_read().unwrap() 97 | } 98 | 99 | fn new_shared(value: T) -> Self::Shared { 100 | Self::Shared::new(value) 101 | } 102 | fn shared_read_count(shared: &Self::Shared) -> usize { 103 | Self::Shared::read_count(shared) 104 | } 105 | fn shared_into_inner(shared: Self::Shared) -> Arc> { 106 | Self::Shared::into_inner(shared) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /eyeball/src/read_guard.rs: -------------------------------------------------------------------------------- 1 | use std::{fmt, ops}; 2 | 3 | use crate::{lock::Lock, state::ObservableState, SyncLock}; 4 | 5 | /// A read guard for the inner value of an observable. 6 | /// 7 | /// Note that as long as an `ObservableReadGuard` is kept alive, the associated 8 | /// `Observable` is locked and can not be updated. 9 | #[must_use] 10 | #[clippy::has_significant_drop] 11 | pub struct ObservableReadGuard<'a, T: 'a, L: Lock = SyncLock> { 12 | inner: L::SharedReadGuard<'a, ObservableState>, 13 | } 14 | 15 | impl<'a, T: 'a, L: Lock> ObservableReadGuard<'a, T, L> { 16 | pub(crate) fn new(inner: L::SharedReadGuard<'a, ObservableState>) -> Self { 17 | Self { inner } 18 | } 19 | } 20 | 21 | impl fmt::Debug for ObservableReadGuard<'_, T, L> { 22 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 23 | self.inner.fmt(f) 24 | } 25 | } 26 | 27 | impl ops::Deref for ObservableReadGuard<'_, T, L> { 28 | type Target = T; 29 | 30 | fn deref(&self) -> &Self::Target { 31 | self.inner.get() 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /eyeball/src/state.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | hash::{Hash, Hasher}, 3 | mem, 4 | sync::RwLock, 5 | task::{Context, Poll, Waker}, 6 | }; 7 | 8 | #[derive(Debug)] 9 | pub struct ObservableState { 10 | /// The wrapped value. 11 | value: T, 12 | 13 | /// The attached observable metadata. 14 | metadata: RwLock, 15 | } 16 | 17 | #[derive(Debug)] 18 | struct ObservableStateMetadata { 19 | /// The version of the value. 20 | /// 21 | /// Starts at 1 and is incremented by 1 each time the value is updated. 22 | /// When the observable is dropped, this is set to 0 to indicate no further 23 | /// updates will happen. 24 | version: u64, 25 | 26 | /// List of wakers. 27 | /// 28 | /// This is part of `ObservableState` and uses extra locking so that it is 29 | /// guaranteed that it's only updated by subscribers while the value is 30 | /// locked for reading. This way, it is guaranteed that between a subscriber 31 | /// reading the value and adding a waker because the value hasn't changed 32 | /// yet, no updates to the value could have happened. 33 | wakers: Vec, 34 | } 35 | 36 | impl Default for ObservableStateMetadata { 37 | fn default() -> Self { 38 | Self { version: 1, wakers: Vec::new() } 39 | } 40 | } 41 | 42 | impl ObservableState { 43 | pub(crate) fn new(value: T) -> Self { 44 | Self { value, metadata: Default::default() } 45 | } 46 | 47 | /// Get a reference to the inner value. 48 | pub(crate) fn get(&self) -> &T { 49 | &self.value 50 | } 51 | 52 | /// Get the current version of the inner value. 53 | pub(crate) fn version(&self) -> u64 { 54 | self.metadata.read().unwrap().version 55 | } 56 | 57 | pub(crate) fn poll_update( 58 | &self, 59 | observed_version: &mut u64, 60 | cx: &Context<'_>, 61 | ) -> Poll> { 62 | let mut metadata = self.metadata.write().unwrap(); 63 | 64 | if metadata.version == 0 { 65 | Poll::Ready(None) 66 | } else if *observed_version < metadata.version { 67 | *observed_version = metadata.version; 68 | Poll::Ready(Some(())) 69 | } else { 70 | metadata.wakers.push(cx.waker().clone()); 71 | Poll::Pending 72 | } 73 | } 74 | 75 | pub(crate) fn set(&mut self, value: T) -> T { 76 | let result = mem::replace(&mut self.value, value); 77 | self.incr_version_and_wake(); 78 | result 79 | } 80 | 81 | pub(crate) fn set_if_not_eq(&mut self, value: T) -> Option 82 | where 83 | T: PartialEq, 84 | { 85 | if self.value != value { 86 | Some(self.set(value)) 87 | } else { 88 | None 89 | } 90 | } 91 | 92 | pub(crate) fn set_if_hash_not_eq(&mut self, value: T) -> Option 93 | where 94 | T: Hash, 95 | { 96 | if hash(&self.value) != hash(&value) { 97 | Some(self.set(value)) 98 | } else { 99 | None 100 | } 101 | } 102 | 103 | pub(crate) fn update(&mut self, f: impl FnOnce(&mut T)) { 104 | f(&mut self.value); 105 | self.incr_version_and_wake(); 106 | } 107 | 108 | pub(crate) fn update_if(&mut self, f: impl FnOnce(&mut T) -> bool) { 109 | if f(&mut self.value) { 110 | self.incr_version_and_wake(); 111 | } 112 | } 113 | 114 | /// "Close" the state – indicate that no further updates will happen. 115 | pub(crate) fn close(&self) { 116 | let mut metadata = self.metadata.write().unwrap(); 117 | metadata.version = 0; 118 | // Clear the backing buffer for the wakers, no new ones will be added. 119 | wake(mem::take(&mut metadata.wakers)); 120 | } 121 | 122 | fn incr_version_and_wake(&mut self) { 123 | let metadata = self.metadata.get_mut().unwrap(); 124 | metadata.version += 1; 125 | wake(metadata.wakers.drain(..)); 126 | } 127 | } 128 | 129 | fn hash(value: &T) -> u64 { 130 | use std::collections::hash_map::DefaultHasher; 131 | 132 | let mut hasher = DefaultHasher::new(); 133 | value.hash(&mut hasher); 134 | hasher.finish() 135 | } 136 | 137 | fn wake(wakers: I) 138 | where 139 | I: IntoIterator, 140 | I::IntoIter: ExactSizeIterator, 141 | { 142 | let iter = wakers.into_iter(); 143 | #[cfg(feature = "tracing")] 144 | { 145 | let num_wakers = iter.len(); 146 | if num_wakers > 0 { 147 | tracing::debug!("Waking up {num_wakers} waiting subscribers"); 148 | } else { 149 | tracing::debug!("No wakers"); 150 | } 151 | } 152 | for waker in iter { 153 | waker.wake(); 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /eyeball/src/subscriber.rs: -------------------------------------------------------------------------------- 1 | //! Details of observable [`Subscriber`]s. 2 | //! 3 | //! Usually, you don't need to interact with this module at all, since its most 4 | //! important type `Subscriber` is re-exported at the crate root. 5 | 6 | use std::{ 7 | fmt, 8 | future::{poll_fn, Future}, 9 | pin::Pin, 10 | task::{Context, Poll}, 11 | }; 12 | 13 | use futures_core::Stream; 14 | 15 | use crate::{lock::Lock, state::ObservableState, ObservableReadGuard, SyncLock}; 16 | 17 | #[cfg(feature = "async-lock")] 18 | pub(crate) mod async_lock; 19 | 20 | /// A subscriber for updates of an `Observable`. 21 | #[must_use] 22 | pub struct Subscriber { 23 | state: L::SubscriberState, 24 | observed_version: u64, 25 | } 26 | 27 | impl Subscriber { 28 | pub(crate) fn new(state: readlock::SharedReadLock>, version: u64) -> Self { 29 | Self { state, observed_version: version } 30 | } 31 | 32 | /// Wait for an update and get a clone of the updated value. 33 | /// 34 | /// Awaiting returns `Some(_)` after an update happened, or `None` after the 35 | /// `Observable` (and all clones for `shared::Observable`) is dropped. 36 | /// 37 | /// This method is a convenience so you don't have to import a `Stream` 38 | /// extension trait such as `futures::StreamExt` or 39 | /// `tokio_stream::StreamExt`. 40 | #[allow(clippy::should_implement_trait)] 41 | pub fn next(&mut self) -> Next<'_, T> 42 | where 43 | T: Clone, 44 | { 45 | Next::new(self) 46 | } 47 | 48 | /// Get a clone of the inner value without waiting for an update. 49 | /// 50 | /// If the returned value has not been observed by this subscriber before, 51 | /// it is marked as observed such that a subsequent call of 52 | /// [`next`][Self::next] or [`next_ref`][Self::next_ref] won't return the 53 | /// same value again. See [`get`][Self::get] for a function that doesn't 54 | /// mark the value as observed. 55 | #[must_use] 56 | pub fn next_now(&mut self) -> T 57 | where 58 | T: Clone, 59 | { 60 | let lock = self.state.lock(); 61 | self.observed_version = lock.version(); 62 | lock.get().clone() 63 | } 64 | 65 | /// Get a clone of the inner value without waiting for an update. 66 | /// 67 | /// If the returned value has not been observed by this subscriber before, 68 | /// it is **not** marked as observed such that a subsequent call of 69 | /// [`next`][Self::next] or [`next_ref`][Self::next_ref] will return the 70 | /// same value again. 71 | #[must_use] 72 | pub fn get(&self) -> T 73 | where 74 | T: Clone, 75 | { 76 | self.read().clone() 77 | } 78 | 79 | /// Wait for an update and get a read lock for the updated value. 80 | /// 81 | /// Awaiting returns `Some(_)` after an update happened, or `None` after the 82 | /// `Observable` (and all clones for `shared::Observable`) is dropped. 83 | /// 84 | /// You can use this method to get updates of an `Observable` where the 85 | /// inner type does not implement `Clone`. However, the `Observable` 86 | /// will be locked (not updateable) while any read guards are alive. 87 | #[must_use] 88 | pub async fn next_ref(&mut self) -> Option> { 89 | // Unclear how to implement this as a named future. 90 | poll_fn(|cx| self.poll_next_ref(cx).map(|opt| opt.map(|_| {}))).await?; 91 | Some(self.next_ref_now()) 92 | } 93 | 94 | /// Lock the inner value for reading without waiting for an update. 95 | /// 96 | /// Note that as long as the returned [`ObservableReadGuard`] is kept alive, 97 | /// the associated `Observable` is locked and can not be updated. 98 | /// 99 | /// If the returned value has not been observed by this subscriber before, 100 | /// it is marked as observed such that a subsequent call of 101 | /// [`next`][Self::next] or [`next_ref`][Self::next_ref] won't return the 102 | /// same value again. See [`get`][Self::get] for a function that doesn't 103 | /// mark the value as observed. 104 | pub fn next_ref_now(&mut self) -> ObservableReadGuard<'_, T> { 105 | let lock = self.state.lock(); 106 | self.observed_version = lock.version(); 107 | ObservableReadGuard::new(lock) 108 | } 109 | 110 | /// Lock the inner value for reading without waiting for an update. 111 | /// 112 | /// Note that as long as the returned [`ObservableReadGuard`] is kept alive, 113 | /// the associated `Observable` is locked and can not be updated. 114 | /// 115 | /// If the returned value has not been observed by this subscriber before, 116 | /// it is **not** marked as observed such that a subsequent call of 117 | /// [`next`][Self::next] or [`next_ref`][Self::next_ref] will return the 118 | /// same value again. 119 | pub fn read(&self) -> ObservableReadGuard<'_, T> { 120 | ObservableReadGuard::new(self.state.lock()) 121 | } 122 | 123 | fn poll_next_ref(&mut self, cx: &Context<'_>) -> Poll>> { 124 | let state = self.state.lock(); 125 | state 126 | .poll_update(&mut self.observed_version, cx) 127 | .map(|ready| ready.map(|_| ObservableReadGuard::new(state))) 128 | } 129 | } 130 | 131 | impl Subscriber { 132 | /// Reset the observed version of the inner value. 133 | /// 134 | /// After calling this, it is guaranteed that the next call to 135 | /// `.next().await` or `.next_ref().await` will resolve immediately. 136 | /// 137 | /// This is only useful if you do this before passing the subscriber to some 138 | /// other generic function or returning it, if you would be calling 139 | /// `.next().await` right afterwards, you can call 140 | /// [`.next_now()`][Self::next_now] instead (same for `.reset()` plus 141 | /// `.next_ref().await`, which can be expressed by 142 | /// [`.next_ref_now()`](Self::next_ref_now)). 143 | pub fn reset(&mut self) { 144 | self.observed_version = 0; 145 | } 146 | 147 | /// Clone this `Subscriber` and reset the observed version of the inner 148 | /// value. 149 | /// 150 | /// This is equivalent to using the regular [`clone`][Self::clone] method 151 | /// and calling [`reset`][Self::reset] on the clone afterwards. 152 | pub fn clone_reset(&self) -> Self 153 | where 154 | L::SubscriberState: Clone, 155 | { 156 | Self { state: self.state.clone(), observed_version: 0 } 157 | } 158 | } 159 | 160 | /// Clone this `Subscriber` exactly, including the observed version of the inner 161 | /// value. 162 | /// 163 | /// That means that if the original `Subscriber` was up-to-date with the latest 164 | /// value of the observable, the new one will be as well, and vice-versa. 165 | /// 166 | /// See [`clone_reset`][Self::clone_reset] for a convenient way of making a new 167 | /// `Subscriber` from an existing one without inheriting the observed version of 168 | /// the inner value. 169 | impl Clone for Subscriber 170 | where 171 | L::SubscriberState: Clone, 172 | { 173 | fn clone(&self) -> Self { 174 | Self { state: self.state.clone(), observed_version: self.observed_version } 175 | } 176 | } 177 | 178 | impl fmt::Debug for Subscriber 179 | where 180 | L::SubscriberState: fmt::Debug, 181 | { 182 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 183 | f.debug_struct("Subscriber") 184 | .field("state", &self.state) 185 | .field("observed_version", &self.observed_version) 186 | .finish() 187 | } 188 | } 189 | 190 | impl Stream for Subscriber { 191 | type Item = T; 192 | 193 | fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 194 | self.poll_next_ref(cx).map(opt_guard_to_owned) 195 | } 196 | } 197 | 198 | /// Future returned by [`Subscriber::next`]. 199 | #[must_use] 200 | #[allow(missing_debug_implementations)] 201 | pub struct Next<'a, T, L: Lock = SyncLock> { 202 | subscriber: &'a mut Subscriber, 203 | } 204 | 205 | impl<'a, T> Next<'a, T> { 206 | fn new(subscriber: &'a mut Subscriber) -> Self { 207 | Self { subscriber } 208 | } 209 | } 210 | 211 | impl Future for Next<'_, T> { 212 | type Output = Option; 213 | 214 | fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 215 | self.subscriber.poll_next_ref(cx).map(opt_guard_to_owned) 216 | } 217 | } 218 | 219 | fn opt_guard_to_owned(value: Option>) -> Option { 220 | value.map(|guard| guard.to_owned()) 221 | } 222 | -------------------------------------------------------------------------------- /eyeball/src/subscriber/async_lock.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fmt, 3 | future::{poll_fn, Future}, 4 | pin::Pin, 5 | task::{ready, Context, Poll}, 6 | }; 7 | 8 | use futures_core::Stream; 9 | use readlock_tokio::{OwnedSharedReadGuard, SharedReadLock}; 10 | use tokio_util::sync::ReusableBoxFuture; 11 | 12 | use super::{Next, Subscriber}; 13 | use crate::{state::ObservableState, AsyncLock, ObservableReadGuard}; 14 | 15 | pub struct AsyncSubscriberState { 16 | inner: SharedReadLock>, 17 | get_lock: ReusableBoxFuture<'static, OwnedSharedReadGuard>>, 18 | } 19 | 20 | impl Clone for AsyncSubscriberState { 21 | fn clone(&self) -> Self { 22 | Self { 23 | inner: self.inner.clone(), 24 | get_lock: ReusableBoxFuture::new(self.inner.clone().lock_owned()), 25 | } 26 | } 27 | } 28 | 29 | impl fmt::Debug for AsyncSubscriberState { 30 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 31 | self.inner.fmt(f) 32 | } 33 | } 34 | 35 | impl Subscriber { 36 | pub(crate) fn new_async(inner: SharedReadLock>, version: u64) -> Self { 37 | let get_lock = ReusableBoxFuture::new(inner.clone().lock_owned()); 38 | Self { state: AsyncSubscriberState { inner, get_lock }, observed_version: version } 39 | } 40 | 41 | /// Wait for an update and get a clone of the updated value. 42 | /// 43 | /// Awaiting returns `Some(_)` after an update happened, or `None` after the 44 | /// `Observable` (and all clones for `shared::Observable`) is dropped. 45 | /// 46 | /// This method is a convenience so you don't have to import a `Stream` 47 | /// extension trait such as `futures::StreamExt` or 48 | /// `tokio_stream::StreamExt`. 49 | #[allow(clippy::should_implement_trait)] 50 | pub async fn next(&mut self) -> Option 51 | where 52 | T: Clone, 53 | { 54 | self.next_ref().await.map(|read_guard| read_guard.clone()) 55 | } 56 | 57 | /// Get a clone of the inner value without waiting for an update. 58 | /// 59 | /// If the returned value has not been observed by this subscriber before, 60 | /// it is marked as observed such that a subsequent call of 61 | /// [`next`][Self::next] or [`next_ref`][Self::next_ref] won't return the 62 | /// same value again. See [`get`][Self::get] for a function that doesn't 63 | /// mark the value as observed. 64 | #[must_use] 65 | pub async fn next_now(&mut self) -> T 66 | where 67 | T: Clone, 68 | { 69 | let lock = self.state.inner.lock().await; 70 | self.observed_version = lock.version(); 71 | lock.get().clone() 72 | } 73 | 74 | /// Get a clone of the inner value without waiting for an update. 75 | /// 76 | /// If the returned value has not been observed by this subscriber before, 77 | /// it is **not** marked as observed such that a subsequent call of 78 | /// [`next`][Self::next] or [`next_ref`][Self::next_ref] will return the 79 | /// same value again. 80 | #[must_use] 81 | pub async fn get(&self) -> T 82 | where 83 | T: Clone, 84 | { 85 | self.read().await.clone() 86 | } 87 | 88 | /// Wait for an update and get a read lock for the updated value. 89 | /// 90 | /// Awaiting returns `Some(_)` after an update happened, or `None` after the 91 | /// `Observable` (and all clones for `shared::Observable`) is dropped. 92 | /// 93 | /// You can use this method to get updates of an `Observable` where the 94 | /// inner type does not implement `Clone`. However, the `Observable` 95 | /// will be locked (not updateable) while any read guards are alive. 96 | #[must_use] 97 | pub async fn next_ref(&mut self) -> Option> { 98 | // Unclear how to implement this as a named future. 99 | poll_fn(|cx| self.poll_update(cx)).await?; 100 | Some(self.next_ref_now().await) 101 | } 102 | 103 | /// Lock the inner value for reading without waiting for an update. 104 | /// 105 | /// Note that as long as the returned [`ObservableReadGuard`] is kept alive, 106 | /// the associated `Observable` is locked and can not be updated. 107 | /// 108 | /// If the returned value has not been observed by this subscriber before, 109 | /// it is marked as observed such that a subsequent call of 110 | /// [`next`][Self::next] or [`next_ref`][Self::next_ref] won't return the 111 | /// same value again. See [`get`][Self::get] for a function that doesn't 112 | /// mark the value as observed. 113 | pub async fn next_ref_now(&mut self) -> ObservableReadGuard<'_, T, AsyncLock> { 114 | let lock = self.state.inner.lock().await; 115 | self.observed_version = lock.version(); 116 | ObservableReadGuard::new(lock) 117 | } 118 | 119 | /// Lock the inner value for reading without waiting for an update. 120 | /// 121 | /// Note that as long as the returned [`ObservableReadGuard`] is kept alive, 122 | /// the associated `Observable` is locked and can not be updated. 123 | /// 124 | /// If the returned value has not been observed by this subscriber before, 125 | /// it is **not** marked as observed such that a subsequent call of 126 | /// [`next`][Self::next] or [`next_ref`][Self::next_ref] will return the 127 | /// same value again. 128 | pub async fn read(&self) -> ObservableReadGuard<'_, T, AsyncLock> { 129 | ObservableReadGuard::new(self.state.inner.lock().await) 130 | } 131 | 132 | fn poll_update(&mut self, cx: &mut Context<'_>) -> Poll> { 133 | let state = ready!(self.state.get_lock.poll(cx)); 134 | self.state.get_lock.set(self.state.inner.clone().lock_owned()); 135 | state.poll_update(&mut self.observed_version, cx) 136 | } 137 | 138 | fn poll_next_nopin(&mut self, cx: &mut Context<'_>) -> Poll> 139 | where 140 | T: Clone, 141 | { 142 | let state = ready!(self.state.get_lock.poll(cx)); 143 | self.state.get_lock.set(self.state.inner.clone().lock_owned()); 144 | state 145 | .poll_update(&mut self.observed_version, cx) 146 | .map(|ready| ready.map(|_| state.get().clone())) 147 | } 148 | } 149 | 150 | impl Stream for Subscriber { 151 | type Item = T; 152 | 153 | fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 154 | self.poll_next_nopin(cx) 155 | } 156 | } 157 | 158 | impl Future for Next<'_, T, AsyncLock> { 159 | type Output = Option; 160 | 161 | fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 162 | self.subscriber.poll_next_nopin(cx) 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /eyeball/src/unique.rs: -------------------------------------------------------------------------------- 1 | //! This module defines a unique [`Observable`] type that requires `&mut` access 2 | //! to update its inner value but can be dereferenced (immutably). 3 | //! 4 | //! Use this in situations where only a single location in the code should be 5 | //! able to update the inner value. 6 | 7 | use std::{fmt, hash::Hash, mem, ops, ptr}; 8 | 9 | use readlock::Shared; 10 | #[cfg(feature = "async-lock")] 11 | use readlock_tokio::Shared as SharedAsync; 12 | 13 | #[cfg(feature = "async-lock")] 14 | use crate::AsyncLock; 15 | use crate::{lock::Lock, shared::SharedObservable, state::ObservableState, Subscriber, SyncLock}; 16 | 17 | /// A value whose changes will be broadcast to subscribers. 18 | /// 19 | /// `Observable` dereferences to `T`, and does not have methods of its own to 20 | /// not clash with methods of the inner type. Instead, to interact with the 21 | /// `Observable` itself rather than the inner value, use its associated 22 | /// functions (e.g. `Observable::subscribe(observable)`). 23 | /// 24 | /// # Async-aware locking 25 | /// 26 | /// Contrary to [`SharedObservable`]'s async-aware locking support, using 27 | /// `Observable` with `L` = [`AsyncLock`] with this type is rarely useful since 28 | /// having access to the `Observable` means nobody can be mutating the inner 29 | /// value in parallel. It allows a subscriber to read-lock the value over a 30 | /// `.await` point without losing `Send`-ness of the future though. 31 | pub struct Observable { 32 | state: L::Shared>, 33 | } 34 | 35 | impl Observable { 36 | /// Create a new `Observable` with the given initial value. 37 | #[must_use] 38 | pub fn new(value: T) -> Self { 39 | let state = Shared::new(ObservableState::new(value)); 40 | Self::from_inner(state) 41 | } 42 | 43 | /// Obtain a new subscriber. 44 | /// 45 | /// Calling `.next().await` or `.next_ref().await` on the returned 46 | /// subscriber only resolves once the inner value has been updated again 47 | /// after the call to `subscribe`. 48 | /// 49 | /// See [`subscribe_reset`][Self::subscribe_reset] if you want to obtain a 50 | /// subscriber that immediately yields without any updates. 51 | pub fn subscribe(this: &Self) -> Subscriber { 52 | Subscriber::new(Shared::get_read_lock(&this.state), this.state.version()) 53 | } 54 | 55 | /// Obtain a new subscriber that immediately yields. 56 | /// 57 | /// `.subscribe_reset()` is equivalent to `.subscribe()` with a subsequent 58 | /// call to [`.reset()`][Subscriber::reset] on the returned subscriber. 59 | /// 60 | /// In contrast to [`subscribe`][Self::subscribe], calling `.next().await` 61 | /// or `.next_ref().await` on the returned subscriber before updating the 62 | /// inner value yields the current value instead of waiting. Further calls 63 | /// to either of the two will wait for updates. 64 | pub fn subscribe_reset(this: &Self) -> Subscriber { 65 | Subscriber::new(Shared::get_read_lock(&this.state), 0) 66 | } 67 | 68 | /// Get a reference to the inner value. 69 | /// 70 | /// Usually, you don't need to call this function since `Observable` 71 | /// implements `Deref`. Use this if you want to pass the inner value to a 72 | /// generic function where the compiler can't infer that you want to have 73 | /// the `Observable` dereferenced otherwise. 74 | pub fn get(this: &Self) -> &T { 75 | this.state.get() 76 | } 77 | 78 | /// Set the inner value to the given `value`, notify subscribers and return 79 | /// the previous value. 80 | pub fn set(this: &mut Self, value: T) -> T { 81 | Shared::lock(&mut this.state).set(value) 82 | } 83 | 84 | /// Set the inner value to the given `value` if it doesn't compare equal to 85 | /// the existing value. 86 | /// 87 | /// If the inner value is set, subscribers are notified and 88 | /// `Some(previous_value)` is returned. Otherwise, `None` is returned. 89 | pub fn set_if_not_eq(this: &mut Self, value: T) -> Option 90 | where 91 | T: PartialEq, 92 | { 93 | Shared::lock(&mut this.state).set_if_not_eq(value) 94 | } 95 | 96 | /// Set the inner value to the given `value` if it has a different hash than 97 | /// the existing value. 98 | /// 99 | /// If the inner value is set, subscribers are notified and 100 | /// `Some(previous_value)` is returned. Otherwise, `None` is returned. 101 | pub fn set_if_hash_not_eq(this: &mut Self, value: T) -> Option 102 | where 103 | T: Hash, 104 | { 105 | Shared::lock(&mut this.state).set_if_hash_not_eq(value) 106 | } 107 | 108 | /// Set the inner value to a `Default` instance of its type, notify 109 | /// subscribers and return the previous value. 110 | /// 111 | /// Shorthand for `Observable::set(this, T::default())`. 112 | pub fn take(this: &mut Self) -> T 113 | where 114 | T: Default, 115 | { 116 | Self::set(this, T::default()) 117 | } 118 | 119 | /// Update the inner value and notify subscribers. 120 | /// 121 | /// Note that even if the inner value is not actually changed by the 122 | /// closure, subscribers will be notified as if it was. Use 123 | /// [`update_if`][Self::update_if] if you want to conditionally mutate the 124 | /// inner value. 125 | pub fn update(this: &mut Self, f: impl FnOnce(&mut T)) { 126 | Shared::lock(&mut this.state).update(f); 127 | } 128 | 129 | /// Maybe update the inner value and notify subscribers if it changed. 130 | /// 131 | /// The closure given to this function must return `true` if subscribers 132 | /// should be notified of a change to the inner value. 133 | pub fn update_if(this: &mut Self, f: impl FnOnce(&mut T) -> bool) { 134 | Shared::lock(&mut this.state).update_if(f); 135 | } 136 | } 137 | 138 | #[cfg(feature = "async-lock")] 139 | impl Observable { 140 | /// Create a new `Observable` with the given initial value. 141 | #[must_use] 142 | pub fn new_async(value: T) -> Self { 143 | let state = SharedAsync::new(ObservableState::new(value)); 144 | Self::from_inner(state) 145 | } 146 | 147 | /// Obtain a new subscriber. 148 | /// 149 | /// Calling `.next().await` or `.next_ref().await` on the returned 150 | /// subscriber only resolves once the inner value has been updated again 151 | /// after the call to `subscribe`. 152 | /// 153 | /// See [`subscribe_reset`][Self::subscribe_reset] if you want to obtain a 154 | /// subscriber that immediately yields without any updates. 155 | pub fn subscribe_async(this: &Self) -> Subscriber { 156 | Subscriber::new_async(SharedAsync::get_read_lock(&this.state), this.state.version()) 157 | } 158 | 159 | /// Obtain a new subscriber that immediately yields. 160 | /// 161 | /// `.subscribe_reset()` is equivalent to `.subscribe()` with a subsequent 162 | /// call to [`.reset()`][Subscriber::reset] on the returned subscriber. 163 | /// 164 | /// In contrast to [`subscribe`][Self::subscribe], calling `.next().await` 165 | /// or `.next_ref().await` on the returned subscriber before updating the 166 | /// inner value yields the current value instead of waiting. Further calls 167 | /// to either of the two will wait for updates. 168 | pub fn subscribe_reset_async(this: &Self) -> Subscriber { 169 | Subscriber::new_async(SharedAsync::get_read_lock(&this.state), 0) 170 | } 171 | 172 | /// Get a reference to the inner value. 173 | /// 174 | /// Usually, you don't need to call this function since `Observable` 175 | /// implements `Deref`. Use this if you want to pass the inner value to a 176 | /// generic function where the compiler can't infer that you want to have 177 | /// the `Observable` dereferenced otherwise. 178 | pub fn get_async(this: &Self) -> &T { 179 | this.state.get() 180 | } 181 | 182 | /// Set the inner value to the given `value`, notify subscribers and return 183 | /// the previous value. 184 | pub async fn set_async(this: &mut Self, value: T) -> T { 185 | SharedAsync::lock(&mut this.state).await.set(value) 186 | } 187 | 188 | /// Set the inner value to the given `value` if it doesn't compare equal to 189 | /// the existing value. 190 | /// 191 | /// If the inner value is set, subscribers are notified and 192 | /// `Some(previous_value)` is returned. Otherwise, `None` is returned. 193 | pub async fn set_if_not_eq_async(this: &mut Self, value: T) -> Option 194 | where 195 | T: PartialEq, 196 | { 197 | SharedAsync::lock(&mut this.state).await.set_if_not_eq(value) 198 | } 199 | 200 | /// Set the inner value to the given `value` if it has a different hash than 201 | /// the existing value. 202 | /// 203 | /// If the inner value is set, subscribers are notified and 204 | /// `Some(previous_value)` is returned. Otherwise, `None` is returned. 205 | pub async fn set_if_hash_not_eq_async(this: &mut Self, value: T) -> Option 206 | where 207 | T: Hash, 208 | { 209 | SharedAsync::lock(&mut this.state).await.set_if_hash_not_eq(value) 210 | } 211 | 212 | /// Set the inner value to a `Default` instance of its type, notify 213 | /// subscribers and return the previous value. 214 | /// 215 | /// Shorthand for `Observable::set(this, T::default())`. 216 | pub async fn take_async(this: &mut Self) -> T 217 | where 218 | T: Default, 219 | { 220 | Self::set_async(this, T::default()).await 221 | } 222 | 223 | /// Update the inner value and notify subscribers. 224 | /// 225 | /// Note that even if the inner value is not actually changed by the 226 | /// closure, subscribers will be notified as if it was. Use 227 | /// [`update_if`][Self::update_if] if you want to conditionally mutate the 228 | /// inner value. 229 | pub async fn update_async(this: &mut Self, f: impl FnOnce(&mut T)) { 230 | SharedAsync::lock(&mut this.state).await.update(f); 231 | } 232 | 233 | /// Maybe update the inner value and notify subscribers if it changed. 234 | /// 235 | /// The closure given to this function must return `true` if subscribers 236 | /// should be notified of a change to the inner value. 237 | pub async fn update_if_async(this: &mut Self, f: impl FnOnce(&mut T) -> bool) { 238 | SharedAsync::lock(&mut this.state).await.update_if(f); 239 | } 240 | } 241 | 242 | impl Observable { 243 | pub(crate) fn from_inner(state: L::Shared>) -> Self { 244 | Self { state } 245 | } 246 | 247 | /// Get the number of subscribers. 248 | /// 249 | /// Be careful when using this. The result is only reliable if it is exactly 250 | /// `0`, as otherwise it could be incremented right after your call to this 251 | /// function, before you look at its result or do anything based on that. 252 | #[must_use] 253 | pub fn subscriber_count(this: &Self) -> usize { 254 | L::shared_read_count(&this.state) 255 | } 256 | 257 | /// Convert this unique `Observable` into a [`SharedObservable`]. 258 | /// 259 | /// Any subscribers created for `self` remain valid. 260 | pub fn into_shared(this: Self) -> SharedObservable { 261 | // Destructure `this` without running `Drop`. 262 | let state = unsafe { ptr::read(&this.state) }; 263 | mem::forget(this); 264 | 265 | let rwlock = L::shared_into_inner(state); 266 | SharedObservable::from_inner(rwlock) 267 | } 268 | } 269 | 270 | impl fmt::Debug for Observable 271 | where 272 | L::Shared>: fmt::Debug, 273 | { 274 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 275 | f.debug_struct("SharedObservable").field("state", &self.state).finish() 276 | } 277 | } 278 | 279 | impl Default for Observable 280 | where 281 | T: Default, 282 | L: Lock, 283 | { 284 | fn default() -> Self { 285 | let shared = L::new_shared(ObservableState::new(T::default())); 286 | Self::from_inner(shared) 287 | } 288 | } 289 | 290 | // Note: No DerefMut because all mutating must go through inherent methods that 291 | // notify subscribers 292 | impl ops::Deref for Observable { 293 | type Target = T; 294 | 295 | fn deref(&self) -> &Self::Target { 296 | self.state.get() 297 | } 298 | } 299 | 300 | impl Drop for Observable { 301 | fn drop(&mut self) { 302 | self.state.close(); 303 | } 304 | } 305 | -------------------------------------------------------------------------------- /eyeball/tests/it/async_lock.rs: -------------------------------------------------------------------------------- 1 | use eyeball::Observable; 2 | use futures_util::FutureExt; 3 | use stream_assert::{assert_next_eq, assert_pending}; 4 | 5 | #[tokio::test] 6 | async fn smoke_test() { 7 | let mut ob = Observable::new_async("hello, world!"); 8 | let mut rx1 = Observable::subscribe_async(&ob); 9 | 10 | assert_eq!(rx1.get().await, "hello, world!"); 11 | assert_pending!(rx1); 12 | 13 | let join_hdl = tokio::spawn(async move { 14 | // Make sure this doesn't happen before the parent task starts waiting 15 | tokio::task::yield_now().await; 16 | Observable::set_async(&mut ob, "this is a test").await; 17 | ob 18 | }); 19 | 20 | assert_eq!(rx1.next().await, Some("this is a test")); 21 | let ob = join_hdl.now_or_never().unwrap().unwrap(); 22 | 23 | let ob = Observable::into_shared(ob); 24 | let mut rx2 = ob.subscribe().await; 25 | 26 | ob.set("A").await; 27 | assert_next_eq!(rx1, "A"); 28 | 29 | ob.set("B").await; 30 | assert_next_eq!(rx1, "B"); 31 | assert_next_eq!(rx2, "B"); 32 | } 33 | -------------------------------------------------------------------------------- /eyeball/tests/it/main.rs: -------------------------------------------------------------------------------- 1 | #![allow(missing_docs)] 2 | 3 | macro_rules! test { 4 | ( 5 | $(#[$post_attr:meta])* 6 | async fn $name:ident() $(-> $ret:ty)? $bl:block 7 | ) => { 8 | $(#[$post_attr])* 9 | #[core::prelude::v1::test] 10 | fn $name () $(-> $ret)? { 11 | futures_executor::block_on(async { 12 | $bl 13 | }) 14 | } 15 | }; 16 | } 17 | 18 | #[cfg(feature = "async-lock")] 19 | mod async_lock; 20 | mod shared; 21 | mod unique; 22 | -------------------------------------------------------------------------------- /eyeball/tests/it/shared.rs: -------------------------------------------------------------------------------- 1 | use eyeball::SharedObservable; 2 | use futures_util::future::join; 3 | use macro_rules_attribute::apply; 4 | 5 | #[apply(test!)] 6 | async fn lag() { 7 | let ob = SharedObservable::new("hello, world!".to_owned()); 8 | let mut rx1 = ob.subscribe(); 9 | let mut rx2 = ob.subscribe(); 10 | 11 | ob.set("A".to_owned()); 12 | assert_eq!(rx1.next().await, Some("A".to_owned())); 13 | 14 | ob.set("B".to_owned()); 15 | assert_eq!(rx1.next().await, Some("B".to_owned())); 16 | assert_eq!(rx2.next().await, Some("B".to_owned())); 17 | } 18 | 19 | #[apply(test!)] 20 | async fn separate_tasks() { 21 | let ob = SharedObservable::new(Box::new([0; 256])); 22 | let mut subscriber = ob.subscribe(); 23 | 24 | let recv_fut = async { 25 | let mut value = subscriber.next().await.unwrap(); 26 | while let Some(update) = subscriber.next().await { 27 | value = update; 28 | } 29 | assert_eq!(value, Box::new([32; 256])); 30 | assert_eq!(subscriber.next().await, None); 31 | }; 32 | let set_fut = async { 33 | for i in 1..=32 { 34 | ob.set(Box::new([i; 256])); 35 | tokio::task::yield_now().await; 36 | } 37 | drop(ob); 38 | }; 39 | 40 | join(recv_fut, set_fut).await; 41 | } 42 | 43 | #[apply(test!)] 44 | async fn lag_no_clone() { 45 | // no Clone impl 46 | struct Foo(String); 47 | 48 | let ob = SharedObservable::new(Foo("hello, world!".to_owned())); 49 | let mut rx1 = ob.subscribe(); 50 | let mut rx2 = ob.subscribe(); 51 | 52 | ob.set(Foo("A".to_owned())); 53 | assert_eq!(rx1.next_ref().await.as_ref().map(|f| f.0.as_str()), Some("A")); 54 | 55 | ob.set(Foo("B".to_owned())); 56 | assert_eq!(rx1.next_ref().await.as_ref().map(|f| f.0.as_str()), Some("B")); 57 | assert_eq!(rx2.next_ref().await.as_ref().map(|f| f.0.as_str()), Some("B")); 58 | } 59 | -------------------------------------------------------------------------------- /eyeball/tests/it/unique.rs: -------------------------------------------------------------------------------- 1 | use eyeball::Observable; 2 | use futures_util::future::join; 3 | use macro_rules_attribute::apply; 4 | 5 | #[apply(test!)] 6 | async fn lag() { 7 | let mut ob = Observable::new("hello, world!".to_owned()); 8 | let mut rx1 = Observable::subscribe(&ob); 9 | let mut rx2 = Observable::subscribe(&ob); 10 | 11 | Observable::set(&mut ob, "A".to_owned()); 12 | assert_eq!(rx1.next().await, Some("A".to_owned())); 13 | 14 | Observable::set(&mut ob, "B".to_owned()); 15 | assert_eq!(rx1.next().await, Some("B".to_owned())); 16 | assert_eq!(rx2.next().await, Some("B".to_owned())); 17 | } 18 | 19 | #[apply(test!)] 20 | async fn separate_tasks() { 21 | let mut ob = Observable::new(Box::new([0; 256])); 22 | let mut subscriber = Observable::subscribe(&ob); 23 | 24 | let recv_fut = async { 25 | let mut value = subscriber.next().await.unwrap(); 26 | while let Some(update) = subscriber.next().await { 27 | value = update; 28 | } 29 | assert_eq!(value, Box::new([32; 256])); 30 | assert_eq!(subscriber.next().await, None); 31 | }; 32 | let set_fut = async { 33 | for i in 1..=32 { 34 | Observable::set(&mut ob, Box::new([i; 256])); 35 | tokio::task::yield_now().await; 36 | } 37 | drop(ob); 38 | }; 39 | 40 | join(recv_fut, set_fut).await; 41 | } 42 | 43 | #[apply(test!)] 44 | async fn lag_no_clone() { 45 | // no Clone impl 46 | struct Foo(String); 47 | 48 | let mut ob = Observable::new(Foo("hello, world!".to_owned())); 49 | let mut rx1 = Observable::subscribe(&ob); 50 | let mut rx2 = Observable::subscribe(&ob); 51 | 52 | Observable::set(&mut ob, Foo("A".to_owned())); 53 | assert_eq!(rx1.next_ref().await.as_ref().map(|f| f.0.as_str()), Some("A")); 54 | 55 | Observable::set(&mut ob, Foo("B".to_owned())); 56 | assert_eq!(rx1.next_ref().await.as_ref().map(|f| f.0.as_str()), Some("B")); 57 | assert_eq!(rx2.next_ref().await.as_ref().map(|f| f.0.as_str()), Some("B")); 58 | } 59 | --------------------------------------------------------------------------------