use std::any::Any;
use std::mem;
use std::rc::Weak;
use super::*;
use std::cell::RefCell;
use std::collections::HashSet;
use std::hash::{Hash, Hasher};
use std::ptr;
use std::rc::Rc;
thread_local! {
pub(super) static CONTEXTS: RefCell<Vec<Weak<RefCell<Option<Running>>>>> = RefCell::new(Vec::new());
pub(super) static OWNER: RefCell<Option<Owner>> = RefCell::new(None);
}
pub(super) struct Running {
pub(super) execute: Rc<dyn Fn()>,
pub(super) dependencies: HashSet<Dependency>,
owner: Owner,
}
impl Running {
fn clear_dependencies(&mut self) {
for dependency in &self.dependencies {
dependency
.signal()
.unsubscribe(&Callback(Rc::downgrade(&self.execute)));
}
self.dependencies.clear();
}
}
impl Drop for Running {
fn drop(&mut self) {
self.clear_dependencies();
}
}
#[derive(Default)]
pub struct Owner {
effects: Vec<Rc<RefCell<Option<Running>>>>,
cleanup: Vec<Box<dyn FnOnce()>>,
}
impl Owner {
pub fn new() -> Self {
Self::default()
}
pub(super) fn add_effect_state(&mut self, effect: Rc<RefCell<Option<Running>>>) {
self.effects.push(effect);
}
pub(super) fn add_cleanup(&mut self, cleanup: Box<dyn FnOnce()>) {
self.cleanup.push(cleanup);
}
}
impl Drop for Owner {
fn drop(&mut self) {
for effect in &self.effects {
effect.borrow_mut().as_mut().unwrap().clear_dependencies();
}
for cleanup in mem::take(&mut self.cleanup) {
cleanup();
}
}
}
#[derive(Clone)]
pub(super) struct Callback(pub(super) Weak<dyn Fn()>);
impl Callback {
#[track_caller]
#[must_use = "returned value must be manually called"]
pub fn callback(&self) -> Rc<dyn Fn()> {
self.try_callback().expect("callback is not valid anymore")
}
#[must_use = "returned value must be manually called"]
pub fn try_callback(&self) -> Option<Rc<dyn Fn()>> {
self.0.upgrade()
}
}
impl Hash for Callback {
fn hash<H: Hasher>(&self, state: &mut H) {
Rc::as_ptr(&self.callback()).hash(state);
}
}
impl PartialEq for Callback {
fn eq(&self, other: &Self) -> bool {
ptr::eq::<()>(
Rc::as_ptr(&self.callback()).cast(),
Rc::as_ptr(&other.callback()).cast(),
)
}
}
impl Eq for Callback {}
#[derive(Clone)]
pub(super) struct Dependency(pub(super) Weak<dyn AnySignalInner>);
impl Dependency {
fn signal(&self) -> Rc<dyn AnySignalInner> {
self.0.upgrade().expect("backlink should always be valid")
}
}
impl Hash for Dependency {
fn hash<H: Hasher>(&self, state: &mut H) {
Rc::as_ptr(&self.signal()).hash(state);
}
}
impl PartialEq for Dependency {
fn eq(&self, other: &Self) -> bool {
ptr::eq::<()>(
Rc::as_ptr(&self.signal()).cast(),
Rc::as_ptr(&other.signal()).cast(),
)
}
}
impl Eq for Dependency {}
pub fn create_effect_initial<R: 'static>(
initial: impl FnOnce() -> (Rc<dyn Fn()>, R) + 'static,
) -> R {
type InitialFn = dyn FnOnce() -> (Rc<dyn Fn()>, Box<dyn Any>);
fn internal(initial: Box<InitialFn>) -> Box<dyn Any> {
let running: Rc<RefCell<Option<Running>>> = Rc::new(RefCell::new(None));
type MutEffect = Rc<RefCell<Option<Rc<dyn Fn()>>>>;
let effect: MutEffect = Rc::new(RefCell::new(None));
let ret: Rc<RefCell<Option<Box<dyn Any>>>> = Rc::new(RefCell::new(None));
let initial = RefCell::new(Some(initial));
let execute: Rc<dyn Fn()> = Rc::new({
let running = Rc::downgrade(&running);
let ret = Rc::downgrade(&ret);
move || {
CONTEXTS.with(|contexts| {
let initial_context_size = contexts.borrow().len();
let running = running.upgrade().unwrap();
running.borrow_mut().as_mut().unwrap().clear_dependencies();
contexts.borrow_mut().push(Rc::downgrade(&running));
if let Some(initial) = initial.take() {
let effect = Rc::clone(&effect);
let ret = Weak::upgrade(&ret).unwrap();
let owner = create_root(move || {
let (effect_tmp, ret_tmp) = initial(); *effect.borrow_mut() = Some(effect_tmp);
*ret.borrow_mut() = Some(ret_tmp);
});
running.borrow_mut().as_mut().unwrap().owner = owner;
} else {
let old_owner = mem::replace(
&mut running.borrow_mut().as_mut().unwrap().owner,
Owner::new(),
);
drop(old_owner);
let effect = Rc::clone(&effect);
let owner = create_root(move || {
effect.borrow().as_ref().unwrap()();
});
running.borrow_mut().as_mut().unwrap().owner = owner;
}
for dependency in &running.borrow().as_ref().unwrap().dependencies {
dependency.signal().subscribe(Callback(Rc::downgrade(
&running.borrow().as_ref().unwrap().execute,
)));
}
contexts.borrow_mut().pop();
debug_assert_eq!(
initial_context_size,
contexts.borrow().len(),
"context size should not change before and after create_effect_initial"
);
});
}
});
*running.borrow_mut() = Some(Running {
execute: Rc::clone(&execute),
dependencies: HashSet::new(),
owner: Owner::new(),
});
debug_assert_eq!(
Rc::strong_count(&running),
1,
"Running should be owned exclusively by owner"
);
OWNER.with(|owner| {
if owner.borrow().is_some() {
owner
.borrow_mut()
.as_mut()
.unwrap()
.add_effect_state(running);
} else {
#[cfg(all(target_arch = "wasm32", debug_assertions))]
web_sys::console::warn_1(
&"Effects created outside of a reactive root will never get disposed.".into(),
);
#[cfg(all(not(target_arch = "wasm32"), debug_assertions))]
eprintln!(
"WARNING: Effects created outside of a reactive root will never get dropped."
);
Rc::into_raw(running); }
});
execute();
let ret = Rc::try_unwrap(ret).expect("ret should only have 1 strong reference");
ret.into_inner().unwrap()
}
let ret = internal(Box::new(|| {
let (effect, ret) = initial();
(effect, Box::new(ret))
}));
*ret.downcast::<R>().unwrap()
}
pub fn create_effect<F>(effect: F)
where
F: Fn() + 'static,
{
fn internal(effect: Rc<dyn Fn()>) {
create_effect_initial(move || {
effect();
(effect, ())
})
}
internal(Rc::new(effect));
}
pub fn create_memo<F, Out>(derived: F) -> StateHandle<Out>
where
F: Fn() -> Out + 'static,
Out: 'static,
{
create_selector_with(derived, |_, _| false)
}
pub fn create_selector<F, Out>(derived: F) -> StateHandle<Out>
where
F: Fn() -> Out + 'static,
Out: PartialEq + 'static,
{
create_selector_with(derived, PartialEq::eq)
}
pub fn create_selector_with<F, Out, C>(derived: F, comparator: C) -> StateHandle<Out>
where
F: Fn() -> Out + 'static,
Out: 'static,
C: Fn(&Out, &Out) -> bool + 'static,
{
let derived = Rc::new(derived);
let comparator = Rc::new(comparator);
create_effect_initial(move || {
let memo = Signal::new(derived());
let effect = Rc::new({
let memo = memo.clone();
let derived = Rc::clone(&derived);
move || {
let new_value = derived();
if !comparator(&memo.get_untracked(), &new_value) {
memo.set(new_value);
}
}
});
(effect, memo.into_handle())
})
}
pub fn untrack<T>(f: impl FnOnce() -> T) -> T {
CONTEXTS.with(|contexts| {
let tmp = contexts.take();
let ret = f();
*contexts.borrow_mut() = tmp;
ret
})
}
pub fn on_cleanup(f: impl FnOnce() + 'static) {
OWNER.with(|owner| {
if owner.borrow().is_some() {
owner
.borrow_mut()
.as_mut()
.unwrap()
.add_cleanup(Box::new(f));
} else {
#[cfg(all(target_arch = "wasm32", debug_assertions))]
web_sys::console::warn_1(
&"Cleanup callbacks created outside of a reactive root will never run.".into(),
);
#[cfg(all(not(target_arch = "wasm32"), debug_assertions))]
eprintln!(
"WARNING: Cleanup callbacks created outside of a reactive root will never run."
);
}
});
}
#[cfg(test)]
mod tests {
use super::*;
use crate::cloned;
#[test]
fn effects() {
let state = Signal::new(0);
let double = Signal::new(-1);
create_effect(cloned!((state, double) => move || {
double.set(*state.get() * 2);
}));
assert_eq!(*double.get(), 0);
state.set(1);
assert_eq!(*double.get(), 2);
state.set(2);
assert_eq!(*double.get(), 4);
}
#[test]
#[ignore]
#[should_panic(expected = "cannot create cyclic dependency")]
fn cyclic_effects_fail() {
let state = Signal::new(0);
create_effect(cloned!((state) => move || {
state.set(*state.get() + 1);
}));
state.set(1);
}
#[test]
#[ignore]
#[should_panic(expected = "cannot create cyclic dependency")]
fn cyclic_effects_fail_2() {
let state = Signal::new(0);
create_effect(cloned!((state) => move || {
let value = *state.get();
state.set(value + 1);
}));
state.set(1);
}
#[test]
fn effect_should_subscribe_once() {
let state = Signal::new(0);
let counter = Signal::new(0);
create_effect(cloned!((state, counter) => move || {
counter.set(*counter.get_untracked() + 1);
state.get();
state.get();
}));
assert_eq!(*counter.get(), 1);
state.set(1);
assert_eq!(*counter.get(), 2);
}
#[test]
fn effect_should_recreate_dependencies() {
let condition = Signal::new(true);
let state1 = Signal::new(0);
let state2 = Signal::new(1);
let counter = Signal::new(0);
create_effect(cloned!((condition, state1, state2, counter) => move || {
counter.set(*counter.get_untracked() + 1);
if *condition.get() {
state1.get();
} else {
state2.get();
}
}));
assert_eq!(*counter.get(), 1);
state1.set(1);
assert_eq!(*counter.get(), 2);
state2.set(1);
assert_eq!(*counter.get(), 2);
condition.set(false);
assert_eq!(*counter.get(), 3);
state1.set(2);
assert_eq!(*counter.get(), 3);
state2.set(2);
assert_eq!(*counter.get(), 4); }
#[test]
fn nested_effects_should_recreate_inner() {
let counter = Signal::new(0);
let trigger = Signal::new(());
create_effect(cloned!((trigger, counter) => move || {
trigger.get();
create_effect(cloned!((counter) => move || {
counter.set(*counter.get_untracked() + 1);
}));
}));
assert_eq!(*counter.get(), 1);
trigger.set(());
assert_eq!(*counter.get(), 2); }
#[test]
fn destroy_effects_on_owner_drop() {
let counter = Signal::new(0);
let trigger = Signal::new(());
let owner = create_root(cloned!((trigger, counter) => move || {
create_effect(move || {
trigger.get(); counter.set(*counter.get_untracked() + 1);
});
}));
assert_eq!(*counter.get(), 1);
trigger.set(());
assert_eq!(*counter.get(), 2);
drop(owner);
trigger.set(());
assert_eq!(*counter.get(), 2); }
#[test]
fn memo() {
let state = Signal::new(0);
let double = create_memo(cloned!((state) => move || *state.get() * 2));
assert_eq!(*double.get(), 0);
state.set(1);
assert_eq!(*double.get(), 2);
state.set(2);
assert_eq!(*double.get(), 4);
}
#[test]
fn memo_only_run_once() {
let state = Signal::new(0);
let counter = Signal::new(0);
let double = create_memo(cloned!((state, counter) => move || {
counter.set(*counter.get_untracked() + 1);
*state.get() * 2
}));
assert_eq!(*counter.get(), 1);
state.set(2);
assert_eq!(*counter.get(), 2);
assert_eq!(*double.get(), 4);
assert_eq!(*counter.get(), 2); }
#[test]
fn dependency_on_memo() {
let state = Signal::new(0);
let double = create_memo(cloned!((state) => move || *state.get() * 2));
let quadruple = create_memo(move || *double.get() * 2);
assert_eq!(*quadruple.get(), 0);
state.set(1);
assert_eq!(*quadruple.get(), 4);
}
#[test]
fn untracked_memo() {
let state = Signal::new(1);
let double = create_memo(cloned!((state) => move || *state.get_untracked() * 2));
assert_eq!(*double.get(), 2);
state.set(2);
assert_eq!(*double.get(), 2); }
#[test]
fn selector() {
let state = Signal::new(0);
let double = create_selector(cloned!((state) => move || *state.get() * 2));
let counter = Signal::new(0);
create_effect(cloned!((counter, double) => move || {
counter.set(*counter.get_untracked() + 1);
double.get();
}));
assert_eq!(*double.get(), 0);
assert_eq!(*counter.get(), 1);
state.set(0);
assert_eq!(*double.get(), 0);
assert_eq!(*counter.get(), 1);
state.set(2);
assert_eq!(*double.get(), 4);
assert_eq!(*counter.get(), 2);
}
#[test]
fn cleanup() {
let cleanup_called = Signal::new(false);
let owner = create_root(cloned!((cleanup_called) => move || {
on_cleanup(move || {
cleanup_called.set(true);
})
}));
assert_eq!(*cleanup_called.get(), false);
drop(owner);
assert_eq!(*cleanup_called.get(), true);
}
#[test]
fn cleanup_in_effect() {
let trigger = Signal::new(());
let counter = Signal::new(0);
create_effect(cloned!((trigger, counter) => move || {
trigger.get();
on_cleanup(cloned!((counter) => move || {
counter.set(*counter.get() + 1);
}));
}));
assert_eq!(*counter.get(), 0);
trigger.set(());
assert_eq!(*counter.get(), 1);
trigger.set(());
assert_eq!(*counter.get(), 2);
}
}