Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add experimental support for automatic fork-join parallelism #6

Merged
merged 8 commits into from
Jan 12, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down
17 changes: 17 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,25 @@
//! 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))]

#[cfg(feature = "parallel")]
extern crate rayon;

mod macros;
mod mock;

mod reactor;
mod reducer;
Expand Down
47 changes: 47 additions & 0 deletions src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,50 @@ macro_rules! dedupe_docs {
$( $definition )+
};
}

macro_rules! specialize {
( #[cfg($conditional:meta)] default $( $definition:tt )+ ) => {
#[cfg($conditional)]
default $( $definition )+

#[cfg(not($conditional))]
$( $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));
}
}
106 changes: 106 additions & 0 deletions src/mock.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
#![cfg(test)]

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<T: Clone>(RefCell<T>);

impl<T: Clone> NotSync<T> {
pub fn new(t: T) -> Self {
NotSync(RefCell::new(t))
}
}

#[derive(Debug, Default, Clone, Eq, PartialEq)]
pub struct NotSyncOrSend<T: Clone>(Rc<T>);

impl<T: Clone> NotSyncOrSend<T> {
pub fn new(t: T) -> Self {
NotSyncOrSend(Rc::new(t))
}
}

#[derive(Debug, Default, Clone, Eq, PartialEq)]
pub struct MockReducer<A: 'static> {
actions: Vec<A>,
}

impl<A> MockReducer<A> {
pub fn new(actions: Vec<A>) -> Self {
Self { actions }
}
}

impl<A> Reducer<A> for MockReducer<A> {
fn reduce(&mut self, action: A) {
self.actions.push(action);
}
}

impl<A: Clone> Reducer<NotSync<A>> for MockReducer<A> {
fn reduce(&mut self, action: NotSync<A>) {
self.actions.push((*action.0.borrow()).clone());
}
}

impl<A: Clone> Reducer<NotSyncOrSend<A>> for MockReducer<A> {
fn reduce(&mut self, action: NotSyncOrSend<A>) {
self.actions.push((*action.0).clone());
}
}

#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
pub struct MockReactor<S>(PhantomData<S>);

impl<S: Clone> Reactor<S> for MockReactor<S> {
type Output = S;

fn react(&self, state: &S) -> Self::Output {
state.clone()
}
}

impl<S: Clone> Reactor<NotSync<S>> for MockReactor<S> {
type Output = S;

fn react(&self, action: &NotSync<S>) -> Self::Output {
(*action.0.borrow()).clone()
}
}

impl<S: Clone> Reactor<NotSyncOrSend<S>> for MockReactor<S> {
type Output = S;

fn react(&self, action: &NotSyncOrSend<S>) -> Self::Output {
(*action.0).clone()
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn react() {
let reactor = MockReactor::default();

assert_eq!(reactor.react(&5), 5);
assert_eq!(reactor.react(&NotSync::new(1)), 1);
assert_eq!(reactor.react(&NotSyncOrSend::new(3)), 3);
}

#[test]
fn reduce() {
let mut state = MockReducer::default();

state.reduce(5);
state.reduce(NotSync::new(1));
state.reduce(NotSyncOrSend::new(3));

assert_eq!(state, MockReducer::new(vec![5, 1, 3]));
}
}
7 changes: 4 additions & 3 deletions src/reactor/array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,19 @@ impl_reactor_for_array!(
#[cfg(test)]
mod tests {
use super::*;
use mock::*;

macro_rules! test_reactor_for_array {
() => {};

( $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, )*)]);
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, )*);
Expand Down
28 changes: 0 additions & 28 deletions src/reactor/mock.rs

This file was deleted.

7 changes: 2 additions & 5 deletions src/reactor/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
mod array;
mod mock;
mod option;
mod reference;
mod sender;
Expand Down Expand Up @@ -79,16 +78,14 @@ pub trait Reactor<S> {
fn react(&self, state: &S) -> Self::Output;
}

#[cfg(test)]
pub use self::mock::*;

#[cfg(test)]
mod tests {
use super::*;
use mock::*;

#[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);
Expand Down
13 changes: 7 additions & 6 deletions src/reactor/option.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,23 @@ where
#[cfg(test)]
mod tests {
use super::*;
use mock::*;

#[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));
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]
fn none() {
let reactor: Option<MockReactor> = None;
let reactor: Option<MockReactor<_>> = 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);
}
}
7 changes: 4 additions & 3 deletions src/reactor/reference.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,15 @@ where
#[cfg(test)]
mod tests {
use super::*;
use mock::*;

#[test]
fn react() {
let reactor = &MockReactor;
let reactor = &MockReactor::default();
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);
}
}
9 changes: 5 additions & 4 deletions src/reactor/slice.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,14 @@ where
#[cfg(test)]
mod tests {
use super::*;
use mock::*;

#[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());
assert_eq!(reactor.react(&NotSync::new(1)), vec![1; 42].into());
assert_eq!(reactor.react(&NotSyncOrSend::new(3)), vec![3; 42].into());
}
}
Loading