use std::{cell::RefCell, mem, ops::DerefMut, panic::Location};
use crate::{Lock, Lockable, Scope, Sendable, Shared, WeakCallback, WeakCallbackEmitter};
thread_local! {
static EFFECTS: RefCell<Vec<*mut EffectState<'static>>> = Default::default();
}
struct EffectState<'a> {
location: &'static Location<'static>,
#[cfg(feature = "multithread")]
callback: Shared<Lock<dyn FnMut() + Send + 'a>>,
#[cfg(not(feature = "multithread"))]
callback: Shared<Lock<dyn FnMut() + 'a>>,
dependencies: Vec<WeakCallbackEmitter>,
}
impl<'a> EffectState<'a> {
#[track_caller]
fn empty() -> Self {
Self {
location: Location::caller(),
callback: Shared::new(Lock::new(|| {})),
dependencies: Vec::new(),
}
}
fn clear_dependencies(&mut self) {
for dependency in &self.dependencies {
if let Some(dependency) = dependency.upgrade() {
let ptr = Shared::as_ptr(&self.callback);
dependency.unsubscribe(unsafe { mem::transmute(ptr) });
}
}
self.dependencies.clear();
}
}
pub(crate) fn track_callback(callback: WeakCallbackEmitter) {
EFFECTS.with(|effects| {
if let Some(effect) = effects.borrow().last() {
let effect = unsafe { &mut **effect };
effect.dependencies.push(callback);
}
});
}
pub(crate) fn untrack<T>(f: impl FnOnce() -> T) -> T {
EFFECTS.with(|effects| {
let tmp = effects.take();
let result = f();
effects.replace(tmp);
result
})
}
#[track_caller]
pub(crate) fn create_effect<'a>(cx: Scope<'a>, mut f: impl FnMut() + Sendable + 'a) {
let effect = unsafe { cx.alloc_unsafe(Lock::new(EffectState::empty())) };
let callback = Shared::new(Lock::new(move || {
EFFECTS.with(|effects| {
let len = effects.borrow().len();
let mut effect = effect.lock_mut();
let effect_ptr = effect.deref_mut() as *mut EffectState<'a>;
let static_effect_ptr = effect_ptr.cast::<EffectState<'static>>();
effect.clear_dependencies();
effects.borrow_mut().push(static_effect_ptr);
cx.drop_lock();
tracing::trace!("running effect at {}", effect.location);
f();
cx.release_drop_lock();
effects.borrow_mut().pop().expect("effect stack underflow");
for emitter in &effect.dependencies {
if let Some(emitter) = emitter.upgrade() {
let callback = unsafe { mem::transmute(&effect.callback) };
let callback = WeakCallback::new(Shared::downgrade(callback));
emitter.subscribe_weak(callback);
}
}
debug_assert_eq!(len, effects.borrow().len());
})
}));
effect.lock_mut().callback = callback.clone();
callback.lock_mut()();
}