From 1e8e5d7f4bc754c5f445a915472a75548396bd57 Mon Sep 17 00:00:00 2001 From: Bruno Dutra Date: Mon, 7 Jan 2019 22:00:06 +0100 Subject: [PATCH 1/8] move all mocks to a common module --- src/lib.rs | 1 + src/{reducer => }/mock.rs | 21 +++++++++++++++++++++ src/reactor/array.rs | 1 + src/reactor/mock.rs | 28 ---------------------------- src/reactor/mod.rs | 5 +---- src/reactor/option.rs | 1 + src/reactor/reference.rs | 1 + src/reactor/slice.rs | 1 + src/reducer/arc.rs | 1 + src/reducer/mod.rs | 5 +---- src/reducer/rc.rs | 1 + src/reducer/tuple.rs | 1 + src/store.rs | 3 +-- 13 files changed, 32 insertions(+), 38 deletions(-) rename src/{reducer => }/mock.rs (58%) delete mode 100644 src/reactor/mock.rs diff --git a/src/lib.rs b/src/lib.rs index 173cac3..5a487a0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -83,6 +83,7 @@ //! ``` mod macros; +mod mock; mod reactor; mod reducer; diff --git a/src/reducer/mock.rs b/src/mock.rs similarity index 58% rename from src/reducer/mock.rs rename to src/mock.rs index 6143a10..b550911 100644 --- a/src/reducer/mock.rs +++ b/src/mock.rs @@ -1,5 +1,6 @@ #![cfg(test)] +use reactor::Reactor; use reducer::Reducer; #[derive(Debug, Default, Clone, Eq, PartialEq)] @@ -19,10 +20,30 @@ impl Reducer for MockReducer { } } +#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)] +pub struct MockReactor; + +impl Reactor for MockReactor { + type Output = S; + + fn react(&self, state: &S) -> Self::Output { + state.clone() + } +} + #[cfg(test)] mod tests { use super::*; + #[test] + fn react() { + let reactor = MockReactor; + + assert_eq!(reactor.react(&5), 5); + assert_eq!(reactor.react(&1), 1); + assert_eq!(reactor.react(&3), 3); + } + #[test] fn reduce() { let mut state = MockReducer::default(); diff --git a/src/reactor/array.rs b/src/reactor/array.rs index e28accb..b0294a5 100644 --- a/src/reactor/array.rs +++ b/src/reactor/array.rs @@ -33,6 +33,7 @@ impl_reactor_for_array!( #[cfg(test)] mod tests { use super::*; + use mock::*; macro_rules! test_reactor_for_array { () => {}; diff --git a/src/reactor/mock.rs b/src/reactor/mock.rs deleted file mode 100644 index 4b9b61e..0000000 --- a/src/reactor/mock.rs +++ /dev/null @@ -1,28 +0,0 @@ -#![cfg(test)] - -use reactor::Reactor; - -#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)] -pub struct MockReactor; - -impl Reactor for MockReactor { - type Output = S; - - fn react(&self, state: &S) -> Self::Output { - state.clone() - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn react() { - let reactor = MockReactor; - - assert_eq!(reactor.react(&5), 5); - assert_eq!(reactor.react(&1), 1); - assert_eq!(reactor.react(&3), 3); - } -} diff --git a/src/reactor/mod.rs b/src/reactor/mod.rs index 782b219..9f123eb 100644 --- a/src/reactor/mod.rs +++ b/src/reactor/mod.rs @@ -1,5 +1,4 @@ mod array; -mod mock; mod option; mod reference; mod sender; @@ -79,12 +78,10 @@ pub trait Reactor { fn react(&self, state: &S) -> Self::Output; } -#[cfg(test)] -pub use self::mock::*; - #[cfg(test)] mod tests { use super::*; + use mock::*; #[test] fn react() { diff --git a/src/reactor/option.rs b/src/reactor/option.rs index 0017688..ac876b4 100644 --- a/src/reactor/option.rs +++ b/src/reactor/option.rs @@ -18,6 +18,7 @@ where #[cfg(test)] mod tests { use super::*; + use mock::*; #[test] fn some() { diff --git a/src/reactor/reference.rs b/src/reactor/reference.rs index f355421..84b74ff 100644 --- a/src/reactor/reference.rs +++ b/src/reactor/reference.rs @@ -14,6 +14,7 @@ where #[cfg(test)] mod tests { use super::*; + use mock::*; #[test] fn react() { diff --git a/src/reactor/slice.rs b/src/reactor/slice.rs index f0f84bd..496f8e3 100644 --- a/src/reactor/slice.rs +++ b/src/reactor/slice.rs @@ -18,6 +18,7 @@ where #[cfg(test)] mod tests { use super::*; + use mock::*; #[test] fn react() { diff --git a/src/reducer/arc.rs b/src/reducer/arc.rs index e81cec6..9d54597 100644 --- a/src/reducer/arc.rs +++ b/src/reducer/arc.rs @@ -17,6 +17,7 @@ where #[cfg(test)] mod tests { use super::*; + use mock::*; #[test] fn reduce() { diff --git a/src/reducer/mod.rs b/src/reducer/mod.rs index 5d6ee32..9ab4898 100644 --- a/src/reducer/mod.rs +++ b/src/reducer/mod.rs @@ -1,5 +1,4 @@ mod arc; -mod mock; mod rc; mod tuple; @@ -101,12 +100,10 @@ pub trait Reducer: 'static { fn reduce(&mut self, action: A); } -#[cfg(test)] -pub use self::mock::*; - #[cfg(test)] mod tests { use super::*; + use mock::*; #[test] fn reduce() { diff --git a/src/reducer/rc.rs b/src/reducer/rc.rs index a6250c6..585e26f 100644 --- a/src/reducer/rc.rs +++ b/src/reducer/rc.rs @@ -14,6 +14,7 @@ where #[cfg(test)] mod tests { use super::*; + use mock::*; #[test] fn reduce() { diff --git a/src/reducer/tuple.rs b/src/reducer/tuple.rs index f8f53f2..3841d64 100644 --- a/src/reducer/tuple.rs +++ b/src/reducer/tuple.rs @@ -31,6 +31,7 @@ impl_reducer_for_tuples!(_12, _11, _10, _09, _08, _07, _06, _05, _04, _03, _02, #[cfg(test)] mod tests { use super::*; + use mock::*; macro_rules! test_reducer_for_tuples { () => {}; diff --git a/src/store.rs b/src/store.rs index 853125d..cc024a8 100644 --- a/src/store.rs +++ b/src/store.rs @@ -44,8 +44,7 @@ impl> Store { #[cfg(test)] mod tests { use super::*; - use reactor::MockReactor; - use reducer::MockReducer; + use mock::*; #[test] fn default() { From f447da8f6bc6e6e040a72d9a6b2681f8a0ecec6a Mon Sep 17 00:00:00 2001 From: Bruno Dutra Date: Mon, 7 Jan 2019 23:28:46 +0100 Subject: [PATCH 2/8] make MockReactor parametric on the type it returns --- src/mock.rs | 7 ++++--- src/reactor/array.rs | 2 +- src/reactor/mod.rs | 2 +- src/reactor/option.rs | 4 ++-- src/reactor/reference.rs | 2 +- src/reactor/slice.rs | 8 ++++---- src/store.rs | 12 ++++++------ 7 files changed, 19 insertions(+), 18 deletions(-) diff --git a/src/mock.rs b/src/mock.rs index b550911..644ce1d 100644 --- a/src/mock.rs +++ b/src/mock.rs @@ -2,6 +2,7 @@ use reactor::Reactor; use reducer::Reducer; +use std::marker::PhantomData; #[derive(Debug, Default, Clone, Eq, PartialEq)] pub struct MockReducer { @@ -21,9 +22,9 @@ impl Reducer for MockReducer { } #[derive(Debug, Default, Copy, Clone, Eq, PartialEq)] -pub struct MockReactor; +pub struct MockReactor(PhantomData); -impl Reactor for MockReactor { +impl Reactor for MockReactor { type Output = S; fn react(&self, state: &S) -> Self::Output { @@ -37,7 +38,7 @@ mod tests { #[test] fn react() { - let reactor = MockReactor; + let reactor = MockReactor::default(); assert_eq!(reactor.react(&5), 5); assert_eq!(reactor.react(&1), 1); diff --git a/src/reactor/array.rs b/src/reactor/array.rs index b0294a5..2590d07 100644 --- a/src/reactor/array.rs +++ b/src/reactor/array.rs @@ -41,7 +41,7 @@ mod tests { ( $head:ident $(, $tail:ident )* $(,)* ) => { #[test] fn $head() { - let reactors = [MockReactor; count!($( $tail, )*)]; + let reactors = [MockReactor::default(); count!($( $tail, )*)]; assert_eq!(reactors.react(&5), [5; count!($( $tail, )*)]); assert_eq!(reactors.react(&1), [1; count!($( $tail, )*)]); diff --git a/src/reactor/mod.rs b/src/reactor/mod.rs index 9f123eb..8492048 100644 --- a/src/reactor/mod.rs +++ b/src/reactor/mod.rs @@ -85,7 +85,7 @@ mod tests { #[test] fn react() { - let reactor: &Reactor<_, Output = _> = &MockReactor; + let reactor: &Reactor<_, Output = _> = &MockReactor::default(); assert_eq!(reactor.react(&5), 5); assert_eq!(reactor.react(&1), 1); diff --git a/src/reactor/option.rs b/src/reactor/option.rs index ac876b4..5092e41 100644 --- a/src/reactor/option.rs +++ b/src/reactor/option.rs @@ -22,7 +22,7 @@ mod tests { #[test] fn some() { - let reactor = Some(MockReactor); + let reactor = Some(MockReactor::default()); assert_eq!(reactor.react(&5), Some(5)); assert_eq!(reactor.react(&1), Some(1)); @@ -31,7 +31,7 @@ mod tests { #[test] fn none() { - let reactor: Option = None; + let reactor: Option> = None; assert_eq!(reactor.react(&5), None); assert_eq!(reactor.react(&1), None); diff --git a/src/reactor/reference.rs b/src/reactor/reference.rs index 84b74ff..3aab063 100644 --- a/src/reactor/reference.rs +++ b/src/reactor/reference.rs @@ -18,7 +18,7 @@ mod tests { #[test] fn react() { - let reactor = &MockReactor; + let reactor = &MockReactor::default(); let reactor = &reactor; assert_eq!(reactor.react(&5), 5); diff --git a/src/reactor/slice.rs b/src/reactor/slice.rs index 496f8e3..bc4ff92 100644 --- a/src/reactor/slice.rs +++ b/src/reactor/slice.rs @@ -22,10 +22,10 @@ mod tests { #[test] fn react() { - let reactor: &[MockReactor] = &[MockReactor, MockReactor, MockReactor]; + let reactor: &[MockReactor<_>] = &[MockReactor::default(); 42]; - assert_eq!(reactor.react(&5), vec![5, 5, 5].into_boxed_slice()); - assert_eq!(reactor.react(&1), vec![1, 1, 1].into_boxed_slice()); - assert_eq!(reactor.react(&3), vec![3, 3, 3].into_boxed_slice()); + assert_eq!(reactor.react(&5), vec![5; 42].into_boxed_slice()); + assert_eq!(reactor.react(&1), vec![1; 42].into_boxed_slice()); + assert_eq!(reactor.react(&3), vec![3; 42].into_boxed_slice()); } } diff --git a/src/store.rs b/src/store.rs index cc024a8..46d7c20 100644 --- a/src/store.rs +++ b/src/store.rs @@ -48,7 +48,7 @@ mod tests { #[test] fn default() { - let store = Store::, MockReactor>::default(); + let store = Store::, MockReactor<_>>::default(); assert_eq!(store.state, MockReducer::default()); assert_eq!(store.reactor, MockReactor::default()); @@ -57,7 +57,7 @@ mod tests { #[test] fn new() { let state = MockReducer::new(vec![42]); - let reactor = MockReactor; + let reactor = MockReactor::default(); let store = Store::new(state.clone(), &reactor); assert_eq!(store.state, state); @@ -66,13 +66,13 @@ mod tests { #[test] fn clone() { - let store = Store::new(MockReducer::<()>::default(), MockReactor); + let store = Store::new(MockReducer::<()>::default(), MockReactor::default()); assert_eq!(store, store.clone()); } #[test] fn dispatch() { - let mut store = Store::, MockReactor>::default(); + let mut store = Store::, MockReactor<_>>::default(); assert_eq!(store.dispatch(5), MockReducer::new(vec![5])); assert_eq!(store.dispatch(1), MockReducer::new(vec![5, 1])); @@ -81,11 +81,11 @@ mod tests { #[test] fn subscribe() { - let mut store: Store<_, Option> = Store::new(MockReducer::default(), None); + let mut store: Store<_, Option>> = Store::new(MockReducer::default(), None); assert_eq!(store.dispatch(0), None); - store.subscribe(Some(MockReactor)); + store.subscribe(Some(MockReactor::default())); assert_eq!(store.dispatch(5), Some(MockReducer::new(vec![0, 5]))); assert_eq!(store.dispatch(1), Some(MockReducer::new(vec![0, 5, 1]))); From 580bed4b7d3e952a27ed835363d74c0dc8885333 Mon Sep 17 00:00:00 2001 From: Bruno Dutra Date: Mon, 7 Jan 2019 22:31:52 +0100 Subject: [PATCH 3/8] add tests for !Sync and !Send types --- src/mock.rs | 56 +++++++++++++++++++++++++++++++++++++--- src/reactor/array.rs | 4 +-- src/reactor/option.rs | 8 +++--- src/reactor/reference.rs | 4 +-- src/reactor/slice.rs | 6 ++--- src/reactor/tuple.rs | 12 ++++++--- src/reducer/arc.rs | 4 +-- src/reducer/rc.rs | 4 +-- src/reducer/tuple.rs | 9 ++++--- src/store.rs | 22 +++++++++++----- 10 files changed, 97 insertions(+), 32 deletions(-) diff --git a/src/mock.rs b/src/mock.rs index 644ce1d..c56869b 100644 --- a/src/mock.rs +++ b/src/mock.rs @@ -2,7 +2,27 @@ use reactor::Reactor; use reducer::Reducer; +use std::cell::RefCell; use std::marker::PhantomData; +use std::rc::Rc; + +#[derive(Debug, Default, Clone, Eq, PartialEq)] +pub struct NotSync(RefCell); + +impl NotSync { + pub fn new(t: T) -> Self { + NotSync(RefCell::new(t)) + } +} + +#[derive(Debug, Default, Clone, Eq, PartialEq)] +pub struct NotSyncOrSend(Rc); + +impl NotSyncOrSend { + pub fn new(t: T) -> Self { + NotSyncOrSend(Rc::new(t)) + } +} #[derive(Debug, Default, Clone, Eq, PartialEq)] pub struct MockReducer { @@ -21,6 +41,18 @@ impl Reducer for MockReducer { } } +impl Reducer> for MockReducer { + fn reduce(&mut self, action: NotSync) { + self.actions.push((*action.0.borrow()).clone()); + } +} + +impl Reducer> for MockReducer { + fn reduce(&mut self, action: NotSyncOrSend) { + self.actions.push((*action.0).clone()); + } +} + #[derive(Debug, Default, Copy, Clone, Eq, PartialEq)] pub struct MockReactor(PhantomData); @@ -32,6 +64,22 @@ impl Reactor for MockReactor { } } +impl Reactor> for MockReactor { + type Output = S; + + fn react(&self, action: &NotSync) -> Self::Output { + (*action.0.borrow()).clone() + } +} + +impl Reactor> for MockReactor { + type Output = S; + + fn react(&self, action: &NotSyncOrSend) -> Self::Output { + (*action.0).clone() + } +} + #[cfg(test)] mod tests { use super::*; @@ -41,8 +89,8 @@ mod tests { let reactor = MockReactor::default(); assert_eq!(reactor.react(&5), 5); - assert_eq!(reactor.react(&1), 1); - assert_eq!(reactor.react(&3), 3); + assert_eq!(reactor.react(&NotSync::new(1)), 1); + assert_eq!(reactor.react(&NotSyncOrSend::new(3)), 3); } #[test] @@ -50,8 +98,8 @@ mod tests { let mut state = MockReducer::default(); state.reduce(5); - state.reduce(1); - state.reduce(3); + state.reduce(NotSync::new(1)); + state.reduce(NotSyncOrSend::new(3)); assert_eq!(state, MockReducer::new(vec![5, 1, 3])); } diff --git a/src/reactor/array.rs b/src/reactor/array.rs index 2590d07..9d7f3c2 100644 --- a/src/reactor/array.rs +++ b/src/reactor/array.rs @@ -44,8 +44,8 @@ mod tests { let reactors = [MockReactor::default(); count!($( $tail, )*)]; assert_eq!(reactors.react(&5), [5; count!($( $tail, )*)]); - assert_eq!(reactors.react(&1), [1; count!($( $tail, )*)]); - assert_eq!(reactors.react(&3), [3; count!($( $tail, )*)]); + assert_eq!(reactors.react(&NotSync::new(1)), [1; count!($( $tail, )*)]); + assert_eq!(reactors.react(&NotSyncOrSend::new(3)), [3; count!($( $tail, )*)]); } test_reactor_for_array!($( $tail, )*); diff --git a/src/reactor/option.rs b/src/reactor/option.rs index 5092e41..ae13994 100644 --- a/src/reactor/option.rs +++ b/src/reactor/option.rs @@ -25,8 +25,8 @@ mod tests { let reactor = Some(MockReactor::default()); assert_eq!(reactor.react(&5), Some(5)); - assert_eq!(reactor.react(&1), Some(1)); - assert_eq!(reactor.react(&3), Some(3)); + assert_eq!(reactor.react(&NotSync::new(1)), Some(1)); + assert_eq!(reactor.react(&NotSyncOrSend::new(3)), Some(3)); } #[test] @@ -34,7 +34,7 @@ mod tests { let reactor: Option> = None; assert_eq!(reactor.react(&5), None); - assert_eq!(reactor.react(&1), None); - assert_eq!(reactor.react(&3), None); + assert_eq!(reactor.react(&NotSync::new(1)), None); + assert_eq!(reactor.react(&NotSyncOrSend::new(3)), None); } } diff --git a/src/reactor/reference.rs b/src/reactor/reference.rs index 3aab063..b6f9d02 100644 --- a/src/reactor/reference.rs +++ b/src/reactor/reference.rs @@ -22,7 +22,7 @@ mod tests { let reactor = &reactor; assert_eq!(reactor.react(&5), 5); - assert_eq!(reactor.react(&1), 1); - assert_eq!(reactor.react(&3), 3); + assert_eq!(reactor.react(&NotSync::new(1)), 1); + assert_eq!(reactor.react(&NotSyncOrSend::new(3)), 3); } } diff --git a/src/reactor/slice.rs b/src/reactor/slice.rs index bc4ff92..593f3e2 100644 --- a/src/reactor/slice.rs +++ b/src/reactor/slice.rs @@ -24,8 +24,8 @@ mod tests { fn react() { let reactor: &[MockReactor<_>] = &[MockReactor::default(); 42]; - assert_eq!(reactor.react(&5), vec![5; 42].into_boxed_slice()); - assert_eq!(reactor.react(&1), vec![1; 42].into_boxed_slice()); - assert_eq!(reactor.react(&3), vec![3; 42].into_boxed_slice()); + assert_eq!(reactor.react(&5), vec![5; 42].into()); + assert_eq!(reactor.react(&NotSync::new(1)), vec![1; 42].into()); + assert_eq!(reactor.react(&NotSyncOrSend::new(3)), vec![3; 42].into()); } } diff --git a/src/reactor/tuple.rs b/src/reactor/tuple.rs index be030fc..c002c0c 100644 --- a/src/reactor/tuple.rs +++ b/src/reactor/tuple.rs @@ -31,6 +31,7 @@ impl_reactor_for_tuples!(_12, _11, _10, _09, _08, _07, _06, _05, _04, _03, _02, #[cfg(test)] mod tests { use super::*; + use mock::*; macro_rules! test_reactor_for_tuples { () => {}; @@ -47,11 +48,14 @@ mod tests { } } - impl Reactor for $head { + impl Reactor for $head + where + MockReactor: Reactor, + { type Output = Self; fn react(&self, state: &S) -> Self::Output { - $head::new(state.clone()) + $head::new(MockReactor::default().react(state)) } } @@ -60,8 +64,8 @@ mod tests { let reactor = ($head::default(), $( $tail::default(), )*); assert_eq!(reactor.react(&5), ($head::new(5), $( $tail::new(5), )*)); - assert_eq!(reactor.react(&1), ($head::new(1), $( $tail::new(1), )*)); - assert_eq!(reactor.react(&3), ($head::new(3), $( $tail::new(3), )*)); + assert_eq!(reactor.react(&NotSync::new(1)), ($head::new(1), $( $tail::new(1), )*)); + assert_eq!(reactor.react(&NotSyncOrSend::new(3)), ($head::new(3), $( $tail::new(3), )*)); } test_reactor_for_tuples!($( $tail, )*); diff --git a/src/reducer/arc.rs b/src/reducer/arc.rs index 9d54597..c8f93e5 100644 --- a/src/reducer/arc.rs +++ b/src/reducer/arc.rs @@ -24,8 +24,8 @@ mod tests { let mut state = Arc::new(MockReducer::default()); state.reduce(5); - state.reduce(1); - state.reduce(3); + state.reduce(NotSync::new(1)); + state.reduce(NotSyncOrSend::new(3)); assert_eq!(state, Arc::new(MockReducer::new(vec![5, 1, 3]))); } diff --git a/src/reducer/rc.rs b/src/reducer/rc.rs index 585e26f..89f1ad8 100644 --- a/src/reducer/rc.rs +++ b/src/reducer/rc.rs @@ -21,8 +21,8 @@ mod tests { let mut state = Rc::new(MockReducer::default()); state.reduce(5); - state.reduce(1); - state.reduce(3); + state.reduce(NotSync::new(1)); + state.reduce(NotSyncOrSend::new(3)); assert_eq!(state, Rc::new(MockReducer::new(vec![5, 1, 3]))); } diff --git a/src/reducer/tuple.rs b/src/reducer/tuple.rs index 3841d64..46d27f1 100644 --- a/src/reducer/tuple.rs +++ b/src/reducer/tuple.rs @@ -42,7 +42,10 @@ mod tests { inner: MockReducer, } - impl Reducer for $head { + impl Reducer for $head + where + MockReducer: Reducer, + { fn reduce(&mut self, action: A) { self.inner.reduce(action); } @@ -53,8 +56,8 @@ mod tests { let mut states = ($head::default(), $( $tail::default(), )*); states.reduce(5); - states.reduce(1); - states.reduce(3); + states.reduce(NotSync::new(1)); + states.reduce(NotSyncOrSend::new(3)); let ($head, $( $tail, )*) = states; diff --git a/src/store.rs b/src/store.rs index 46d7c20..24e901e 100644 --- a/src/store.rs +++ b/src/store.rs @@ -74,9 +74,14 @@ mod tests { fn dispatch() { let mut store = Store::, MockReactor<_>>::default(); - assert_eq!(store.dispatch(5), MockReducer::new(vec![5])); - assert_eq!(store.dispatch(1), MockReducer::new(vec![5, 1])); - assert_eq!(store.dispatch(3), MockReducer::new(vec![5, 1, 3])); + let a = NotSync::new(5); + assert_eq!(store.dispatch(a), MockReducer::new(vec![5])); + + let a = NotSync::new(1); + assert_eq!(store.dispatch(a), MockReducer::new(vec![5, 1])); + + let a = NotSyncOrSend::new(3); + assert_eq!(store.dispatch(a), MockReducer::new(vec![5, 1, 3])); } #[test] @@ -87,8 +92,13 @@ mod tests { store.subscribe(Some(MockReactor::default())); - assert_eq!(store.dispatch(5), Some(MockReducer::new(vec![0, 5]))); - assert_eq!(store.dispatch(1), Some(MockReducer::new(vec![0, 5, 1]))); - assert_eq!(store.dispatch(3), Some(MockReducer::new(vec![0, 5, 1, 3]))); + let a = NotSync::new(5); + assert_eq!(store.dispatch(a), Some(MockReducer::new(vec![0, 5]))); + + let a = NotSync::new(1); + assert_eq!(store.dispatch(a), Some(MockReducer::new(vec![0, 5, 1]))); + + let a = NotSyncOrSend::new(3); + assert_eq!(store.dispatch(a), Some(MockReducer::new(vec![0, 5, 1, 3]))); } } From f06b7cee3c574a381c0c6654bf64bbe0fde71f84 Mon Sep 17 00:00:00 2001 From: Bruno Dutra Date: Sun, 6 Jan 2019 16:26:32 +0100 Subject: [PATCH 4/8] add feature gate "parallel" to enable automatic fork-join parallelism using Rayon --- .travis.yml | 21 +++++++++++++++++++++ Cargo.toml | 4 ++++ src/lib.rs | 5 +++++ 3 files changed, 30 insertions(+) diff --git a/.travis.yml b/.travis.yml index 284525d..49a6182 100644 --- a/.travis.yml +++ b/.travis.yml @@ -54,3 +54,24 @@ jobs: - cargo +nightly tarpaulin -v --all-features --out Xml after_success: - bash <(curl -s https://codecov.io/bash) + + - name: experimental + stage: test + rust: nightly + os: linux + script: + - cargo +nightly test -v --all-features + + - name: experimental + stage: test + rust: nightly + os: osx + script: + - cargo +nightly test -v --all-features + + - name: experimental + stage: test + rust: nightly + os: windows + script: + - cargo +nightly test -v --all-features diff --git a/Cargo.toml b/Cargo.toml index 92d9b40..f9a5ff9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,11 +9,15 @@ readme = "README.md" keywords = ["redux", "flux", "reactive", "state"] categories = ["asynchronous", "gui"] +[features] +parallel = ["rayon"] + [badges] travis-ci = { repository = "brunocodutra/reducer" } codecov = { repository = "brunocodutra/reducer" } [dependencies] +rayon = { version = "1.0.3", optional = true } [dev-dependencies] iui = "0.3.0" diff --git a/src/lib.rs b/src/lib.rs index 5a487a0..f45b35d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -82,6 +82,11 @@ //! } //! ``` +#![cfg_attr(feature = "parallel", feature(specialization))] + +#[cfg(feature = "parallel")] +extern crate rayon; + mod macros; mod mock; From 9f1255aa9616202632edf9b149c27ac831e349a9 Mon Sep 17 00:00:00 2001 From: Bruno Dutra Date: Mon, 7 Jan 2019 22:55:14 +0100 Subject: [PATCH 5/8] add helper macro join! to recurse rayon::join --- src/macros.rs | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/macros.rs b/src/macros.rs index 27c92ca..b1b7577 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -16,3 +16,40 @@ macro_rules! dedupe_docs { $( $definition )+ }; } + +#[cfg(feature = "parallel")] +macro_rules! join { + ( ) => { + () + }; + + ( $a:ident $(,)* ) => { + ($a(),) + }; + + ( $a:ident, $b:ident $(,)* ) => { + rayon::join($a, $b) + }; + + ( $a:ident, $b:ident $(, $tail:ident )+ $(,)* ) => { + { + let ($a, ($b $(, $tail )+)) = rayon::join($a, || join!($b $(, $tail )+)); + ($a, $b $(, $tail )+) + } + }; +} + +#[cfg(test)] +mod test { + #[cfg(feature = "parallel")] + #[test] + fn join() { + let a = || 5; + let b = || 1; + let c = || 3; + + assert_eq!(join!(a), (5,)); + assert_eq!(join!(a, b), (5, 1)); + assert_eq!(join!(a, b, c), (5, 1, 3)); + } +} From 0c48aedf3b4a45d9f8a65f416659826c04d9313d Mon Sep 17 00:00:00 2001 From: Bruno Dutra Date: Mon, 7 Jan 2019 22:58:22 +0100 Subject: [PATCH 6/8] add helper macro to conditionally specialize traits --- src/macros.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/macros.rs b/src/macros.rs index b1b7577..16ca73a 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -17,6 +17,16 @@ macro_rules! dedupe_docs { }; } +macro_rules! specialize { + ( #[cfg($conditional:meta)] default $( $definition:tt )+ ) => { + #[cfg($conditional)] + default $( $definition )+ + + #[cfg(not($conditional))] + $( $definition )+ + } +} + #[cfg(feature = "parallel")] macro_rules! join { ( ) => { From a1631dd50c3ef56b302f72cb3b33de5f30bf10c5 Mon Sep 17 00:00:00 2001 From: Bruno Dutra Date: Sun, 6 Jan 2019 19:53:06 +0100 Subject: [PATCH 7/8] specialize Reducer for tuples on nightly Rust so that reducers are updated in parallel using Rayon --- src/reducer/tuple.rs | 38 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/src/reducer/tuple.rs b/src/reducer/tuple.rs index 46d27f1..6c003f1 100644 --- a/src/reducer/tuple.rs +++ b/src/reducer/tuple.rs @@ -13,11 +13,45 @@ macro_rules! impl_reducer_for_tuples { A: Clone, $head: Reducer, $( $tail: Reducer, )* + { + specialize!( + #[cfg(feature = "parallel")] + default fn reduce(&mut self, action: A) { + let ($head, $( $tail, )*) = self; + $head.reduce(action.clone()); + $( $tail.reduce(action.clone()); )* + } + ); + } + ); + + dedupe_docs!(($( $tail, )*), + /// Updates all reducers in the tuple in parallel. + /// + /// Currently implemented for tuples of up to 12 elements. + #[cfg(feature = "parallel")] + impl Reducer for ($head, $( $tail, )*) + where + A: Clone + Send, + $head: Reducer + Send, + $( $tail: Reducer + Send, )* { fn reduce(&mut self, action: A) { let ($head, $( $tail, )*) = self; - $head.reduce(action.clone()); - $( $tail.reduce(action.clone()); )* + + let $head = { + let action = action.clone(); + move || $head.reduce(action) + }; + + $( + let $tail = { + let action = action.clone(); + move || $tail.reduce(action) + }; + )* + + join!($head $(, $tail )*); } } ); From afd3293af3c5aabe4055e3ced17fcd7984bf4af2 Mon Sep 17 00:00:00 2001 From: Bruno Dutra Date: Wed, 9 Jan 2019 23:28:17 +0100 Subject: [PATCH 8/8] document the feature gate "parallel" --- README.md | 9 +++++++++ src/lib.rs | 11 +++++++++++ 2 files changed, 20 insertions(+) diff --git a/README.md b/README.md index a83643c..042a485 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,12 @@ use reducer::*; The full API documentation is available on [docs.rs] +# Experimental Features + +The following cargo feature flags are available (refer to the [documentation][docs.rs] for details): +* `parallel` (depends on nightly Rust) + + Relies on specialization ([RFC 1210]) to provide automatic fork-join parallelism using [Rayon]. ## Examples @@ -63,6 +69,9 @@ Reducer is distributed under the terms of the MIT license, see [LICENSE] for det [crates.io]: https://crates.io/crates/reducer [docs.rs]: https://docs.rs/reducer +[RFC 1210]: https://github.com/rust-lang/rust/issues/31844 +[Rayon]: https://crates.io/crates/rayon + [issues]: https://github.com/brunocodutra/reducer/issues [pulls]: https://github.com/brunocodutra/reducer/pulls [examples]: https://github.com/brunocodutra/reducer/tree/master/examples diff --git a/src/lib.rs b/src/lib.rs index f45b35d..3c5bff9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -81,6 +81,17 @@ //! store.dispatch(Div(7)).unwrap(); // displays "2" //! } //! ``` +//! # Experimental Features +//! +//! The following cargo feature flags are available: +//! * `parallel` (depends on nightly Rust) +//! +//! This feature flag takes advantage of the experimental support for specialization available +//! on nightly Rust ([RFC 1210](https://github.com/rust-lang/rust/issues/31844)), to +//! automatically parallelize tuples of +//! [Sync](https://doc.rust-lang.org/nightly/std/marker/trait.Sync.html) / +//! [Send](https://doc.rust-lang.org/nightly/std/marker/trait.Send.html) Reducers +//! using [Rayon](https://crates.io/crates/rayon). #![cfg_attr(feature = "parallel", feature(specialization))]