Skip to content

Commit

Permalink
Add create_updater and create_stateful_updater (#172)
Browse files Browse the repository at this point in the history
  • Loading branch information
Zoxc authored Nov 13, 2023
1 parent 72167e4 commit 4a308fb
Show file tree
Hide file tree
Showing 5 changed files with 152 additions and 14 deletions.
139 changes: 138 additions & 1 deletion reactive/src/effect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -59,6 +59,62 @@ where
run_effect(effect);
}

struct UpdaterEffect<T, I, C, U>
where
C: Fn(Option<T>) -> (I, T),
U: Fn(I, T) -> T,
{
id: Id,
compute: C,
on_change: U,
value: RefCell<Box<dyn Any>>,
ty: PhantomData<T>,
observers: RefCell<Option<HashSet<Id>>>,
}

impl<T, I, C, U> Drop for UpdaterEffect<T, I, C, U>
where
C: Fn(Option<T>) -> (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<R>(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<T, R>(
compute: impl Fn(Option<T>) -> (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::<T>)),
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<T>(f: impl FnOnce() -> T) -> T {
let prev_effect = RUNTIME.with(|runtime| runtime.current_effect.borrow_mut().take());
Expand Down Expand Up @@ -111,6 +167,39 @@ pub(crate) fn run_effect(effect: Rc<dyn EffectTrait>) {
});
}

fn run_initial_updater_effect<T, I, C, U>(effect: Rc<UpdaterEffect<T, I, C, U>>) -> I
where
T: 'static,
I: 'static,
C: Fn(Option<T>) -> (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::<Option<T>>()
.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.
Expand Down Expand Up @@ -171,3 +260,51 @@ where
self.observers.borrow_mut().take()
}
}

impl<T, I, C, U> EffectTrait for UpdaterEffect<T, I, C, U>
where
T: 'static,
C: Fn(Option<T>) -> (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::<Option<T>>()
.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::<Option<T>>()
.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<HashSet<Id>> {
self.observers.borrow_mut().take()
}
}
2 changes: 1 addition & 1 deletion reactive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down
2 changes: 1 addition & 1 deletion reactive/src/scope.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ impl Scope {
}

/// Runs the given code with the given Scope
pub fn with_scope<T>(scope: Scope, f: impl FnOnce() -> T + 'static) -> T
pub fn with_scope<T>(scope: Scope, f: impl FnOnce() -> T) -> T
where
T: 'static,
{
Expand Down
11 changes: 6 additions & 5 deletions src/views/dyn_container.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -74,15 +74,16 @@ pub fn dyn_container<CF: Fn(T) -> Box<dyn View> + 'static, T: 'static>(
) -> DynamicContainer<T> {
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,
}
}
Expand Down
12 changes: 6 additions & 6 deletions src/views/label.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -62,11 +62,11 @@ pub fn static_label(label: impl Into<String>) -> Label {

pub fn label<S: Display + 'static>(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 {
Expand Down

0 comments on commit 4a308fb

Please sign in to comment.