use crate::{node::NodeId, with_runtime, Disposer, Runtime, SignalDispose};
use cfg_if::cfg_if;
use std::{any::Any, cell::RefCell, marker::PhantomData, rc::Rc};
#[cfg_attr(
any(debug_assertions, feature="ssr"),
instrument(
level = "trace",
skip_all,
fields(
ty = %std::any::type_name::<T>()
)
)
)]
#[track_caller]
#[inline(always)]
pub fn create_effect<T>(f: impl Fn(Option<T>) -> T + 'static) -> Effect<T>
where
T: 'static,
{
cfg_if! {
if #[cfg(not(feature = "ssr"))] {
use crate::{Owner, queue_microtask, with_owner};
let runtime = Runtime::current();
let owner = Owner::current();
let id = runtime.create_effect(f);
queue_microtask(move || {
with_owner(owner.unwrap(), move || {
_ = with_runtime( |runtime| {
runtime.update_if_necessary(id);
});
});
});
Effect { id, ty: PhantomData }
} else {
_ = f;
Effect { id: Default::default(), ty: PhantomData }
}
}
}
impl<T> Effect<T>
where
T: 'static,
{
#[track_caller]
#[inline(always)]
pub fn new(f: impl Fn(Option<T>) -> T + 'static) -> Self {
create_effect(f)
}
#[track_caller]
#[inline(always)]
pub fn new_isomorphic(f: impl Fn(Option<T>) -> T + 'static) -> Self {
create_isomorphic_effect(f)
}
pub fn with_value_mut<U>(
&self,
f: impl FnOnce(&mut Option<T>) -> U,
) -> Option<U> {
with_runtime(|runtime| {
let nodes = runtime.nodes.borrow();
let node = nodes.get(self.id)?;
let value = node.value.clone()?;
let mut value = value.borrow_mut();
let value = value.downcast_mut()?;
Some(f(value))
})
.ok()
.flatten()
}
}
#[cfg_attr(
any(debug_assertions, feature="ssr"),
instrument(
level = "trace",
skip_all,
fields(
ty = %std::any::type_name::<T>()
)
)
)]
#[track_caller]
#[inline(always)]
pub fn create_isomorphic_effect<T>(
f: impl Fn(Option<T>) -> T + 'static,
) -> Effect<T>
where
T: 'static,
{
let runtime = Runtime::current();
let id = runtime.create_effect(f);
_ = with_runtime(|runtime| {
runtime.update_if_necessary(id);
});
Effect {
id,
ty: PhantomData,
}
}
#[cfg_attr(
any(debug_assertions, feature="ssr"),
instrument(
level = "trace",
skip_all,
fields(
ty = %std::any::type_name::<T>()
)
)
)]
#[inline(always)]
pub fn create_render_effect<T>(
f: impl Fn(Option<T>) -> T + 'static,
) -> Effect<T>
where
T: 'static,
{
cfg_if! {
if #[cfg(not(feature = "ssr"))] {
let runtime = Runtime::current();
let id = runtime.create_effect(f);
_ = with_runtime( |runtime| {
runtime.update_if_necessary(id);
});
Effect { id, ty: PhantomData }
} else {
_ = f;
Effect { id: Default::default(), ty: PhantomData }
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub struct Effect<T> {
pub(crate) id: NodeId,
ty: PhantomData<T>,
}
impl<T> From<Effect<T>> for Disposer {
fn from(effect: Effect<T>) -> Self {
Disposer(effect.id)
}
}
impl<T> SignalDispose for Effect<T> {
fn dispose(self) {
drop(Disposer::from(self));
}
}
pub(crate) struct EffectState<T, F>
where
T: 'static,
F: Fn(Option<T>) -> T,
{
pub(crate) f: F,
pub(crate) ty: PhantomData<T>,
#[cfg(any(debug_assertions, feature = "ssr"))]
pub(crate) defined_at: &'static std::panic::Location<'static>,
}
pub(crate) trait AnyComputation {
fn run(&self, value: Rc<RefCell<dyn Any>>) -> bool;
}
impl<T, F> AnyComputation for EffectState<T, F>
where
T: 'static,
F: Fn(Option<T>) -> T,
{
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
instrument(
name = "Effect::run()",
level = "trace",
skip_all,
fields(
defined_at = %self.defined_at,
ty = %std::any::type_name::<T>()
)
)
)]
fn run(&self, value: Rc<RefCell<dyn Any>>) -> bool {
let curr_value = {
let mut value = value.borrow_mut();
let value = value
.downcast_mut::<Option<T>>()
.expect("to downcast effect value");
value.take()
};
let new_value = (self.f)(curr_value);
let mut value = value.borrow_mut();
let value = value
.downcast_mut::<Option<T>>()
.expect("to downcast effect value");
*value = Some(new_value);
true
}
}