From b1043c372941a2d4c28a3713a023fe39fd164d1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20K=C3=A5re=20Alsaker?= Date: Mon, 13 Nov 2023 18:46:16 +0100 Subject: [PATCH] Add `create_updater` and `create_stateful_updater` --- reactive/src/effect.rs | 139 ++++++++++++++++++++++++++++++++++++- reactive/src/lib.rs | 2 +- reactive/src/scope.rs | 2 +- src/views/dyn_container.rs | 11 +-- src/views/label.rs | 12 ++-- 5 files changed, 152 insertions(+), 14 deletions(-) diff --git a/reactive/src/effect.rs b/reactive/src/effect.rs index 9d43162f..69c4129b 100644 --- a/reactive/src/effect.rs +++ b/reactive/src/effect.rs @@ -38,7 +38,7 @@ where /// Create an Effect that runs the given function whenever the Signals that subscribed /// to it in the function. /// -/// The given function will be run immdietly once, and tracks all the signals that +/// The given function will be run immediately once, and tracks all the signals that /// subscribed in that run. And when these Signals update, it will rerun the function. /// And the effect re-tracks the signals in each run, so that it will only be re-run /// by the Signals that actually ran in the last effect run. @@ -59,6 +59,62 @@ where run_effect(effect); } +struct UpdaterEffect +where + C: Fn(Option) -> (I, T), + U: Fn(I, T) -> T, +{ + id: Id, + compute: C, + on_change: U, + value: RefCell>, + ty: PhantomData, + observers: RefCell>>, +} + +impl Drop for UpdaterEffect +where + C: Fn(Option) -> (I, T), + U: Fn(I, T) -> T, +{ + fn drop(&mut self) { + self.id.dispose(); + } +} + +/// Create an effect updater that runs `on_change` when any signals `compute` subscribes to +/// changes. `compute` is immediately run and its return value is returned from `create_updater`. +pub fn create_updater(compute: impl Fn() -> R + 'static, on_change: impl Fn(R) + 'static) -> R +where + R: 'static, +{ + create_stateful_updater(move |_| (compute(), ()), move |r, _| on_change(r)) +} + +/// Create an effect updater that runs `on_change` when any signals `compute` subscribes to +/// changes. `compute` is immediately run and its return value is returned from `create_updater`. +pub fn create_stateful_updater( + compute: impl Fn(Option) -> (R, T) + 'static, + on_change: impl Fn(R, T) -> T + 'static, +) -> R +where + T: Any + 'static, + R: 'static, +{ + let id = Id::next(); + let effect = Rc::new(UpdaterEffect { + id, + compute, + on_change, + value: RefCell::new(Box::new(None::)), + ty: PhantomData, + observers: RefCell::new(None), + }); + id.set_scope(); + + run_initial_updater_effect(effect) +} + /// Signals that's wrapped this untrack will not subscribe to any effect pub fn untrack(f: impl FnOnce() -> T) -> T { let prev_effect = RUNTIME.with(|runtime| runtime.current_effect.borrow_mut().take()); @@ -111,6 +167,39 @@ pub(crate) fn run_effect(effect: Rc) { }); } +fn run_initial_updater_effect(effect: Rc>) -> I +where + T: 'static, + I: 'static, + C: Fn(Option) -> (I, T) + 'static, + U: Fn(I, T) -> T + 'static, +{ + let effect_id = effect.id(); + + let result = RUNTIME.with(|runtime| { + *runtime.current_effect.borrow_mut() = Some(effect.clone()); + + let effect_scope = Scope(effect_id); + let (result, new_value) = with_scope(effect_scope, || { + effect_scope.track(); + (effect.compute)(None) + }); + + // set new value + let mut value = effect.value.borrow_mut(); + let value = value + .downcast_mut::>() + .expect("to downcast effect value"); + *value = Some(new_value); + + *runtime.current_effect.borrow_mut() = None; + + result + }); + + result +} + /// Do a observer clean up at the beginning of each effect run. It clears the effect /// from all the Signals that this effect subscribes to, and clears all the signals /// that's stored in this effect, so that the next effect run can re-track signals. @@ -171,3 +260,51 @@ where self.observers.borrow_mut().take() } } + +impl EffectTrait for UpdaterEffect +where + T: 'static, + C: Fn(Option) -> (I, T), + U: Fn(I, T) -> T, +{ + fn id(&self) -> Id { + self.id + } + + fn run(&self) -> bool { + let curr_value = { + // downcast value + let mut value = self.value.borrow_mut(); + let value = value + .downcast_mut::>() + .expect("to downcast effect value"); + value.take() + }; + + // run the effect + let (i, t) = (self.compute)(curr_value); + let new_value = (self.on_change)(i, t); + + // set new value + let mut value = self.value.borrow_mut(); + let value = value + .downcast_mut::>() + .expect("to downcast effect value"); + *value = Some(new_value); + + true + } + + fn add_observer(&self, id: Id) { + let mut observers = self.observers.borrow_mut(); + if let Some(observers) = observers.as_mut() { + observers.insert(id); + } else { + *observers = Some(HashSet::from_iter([id])); + } + } + + fn clear_observers(&self) -> Option> { + self.observers.borrow_mut().take() + } +} diff --git a/reactive/src/lib.rs b/reactive/src/lib.rs index 6f961fc3..e867a3c8 100644 --- a/reactive/src/lib.rs +++ b/reactive/src/lib.rs @@ -8,7 +8,7 @@ mod signal; mod trigger; pub use context::{provide_context, use_context}; -pub use effect::{batch, create_effect, untrack}; +pub use effect::{batch, create_effect, create_stateful_updater, create_updater, untrack}; pub use memo::{create_memo, Memo}; pub use scope::{as_child_of_current_scope, with_scope, Scope}; pub use signal::{create_rw_signal, create_signal, ReadSignal, RwSignal, WriteSignal}; diff --git a/reactive/src/scope.rs b/reactive/src/scope.rs index 41443af0..aada9942 100644 --- a/reactive/src/scope.rs +++ b/reactive/src/scope.rs @@ -115,7 +115,7 @@ impl Scope { } /// Runs the given code with the given Scope -pub fn with_scope(scope: Scope, f: impl FnOnce() -> T + 'static) -> T +pub fn with_scope(scope: Scope, f: impl FnOnce() -> T) -> T where T: 'static, { diff --git a/src/views/dyn_container.rs b/src/views/dyn_container.rs index d9f40b9b..c45917f9 100644 --- a/src/views/dyn_container.rs +++ b/src/views/dyn_container.rs @@ -1,4 +1,4 @@ -use floem_reactive::{as_child_of_current_scope, create_effect, Scope}; +use floem_reactive::{as_child_of_current_scope, create_updater, Scope}; use crate::{ id::Id, @@ -74,15 +74,16 @@ pub fn dyn_container Box + 'static, T: 'static>( ) -> DynamicContainer { let id = Id::next(); - create_effect(move |_| { - id.update_state(update_view(), false); + let initial = create_updater(update_view, move |new_state| { + id.update_state(new_state, false) }); let child_fn = Box::new(as_child_of_current_scope(child_fn)); + let (child, child_scope) = child_fn(initial); DynamicContainer { id, - child: Box::new(crate::views::empty()), - child_scope: Scope::new(), + child, + child_scope, child_fn, } } diff --git a/src/views/label.rs b/src/views/label.rs index f4ee8393..c8d6564b 100644 --- a/src/views/label.rs +++ b/src/views/label.rs @@ -10,7 +10,7 @@ use crate::{ unit::PxPct, view::View, }; -use floem_reactive::create_effect; +use floem_reactive::create_updater; use floem_renderer::Renderer; use kurbo::{Point, Rect}; use peniko::Color; @@ -62,11 +62,11 @@ pub fn static_label(label: impl Into) -> Label { pub fn label(label: impl Fn() -> S + 'static) -> Label { let id = Id::next(); - create_effect(move |_| { - let new_label = label().to_string(); - id.update_state(new_label, false); - }); - Label::new(id, String::new()) + let initial_label = create_updater( + move || label().to_string(), + move |new_label| id.update_state(new_label, false), + ); + Label::new(id, initial_label) } impl Label {