diff --git a/Cargo.lock b/Cargo.lock index 5774562..1e844a1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -882,6 +882,7 @@ name = "bevy_pipe_affect" version = "0.1.0" dependencies = [ "bevy", + "blake2", "proptest", "proptest-derive", ] @@ -1307,6 +1308,15 @@ dependencies = [ "serde", ] +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest", +] + [[package]] name = "blake3" version = "1.5.5" @@ -1326,6 +1336,15 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + [[package]] name = "block2" version = "0.5.1" @@ -1718,6 +1737,16 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + [[package]] name = "ctrlc" version = "3.4.5" @@ -1767,6 +1796,17 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + [[package]] name = "dispatch" version = "0.2.0" @@ -2051,6 +2091,16 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "gethostname" version = "0.4.3" @@ -3731,6 +3781,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "svg_fmt" version = "0.4.4" @@ -4003,6 +4059,12 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e13db2e0ccd5e14a544e8a246ba2312cd25223f616442d7f2cb0e3db614236e" +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + [[package]] name = "unarray" version = "0.1.4" diff --git a/Cargo.toml b/Cargo.toml index 6de4a45..0196e60 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,3 +9,4 @@ bevy = "0.15.2" [dev-dependencies] proptest = "1.6.0" proptest-derive = "0.5.1" +blake2 = "0.10.2" diff --git a/mvp-effects.md b/mvp-effects.md index 55ebfbb..e148029 100644 --- a/mvp-effects.md +++ b/mvp-effects.md @@ -1,6 +1,6 @@ # Resource Effects - [x] `ResPut` -- [ ] `ResWith` +- [x] `ResWith` # Local? - [ ] `LocalPut`? diff --git a/src/effects/mod.rs b/src/effects/mod.rs index e4ac22f..5e7fd3e 100644 --- a/src/effects/mod.rs +++ b/src/effects/mod.rs @@ -3,4 +3,4 @@ //! [`Effect`]: crate::Effect mod resource; -pub use resource::ResPut; +pub use resource::{ResPut, ResWith}; diff --git a/src/effects/resource.rs b/src/effects/resource.rs index 0bea4e8..a474659 100644 --- a/src/effects/resource.rs +++ b/src/effects/resource.rs @@ -1,3 +1,5 @@ +use std::marker::PhantomData; + use bevy::ecs::system::SystemParam; use bevy::prelude::*; @@ -19,8 +21,47 @@ where } } +/// [`Effect`] that transforms a `Resource` with the provided function. +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] +pub struct ResWith +where + F: FnOnce(R) -> R, + R: Resource + Clone, +{ + f: F, + phantom: PhantomData, +} + +impl ResWith +where + F: FnOnce(R) -> R, + R: Resource + Clone, +{ + /// Construct a new [`ResWith`]. + pub fn new(f: F) -> Self { + ResWith { + f, + phantom: PhantomData, + } + } +} + +impl Effect for ResWith +where + F: FnOnce(R) -> R, + R: Resource + Clone, +{ + type MutParam = ResMut<'static, R>; + + fn affect(self, param: &mut ::Item<'_, '_>) { + **param = (self.f)(param.clone()); + } +} + #[cfg(test)] mod tests { + use blake2::digest::consts::U4; + use blake2::{Blake2b, Digest}; use proptest::prelude::*; use proptest_derive::Arbitrary; @@ -30,6 +71,23 @@ mod tests { #[derive(Copy, Clone, Debug, PartialEq, Eq, Resource, Arbitrary)] struct NumberResource(i32); + fn number_resource_one_way_fn_fn( + salt: Vec, + ) -> impl FnOnce(NumberResource) -> NumberResource + Clone { + move |NumberResource(input)| { + let data = salt + .into_iter() + .chain(input.to_be_bytes()) + .collect::>(); + + let mut hasher = Blake2b::::new(); + hasher.update(data); + let res = hasher.finalize(); + + NumberResource(i32::from_be_bytes(res.into())) + } + } + proptest! { #[test] fn res_put_overwrites_initial_state(initial: NumberResource, put: NumberResource) { @@ -37,14 +95,32 @@ mod tests { prop_assume!(initial != put); - app.insert_resource(initial).add_systems(Update, (move || ResPut(put)).pipe(affect)); + app.insert_resource(initial) + .add_systems(Update, (move || ResPut(put)).pipe(affect)); prop_assert_eq!(app.world().resource::(), &initial); app.update(); prop_assert_eq!(app.world().resource::(), &put); + } + + #[test] + fn res_with_correctly_executes_one_way_function(initial: NumberResource, salt: Vec) { + let f = number_resource_one_way_fn_fn(salt); + + let expected = f.clone()(initial); + + let mut app = App::new(); + + app.insert_resource(initial.clone()) + .add_systems(Update, (move || ResWith::new(f.clone())).pipe(affect)); + + prop_assert_eq!(app.world().resource::(), &initial); + + app.update(); + prop_assert_eq!(app.world().resource::(), &expected); } } } diff --git a/src/prelude.rs b/src/prelude.rs index 156eff8..07a4f61 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -1,5 +1,5 @@ //! `use bevy_pipe_affect::prelude::*;` to import common items. -pub use crate::effects::ResPut; +pub use crate::effects::{ResPut, ResWith}; pub use crate::system_combinators::{affect, and_compose}; pub use crate::{Effect, EffectOut};