bevy_pipe_affect 0.3.0

Write systems as pure functions.
Documentation
//! [`Effect`] implementations for generic algebraic data types.
//!
//! The implementations here are on these foreign types...
//! - tuples of `Effect`s
//! - `Either<Left, Right>` where the `Left` and `Right` are both effects
//! - the unit type `()` (trivial no-op effect)
//!
//! It's also worth noting that `Option<T>` where `T: Effect` also implements [`Effect`], but that is
//! implemented in the [`iter`] module.
//!
//! Also relevant, [`Effect`] [can be derived] for any custom `struct`/`enum` where the internal
//! types implement [`Effect`].
//!
//! [`iter`]: crate::effects::iter
//! [can be derived]: crate::effect::Effect#derive

use bevy::ecs::system::SystemParam;
use bevy::prelude::*;
use either::Either;
use variadics_please::all_tuples;

use crate::Effect;

macro_rules! impl_effect {
    ($(($E:ident, $e:ident, $p:ident)),*) => {
        impl<$($E),*> Effect for ($($E,)*)
        where $($E: Effect,)* {
            type MutParam = ParamSet<'static, 'static, ($(<$E as Effect>::MutParam,)*)>;

            fn affect(self, param: &mut <Self::MutParam as SystemParam>::Item<'_, '_>) {
                let ($($e,)*) = self;
                $($e.affect(&mut param.$p());)*
            }
        }
    };
}

all_tuples!(impl_effect, 1, 8, E, e, p);

impl Effect for () {
    type MutParam = ();

    fn affect(self, _: &mut <Self::MutParam as SystemParam>::Item<'_, '_>) {}
}

impl<E0, E1> Effect for Either<E0, E1>
where
    E0: Effect,
    E1: Effect,
{
    type MutParam = ParamSet<'static, 'static, (E0::MutParam, E1::MutParam)>;

    fn affect(self, param: &mut <Self::MutParam as SystemParam>::Item<'_, '_>) {
        match self {
            Either::Left(e0) => e0.affect(&mut param.p0()),
            Either::Right(e1) => e1.affect(&mut param.p1()),
        }
    }
}

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

    use super::*;
    use crate::effects::number_data::NumberResource;
    use crate::effects::resource::res_set;
    use crate::prelude::affect;

    proptest! {
        #[test]
        fn either_can_simulate_collatz(mut current_value in 1..(1u128 << 32), num_rounds in 1..(1u128 << 8)) {
            let mut app = App::new();

            app.insert_resource(NumberResource(current_value)).add_systems(
                Update,
                (|num: Res<NumberResource>| if num.0 % 2 == 0 {
                    Either::Left(res_set(NumberResource(num.0 / 2)))
                } else {
                    Either::Right(res_set(NumberResource(3 * num.0 + 1)))
                })
                .pipe(affect),
            );

            for _ in 0..num_rounds {
                app.update();

                current_value = if current_value % 2 == 0 { current_value / 2 } else { 3 * current_value + 1 };

                assert_eq!(app.world().resource::<NumberResource>().0, current_value);
            }
        }
    }
}