├── .gitignore ├── .travis.yml ├── Cargo.toml ├── LICENSE ├── README.md ├── rustfmt.toml ├── src ├── lib.rs └── stream.rs └── tests └── test_stream.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /Cargo.lock 2 | /target/ 3 | /.idea/ 4 | *.rs.bk 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: rust 3 | addons: 4 | apt: 5 | packages: 6 | - libcurl4-openssl-dev 7 | - libelf-dev 8 | - libdw-dev 9 | rust: 10 | - nightly 11 | - beta 12 | - stable 13 | before_script: 14 | - pip install 'travis-cargo<0.2' --user && export PATH=$HOME/.local/bin:$PATH 15 | script: 16 | - travis-cargo build && travis-cargo test && travis-cargo --only stable doc 17 | after_success: 18 | - travis-cargo --only stable doc-upload 19 | env: 20 | global: 21 | - TRAVIS_CARGO_NIGHTLY_FEATURE="" 22 | - secure: "Ux2/Orps9QIQtethGXiQ0PBYEiHY171zBBgi92/ga+Ao8Y3TQHL1cs4H/n9jOOCfgshUmO5Jj1XGecV3FCS6oTaFSRVSK46AQv+NF5dTCRYBiaxgNjEkHGIn/j7yub0qkpCwGhM3mwZwoqP8rRK7qELNR5EKBQymkUj/iD5+8Krve8sY9YFC/ZWeh6Z5ocX395Wsl7KSUersZkIH4VZ7OqrKjdpDZdbWCxdlarWeUfowK7GZeIXtLQQEcqYXVSEzZrw649zLPYzLocN1ypPTL9Kx/Pd9SsQ48smFmcylRQ4N/fnbGKAwzobt7+c8KCmF3+eK0uNFEFdqz/Rf9dKoeDxRpM/atf6sHh5A0bjAjnAS3/VKqQQ/DMd9413sxp91Y/ssJ3IBhe2lyPzzVC7r4pH5C0uBw4/XazJTQhKLCEIxJ2bBPwjQ+4R4ezE4W3pOBk/y+mk8TS5jHb8wiOdNqDVIWTAe49xy9o483CJ/co/RRLyN3WaMP4LQasFfvlucauEU3qjOnujMbKFjy0EReSuY2yfLGIh4K5poaYF4YFRkhBZo+xR64KT46NiZapC5l31jGCKyOva8MPG9OYN61DpMQAMyZw3xBh3f4hMIi3TqD9nfgeS+lZRqOu2rcpnABMyqnNdz1PvzvyGEEm6gk9miv69GYZdUoGJBaz7oXQI=" 23 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "reactive-rs" 3 | version = "0.1.1" 4 | authors = ["Ivan Smirnov "] 5 | description = "FRP in Rust: streams and broadcasts." 6 | keywords = ["reactive", "frp", "stream", "broadcast"] 7 | documentation = "http://docs.rs/reactive-rs/" 8 | homepage = "https://github.com/aldanor/reactive-rs" 9 | repository = "https://github.com/aldanor/reactive-rs" 10 | readme = "README.md" 11 | license = "MIT" 12 | 13 | [badges] 14 | travis-ci = { repository = "aldanor/reactive-rs" } 15 | 16 | [dependencies] 17 | slice-deque = { version = "0.1", optional = true } 18 | 19 | [dev-dependencies] 20 | slice-deque = "0.1" 21 | 22 | [features] 23 | default = ["slice-deque"] 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Ivan Smirnov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | reactive-rs 2 | =========== 3 | 4 | [![Build](https://api.travis-ci.org/aldanor/reactive-rs.svg)](https://travis-ci.org/aldanor/reactive-rs) 5 | [![Crate](http://meritbadge.herokuapp.com/reactive-rs)](https://crates.io/crates/reactive-rs) 6 | [![Docs](https://docs.rs/reactive-rs/badge.svg)](https://docs.rs/reactive-rs) 7 | 8 | This crate provides the building blocks for functional reactive programming (FRP) 9 | in Rust. It is inspired by 10 | [carboxyl](https://crates.io/crates/carboxyl), 11 | [frappe](https://crates.io/crates/frappe) 12 | and [bidule](https://crates.io/crates/bidule) crates, and 13 | various [ReactiveX](http://reactivex.io/) implementations. 14 | 15 | ### Documentation 16 | 17 | [docs.rs/reactive-rs](https://docs.rs/reactive-rs) 18 | 19 | ### Purpose 20 | 21 | The main use case of this library is to simplify creating efficient 22 | computational DAGs (or computational trees, to be precise) that operate 23 | on streams of values. It does not aim to replicate the entire galaxy of 24 | ReactiveX operators, nor does it attempt to delve into 25 | futures/concurrency territory. 26 | 27 | What is a computational tree? First, there's the root at the top, that's 28 | where the input values get fed into continuously. Then, we perform 29 | computations on these values – each of which may yield zero, 30 | one or more values that are sent further down. Some downstream 31 | nodes may share their parents – for instance, `g(f(x))` and `h(f(x))`, where `x` is 32 | the input and `f` is the intermediate transformation; in this case, we want 33 | to make sure we don't have to recompute `f(x)` twice. Moreover, this 34 | being Rust, we'd like to ensure we're not copying and cloning any values 35 | needlessly, and we generally prefer things to be zero-cost/inlineable 36 | when possible. Finally, there are leaves – these are observers, functions 37 | that receive transform values and do something with them, likely recording 38 | them somewhere or mutating the environment in some other way. 39 | 40 | ### Context 41 | 42 | Streams, broadcasts and observers in this crate operate on pairs of 43 | values: the *context* and the *element*. Context can be viewed as 44 | optional metadata attached to the original value. Closures required in 45 | methods like `.map()` only take one argument (the element) and are 46 | expected to return a single value; this way, the element can be changed 47 | without touching the context. This can be extremely convenient if you 48 | need to access the original input value (or any "upstream" value) way 49 | down the computation chain – this way you don't have to propagate 50 | it explicitly. 51 | 52 | Most stream/broadcast methods have an alternative "full" version that 53 | operates on both context/element, with `_ctx` suffix. 54 | 55 | ### Usage example 56 | 57 | Consider the following problem: we have an incoming stream of 58 | buy/sell price pairs, and for each incoming event we would like to 59 | compute how the current mid-price (the average between the two) 60 | compares relatively to the minimum buy price and the maximum sell 61 | price over the last three observations. Moreover, we would like to 62 | skip the first few events in order to allow the buffer to fill up. 63 | 64 | Here's one way we could do it (not the most ultimately efficient 65 | way of solving this particular problem, but it serves quite well 66 | to demonstrate the basic functionality of the crate): 67 | 68 | ```rust 69 | use std::cell::Cell; 70 | use std::f64; 71 | use reactive_rs::*; 72 | 73 | let min_rel = Cell::new(0.); 74 | let max_rel = Cell::new(0.); 75 | 76 | // create a broadcast of (buy, sell) pairs 77 | let quotes = SimpleBroadcast::new(); 78 | 79 | // clone the broadcast so we can feed values to it later 80 | let last = quotes.clone() 81 | // save the mid-price for later use 82 | .with_ctx_map(|_, &(buy, sell)| (buy + sell) / 2.) 83 | // cache the last three observations 84 | .last_n(3) 85 | // wait until the queue fills up 86 | .filter(|quotes| quotes.len() > 2) 87 | // share the output (slices of values) 88 | .broadcast(); 89 | 90 | // subscribe to the stream of slices 91 | let min = last.clone() 92 | // compute min buy price 93 | .map(|p| p.iter().map(|q| q.0).fold(1./0., f64::min)); 94 | // subscribe to the stream of slices 95 | let max = last.clone() 96 | // compute max sell price 97 | .map(|p| p.iter().map(|q| q.1).fold(-1./0., f64::max)); 98 | 99 | // finally, attach observers 100 | min.subscribe_ctx(|p, min| min_rel.set(min / p)); 101 | max.subscribe_ctx(|p, max| max_rel.set(max / p)); 102 | 103 | quotes.send((100., 102.)); 104 | quotes.send((101., 103.)); 105 | assert_eq!((min_rel.get(), max_rel.get()), (0., 0.)); 106 | quotes.send((99., 101.)); 107 | assert_eq!((min_rel.get(), max_rel.get()), (0.99, 1.03)); 108 | quotes.send((97., 103.)); 109 | assert_eq!((min_rel.get(), max_rel.get()), (0.97, 1.03)); 110 | ``` 111 | 112 | ### License 113 | 114 | The MIT License (MIT) 115 | 116 | Copyright (c) 2018 Ivan Smirnov 117 | 118 | Permission is hereby granted, free of charge, to any person obtaining a copy 119 | of this software and associated documentation files (the "Software"), to deal 120 | in the Software without restriction, including without limitation the rights 121 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 122 | copies of the Software, and to permit persons to whom the Software is 123 | furnished to do so, subject to the following conditions: 124 | 125 | The above copyright notice and this permission notice shall be included in all 126 | copies or substantial portions of the Software. 127 | 128 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 129 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 130 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 131 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 132 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 133 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 134 | SOFTWARE. 135 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | use_small_heuristics = "Max" 2 | use_field_init_shorthand = true 3 | use_try_shorthand = true 4 | empty_item_single_line = true 5 | edition = "2018" 6 | unstable_features = true 7 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This crate provides the building blocks for functional reactive programming (FRP) 2 | //! in Rust. It is inspired by 3 | //! [carboxyl](https://crates.io/crates/carboxyl), 4 | //! [frappe](https://crates.io/crates/frappe) 5 | //! and [bidule](https://crates.io/crates/bidule) crates, and 6 | //! various [ReactiveX](http://reactivex.io/) implementations. 7 | //! 8 | //! # Overview 9 | //! 10 | //! ## Purpose 11 | //! 12 | //! The main use case of this library is to simplify creating efficient 13 | //! computational DAGs (or computational trees, to be precise) that operate 14 | //! on streams of values. It does not aim to replicate the entire galaxy of 15 | //! ReactiveX operators, nor does it attempt to delve into 16 | //! futures/concurrency territory. 17 | //! 18 | //! What is a computational tree? First, there's the root at the top, that's 19 | //! where the input values get fed into continuously. Then, we perform 20 | //! computations on these values – each of which may yield zero, 21 | //! one or more values that are sent further down. Some downstream 22 | //! nodes may share their parents – for instance, `g(f(x))` and `h(f(x))`, where `x` is 23 | //! the input and `f` is the intermediate transformation; in this case, we want 24 | //! to make sure we don't have to recompute `f(x)` twice. Moreover, this 25 | //! being Rust, we'd like to ensure we're not copying and cloning any values 26 | //! needlessly, and we generally prefer things to be zero-cost/inlineable 27 | //! when possible. Finally, there are leaves – these are observers, functions 28 | //! that receive transform values and do something with them, likely recording 29 | //! them somewhere or mutating the environment in some other way. 30 | //! 31 | //! ## Terminology 32 | //! 33 | //! - *Observer* - a function that accepts a value and returns nothing (it will 34 | //! most often that note mutate the external environment in some way). 35 | //! - *Stream* - an object can be subscribed to by passing an observer to it. 36 | //! Subscribing to a stream consumes the stream, thus at most one observer 37 | //! can ever be attached to a given stream. 38 | //! - *Broadcast* - an observer that owns a collection of other observers and 39 | //! transmits its input to each one of them sequentially. A broadcast can 40 | //! produce new streams, *subscriptions*, each one receiving the same input 41 | //! as the broadcast itself. Subscription is a stream that adds its 42 | //! subscribers to the broadcast's collection when being subscribed to. 43 | //! 44 | //! ## Context 45 | //! 46 | //! Streams, broadcasts and observers in this crate operate on pairs of 47 | //! values: the *context* and the *element*. Context can be viewed as 48 | //! optional metadata attached to the original value. Closures required in 49 | //! methods like `.map()` only take one argument (the element) and are 50 | //! expected to return a single value; this way, the element can be changed 51 | //! without touching the context. This can be extremely convenient if you 52 | //! need to access the original input value (or any "upstream" value) way 53 | //! down the computation chain – this way you don't have to propagate 54 | //! it explicitly. 55 | //! 56 | //! Most stream/broadcast methods have an alternative "full" version that 57 | //! operates on both context/element, with `_ctx` suffix. 58 | //! 59 | //! ## Examples 60 | //! 61 | //! Consider the following problem: we have an incoming stream of 62 | //! buy/sell price pairs, and for each incoming event we would like to 63 | //! compute how the current mid-price (the average between the two) 64 | //! compares relatively to the minimum buy price and the maximum sell 65 | //! price over the last three observations. Moreover, we would like to 66 | //! skip the first few events in order to allow the buffer to fill up. 67 | //! 68 | //! Here's one way we could do it (not the most ultimately efficient 69 | //! way of solving this particular problem, but it serves quite well 70 | //! to demonstrate the basic functionality of the crate): 71 | //! 72 | //! ``` 73 | //! use std::cell::Cell; 74 | //! use std::f64; 75 | //! use reactive_rs::*; 76 | //! 77 | //! let min_rel = Cell::new(0.); 78 | //! let max_rel = Cell::new(0.); 79 | //! 80 | //! // create a broadcast of (buy, sell) pairs 81 | //! let quotes = SimpleBroadcast::new(); 82 | //! 83 | //! // clone the broadcast so we can feed values to it later 84 | //! let last = quotes.clone() 85 | //! // save the mid-price for later use 86 | //! .with_ctx_map(|_, &(buy, sell)| (buy + sell) / 2.) 87 | //! // cache the last three observations 88 | //! .last_n(3) 89 | //! // wait until the queue fills up 90 | //! .filter(|quotes| quotes.len() > 2) 91 | //! // share the output (slices of values) 92 | //! .broadcast(); 93 | //! 94 | //!// subscribe to the stream of slices 95 | //! let min = last.clone() 96 | //! // compute min buy price 97 | //! .map(|p| p.iter().map(|q| q.0).fold(1./0., f64::min)); 98 | //!// subscribe to the stream of slices 99 | //! let max = last.clone() 100 | //! // compute max sell price 101 | //! .map(|p| p.iter().map(|q| q.1).fold(-1./0., f64::max)); 102 | //! 103 | //! // finally, attach observers 104 | //! min.subscribe_ctx(|p, min| min_rel.set(min / p)); 105 | //! max.subscribe_ctx(|p, max| max_rel.set(max / p)); 106 | //! 107 | //! quotes.send((100., 102.)); 108 | //! quotes.send((101., 103.)); 109 | //! assert_eq!((min_rel.get(), max_rel.get()), (0., 0.)); 110 | //! quotes.send((99., 101.)); 111 | //! assert_eq!((min_rel.get(), max_rel.get()), (0.99, 1.03)); 112 | //! quotes.send((97., 103.)); 113 | //! assert_eq!((min_rel.get(), max_rel.get()), (0.97, 1.03)); 114 | //! ``` 115 | //! 116 | //! # Lifetimes 117 | //! 118 | //! Many `Stream` trait methods accept mutable closures; observers are 119 | //! also essentially just closures, and they are the only way you can 120 | //! get results from the stream out into the environment. Rest assured, 121 | //! at some point you'll run into lifetime problems (this being Rust, 122 | //! it's pretty much certain). 123 | //! 124 | //! Here's the main rule: lifetimes of observers (that is, lifetimes of 125 | //! what they capture, if anything) may not be shorter than the lifetime of 126 | //! the stream object. Same applies to lifetimes of closures in methods 127 | //! like `.map()`. 128 | //! 129 | //! In some situations it' tough to prove to the compiler you're doing 130 | //! something sane, in which case arena-based allocators (like 131 | //! [`typed-arena`](https://crates.io/crates/typed-arena)) may be 132 | //! of great help – allowing you to tie lifetimes of a bunch of 133 | //! objects together, ensuring simultaneous deallocation. 134 | 135 | #![crate_type = "lib"] 136 | #![warn(missing_docs)] 137 | 138 | #[cfg(any(test, feature = "slice-deque"))] 139 | extern crate slice_deque; 140 | 141 | pub use stream::{Broadcast, SimpleBroadcast, Stream}; 142 | 143 | mod stream; 144 | -------------------------------------------------------------------------------- /src/stream.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Borrow; 2 | use std::cell::RefCell; 3 | use std::iter::Iterator; 4 | use std::rc::Rc; 5 | 6 | #[cfg(any(test, feature = "slice-deque"))] 7 | use slice_deque::SliceDeque; 8 | 9 | /// A stream of context/value pairs that can be subscribed to. 10 | /// 11 | /// Note: in order to use stream trait methods, this trait 12 | /// must imported into the current scope. 13 | pub trait Stream<'a>: Sized { 14 | /// The type of the context attached to emitted elements. 15 | /// 16 | /// Can be set to `()` to ignore the context part of the stream. 17 | type Context: ?Sized; 18 | 19 | /// The type of the elements being emitted. 20 | type Item: ?Sized; 21 | 22 | /// Attaches an observer (a user-provided mutable closure) to the 23 | /// stream, which consumes the stream object. 24 | /// 25 | /// A stream with no observer attached is essentially just 26 | /// a function of an observer; it will not react to incoming events 27 | /// until it is subscribed to. 28 | /// 29 | /// # Examples 30 | /// 31 | /// ``` 32 | /// # use reactive_rs::*; use std::cell::RefCell; 33 | /// let out = RefCell::new(Vec::new()); 34 | /// let stream = SimpleBroadcast::::new(); 35 | /// stream 36 | /// .clone() 37 | /// .filter(|x| x % 2 != 0) 38 | /// .subscribe(|x| out.borrow_mut().push(*x)); 39 | /// stream.feed(0..=5); 40 | /// assert_eq!(&*out.borrow(), &[1, 3, 5]); 41 | /// ``` 42 | fn subscribe(self, mut observer: O) 43 | where 44 | O: 'a + FnMut(&Self::Item), 45 | { 46 | self.subscribe_ctx(move |_ctx, item| observer(item)) 47 | } 48 | 49 | /// Same as `subscribe()`, but the closure receives two arguments 50 | /// (context/value), by reference. 51 | /// 52 | /// # Examples 53 | /// 54 | /// ``` 55 | /// # use reactive_rs::*; use std::cell::Cell; 56 | /// let result = Cell::new((0, 0.)); 57 | /// let stream = Broadcast::::new(); 58 | /// stream 59 | /// .clone() 60 | /// .map_ctx(|c, x| (*c as f64) + *x) 61 | /// .subscribe_ctx(|c, x| result.set((*c, *x))); 62 | /// stream.send_ctx(3, 7.5); 63 | /// assert_eq!(result.get(), (3, 10.5)); 64 | /// ``` 65 | fn subscribe_ctx(self, observer: O) 66 | where 67 | O: 'a + FnMut(&Self::Context, &Self::Item); 68 | 69 | /// Create a broadcast from a stream, enabling multiple observers. This is the only 70 | /// `Stream` trait method that incurs a slight runtime cost, due to the broadcast 71 | /// object having to store observers as boxed trait objects in a reference-counted 72 | /// container; all other methods can be inlined. 73 | /// 74 | /// Note: this is equivalent to creating a broadcast via 75 | /// [`from_stream()`](struct.Broadcast.html#provided-methods) 76 | /// constructor. 77 | fn broadcast(self) -> Broadcast<'a, Self::Context, Self::Item> 78 | where 79 | Self: 'a, 80 | { 81 | Broadcast::from_stream(self) 82 | } 83 | 84 | /// Convenience method to extract the context into a separate stream. 85 | /// 86 | /// # Examples 87 | /// 88 | /// ``` 89 | /// # use reactive_rs::*; 90 | /// let stream = Broadcast::::new(); 91 | /// let double_ctx = stream.ctx().map(|x| x * 2); 92 | /// ``` 93 | /// 94 | /// # Notes 95 | /// 96 | /// - Resulting stream's context/value will reference the same object 97 | /// (original stream's context). 98 | /// - The return value is a `Stream` object (both context/value types 99 | /// are the original stream's context type). 100 | fn ctx(self) -> Context { 101 | Context { stream: self } 102 | } 103 | 104 | /// Set the context to a fixed constant value. 105 | /// 106 | /// # Examples 107 | /// 108 | /// ``` 109 | /// # use reactive_rs::*; 110 | /// let stream = SimpleBroadcast::::new(); 111 | /// let stream_with_ctx = stream.with_ctx(42); 112 | /// ``` 113 | /// 114 | /// # Notes 115 | /// 116 | /// - The value is passed down unchanged. 117 | /// - The return value is a `Stream` object (context type is the type 118 | /// of the provided value; value type is unchanged). 119 | fn with_ctx(self, ctx: T) -> WithContext { 120 | WithContext { stream: self, ctx } 121 | } 122 | 123 | /// Creates a new stream which calls a closure on each context/value and uses 124 | /// that as the context. 125 | /// 126 | /// # Examples 127 | /// 128 | /// ``` 129 | /// # use reactive_rs::*; 130 | /// let stream = SimpleBroadcast::::new(); 131 | /// let string_and_len = stream.with_ctx_map(|_, s| s.len()); 132 | /// ``` 133 | /// 134 | /// # Notes 135 | /// 136 | /// - The value is passed down unchanged. 137 | /// - The closure receives all of its arguments by reference. 138 | /// - The return value is a `Stream` object (context type is the return 139 | /// type of the closure; value type is unchanged). 140 | fn with_ctx_map(self, func: F) -> WithContextMap 141 | where 142 | F: 'a + FnMut(&Self::Context, &Self::Item) -> T, 143 | { 144 | WithContextMap { stream: self, func } 145 | } 146 | 147 | /// Creates a new stream which calls a closure on each element and uses 148 | /// that as the value. 149 | /// 150 | /// # Examples 151 | /// 152 | /// ``` 153 | /// # use reactive_rs::*; 154 | /// let stream = SimpleBroadcast::::new(); 155 | /// let contains_foo = stream.map(|s| s.contains("foo")); 156 | /// ``` 157 | /// 158 | /// # Notes 159 | /// 160 | /// - The context is passed down unchanged. 161 | /// - The closure receives its argument by reference. 162 | /// - The return value is a `Stream` object (context type is unchanged; 163 | /// value type is the return type of the closure). 164 | fn map(self, func: F) -> Map> 165 | where 166 | F: 'a + FnMut(&Self::Item) -> T, 167 | { 168 | Map { stream: self, func: NoContext(func) } 169 | } 170 | 171 | /// Same as `map()`, but the closure receives two arguments 172 | /// (context/value), by reference. 173 | /// 174 | /// # Examples 175 | /// 176 | /// ``` 177 | /// # use reactive_rs::*; 178 | /// let stream = Broadcast::::new(); 179 | /// let div2 = stream.map_ctx(|c, x| (*x % 2 == 0) == *c); 180 | /// ``` 181 | fn map_ctx(self, func: F) -> Map 182 | where 183 | F: 'a + FnMut(&Self::Context, &Self::Item) -> T, 184 | { 185 | Map { stream: self, func } 186 | } 187 | 188 | /// Same as `map()`, but the closure is expected to return a `(context, value)` 189 | /// tuple, so that both the context and the value can be changed at the same time. 190 | /// 191 | /// # Examples 192 | /// 193 | /// ``` 194 | /// # use reactive_rs::*; type A = i32; type B = u32; 195 | /// let stream = SimpleBroadcast::::new(); 196 | /// let string_context = stream.map_both(|x| (x.to_string(), *x)); 197 | /// ``` 198 | /// 199 | /// # Notes 200 | /// 201 | /// - The context and the value are changed simultaneously. 202 | /// - The closure receives its argument by reference. 203 | /// - The return value is a `Stream` object (context type and value type 204 | /// depend on the return type of the closure). 205 | fn map_both(self, func: F) -> MapBoth> 206 | where 207 | F: 'a + FnMut(&Self::Item) -> (C, T), 208 | { 209 | MapBoth { stream: self, func: NoContext(func) } 210 | } 211 | 212 | /// Same as `map_both()`, but the closure receives two arguments 213 | /// (context/value), by reference. 214 | /// 215 | /// # Examples 216 | /// 217 | /// ``` 218 | /// # use reactive_rs::*; type A = i32; type B = u32; 219 | /// let stream = Broadcast::::new(); 220 | /// let swapped = stream.map_both_ctx(|a, b| (*a, *b)); 221 | /// ``` 222 | fn map_both_ctx(self, func: F) -> MapBoth 223 | where 224 | F: 'a + FnMut(&Self::Context, &Self::Item) -> (C, T), 225 | { 226 | MapBoth { stream: self, func } 227 | } 228 | 229 | /// Creates a stream which uses a closure to determine if an element should be 230 | /// yielded. 231 | /// 232 | /// The closure must return `true` or `false` and is called on each element of the 233 | /// original stream. If `true` is returned, the element is passed downstream. 234 | /// 235 | /// # Examples 236 | /// 237 | /// ``` 238 | /// # use reactive_rs::*; 239 | /// let stream = SimpleBroadcast::>::new(); 240 | /// let non_empty = stream.filter(|v| !v.is_empty()); 241 | /// ``` 242 | /// 243 | /// # Notes 244 | /// 245 | /// - The context is passed down unchanged. 246 | /// - The closure receives its argument by reference. 247 | /// - The return value is a `Stream` object (same context type and 248 | /// value type as the original stream). 249 | fn filter(self, func: F) -> Filter> 250 | where 251 | F: 'a + FnMut(&Self::Item) -> bool, 252 | { 253 | Filter { stream: self, func: NoContext(func) } 254 | } 255 | 256 | /// Same as `filter()`, but the closure receives two arguments 257 | /// (context/value), by reference. 258 | /// 259 | /// # Examples 260 | /// 261 | /// ``` 262 | /// # use reactive_rs::*; 263 | /// let stream = Broadcast::>::new(); 264 | /// let filter_len = stream.filter_ctx(|ctx, v| v.len() == *ctx); 265 | /// ``` 266 | fn filter_ctx(self, func: F) -> Filter 267 | where 268 | F: 'a + FnMut(&Self::Context, &Self::Item) -> bool, 269 | { 270 | Filter { stream: self, func } 271 | } 272 | 273 | /// Creates a stream that both filters and maps. 274 | /// 275 | /// The closure must return an `Option`. If it returns `Some(element)`, then 276 | /// that element is returned; otherwise it is skipped. 277 | /// 278 | /// # Examples 279 | /// 280 | /// ``` 281 | /// # use reactive_rs::*; 282 | /// let stream = SimpleBroadcast::::new(); 283 | /// let valid_ints = stream.filter_map(|s| s.parse::().ok()); 284 | /// ``` 285 | /// 286 | /// # Notes 287 | /// 288 | /// - The context is passed down unchanged. 289 | /// - The closure receives its argument by reference. 290 | /// - The return value is a `Stream` object (context type is unchanged; 291 | /// value type is the is `T` if the return type of the closure is `Option`). 292 | fn filter_map(self, func: F) -> FilterMap> 293 | where 294 | F: 'a + FnMut(&Self::Item) -> Option, 295 | { 296 | FilterMap { stream: self, func: NoContext(func) } 297 | } 298 | 299 | /// Same as `filter_map()`, but the closure receives two arguments 300 | /// (context/value), by reference. 301 | /// 302 | /// # Examples 303 | /// 304 | /// ``` 305 | /// # use reactive_rs::*; 306 | /// let stream = Broadcast::, String>::new(); 307 | /// let int_or_ctx = stream.filter_map_ctx(|c, s| s.parse().ok().or(*c)); 308 | /// ``` 309 | fn filter_map_ctx(self, func: F) -> FilterMap 310 | where 311 | F: 'a + FnMut(&Self::Context, &Self::Item) -> Option, 312 | { 313 | FilterMap { stream: self, func } 314 | } 315 | 316 | /// 'Reduce' operation on streams. 317 | /// 318 | /// This method takes two arguments: an initial value, and a closure with 319 | /// two arguments: an accumulator and an element. The closure returns the 320 | /// value that the accumulator should have for the next iteration; the 321 | /// initial value is the value the accumulator will have on the first call. 322 | /// 323 | /// # Examples 324 | /// 325 | /// ``` 326 | /// # use reactive_rs::*; 327 | /// let stream = SimpleBroadcast::::new(); 328 | /// let cum_sum = stream.fold(0, |acc, x| acc + x); 329 | /// ``` 330 | /// 331 | /// # Notes 332 | /// 333 | /// - The context is passed down unchanged. 334 | /// - The closure receives all of its arguments by reference. 335 | /// - The return value is a `Stream` object (context type is unchanged; 336 | /// value type is the accumulator type). 337 | fn fold(self, init: T, func: F) -> Fold, T> 338 | where 339 | F: 'a + FnMut(&T, &Self::Item) -> T, 340 | { 341 | Fold { stream: self, init, func: NoContext(func) } 342 | } 343 | 344 | /// Same as `fold()`, but the closure receives three arguments 345 | /// (context/accumulator/value), by reference. 346 | /// 347 | /// # Examples 348 | /// 349 | /// ``` 350 | /// # use reactive_rs::*; 351 | /// let stream = Broadcast::::new(); 352 | /// let bnd_sum = stream.fold_ctx(0, |c, acc, x| *c.min(&(acc + x))); 353 | /// ``` 354 | fn fold_ctx(self, init: T, func: F) -> Fold 355 | where 356 | F: 'a + FnMut(&Self::Context, &T, &Self::Item) -> T, 357 | { 358 | Fold { stream: self, init, func } 359 | } 360 | 361 | /// Do something with each element of a stream, passing the value on. 362 | /// 363 | /// The closure will only be called if the stream is actually 364 | /// subscribed to (just calling `inspect()` does nothing on its own). 365 | /// 366 | /// # Examples 367 | /// 368 | /// ``` 369 | /// # use reactive_rs::*; 370 | /// let stream = SimpleBroadcast::::new(); 371 | /// let stream = stream.inspect(|x| println!("{:?}", x)); 372 | /// ``` 373 | /// 374 | /// # Notes 375 | /// 376 | /// - Both context/value are passed down unchanged. 377 | /// - The closure receives its argument by reference. 378 | /// - The return value is a `Stream` object (same context type and 379 | /// value type as the original stream). 380 | fn inspect(self, func: F) -> Inspect> 381 | where 382 | F: 'a + FnMut(&Self::Item), 383 | { 384 | Inspect { stream: self, func: NoContext(func) } 385 | } 386 | 387 | /// Same as `inspect()`, but the closure receives two arguments 388 | /// (context/value), by reference. 389 | /// 390 | /// # Examples 391 | /// 392 | /// ``` 393 | /// # use reactive_rs::*; 394 | /// let stream = Broadcast::::new(); 395 | /// let stream = stream.inspect_ctx(|c, x| println!("{} {}", c, x)); 396 | /// ``` 397 | fn inspect_ctx(self, func: F) -> Inspect 398 | where 399 | F: 'a + FnMut(&Self::Context, &Self::Item), 400 | { 401 | Inspect { stream: self, func } 402 | } 403 | 404 | /// Creates a stream that caches up to `n` last elements. 405 | /// 406 | /// The elements are stored in a contiguous double-ended 407 | /// queue provided via [`slice-deque`](https://crates.io/crates/slice-deque) 408 | /// crate. The output stream yields slice views into this queue. 409 | /// 410 | /// # Examples 411 | /// 412 | /// ``` 413 | /// # use reactive_rs::*; 414 | /// let stream = SimpleBroadcast::::new(); 415 | /// let last_3 = stream.last_n(3); 416 | /// ``` 417 | /// 418 | /// # Notes 419 | /// 420 | /// - The context is passed down unchanged (only values are cached). 421 | /// - The return value is a `Stream` object (context type is unchanged; 422 | /// value type is `[T]` where `T` is the original value type). 423 | /// - Slices may contain less than `n` elements (while the queue 424 | /// is being filled up initially). 425 | /// - The value type of the original stream must implement `Clone`. 426 | /// - This method is only present if `feature = "slice-deque"` is 427 | /// enabled (on by default). 428 | #[cfg(any(test, feature = "slice-deque"))] 429 | fn last_n(self, count: usize) -> LastN 430 | where 431 | Self::Item: 'a + Clone + Sized, 432 | { 433 | LastN { count, stream: self, data: Rc::new(RefCell::new(SliceDeque::with_capacity(count))) } 434 | } 435 | } 436 | 437 | pub trait ContextFn { 438 | type Output; 439 | 440 | fn call_mut(&mut self, ctx: &C, item: &T) -> Self::Output; 441 | } 442 | 443 | impl ContextFn for F 444 | where 445 | F: FnMut(&C, &T) -> V, 446 | { 447 | type Output = V; 448 | 449 | #[inline(always)] 450 | fn call_mut(&mut self, ctx: &C, item: &T) -> Self::Output { 451 | self(ctx, item) 452 | } 453 | } 454 | 455 | pub trait ContextFoldFn { 456 | type Output; 457 | 458 | fn call_mut(&mut self, ctx: &C, acc: &V, item: &T) -> Self::Output; 459 | } 460 | 461 | impl ContextFoldFn for F 462 | where 463 | F: FnMut(&C, &V, &T) -> V, 464 | { 465 | type Output = V; 466 | 467 | #[inline(always)] 468 | fn call_mut(&mut self, ctx: &C, acc: &V, item: &T) -> Self::Output { 469 | self(ctx, acc, item) 470 | } 471 | } 472 | 473 | pub struct NoContext(F); 474 | 475 | impl ContextFn for NoContext 476 | where 477 | F: FnMut(&T) -> V, 478 | { 479 | type Output = V; 480 | 481 | #[inline(always)] 482 | fn call_mut(&mut self, _ctx: &C, item: &T) -> Self::Output { 483 | (self.0)(item) 484 | } 485 | } 486 | 487 | impl ContextFoldFn for NoContext 488 | where 489 | F: FnMut(&V, &T) -> V, 490 | { 491 | type Output = V; 492 | 493 | #[inline(always)] 494 | fn call_mut(&mut self, _ctx: &C, acc: &Self::Output, item: &T) -> Self::Output { 495 | (self.0)(acc, item) 496 | } 497 | } 498 | 499 | type Callback<'a, C, T> = Box<'a + FnMut(&C, &T)>; 500 | 501 | /// Event source that transmits context/value pairs to multiple observers. 502 | /// 503 | /// In order to "fork" the broadcast (creating a new stream that will 504 | /// be subscribed to it), the broadcast object can be simply cloned 505 | /// via the `Clone` trait. Note that cloning the broadcast only 506 | /// increases its reference count; no values are being cloned or copied. 507 | /// 508 | /// A broadcast may receive a value in one of two ways. First, the user 509 | /// may explicitly call one of its methods: `send()`, `send_ctx()`, 510 | /// `feed()`, `feed_ctx()`. Second, the broadcast may be created 511 | /// from a parent stream via [`broadcast()`](trait.Stream.html#provided-methods) 512 | /// method of the stream object. Either way, each context/value pair received 513 | /// is passed on to each of the subscribed observers, by reference. 514 | /// 515 | /// # Examples 516 | /// 517 | /// ``` 518 | /// # use reactive_rs::*; use std::cell::RefCell; use std::rc::Rc; 519 | /// let out = RefCell::new(Vec::new()); 520 | /// let stream = SimpleBroadcast::::new(); 521 | /// let child1 = stream 522 | /// .clone() 523 | /// .subscribe(|x| out.borrow_mut().push(*x + 1)); 524 | /// let child2 = stream 525 | /// .clone() 526 | /// .subscribe(|x| out.borrow_mut().push(*x + 7)); 527 | /// stream.feed(1..=3); 528 | /// assert_eq!(&*out.borrow(), &[2, 8, 3, 9, 4, 10]); 529 | /// ``` 530 | pub struct Broadcast<'a, C: ?Sized, T: ?Sized> { 531 | observers: Rc>>>, 532 | } 533 | 534 | impl<'a, C: 'a + ?Sized, T: 'a + ?Sized> Broadcast<'a, C, T> { 535 | /// Creates a new broadcast with specified context and item types. 536 | pub fn new() -> Self { 537 | Self { observers: Rc::new(RefCell::new(Vec::new())) } 538 | } 539 | 540 | /// Create a broadcast from a stream, enabling multiple observers 541 | /// ("fork" the stream). 542 | /// 543 | /// Note: this is equivalent to calling 544 | /// [`broadcast()`](trait.Stream.html#provided-methods) on the stream object. 545 | pub fn from_stream(stream: S) -> Self 546 | where 547 | S: Stream<'a, Context = C, Item = T>, 548 | { 549 | let broadcast = Self::new(); 550 | let clone = broadcast.clone(); 551 | stream.subscribe_ctx(move |ctx, x| clone.send_ctx(ctx, x)); 552 | broadcast 553 | } 554 | 555 | fn push(&self, func: F) 556 | where 557 | F: FnMut(&C, &T) + 'a, 558 | { 559 | self.observers.borrow_mut().push(Box::new(func)); 560 | } 561 | 562 | /// Send a value along with context to all observers of the broadcast. 563 | pub fn send_ctx(&self, ctx: K, value: B) 564 | where 565 | K: Borrow, 566 | B: Borrow, 567 | { 568 | let ctx = ctx.borrow(); 569 | let value = value.borrow(); 570 | for observer in self.observers.borrow_mut().iter_mut() { 571 | observer(ctx, value); 572 | } 573 | } 574 | 575 | /// Similar to `send_ctx()`, but the context is set to the type's default value. 576 | pub fn send(&self, value: B) 577 | where 578 | B: Borrow, 579 | C: Default, 580 | { 581 | let ctx = C::default(); 582 | self.send_ctx(&ctx, value); 583 | } 584 | 585 | /// Convenience method to feed an iterator of values to all observers of the 586 | /// broadcast, along with a given context. 587 | pub fn feed_ctx(&self, ctx: K, iter: I) 588 | where 589 | K: Borrow, 590 | I: Iterator, 591 | B: Borrow, 592 | { 593 | let ctx = ctx.borrow(); 594 | for value in iter { 595 | self.send_ctx(ctx, value); 596 | } 597 | } 598 | 599 | /// Similar to `feed_ctx()`, but the context is set to the type's default value. 600 | pub fn feed(&self, iter: I) 601 | where 602 | I: Iterator, 603 | B: Borrow, 604 | C: Default, 605 | { 606 | let ctx = C::default(); 607 | self.feed_ctx(&ctx, iter); 608 | } 609 | } 610 | 611 | /// Simplified broadcast that only transmits values without context. 612 | pub type SimpleBroadcast<'a, T> = Broadcast<'a, (), T>; 613 | 614 | impl<'a, C: 'a + ?Sized, T: 'a + ?Sized> Default for Broadcast<'a, C, T> { 615 | fn default() -> Self { 616 | Self::new() 617 | } 618 | } 619 | 620 | impl<'a, C: 'a + ?Sized, T: 'a + ?Sized> Clone for Broadcast<'a, C, T> { 621 | fn clone(&self) -> Self { 622 | Self { observers: self.observers.clone() } 623 | } 624 | } 625 | 626 | impl<'a, C: 'a + ?Sized, T: 'a + ?Sized> Stream<'a> for Broadcast<'a, C, T> { 627 | type Context = C; 628 | type Item = T; 629 | 630 | fn subscribe_ctx(self, observer: O) 631 | where 632 | O: FnMut(&Self::Context, &Self::Item) + 'a, 633 | { 634 | self.push(observer); 635 | } 636 | } 637 | 638 | pub struct WithContext { 639 | stream: S, 640 | ctx: T, 641 | } 642 | 643 | impl<'a, S, T: 'a> Stream<'a> for WithContext 644 | where 645 | S: Stream<'a>, 646 | { 647 | type Context = T; 648 | type Item = S::Item; 649 | 650 | fn subscribe_ctx(self, mut observer: O) 651 | where 652 | O: FnMut(&Self::Context, &Self::Item) + 'a, 653 | { 654 | let ctx = self.ctx; 655 | self.stream.subscribe_ctx(move |_ctx, x| { 656 | observer(&ctx, x); 657 | }) 658 | } 659 | } 660 | 661 | pub struct WithContextMap { 662 | stream: S, 663 | func: F, 664 | } 665 | 666 | impl<'a, S, F, T> Stream<'a> for WithContextMap 667 | where 668 | S: Stream<'a>, 669 | F: 'a + FnMut(&S::Context, &S::Item) -> T, 670 | { 671 | type Context = T; 672 | type Item = S::Item; 673 | 674 | fn subscribe_ctx(self, mut observer: O) 675 | where 676 | O: FnMut(&Self::Context, &Self::Item) + 'a, 677 | { 678 | let mut func = self.func; 679 | self.stream.subscribe_ctx(move |ctx, x| { 680 | observer(&func(ctx, x), x); 681 | }) 682 | } 683 | } 684 | 685 | pub struct Context { 686 | stream: S, 687 | } 688 | 689 | impl<'a, S> Stream<'a> for Context 690 | where 691 | S: Stream<'a>, 692 | { 693 | type Context = S::Context; 694 | type Item = S::Context; 695 | 696 | fn subscribe_ctx(self, mut observer: O) 697 | where 698 | O: FnMut(&Self::Context, &Self::Item) + 'a, 699 | { 700 | self.stream.subscribe_ctx(move |ctx, _x| { 701 | observer(ctx, ctx); 702 | }) 703 | } 704 | } 705 | 706 | pub struct Map { 707 | stream: S, 708 | func: F, 709 | } 710 | 711 | impl<'a, S, F> Stream<'a> for Map 712 | where 713 | S: Stream<'a>, 714 | F: 'a + ContextFn, 715 | { 716 | type Context = S::Context; 717 | type Item = F::Output; 718 | 719 | fn subscribe_ctx(self, mut observer: O) 720 | where 721 | O: FnMut(&Self::Context, &Self::Item) + 'a, 722 | { 723 | let mut func = self.func; 724 | self.stream.subscribe_ctx(move |ctx, x| observer(ctx, &func.call_mut(ctx, x))) 725 | } 726 | } 727 | 728 | pub struct MapBoth { 729 | stream: S, 730 | func: F, 731 | } 732 | 733 | impl<'a, S, F, C, T> Stream<'a> for MapBoth 734 | where 735 | S: Stream<'a>, 736 | F: 'a + ContextFn, 737 | { 738 | type Context = C; 739 | type Item = T; 740 | 741 | fn subscribe_ctx(self, mut observer: O) 742 | where 743 | O: FnMut(&Self::Context, &Self::Item) + 'a, 744 | { 745 | let mut func = self.func; 746 | self.stream.subscribe_ctx(move |ctx, x| { 747 | let (ctx, x) = func.call_mut(ctx, x); 748 | observer(&ctx, &x); 749 | }) 750 | } 751 | } 752 | 753 | pub struct Filter { 754 | stream: S, 755 | func: F, 756 | } 757 | 758 | impl<'a, S, F> Stream<'a> for Filter 759 | where 760 | S: Stream<'a>, 761 | F: 'a + ContextFn, 762 | { 763 | type Context = S::Context; 764 | type Item = S::Item; 765 | 766 | fn subscribe_ctx(self, mut observer: O) 767 | where 768 | O: 'a + FnMut(&Self::Context, &Self::Item), 769 | { 770 | let mut func = self.func; 771 | self.stream.subscribe_ctx(move |ctx, x| { 772 | if func.call_mut(ctx, x) { 773 | observer(ctx, x); 774 | } 775 | }); 776 | } 777 | } 778 | 779 | pub struct FilterMap { 780 | stream: S, 781 | func: F, 782 | } 783 | 784 | impl<'a, S, F, T> Stream<'a> for FilterMap 785 | where 786 | S: Stream<'a>, 787 | F: 'a + ContextFn>, 788 | { 789 | type Context = S::Context; 790 | type Item = T; 791 | 792 | fn subscribe_ctx(self, mut observer: O) 793 | where 794 | O: 'a + FnMut(&Self::Context, &Self::Item), 795 | { 796 | let mut func = self.func; 797 | self.stream.subscribe_ctx(move |ctx, x| { 798 | if let Some(x) = func.call_mut(ctx, x) { 799 | observer(ctx, &x); 800 | } 801 | }); 802 | } 803 | } 804 | 805 | pub struct Fold { 806 | stream: S, 807 | init: T, 808 | func: F, 809 | } 810 | 811 | impl<'a, S, F, T: 'a> Stream<'a> for Fold 812 | where 813 | S: Stream<'a>, 814 | F: 'a + ContextFoldFn, 815 | { 816 | type Context = S::Context; 817 | type Item = T; 818 | 819 | fn subscribe_ctx(self, mut observer: O) 820 | where 821 | O: FnMut(&Self::Context, &Self::Item) + 'a, 822 | { 823 | let mut value = self.init; 824 | let mut func = self.func; 825 | self.stream.subscribe_ctx(move |ctx, x| { 826 | value = func.call_mut(ctx, &value, x); 827 | observer(ctx, &value); 828 | }) 829 | } 830 | } 831 | 832 | pub struct Inspect { 833 | stream: S, 834 | func: F, 835 | } 836 | 837 | impl<'a, S, F> Stream<'a> for Inspect 838 | where 839 | S: Stream<'a>, 840 | F: 'a + ContextFn, 841 | { 842 | type Context = S::Context; 843 | type Item = S::Item; 844 | 845 | fn subscribe_ctx(self, mut observer: O) 846 | where 847 | O: FnMut(&Self::Context, &Self::Item) + 'a, 848 | { 849 | let mut func = self.func; 850 | self.stream.subscribe_ctx(move |ctx, x| { 851 | func.call_mut(ctx, x); 852 | observer(ctx, x); 853 | }) 854 | } 855 | } 856 | 857 | #[cfg(any(test, feature = "slice-deque"))] 858 | pub struct LastN { 859 | count: usize, 860 | stream: S, 861 | data: Rc>>, 862 | } 863 | 864 | #[cfg(any(test, feature = "slice-deque"))] 865 | impl<'a, S, T> Stream<'a> for LastN 866 | where 867 | S: Stream<'a, Item = T>, 868 | T: 'a + Clone + Sized, 869 | { 870 | type Context = S::Context; 871 | type Item = [T]; 872 | 873 | fn subscribe_ctx(self, mut observer: O) 874 | where 875 | O: 'a + FnMut(&Self::Context, &Self::Item), 876 | { 877 | let data = self.data.clone(); 878 | let count = self.count; 879 | self.stream.subscribe_ctx(move |ctx, x| { 880 | let mut queue = data.borrow_mut(); 881 | if queue.len() == count { 882 | queue.pop_front(); 883 | } 884 | queue.push_back(x.clone()); 885 | drop(queue); // this is important, in order to avoid multiple mutable borrows 886 | observer(ctx, &*data.as_ref().borrow()); 887 | }) 888 | } 889 | } 890 | -------------------------------------------------------------------------------- /tests/test_stream.rs: -------------------------------------------------------------------------------- 1 | extern crate reactive_rs; 2 | 3 | use std::cell::{Cell, RefCell}; 4 | 5 | use reactive_rs::{Broadcast, SimpleBroadcast, Stream}; 6 | 7 | fn subscribe_cell<'a, S>(stream: S, cell: &'a Cell) 8 | where 9 | S: Stream<'a>, 10 | S::Item: Sized + Clone, 11 | { 12 | stream.subscribe(move |x| cell.set(x.clone())); 13 | } 14 | 15 | fn subscribe_cell_ctx<'a, S>(stream: S, cell: &'a Cell<(S::Context, S::Item)>) 16 | where 17 | S: Stream<'a>, 18 | S::Item: Sized + Clone, 19 | S::Context: Sized + Clone, 20 | { 21 | stream.subscribe_ctx(move |ctx, x| cell.set((ctx.clone(), x.clone()))); 22 | } 23 | 24 | #[test] 25 | fn test_subscribe_no_ctx() { 26 | let v = Cell::new(0); 27 | let v_ctx = Cell::new(((), 0)); 28 | let s = SimpleBroadcast::::new(); 29 | subscribe_cell(s.clone(), &v); 30 | subscribe_cell_ctx(s.clone(), &v_ctx); 31 | s.send(3); 32 | assert_eq!(v.get(), 3); 33 | assert_eq!(v_ctx.get(), ((), 3)); 34 | s.send_ctx((), 4); 35 | assert_eq!(v.get(), 4); 36 | assert_eq!(v_ctx.get(), ((), 4)); 37 | } 38 | 39 | #[test] 40 | fn test_subscribe_explicit_ctx() { 41 | let v = Cell::new(0); 42 | let v_ctx = Cell::new((0., 0)); 43 | let s = Broadcast::::new(); 44 | subscribe_cell(s.clone(), &v); 45 | subscribe_cell_ctx(s.clone(), &v_ctx); 46 | s.send(3); 47 | assert_eq!(v.get(), 3); 48 | assert_eq!(v_ctx.get(), (0., 3)); 49 | s.send_ctx(3.14, 4); 50 | assert_eq!(v.get(), 4); 51 | assert_eq!(v_ctx.get(), (3.14, 4)); 52 | } 53 | 54 | #[test] 55 | fn test_context_survives_operators() { 56 | let v = Cell::new(-1); 57 | let v_ctx = Cell::new((-1., -1)); 58 | let s = Broadcast::::new(); 59 | let t = s.clone().filter(|x| x % 3 != 0).map(|x| x * 2).broadcast(); 60 | subscribe_cell(t.clone(), &v); 61 | subscribe_cell_ctx(t.clone(), &v_ctx); 62 | s.send(0); 63 | assert_eq!(v.get(), -1); 64 | assert_eq!(v_ctx.get(), (-1., -1)); 65 | s.send_ctx(1., 2); 66 | assert_eq!(v.get(), 4); 67 | assert_eq!(v_ctx.get(), (1., 4)); 68 | s.send(1); 69 | assert_eq!(v.get(), 2); 70 | assert_eq!(v_ctx.get(), (0., 2)); 71 | } 72 | 73 | #[test] 74 | fn test_multiple_broadcast_parents() { 75 | let v = Cell::new(-1); 76 | let v_ctx = Cell::new((-1., -1)); 77 | let s = Broadcast::::new(); 78 | let t = s.clone().filter(|x| x % 3 != 0).map(|x| x * 2).broadcast(); 79 | subscribe_cell(t.clone(), &v); 80 | subscribe_cell_ctx(t.clone(), &v_ctx); 81 | s.send(0); 82 | assert_eq!(v.get(), -1); 83 | assert_eq!(v_ctx.get(), (-1., -1)); 84 | t.send_ctx(3.14, 2); 85 | assert_eq!(v.get(), 2); 86 | assert_eq!(v_ctx.get(), (3.14, 2)); 87 | } 88 | 89 | #[test] 90 | fn test_ctx_methods() { 91 | let v1_ctx = Cell::new((-1., -1)); 92 | let v2_ctx = Cell::new((-1., -1)); 93 | let v3_ctx = Cell::new((-1., -1.)); 94 | let s = Broadcast::::new(); 95 | let s1 = s.clone().with_ctx(1.23); 96 | let s2 = s.clone().with_ctx_map(|ctx, x| *ctx + (*x as f64)); 97 | let s3 = s.clone().ctx(); 98 | subscribe_cell_ctx(s1, &v1_ctx); 99 | subscribe_cell_ctx(s2, &v2_ctx); 100 | subscribe_cell_ctx(s3, &v3_ctx); 101 | s.send_ctx(0.1, 2); 102 | assert_eq!(v1_ctx.get(), (1.23, 2)); 103 | assert_eq!(v2_ctx.get(), (2.1, 2)); 104 | assert_eq!(v3_ctx.get(), (0.1, 0.1)); 105 | } 106 | 107 | #[test] 108 | fn test_map() { 109 | let v1_ctx = Cell::new((-1., -1)); 110 | let v2_ctx = Cell::new((-1., -1.)); 111 | let v3_ctx = Cell::new((-1., -1.)); 112 | let v4_ctx = Cell::new((-1., -1.)); 113 | let s = Broadcast::::new(); 114 | let s1 = s.clone().map(|x| x * 2); 115 | let s2 = s.clone().map_ctx(|ctx, x| *ctx + (*x as f64)); 116 | let s3 = s.clone().map(|x| x * 2).map_ctx(|ctx, x| *ctx + (*x as f64)); 117 | let s4 = s.clone().map_ctx(|ctx, x| *ctx + (*x as f64)).map(|x| x * 2.); 118 | subscribe_cell_ctx(s1, &v1_ctx); 119 | subscribe_cell_ctx(s2, &v2_ctx); 120 | subscribe_cell_ctx(s3, &v3_ctx); 121 | subscribe_cell_ctx(s4, &v4_ctx); 122 | s.send_ctx(0.1, 2); 123 | assert_eq!(v1_ctx.get(), (0.1, 4)); 124 | assert_eq!(v2_ctx.get(), (0.1, 2.1)); 125 | assert_eq!(v3_ctx.get(), (0.1, 4.1)); 126 | assert_eq!(v4_ctx.get(), (0.1, 4.2)); 127 | s.send_ctx(-0.2, 3); 128 | assert_eq!(v1_ctx.get(), (-0.2, 6)); 129 | assert_eq!(v2_ctx.get(), (-0.2, 2.8)); 130 | assert_eq!(v3_ctx.get(), (-0.2, 5.8)); 131 | assert_eq!(v4_ctx.get(), (-0.2, 5.6)); 132 | } 133 | 134 | #[test] 135 | fn test_map_both() { 136 | let v1_ctx = Cell::new((-1, -1)); 137 | let v2_ctx = Cell::new((-1, -1.)); 138 | let s = Broadcast::::new(); 139 | let s1 = s.clone().map_both(|x| (x * 2, x * 3)); 140 | let s2 = s.clone().map_both_ctx(|ctx, x| (*x, *ctx)); 141 | subscribe_cell_ctx(s1, &v1_ctx); 142 | subscribe_cell_ctx(s2, &v2_ctx); 143 | s.send_ctx(0.1, 2); 144 | assert_eq!(v1_ctx.get(), (4, 6)); 145 | assert_eq!(v2_ctx.get(), (2, 0.1)); 146 | } 147 | 148 | #[test] 149 | fn test_filter() { 150 | let v1_ctx = Cell::new((-1., -1)); 151 | let v2_ctx = Cell::new((-1., -1)); 152 | let s = Broadcast::::new(); 153 | let s1 = s.clone().filter(|x| x % 2 == 0); 154 | let s2 = s.clone().filter_ctx(|ctx, x| (*x as f64) > *ctx); 155 | subscribe_cell_ctx(s1, &v1_ctx); 156 | subscribe_cell_ctx(s2, &v2_ctx); 157 | s.send_ctx(4.1, 4); 158 | assert_eq!(v1_ctx.get(), (4.1, 4)); 159 | assert_eq!(v2_ctx.get(), (-1., -1)); 160 | s.send_ctx(2.9, 3); 161 | assert_eq!(v1_ctx.get(), (4.1, 4)); 162 | assert_eq!(v2_ctx.get(), (2.9, 3)); 163 | } 164 | 165 | #[test] 166 | fn test_filter_map() { 167 | let v1_ctx = Cell::new((-1., -1)); 168 | let v2_ctx = Cell::new((-1., -1.)); 169 | let s = Broadcast::::new(); 170 | let s1 = s.clone().filter_map(|x| if x % 2 == 0 { Some(2 * x) } else { None }); 171 | let s2 = 172 | s.clone().filter_map_ctx( 173 | |ctx, x| if (*x as f64) > *ctx { Some(ctx + (*x as f64)) } else { None }, 174 | ); 175 | subscribe_cell_ctx(s1, &v1_ctx); 176 | subscribe_cell_ctx(s2, &v2_ctx); 177 | s.send_ctx(4.1, 4); 178 | assert_eq!(v1_ctx.get(), (4.1, 8)); 179 | assert_eq!(v2_ctx.get(), (-1., -1.)); 180 | s.send_ctx(2.9, 3); 181 | assert_eq!(v1_ctx.get(), (4.1, 8)); 182 | assert_eq!(v2_ctx.get(), (2.9, 5.9)); 183 | } 184 | 185 | #[test] 186 | fn test_fold() { 187 | let v1_ctx = Cell::new((-1., -1)); 188 | let v2_ctx = Cell::new((-1., -1.)); 189 | let s = Broadcast::::new(); 190 | let s1 = s.clone().fold(0, |acc, x| acc + x); 191 | let s2 = s.clone().fold_ctx(0., |ctx, acc, x| (acc + ctx) + (*x as f64)); 192 | subscribe_cell_ctx(s1, &v1_ctx); 193 | subscribe_cell_ctx(s2, &v2_ctx); 194 | s.send_ctx(4.1, 4); 195 | assert_eq!(v1_ctx.get(), (4.1, 4)); 196 | assert_eq!(v2_ctx.get(), (4.1, 8.1)); 197 | s.send_ctx(2.9, 3); 198 | assert_eq!(v1_ctx.get(), (2.9, 7)); 199 | assert_eq!(v2_ctx.get(), (2.9, 14.)); 200 | } 201 | 202 | #[test] 203 | fn test_inspect() { 204 | let i1 = Cell::new(-1); 205 | let i2_ctx = Cell::new((-1., -1)); 206 | let i3 = Cell::new(-1); 207 | let i4_ctx = Cell::new((-1., -1)); 208 | let v1_ctx = Cell::new((-1., -1)); 209 | let v2_ctx = Cell::new((-1., -1)); 210 | let s = Broadcast::::new(); 211 | let s1 = s.clone().inspect(|x| i1.set(*x)); 212 | let s2 = s.clone().inspect_ctx(|ctx, x| i2_ctx.set((*ctx, *x))); 213 | let _s3 = s.clone().inspect(|x| i3.set(*x)); 214 | let _s4 = s.clone().inspect_ctx(|ctx, x| i4_ctx.set((*ctx, *x))); 215 | subscribe_cell_ctx(s1, &v1_ctx); 216 | subscribe_cell_ctx(s2, &v2_ctx); 217 | s.send_ctx(1.23, 4); 218 | assert_eq!(i1.get(), 4); 219 | assert_eq!(i2_ctx.get(), (1.23, 4)); 220 | assert_eq!(i3.get(), -1); 221 | assert_eq!(i4_ctx.get(), (-1., -1)); 222 | assert_eq!(v1_ctx.get(), (1.23, 4)); 223 | assert_eq!(v2_ctx.get(), (1.23, 4)); 224 | } 225 | 226 | #[test] 227 | fn test_last_n() { 228 | let v1_ctx = RefCell::<(f64, Vec)>::new((-1., vec![])); 229 | let s = Broadcast::::new(); 230 | let s1 = s.clone().last_n(2).map(|x| x.iter().cloned().collect()); 231 | s1.subscribe_ctx(|ctx, x: &Vec<_>| *v1_ctx.borrow_mut() = (*ctx, x.clone())); 232 | assert_eq!(*v1_ctx.borrow(), (-1., vec![])); 233 | s.send_ctx(1.23, 4); 234 | assert_eq!(*v1_ctx.borrow(), (1.23, vec![4])); 235 | s.send_ctx(2.34, 6); 236 | assert_eq!(*v1_ctx.borrow(), (2.34, vec![4, 6])); 237 | s.send_ctx(3.45, 8); 238 | assert_eq!(*v1_ctx.borrow(), (3.45, vec![6, 8])); 239 | s.send_ctx(4.56, 10); 240 | assert_eq!(*v1_ctx.borrow(), (4.56, vec![8, 10])); 241 | } 242 | 243 | #[test] 244 | fn test_simple_subscribe() { 245 | let v = Cell::new(1); 246 | let s = SimpleBroadcast::::new(); 247 | s.clone().subscribe(|x| v.set(*x + v.get())); 248 | assert_eq!(v.get(), 1); 249 | s.send(2); 250 | assert_eq!(v.get(), 3); 251 | s.send(&3); 252 | assert_eq!(v.get(), 6); 253 | s.feed(-5..-1); 254 | assert_eq!(v.get(), -8); 255 | } 256 | 257 | #[test] 258 | fn test_broadcast_map() { 259 | let x0 = Cell::new(0); 260 | let x1 = Cell::new(1); 261 | let x2 = Cell::new(2); 262 | let x3 = Cell::new(3); 263 | let x4 = Cell::new(4); 264 | let s = SimpleBroadcast::::new(); 265 | s.clone().subscribe(|x| x0.set(*x)); 266 | let t = s.clone().map(|x| x * 2).broadcast(); 267 | t.clone().map(|x| x + 10).subscribe(|x| x1.set(*x)); 268 | t.clone().map(|x| x + 20).subscribe(|x| x2.set(*x)); 269 | let u = t.map(|x| -x).broadcast(); 270 | u.clone().subscribe(|x| x3.set(*x)); 271 | u.map(|x| x - 1).subscribe(|x| x4.set(*x)); 272 | let x = || vec![x0.get(), x1.get(), x2.get(), x3.get(), x4.get()]; 273 | assert_eq!(x(), &[0, 1, 2, 3, 4]); 274 | s.send(1); 275 | assert_eq!(x(), &[1, 12, 22, -2, -3]); 276 | s.send(-5); 277 | assert_eq!(x(), &[-5, 0, 10, 10, 9]); 278 | } 279 | 280 | #[test] 281 | fn test_broadcast_filter() { 282 | let x0 = Cell::new(0); 283 | let x1 = Cell::new(0); 284 | let s = SimpleBroadcast::::new(); 285 | let t = s.clone().filter(|x| x % 5 != 0).broadcast(); 286 | t.clone() 287 | .filter_map(|x| if x % 2 == 0 { Some(x * 10) } else { None }) 288 | .subscribe(|x| x0.set(*x)); 289 | t.filter(|x| x % 3 != 0).subscribe(|x| x1.set(*x)); 290 | let x = || vec![x0.get(), x1.get()]; 291 | assert_eq!(x(), &[0, 0]); 292 | s.send(0); 293 | assert_eq!(x(), &[0, 0]); 294 | s.send(1); 295 | assert_eq!(x(), &[0, 1]); 296 | s.send(2); 297 | assert_eq!(x(), &[20, 2]); 298 | s.send(2); 299 | assert_eq!(x(), &[20, 2]); 300 | s.send(3); 301 | assert_eq!(x(), &[20, 2]); 302 | s.send(6); 303 | assert_eq!(x(), &[60, 2]); 304 | s.send(7); 305 | assert_eq!(x(), &[60, 7]); 306 | } 307 | --------------------------------------------------------------------------------