#![forbid(unsafe_code)]
use crate::runtime::{with_runtime, RuntimeId};
use crate::{debug_warn, Runtime, Scope, ScopeProperty};
use cfg_if::cfg_if;
use std::cell::RefCell;
use std::fmt::Debug;
#[cfg_attr(
debug_assertions,
instrument(
level = "trace",
skip_all,
fields(
scope = ?cx.id,
ty = %std::any::type_name::<T>()
)
)
)]
#[track_caller]
pub fn create_effect<T>(cx: Scope, f: impl Fn(Option<T>) -> T + 'static)
where
T: 'static,
{
cfg_if! {
if #[cfg(not(feature = "ssr"))] {
let e = cx.runtime.create_effect(f);
cx.with_scope_property(|prop| prop.push(ScopeProperty::Effect(e)))
} else {
_ = cx;
_ = f;
}
}
}
#[cfg_attr(
debug_assertions,
instrument(
level = "trace",
skip_all,
fields(
scope = ?cx.id,
ty = %std::any::type_name::<T>()
)
)
)]
#[track_caller]
pub fn create_isomorphic_effect<T>(cx: Scope, f: impl Fn(Option<T>) -> T + 'static)
where
T: 'static,
{
let e = cx.runtime.create_effect(f);
cx.with_scope_property(|prop| prop.push(ScopeProperty::Effect(e)))
}
#[doc(hidden)]
#[cfg_attr(
debug_assertions,
instrument(
level = "trace",
skip_all,
fields(
scope = ?cx.id,
ty = %std::any::type_name::<T>()
)
)
)]
pub fn create_render_effect<T>(cx: Scope, f: impl Fn(Option<T>) -> T + 'static)
where
T: 'static,
{
create_effect(cx, f);
}
slotmap::new_key_type! {
pub(crate) struct EffectId;
}
pub(crate) struct Effect<T, F>
where
T: 'static,
F: Fn(Option<T>) -> T,
{
pub(crate) f: F,
pub(crate) value: RefCell<Option<T>>,
#[cfg(debug_assertions)]
pub(crate) defined_at: &'static std::panic::Location<'static>,
}
pub(crate) trait AnyEffect {
fn run(&self, id: EffectId, runtime: RuntimeId);
}
impl<T, F> AnyEffect for Effect<T, F>
where
T: 'static,
F: Fn(Option<T>) -> T,
{
#[cfg_attr(
debug_assertions,
instrument(
name = "Effect::run()",
level = "debug",
skip_all,
fields(
id = ?id,
defined_at = %self.defined_at,
ty = %std::any::type_name::<T>()
)
)
)]
fn run(&self, id: EffectId, runtime: RuntimeId) {
_ = with_runtime(runtime, |runtime| {
id.cleanup(runtime);
let prev_observer = runtime.observer.take();
runtime.observer.set(Some(id));
let value = self.value.take();
let new_value = (self.f)(value);
*self.value.borrow_mut() = Some(new_value);
runtime.observer.set(prev_observer);
})
}
}
impl EffectId {
pub(crate) fn run<T>(&self, runtime_id: RuntimeId) {
_ = with_runtime(runtime_id, |runtime| {
let effect = {
let effects = runtime.effects.borrow();
effects.get(*self).cloned()
};
if let Some(effect) = effect {
effect.run(*self, runtime_id);
} else {
debug_warn!("[Effect] Trying to run an Effect that has been disposed. This is probably either a logic error in a component that creates and disposes of scopes, or a Resource resolving after its scope has been dropped without having been cleaned up.")
}
})
}
#[cfg_attr(
debug_assertions,
instrument(
name = "Effect::cleanup()",
level = "debug",
skip_all,
fields(
id = ?self,
)
)
)]
pub(crate) fn cleanup(&self, runtime: &Runtime) {
let sources = runtime.effect_sources.borrow();
if let Some(sources) = sources.get(*self) {
let subs = runtime.signal_subscribers.borrow();
for source in sources.borrow().iter() {
if let Some(source) = subs.get(*source) {
source.borrow_mut().remove(self);
}
}
}
}
}