From 0f8e8643df4a521e142c64f8eab1dad0b36d06d7 Mon Sep 17 00:00:00 2001 From: Gregory Conrad Date: Mon, 15 Jan 2024 21:31:59 -0500 Subject: [PATCH] feat!: add side effect state transformers (#35) Fixes #30 --- Cargo.toml | 2 +- README.md | 5 +- examples/simple-demo/src/main.rs | 4 +- rearch-effects/Cargo.toml | 8 +- rearch-effects/src/cloneable.rs | 33 -- rearch-effects/src/effect_lifetime_fixers.rs | 71 ++++ rearch-effects/src/lib.rs | 320 ++++++++++++------- rearch-effects/src/state_transformers.rs | 119 +++++++ rearch-tokio/src/lib.rs | 87 +++-- 9 files changed, 459 insertions(+), 190 deletions(-) delete mode 100644 rearch-effects/src/cloneable.rs create mode 100644 rearch-effects/src/effect_lifetime_fixers.rs create mode 100644 rearch-effects/src/state_transformers.rs diff --git a/Cargo.toml b/Cargo.toml index 05e1e21..53cfe87 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ readme = "README.md" [workspace.dependencies] rearch-macros = { path = "rearch-macros", version = "0.6.0" } rearch = { path = "rearch", version = "0.9.1" } -rearch-effects = { path = "rearch-effects", version = "0.3.0" } +rearch-effects = { path = "rearch-effects", version = "0.3.0", default-features = false } rearch-tokio = { path = "rearch-tokio", version = "0.8.0" } [workspace.lints.rust] diff --git a/README.md b/README.md index a0423ae..f55abac 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ Define your "capsules" (en-_capsulated_ pieces of state) at the top level: // This capsule provides the count and a way to increment that count. fn count_manager(CapsuleHandle { register, .. }: CapsuleHandle) -> (u8, impl CData + Fn()) { - let (count, set_count) = register(effects::cloneable::state(0)); + let (count, set_count) = register(effects::state::>(0)); let increment_count = move || set_count(count + 1); (count, increment_count) } @@ -87,7 +87,8 @@ Also, there is some WIP [documentation] that will help you learn the core concep ## Minimum Supported Rust Version (MSRV) -The MSRV is currently 1.74.0 and may change in any new ReArch version/release. +The MSRV of `rearch` is currently 1.74.0 and may change in any new ReArch version/release. +The MSRV of other crates in this repo will be the latest stable release for the foreseeable future. It is also worth mentioning that the example shown in "In a Nutshell" above requires nightly for `unboxed_closures` and `fn_traits`, which is feature-gated under the `experimental-api` feature. diff --git a/examples/simple-demo/src/main.rs b/examples/simple-demo/src/main.rs index a138f17..d159dbd 100644 --- a/examples/simple-demo/src/main.rs +++ b/examples/simple-demo/src/main.rs @@ -1,3 +1,4 @@ +use effects::Cloned; use rearch::{CData, CapsuleHandle, Container}; use rearch_effects as effects; @@ -29,8 +30,7 @@ fn uses_factory_capsule(CapsuleHandle { mut get, .. }: CapsuleHandle) -> String } fn stateful_capsule(CapsuleHandle { register, .. }: CapsuleHandle) -> (u32, impl CData + Fn(u32)) { - let (state, set_state) = register.register(effects::state(0)); - (*state, set_state) + register.register(effects::state::>(0)) } fn main() { diff --git a/rearch-effects/Cargo.toml b/rearch-effects/Cargo.toml index 703401e..9c2f8b0 100644 --- a/rearch-effects/Cargo.toml +++ b/rearch-effects/Cargo.toml @@ -16,5 +16,11 @@ readme.workspace = true workspace = true [dependencies] -once_cell = { version = "1.19.0", default-features = false} +once_cell = { version = "1.19.0", default-features = false, optional = true} rearch = { workspace = true } + +[features] +default = ["lazy-state-transformers"] + +# Enable the lazy state transformers via once_cell::unsync::Lazy +lazy-state-transformers = ["dep:once_cell"] diff --git a/rearch-effects/src/cloneable.rs b/rearch-effects/src/cloneable.rs deleted file mode 100644 index 0cb132c..0000000 --- a/rearch-effects/src/cloneable.rs +++ /dev/null @@ -1,33 +0,0 @@ -use rearch::{CData, SideEffect, SideEffectRegistrar}; - -pub fn state( - initial: T, -) -> impl for<'a> SideEffect = (T, impl CData + Fn(T))> { - move |register: SideEffectRegistrar| { - let (state, set_state) = register.register(super::state(initial)); - (state.clone(), set_state) - } -} - -pub fn lazy_state(init: F) -> impl for<'a> SideEffect = (T, impl CData + Fn(T))> -where - T: Clone + Send + 'static, - F: FnOnce() -> T + Send + 'static, -{ - move |register: SideEffectRegistrar| { - let (state, set_state) = register.register(super::lazy_state(init)); - (state.clone(), set_state) - } -} - -pub fn value(value: T) -> impl for<'a> SideEffect = T> { - move |register: SideEffectRegistrar| register.register(super::value(value)).clone() -} - -pub fn lazy_value(init: F) -> impl for<'a> SideEffect = T> -where - T: Clone + Send + 'static, - F: FnOnce() -> T + Send + 'static, -{ - move |register: SideEffectRegistrar| register.register(super::lazy_value(init)).clone() -} diff --git a/rearch-effects/src/effect_lifetime_fixers.rs b/rearch-effects/src/effect_lifetime_fixers.rs new file mode 100644 index 0000000..59440d7 --- /dev/null +++ b/rearch-effects/src/effect_lifetime_fixers.rs @@ -0,0 +1,71 @@ +use crate::StateTransformer; +use rearch::{SideEffect, SideEffectRegistrar}; + +// These workarounds were derived from: +// https://github.com/GregoryConrad/rearch-rs/issues/3#issuecomment-1872869363 +// And are needed because of: +// https://github.com/rust-lang/rust/issues/111662 +// A big thank you to https://github.com/0e4ef622 for all of their help here! + +pub struct EffectLifetimeFixer0(F, std::marker::PhantomData); +impl SideEffect for EffectLifetimeFixer0 +where + F: FnOnce(SideEffectRegistrar) -> ST::Output<'_>, + ST: StateTransformer, +{ + type Api<'a> = ST::Output<'a>; + fn build(self, registrar: SideEffectRegistrar) -> Self::Api<'_> { + self.0(registrar) + } +} +impl EffectLifetimeFixer0 { + pub(super) const fn new(f: F) -> Self + where + F: FnOnce(SideEffectRegistrar) -> ST::Output<'_>, + ST: StateTransformer, + { + Self(f, std::marker::PhantomData) + } +} + +pub struct EffectLifetimeFixer1(F, std::marker::PhantomData); +impl SideEffect for EffectLifetimeFixer1 +where + F: FnOnce(SideEffectRegistrar) -> (ST::Output<'_>, R1), + ST: StateTransformer, +{ + type Api<'a> = (ST::Output<'a>, R1); + fn build(self, registrar: SideEffectRegistrar) -> Self::Api<'_> { + self.0(registrar) + } +} +impl EffectLifetimeFixer1 { + pub(super) const fn new(f: F) -> Self + where + F: FnOnce(SideEffectRegistrar) -> (ST::Output<'_>, R1), + ST: StateTransformer, + { + Self(f, std::marker::PhantomData) + } +} + +pub struct EffectLifetimeFixer2(F, std::marker::PhantomData); +impl SideEffect for EffectLifetimeFixer2 +where + F: FnOnce(SideEffectRegistrar) -> (ST::Output<'_>, R1, R2), + ST: StateTransformer, +{ + type Api<'a> = (ST::Output<'a>, R1, R2); + fn build(self, registrar: SideEffectRegistrar) -> Self::Api<'_> { + self.0(registrar) + } +} +impl EffectLifetimeFixer2 { + pub(super) const fn new(f: F) -> Self + where + F: FnOnce(SideEffectRegistrar) -> (ST::Output<'_>, R1, R2), + ST: StateTransformer, + { + Self(f, std::marker::PhantomData) + } +} diff --git a/rearch-effects/src/lib.rs b/rearch-effects/src/lib.rs index 126f793..512e11f 100644 --- a/rearch-effects/src/lib.rs +++ b/rearch-effects/src/lib.rs @@ -1,99 +1,55 @@ -use once_cell::unsync::Lazy; use rearch::{CData, SideEffect, SideEffectRegistrar}; use std::sync::Arc; -pub mod cloneable; +mod state_transformers; +pub use state_transformers::*; -// This workaround was derived from: -// https://github.com/GregoryConrad/rearch-rs/issues/3#issuecomment-1872869363 -// And is needed because of: -// https://github.com/rust-lang/rust/issues/111662 +mod effect_lifetime_fixers; use effect_lifetime_fixers::{EffectLifetimeFixer0, EffectLifetimeFixer1, EffectLifetimeFixer2}; -mod effect_lifetime_fixers { - use rearch::{SideEffect, SideEffectRegistrar}; - - pub struct EffectLifetimeFixer0(F); - impl SideEffect for EffectLifetimeFixer0 - where - T: Send + 'static, - F: FnOnce(SideEffectRegistrar) -> &mut T, - { - type Api<'a> = &'a mut T; - fn build(self, registrar: SideEffectRegistrar) -> Self::Api<'_> { - self.0(registrar) - } - } - impl EffectLifetimeFixer0 { - pub(super) const fn new(f: F) -> Self - where - F: FnOnce(SideEffectRegistrar) -> &mut T, - { - Self(f) - } - } - pub struct EffectLifetimeFixer1(F); - impl SideEffect for EffectLifetimeFixer1 - where - T: Send + 'static, - F: FnOnce(SideEffectRegistrar) -> (&mut T, R1), - { - type Api<'a> = (&'a mut T, R1); - fn build(self, registrar: SideEffectRegistrar) -> Self::Api<'_> { - self.0(registrar) - } - } - impl EffectLifetimeFixer1 { - pub(super) const fn new(f: F) -> Self - where - F: FnOnce(SideEffectRegistrar) -> (&mut T, R1), - { - Self(f) - } - } +pub trait StateTransformer: Send + 'static { + type Input; + fn from_input(input: Self::Input) -> Self; - pub struct EffectLifetimeFixer2(F); - impl SideEffect for EffectLifetimeFixer2 - where - T: Send + 'static, - F: FnOnce(SideEffectRegistrar) -> (&mut T, R1, R2), - { - type Api<'a> = (&'a mut T, R1, R2); - fn build(self, registrar: SideEffectRegistrar) -> Self::Api<'_> { - self.0(registrar) - } - } - impl EffectLifetimeFixer2 { - pub(super) const fn new(f: F) -> Self - where - F: FnOnce(SideEffectRegistrar) -> (&mut T, R1, R2), - { - Self(f) - } - } + type Inner; + fn as_inner(&mut self) -> &mut Self::Inner; + + type Output<'a>; + fn as_output(&mut self) -> Self::Output<'_>; } -pub fn raw( - initial: T, +type SideEffectMutation = Box::Inner)>; + +// NOTE: returns (), the no-op side effect +#[must_use] +pub fn as_listener() -> impl for<'a> SideEffect = ()> {} + +pub fn raw( + initial: ST::Input, ) -> impl for<'a> SideEffect< Api<'a> = ( - &'a mut T, - impl CData + Fn(Box), + ST::Output<'a>, + impl CData + Fn(Box), Arc)>, ), > { - EffectLifetimeFixer2::new(move |register: SideEffectRegistrar| register.raw(initial)) + EffectLifetimeFixer2::<_, ST>::new(move |register: SideEffectRegistrar| { + let (transformer, run_mutation, run_txn) = register.raw(ST::from_input(initial)); + ( + transformer.as_output(), + move |mutation: SideEffectMutation| { + run_mutation(Box::new(move |st| mutation(st.as_inner()))); + }, + run_txn, + ) + }) } -// NOTE: returns (), the no-op side effect -#[must_use] -pub fn as_listener() -> impl for<'a> SideEffect = ()> {} - -pub fn state( - initial: T, -) -> impl for<'a> SideEffect = (&'a mut T, impl CData + Fn(T))> { - EffectLifetimeFixer1::new(move |register: SideEffectRegistrar| { - let (state, rebuild, _) = register.raw(initial); +pub fn state( + initial: ST::Input, +) -> impl for<'a> SideEffect = (ST::Output<'a>, impl CData + Fn(ST::Inner))> { + EffectLifetimeFixer1::<_, ST>::new(move |register: SideEffectRegistrar| { + let (state, rebuild, _) = register.register(raw::(initial)); let set_state = move |new_state| { rebuild(Box::new(|state| *state = new_state)); }; @@ -101,60 +57,37 @@ pub fn state( }) } -#[allow(clippy::missing_panics_doc)] // false positive -pub fn lazy_state( - init: F, -) -> impl for<'a> SideEffect = (&'a mut T, impl CData + Fn(T))> -where - T: Send + 'static, - F: FnOnce() -> T + Send + 'static, -{ - EffectLifetimeFixer1::new(move |register: SideEffectRegistrar| { - let (cell, rebuild, _) = register.raw(Lazy::new(init)); - let set_state = move |new_state| { - rebuild(Box::new(|cell| **cell = new_state)); - }; - (&mut **cell, set_state) - }) -} - -pub fn value(value: T) -> impl for<'a> SideEffect = &'a mut T> { - EffectLifetimeFixer0::new(move |register: SideEffectRegistrar| register.raw(value).0) -} - -#[allow(clippy::missing_panics_doc)] // false positive -pub fn lazy_value(init: F) -> impl for<'a> SideEffect = &'a mut T> -where - T: Send + 'static, - F: FnOnce() -> T + Send + 'static, -{ - EffectLifetimeFixer0::new(move |register: SideEffectRegistrar| { - let (cell, _, _) = register.raw(Lazy::new(init)); - &mut **cell +pub fn value( + value: ST::Input, +) -> impl for<'a> SideEffect = ST::Output<'a>> { + EffectLifetimeFixer0::<_, ST>::new(move |register: SideEffectRegistrar| { + register.register(raw::(value)).0 }) } #[must_use] pub fn is_first_build() -> impl for<'a> SideEffect = bool> { move |register: SideEffectRegistrar| { - let has_built_before = register.register(value(false)); + let has_built_before = register.register(value::>(false)); let is_first_build = !*has_built_before; *has_built_before = true; is_first_build } } -pub fn reducer( +/// Models the state reducer pattern via side effects. +/// +/// This should normally *not* be used with [`MutRef`]. +pub fn reducer( + initial: ST::Input, reducer: Reducer, - initial: State, -) -> impl for<'a> SideEffect = (&'a mut State, impl CData + Fn(Action))> +) -> impl for<'a> SideEffect = (ST::Output<'a>, impl CData + Fn(Action))> where - State: Send + 'static, Action: 'static, - Reducer: Clone + Send + Sync + 'static + Fn(&State, Action) -> State, + Reducer: Clone + Send + Sync + 'static + Fn(&ST::Inner, Action) -> ST::Inner, { - EffectLifetimeFixer1::new(move |register: SideEffectRegistrar| { - let (state, update_state, _) = register.raw(initial); + EffectLifetimeFixer1::<_, ST>::new(move |register: SideEffectRegistrar| { + let (state, update_state, _) = register.register(raw::(initial)); (state, move |action| { let reducer = reducer.clone(); update_state(Box::new(move |state| *state = reducer(state, action))); @@ -162,10 +95,8 @@ where }) } -// NOTE: Commented out because: -// - This fails to compile due to a compiler bug (&'a mut R compiles fine, but &'a R doesn't) -// - I think people should really be using a hydrate equivalent instead of this -// - A combo of lazy_value and run_on_change probably +// NOTE: Commented out because I think people should really be using a hydrate equivalent +// instead of this. Probably value::>() and run_on_change? // // /// A thin wrapper around the state side effect that enables easy state persistence. // /// @@ -237,3 +168,148 @@ impl SideEffect for RunOnChange { } } */ + +#[cfg(test)] +mod tests { + use crate::*; + use rearch::{CData, CapsuleHandle, Container}; + use std::sync::atomic::{AtomicU8, Ordering}; + + #[allow(clippy::needless_pass_by_value)] + fn assert_type(_actual: Expected) {} + + #[test] + fn transformer_output_types() { + fn dummy_capsule(CapsuleHandle { register, .. }: CapsuleHandle) { + let ((r, _, _), (mr, _, _), (c, _, _)) = register.register(( + raw::>(123), + raw::>(123), + raw::>(123), + )); + assert_type::<&u8>(r); + assert_type::<&mut u8>(mr); + assert_type::(c); + } + Container::new().read(dummy_capsule); + } + + #[cfg(feature = "lazy-state-transformers")] + #[test] + fn lazy_transformer_output_types() { + fn dummy_capsule(CapsuleHandle { register, .. }: CapsuleHandle) { + let ((r, _, _), (mr, _, _), (c, _, _)) = register.register(( + raw::>(|| 123), + raw::>(|| 123), + raw::>(|| 123), + )); + assert_type::<&u8>(r); + assert_type::<&mut u8>(mr); + assert_type::(c); + } + Container::new().read(dummy_capsule); + } + + // NOTE: raw side effect is effectively tested via combination of the other side effects + + #[test] + fn as_listener_gets_changes() { + static BUILD_COUNT: AtomicU8 = AtomicU8::new(0); + + fn rebuildable_capsule(CapsuleHandle { register, .. }: CapsuleHandle) -> impl CData + Fn() { + let ((), rebuild, _) = register.raw(()); + move || rebuild(Box::new(|()| {})) + } + + fn listener_capsule(CapsuleHandle { mut get, register }: CapsuleHandle) { + register.register(as_listener()); + BUILD_COUNT.fetch_add(1, Ordering::SeqCst); + get.as_ref(rebuildable_capsule); + } + + let container = Container::new(); + container.read(listener_capsule); + container.read(rebuildable_capsule)(); + assert_eq!(BUILD_COUNT.fetch_add(1, Ordering::SeqCst), 2); + } + + #[test] + fn state_can_change() { + fn stateful_capsule( + CapsuleHandle { register, .. }: CapsuleHandle, + ) -> (u8, impl CData + Fn(u8)) { + register.register(state::>(0)) + } + + let container = Container::new(); + assert_eq!(container.read(stateful_capsule).0, 0); + container.read(stateful_capsule).1(1); + assert_eq!(container.read(stateful_capsule).0, 1); + } + + #[test] + fn value_can_change() { + fn rebuildable_capsule(CapsuleHandle { register, .. }: CapsuleHandle) -> impl CData + Fn() { + let ((), rebuild, _) = register.raw(()); + move || rebuild(Box::new(|()| {})) + } + + fn build_count_capsule(CapsuleHandle { mut get, register }: CapsuleHandle) -> u8 { + get.as_ref(rebuildable_capsule); + let build_count = register.register(value::>(0)); + *build_count += 1; + *build_count + } + + let container = Container::new(); + assert_eq!(container.read(build_count_capsule), 1); + container.read(rebuildable_capsule)(); + assert_eq!(container.read(build_count_capsule), 2); + container.read(rebuildable_capsule)(); + assert_eq!(container.read(build_count_capsule), 3); + } + + #[test] + fn is_first_build_changes_state() { + fn is_first_build_capsule( + CapsuleHandle { register, .. }: CapsuleHandle, + ) -> (bool, impl CData + Fn()) { + let (is_first_build, ((), rebuild, _)) = + register.register((is_first_build(), raw::>(()))); + (is_first_build, move || rebuild(Box::new(|()| {}))) + } + + let container = Container::new(); + assert!(container.read(is_first_build_capsule).0); + container.read(is_first_build_capsule).1(); + assert!(!container.read(is_first_build_capsule).0); + container.read(is_first_build_capsule).1(); + assert!(!container.read(is_first_build_capsule).0); + } + + #[test] + fn reducer_can_change() { + enum CountAction { + Increment, + Decrement, + } + + fn count_manager( + CapsuleHandle { register, .. }: CapsuleHandle, + ) -> (u8, impl CData + Fn(CountAction)) { + register.register(reducer::, _, _>( + 0, + |state, action| match action { + CountAction::Increment => state + 1, + CountAction::Decrement => state - 1, + }, + )) + } + + let container = Container::new(); + assert_eq!(container.read(count_manager).0, 0); + container.read(count_manager).1(CountAction::Increment); + assert_eq!(container.read(count_manager).0, 1); + container.read(count_manager).1(CountAction::Decrement); + assert_eq!(container.read(count_manager).0, 0); + } +} diff --git a/rearch-effects/src/state_transformers.rs b/rearch-effects/src/state_transformers.rs new file mode 100644 index 0000000..fbdaba3 --- /dev/null +++ b/rearch-effects/src/state_transformers.rs @@ -0,0 +1,119 @@ +use crate::StateTransformer; + +pub struct Ref(T); +impl StateTransformer for Ref { + type Input = T; + fn from_input(input: Self::Input) -> Self { + Self(input) + } + + type Inner = T; + fn as_inner(&mut self) -> &mut Self::Inner { + &mut self.0 + } + + type Output<'a> = &'a T; + fn as_output(&mut self) -> Self::Output<'_> { + &self.0 + } +} + +pub struct MutRef(T); +impl StateTransformer for MutRef { + type Input = T; + fn from_input(input: Self::Input) -> Self { + Self(input) + } + + type Inner = T; + fn as_inner(&mut self) -> &mut Self::Inner { + &mut self.0 + } + + type Output<'a> = &'a mut T; + fn as_output(&mut self) -> Self::Output<'_> { + &mut self.0 + } +} + +pub struct Cloned(T); +impl StateTransformer for Cloned { + type Input = T; + fn from_input(input: Self::Input) -> Self { + Self(input) + } + + type Inner = T; + fn as_inner(&mut self) -> &mut Self::Inner { + &mut self.0 + } + + type Output<'a> = T; + fn as_output(&mut self) -> Self::Output<'_> { + self.0.clone() + } +} + +#[cfg(feature = "lazy-state-transformers")] +pub use lazy_transformers::*; +#[cfg(feature = "lazy-state-transformers")] +mod lazy_transformers { + use crate::StateTransformer; + use once_cell::unsync::Lazy; + + pub struct LazyRef T>(Lazy); + impl T> StateTransformer for LazyRef { + type Input = F; + fn from_input(input: Self::Input) -> Self { + Self(Lazy::new(input)) + } + + type Inner = T; + fn as_inner(&mut self) -> &mut Self::Inner { + &mut self.0 + } + + type Output<'a> = &'a T; + fn as_output(&mut self) -> Self::Output<'_> { + &self.0 + } + } + + pub struct LazyMutRef T>(Lazy); + impl T> StateTransformer for LazyMutRef { + type Input = F; + fn from_input(input: Self::Input) -> Self { + Self(Lazy::new(input)) + } + + type Inner = T; + fn as_inner(&mut self) -> &mut Self::Inner { + &mut self.0 + } + + type Output<'a> = &'a mut T; + fn as_output(&mut self) -> Self::Output<'_> { + &mut self.0 + } + } + + pub struct LazyCloned T>(Lazy); + impl T> StateTransformer + for LazyCloned + { + type Input = F; + fn from_input(input: Self::Input) -> Self { + Self(Lazy::new(input)) + } + + type Inner = T; + fn as_inner(&mut self) -> &mut Self::Inner { + &mut self.0 + } + + type Output<'a> = T; + fn as_output(&mut self) -> Self::Output<'_> { + self.0.clone() + } + } +} diff --git a/rearch-tokio/src/lib.rs b/rearch-tokio/src/lib.rs index 6fccd01..193c6d0 100644 --- a/rearch-tokio/src/lib.rs +++ b/rearch-tokio/src/lib.rs @@ -1,3 +1,4 @@ +use effects::{MutRef, StateTransformer}; use rearch::{CData, SideEffect, SideEffectRegistrar}; use rearch_effects as effects; use std::{future::Future, sync::Arc}; @@ -79,22 +80,70 @@ impl MutationState { Self::Complete(data) => Some(data), } } + + pub fn map(self, f: F) -> MutationState + where + F: FnOnce(T) -> U, + { + match self { + Self::Idle(prev) => MutationState::Idle(prev.map(f)), + Self::Loading(prev) => MutationState::Loading(prev.map(f)), + Self::Complete(state) => MutationState::Complete(f(state)), + } + } + + pub fn as_mut(&mut self) -> MutationState<&mut T> { + match *self { + Self::Idle(ref mut prev) => MutationState::Idle(prev.as_mut()), + Self::Loading(ref mut prev) => MutationState::Loading(prev.as_mut()), + Self::Complete(ref mut state) => MutationState::Complete(state), + } + } +} + +struct MutationLifetimeFixer(F, std::marker::PhantomData); +impl SideEffect for MutationLifetimeFixer +where + F: FnOnce(SideEffectRegistrar) -> (MutationState>, R1, R2), + ST: StateTransformer, +{ + type Api<'a> = (MutationState>, R1, R2); + fn build(self, registrar: SideEffectRegistrar) -> Self::Api<'_> { + self.0(registrar) + } +} +impl MutationLifetimeFixer { + const fn new(f: F) -> Self + where + F: FnOnce(SideEffectRegistrar) -> (MutationState>, R1, R2), + ST: StateTransformer, + { + Self(f, std::marker::PhantomData) + } } +/// Allows you to trigger and cancel query mutations. +/// +/// This should normally *not* be used with [`MutRef`]. #[must_use] -pub fn mutation( -) -> impl for<'a> SideEffect = (&'a MutationState, impl CData + Fn(F), impl CData + Fn())> +pub fn mutation() -> impl for<'a> SideEffect< + Api<'a> = ( + MutationState>, + impl CData + Fn(F), + impl CData + Fn(), + ), +> where - T: Send + 'static, - F: Future + Send + 'static, + F: Future + Send + 'static, { - RefEffectLifetimeFixer2::new(move |register: SideEffectRegistrar| { + MutationLifetimeFixer::<_, ST>::new(move |register: SideEffectRegistrar| { let ((state, mutate_state, run_txn), (_, on_change)) = register.register(( - effects::raw(MutationState::Idle(None)), + effects::raw::>>(MutationState::Idle(None)), // This immitates run_on_change, but for external use (outside of build) - effects::state(FunctionalDrop(None)), + effects::state::>(FunctionalDrop(None)), )); + let state = state.as_mut().map(ST::as_output); let mutate = { let on_change = on_change.clone(); let mutate_state = mutate_state.clone(); @@ -110,7 +159,7 @@ where let mutate_state = mutate_state.clone(); let handle = tokio::spawn(async move { - let data = future.await; + let data = ST::from_input(future.await); mutate_state(Box::new(move |state| { *state = MutationState::Complete(data); })); @@ -130,7 +179,7 @@ where on_change(FunctionalDrop(None)); // abort old future if present })); }; - (&*state, mutate, clear) + (state, mutate, clear) }) } @@ -180,23 +229,3 @@ where } } */ - -struct RefEffectLifetimeFixer2(F); -impl SideEffect for RefEffectLifetimeFixer2 -where - T: Send + 'static, - F: FnOnce(SideEffectRegistrar) -> (&T, R1, R2), -{ - type Api<'a> = (&'a T, R1, R2); - fn build(self, registrar: SideEffectRegistrar) -> Self::Api<'_> { - self.0(registrar) - } -} -impl RefEffectLifetimeFixer2 { - const fn new(f: F) -> Self - where - F: FnOnce(SideEffectRegistrar) -> (&T, R1, R2), - { - Self(f) - } -}