diff --git a/rearch/src/side_effects.rs b/rearch/src/side_effects.rs index 8d248e3..eb65e72 100644 --- a/rearch/src/side_effects.rs +++ b/rearch/src/side_effects.rs @@ -2,6 +2,18 @@ use std::{cell::OnceCell, sync::Arc}; use crate::{SideEffect, SideEffectRegistrar}; +pub fn raw<'a, T: Send + 'static>( + initial: T, +) -> impl SideEffect< + 'a, + Api = ( + &'a mut T, + impl Fn(Box) + Clone + Send + Sync, + ), +> { + move |register: SideEffectRegistrar<'a>| register.raw(initial) +} + pub fn state<'a, T: Send + 'static>( initial: T, ) -> impl SideEffect<'a, Api = (&'a mut T, impl Fn(T) + Clone + Send + Sync)> { @@ -91,6 +103,7 @@ where /// Side effect that runs a callback whenever it changes and is dropped. /// Similar to `useEffect` from React. +#[must_use] pub fn run_on_change<'a, F>() -> impl SideEffect<'a, Api = impl FnMut(F) + 'a> where F: FnOnce() + Send + 'static, @@ -114,18 +127,18 @@ impl Drop for FunctionalDrop { pub fn reducer<'a, State, Action, Reducer>( reducer: Reducer, initial: State, -) -> impl SideEffect<'a, Api = (&'a State, impl Fn(Action) + Clone + Send + Sync + 'static)> +) -> impl SideEffect<'a, Api = (State, impl Fn(Action) + Clone + Send + Sync)> where State: Clone + Send + Sync + 'static, Reducer: Fn(State, Action) -> State + Clone + Send + Sync + 'static, { move |register: SideEffectRegistrar<'a>| { let (state, set_state) = register.register(state(initial)); - (&*state, { + (state.clone(), { let state = state.clone(); move |action| { let state = state.clone(); - set_state(reducer(state, action)) + set_state(reducer(state, action)); } }) } @@ -134,18 +147,18 @@ where pub fn lazy_reducer<'a, State, Action, Reducer>( reducer: Reducer, initial: impl FnOnce() -> State + Send + 'static, -) -> impl SideEffect<'a, Api = (&'a State, impl Fn(Action) + Clone + Send + Sync + 'static)> +) -> impl SideEffect<'a, Api = (State, impl Fn(Action) + Clone + Send + Sync)> where State: Clone + Send + Sync + 'static, Reducer: Fn(State, Action) -> State + Clone + Send + Sync + 'static, { move |register: SideEffectRegistrar<'a>| { let (state, set_state) = register.register(lazy_state(initial)); - (&*state, { + (state.clone(), { let state = state.clone(); move |action| { let state = state.clone(); - set_state(reducer(state, action)) + set_state(reducer(state, action)); } }) } @@ -171,259 +184,181 @@ where Write: Fn(T) -> R + Send + Sync + 'static, { move |register: SideEffectRegistrar<'a>| { - let ((state, set_state), write) = - register.register((lazy_state(read), value(Arc::new(write)))); - - let write = Arc::clone(write); + let (state, set_state) = register.register(lazy_state(read)); + let write = Arc::new(write); let persist = move |new_data| { let persist_result = write(new_data); set_state(persist_result); }; - (&*state, persist) } } -// TODO convert below side effects too -/* #[cfg(feature = "tokio-side-effects")] -fn future_from_fn( - &mut self, - future: impl FnOnce() -> Future, - dependencies: impl DependencyList, -) -> AsyncState -where - R: Send + Sync + 'static, - Future: std::future::Future + Send + 'static, -{ - let rebuild = self.rebuilder(); - let (state, set_state) = self.rebuildless_state(|| AsyncState::Loading(None)); - - self.effect( - || { - let curr_data = state.data(); - set_state(AsyncState::Loading(curr_data)); - - let future = future(); - let handle = tokio::task::spawn(async move { - let data = future.await; - rebuild(move || { - set_state(AsyncState::Complete(Arc::new(data))); - }); - }); - - move || handle.abort() - }, - dependencies, - ); - - state.as_ref().clone() -} - -#[cfg(feature = "tokio-side-effects")] -fn future( - &mut self, - future: Future, - dependencies: impl DependencyList, -) -> AsyncState -where - R: Send + Sync + 'static, - Future: std::future::Future + Send + 'static, -{ - self.future_from_fn(|| future, dependencies) -} +pub use tokio_side_effects::*; #[cfg(feature = "tokio-side-effects")] -fn async_persist( - &mut self, - read: Reader, - write: Writer, -) -> (AsyncState, impl FnMut(T) + Send + Sync + Clone + 'static) -where - T: Send + 'static, - R: Send + Sync + 'static, - Reader: FnOnce() -> ReadFuture + Send + 'static, - Writer: FnOnce(T) -> WriteFuture + Send + 'static, - ReadFuture: std::future::Future + Send + 'static, - WriteFuture: std::future::Future + Send + 'static, -{ - let data_to_persist_mutex = self.callonce(|| Mutex::new(None::)); - let data_to_persist = { - let mut data_to_persist = data_to_persist_mutex - .lock() - .expect("Mutex shouldn't fail to lock"); - std::mem::take(&mut *data_to_persist) - }; - - let rebuild = self.rebuilder(); - let persist = move |new_data| { - let data_to_persist_mutex = data_to_persist_mutex.clone(); - rebuild(move || { - let mut data_to_persist = data_to_persist_mutex - .lock() - .expect("Mutex shouldn't fail to lock"); - *data_to_persist = Some(new_data); - }) - }; - - // Deps changes whenever new data is persisted so that self.future_from_fn will - // always have the most up to date future - let deps_mutex = self.callonce(|| Mutex::new(false)); - let deps = { - let mut deps = deps_mutex.lock().expect("Mutex shouldn't fail to lock"); - if data_to_persist.is_some() { - *deps = !*deps; - } - (*deps,) - }; +mod tokio_side_effects { + use std::{cell::RefCell, future::Future, rc::Rc}; - let future = async move { - match data_to_persist { - Some(data_to_persist) => write(data_to_persist).await, - None => read().await, // this will only actually be called on first build - } - }; - - let state = self.future(future, deps); - - (state, persist) -} - - -fn rebuildless_nonce(&mut self) -> (u16, impl Fn() + Send + Sync + Clone + 'static) { - let (nonce, set_nonce) = self.rebuildless_state(|| 0u16); - (*nonce, move || set_nonce(nonce.overflowing_add(1).0)) -} - -#[cfg(feature = "tokio-side-effects")] -fn mutation( - &mut self, - mutation: Mutation, -) -> ( - AsyncMutationState, - impl Fn() + Send + Sync + Clone + 'static, - impl Fn() + Send + Sync + Clone + 'static, -) -where - T: Send + Sync + 'static, - Mutation: FnOnce() -> Future + Send + 'static, - Future: std::future::Future + Send + 'static, -{ - let (state, set_state) = self.rebuildless_state(|| AsyncMutationState::Idle(None)); - let (nonce, increment_nonce) = self.rebuildless_nonce(); - let (active, set_active) = { - let (active, set_active) = self.rebuildless_state(|| false); - (*active, set_active) - }; - - let curr_data = state.data(); - if !active { - set_state(AsyncMutationState::Idle(curr_data.clone())); - } - - let rebuild = self.rebuilder(); - self.effect( - || { - let handle = active.then(move || { - set_state(AsyncMutationState::Loading(curr_data)); - tokio::task::spawn(async move { - let data = mutation().await; - rebuild(move || { - set_state(AsyncMutationState::Complete(Arc::new(data))); - }); - }) - }); - - move || { - if let Some(handle) = handle { - handle.abort() - } - } - }, - (nonce, active), - ); - - let rebuild = self.rebuilder(); - let mutate = { - let set_active = set_active.clone(); - let increment_nonce = increment_nonce.clone(); - move || { - let set_active = set_active.clone(); - let increment_nonce = increment_nonce.clone(); - rebuild(move || { - set_active(true); - increment_nonce(); - }) - } - }; - - let rebuild = self.rebuilder(); - let clear = move || { - let set_active = set_active.clone(); - let increment_nonce = increment_nonce.clone(); - rebuild(move || { - set_active(false); - increment_nonce(); - }) - }; - - (state.as_ref().clone(), mutate, clear) -} - -#[cfg(feature = "tokio-side-effects")] -pub use async_state::*; - -#[cfg(feature = "tokio-side-effects")] -mod async_state { - use std::sync::Arc; + use super::*; + #[derive(Clone)] pub enum AsyncState { - Loading(Option>), - Complete(Arc), + Idle(Option), + Loading(Option), + Complete(T), } impl AsyncState { - pub fn data(&self) -> Option> { + pub fn data(self) -> Option { match self { - Self::Loading(previous_data) => previous_data.clone(), - Self::Complete(data) => Some(data.clone()), + Self::Idle(previous_data) => previous_data, + Self::Loading(previous_data) => previous_data, + Self::Complete(data) => Some(data), } } } - impl Clone for AsyncState { - fn clone(&self) -> Self { + #[derive(Clone)] + pub enum AsyncPersistState { + Loading(Option), + Complete(T), + } + + impl AsyncPersistState { + pub fn data(self) -> Option { match self { - Self::Loading(previous_data) => Self::Loading(previous_data.clone()), - Self::Complete(data) => Self::Complete(data.clone()), + Self::Loading(previous_data) => previous_data, + Self::Complete(data) => Some(data), } } } - pub enum AsyncMutationState { - Idle(Option>), - Loading(Option>), - Complete(Arc), + pub fn future<'a, T, F>( + ) -> impl SideEffect<'a, Api = (impl Fn() -> AsyncState + 'a, impl FnMut(F) + 'a)> + where + T: Clone + Send + 'static, + F: Future + Send + 'static, + { + move |register: SideEffectRegistrar<'a>| { + let ((state, set_state), mut on_change) = + register.register((state(AsyncState::Idle(None)), run_on_change())); + let state = Rc::new(RefCell::new(state)); + let get = { + let state = Rc::clone(&state); + move || state.borrow().clone() + }; + let set = move |future| { + let mut state = state.borrow_mut(); + let old_state = std::mem::replace(*state, AsyncState::Idle(None)); + **state = AsyncState::Loading(old_state.data()); + + let set_state = set_state.clone(); + let handle = tokio::spawn(async move { + let data = future.await; + set_state(AsyncState::Complete(data)); + }); + on_change(move || handle.abort()); + }; + (get, set) + } } - impl AsyncMutationState { - pub fn data(&self) -> Option> { - match self { - Self::Idle(previous_data) => previous_data.clone(), - Self::Loading(previous_data) => previous_data.clone(), - Self::Complete(data) => Some(data.clone()), - } + pub fn mutation<'a, T, F>() -> impl SideEffect< + 'a, + Api = ( + AsyncState, + impl Fn(F) + Clone + Send + Sync, + impl Fn() + Clone + Send + Sync, + ), + > + where + T: Clone + Send + 'static, + F: Future + Send + 'static, + { + move |register: SideEffectRegistrar<'a>| { + let ((state, rebuild), (_, on_change)) = register.register(( + raw(AsyncState::Idle(None)), + // This immitates run_on_change, but for external use (outside of build) + state(FunctionalDrop(None)), + )); + + let state = state.clone(); + let mutate = { + let rebuild = rebuild.clone(); + move |future| { + rebuild(Box::new(|state| { + let old_state = std::mem::replace(state, AsyncState::Idle(None)); + *state = AsyncState::Loading(old_state.data()); + })); + + let rebuild = rebuild.clone(); + let handle = tokio::spawn(async move { + let data = future.await; + rebuild(Box::new(move |state| { + *state = AsyncState::Complete(data); + })); + }); + on_change(FunctionalDrop(Some(move || handle.abort()))); + } + }; + let clear = move || { + rebuild(Box::new(|state| { + let old_state = std::mem::replace(state, AsyncState::Idle(None)); + *state = AsyncState::Idle(old_state.data()); + })); + }; + (state, mutate, clear) } } - impl Clone for AsyncMutationState { - fn clone(&self) -> Self { - match self { - Self::Idle(previous_data) => Self::Idle(previous_data.clone()), - Self::Loading(previous_data) => Self::Loading(previous_data.clone()), - Self::Complete(data) => Self::Complete(data.clone()), + pub fn async_persist<'a, T, R, Reader, Writer, ReadFuture, WriteFuture>( + read: Reader, + write: Writer, + ) -> impl SideEffect<'a, Api = (AsyncPersistState, impl FnMut(T) + Send + Sync + Clone)> + where + T: Send + 'static, + R: Clone + Send + 'static, + Reader: FnOnce() -> ReadFuture + Send + 'static, + Writer: Fn(T) -> WriteFuture + Send + Sync + 'static, + ReadFuture: Future + Send + 'static, + WriteFuture: Future + Send + 'static, + { + move |register: SideEffectRegistrar<'a>| { + let ((get_read, mut set_read), (write_state, set_write, _), is_first_build) = + register.register((future(), mutation(), is_first_build())); + + if is_first_build { + set_read(read()); } + let state = match (write_state, get_read()) { + (AsyncState::Idle(_), AsyncState::Loading(prev)) => { + AsyncPersistState::Loading(prev) + } + (AsyncState::Idle(_), AsyncState::Complete(data)) => { + AsyncPersistState::Complete(data) + } + + (AsyncState::Loading(None), read_state) => { + AsyncPersistState::Loading(read_state.data()) + } + (AsyncState::Loading(prev @ Some(_)), _) => AsyncPersistState::Loading(prev), + + (AsyncState::Complete(data), _) => AsyncPersistState::Complete(data), + + (_, AsyncState::Idle(_)) => { + unreachable!("Read should never be idle") + } + }; + + let write = Arc::new(write); + let persist = move |new_data| { + let write = Arc::clone(&write); + set_write(async move { write(new_data).await }); + }; + + (state, persist) } } } -*/