use std::cell::RefCell;
use std::ops::{Deref, DerefMut};
use std::rc::{Rc, Weak};
use slotmap::{SlotMap, new_key_type};
use crate::component::ComponentBase;
use crate::render_callbacks::DummyHook;
use crate::signal::{ReactiveHook, RenderingState, SignalMethods, UpdateResult};
use crate::utils::{SmallAny, debug_expect};
pub trait ComponentData: Sized + 'static {
type FieldRef<'a>: IntoIterator<Item = &'a mut dyn SignalMethods>;
type SignalState;
#[doc(hidden)]
fn signals_mut(&mut self) -> Self::FieldRef<'_>;
fn pop_signals(&mut self) -> Self::SignalState;
fn set_signals(&mut self, state: Self::SignalState);
}
pub(crate) type KeepAlive = Box<dyn SmallAny>;
new_key_type! { pub(crate) struct HookKey; }
pub struct State<T> {
pub(crate) data: T,
this: Option<Weak<RefCell<Self>>>,
hooks: SlotMap<HookKey, (Box<dyn ReactiveHook<T>>, u64)>,
next_insertion_order_value: u64,
}
impl<T> Deref for State<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.data
}
}
impl<T> DerefMut for State<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.data
}
}
pub type S<C> = State<<C as ComponentBase>::Data>;
pub type R<'a, 'c, C> = &'a mut RenderCtx<'c, <C as ComponentBase>::Data>;
impl<T> State<T> {
pub(crate) fn new(data: T) -> Rc<RefCell<Self>> {
let this = Self {
data,
this: None,
hooks: SlotMap::default(),
next_insertion_order_value: 0,
};
let this = Rc::new(RefCell::new(this));
this.borrow_mut().this = Some(Rc::downgrade(&this));
this
}
#[expect(clippy::expect_used, reason = "This is always set in the `new` method")]
pub(crate) fn weak(&self) -> Weak<RefCell<Self>> {
self.this.as_ref().expect("Weak not set").clone()
}
}
impl<T: ComponentData> State<T> {
pub(crate) fn clear(&mut self) {
for signal in self.data.signals_mut() {
signal.clear();
}
}
pub(crate) fn insert_hook(&mut self, hook: Box<dyn ReactiveHook<T>>) -> HookKey {
let key = self.hooks.insert((hook, self.next_insertion_order_value));
self.next_insertion_order_value = debug_expect!(
self.next_insertion_order_value.checked_add(1),
or(0),
"Overflowed hook insertion value"
);
key
}
pub(crate) fn set_hook(&mut self, key: HookKey, hook: Box<dyn ReactiveHook<T>>) {
if let Some(slot) = self.hooks.get_mut(key) {
slot.0 = hook;
}
}
pub(crate) fn reg_dep(&mut self, dep: HookKey) {
for signal in self.data.signals_mut() {
signal.register_dep(dep);
}
}
fn run_with_hook_and_self<F, R>(&mut self, hook: HookKey, func: F) -> Option<R>
where
F: FnOnce(&mut Self, &mut Box<dyn ReactiveHook<T>>) -> R,
{
let slot_ref = self.hooks.get_mut(hook)?;
let mut temp_hook: Box<dyn ReactiveHook<T>> = Box::new(DummyHook);
std::mem::swap(&mut slot_ref.0, &mut temp_hook);
let res = func(self, &mut temp_hook);
let slot_ref = self.hooks.get_mut(hook)?;
slot_ref.0 = temp_hook;
Some(res)
}
pub(crate) fn update(&mut self) {
let mut hooks = Vec::new();
for signal in self.data.signals_mut() {
if signal.changed() {
hooks.extend(signal.deps());
}
}
hooks.sort_unstable_by_key(|hook_key| Some(self.hooks.get(*hook_key)?.1));
while !hooks.is_empty() {
for hook_key in std::mem::take(&mut hooks) {
self.run_with_hook_and_self(hook_key, |ctx, hook| {
drop_hook_children(ctx, hook);
match hook.update(ctx, hook_key) {
UpdateResult::Nothing => {}
UpdateResult::RunHook(dep) => {
hooks.push(dep);
ctx.run_with_hook_and_self(dep, |ctx, hook| {
drop_hook_children(ctx, hook);
});
}
}
});
}
}
}
pub fn get<F, A>(&self, guard: &Guard<F>) -> A
where
F: Fn(&Self) -> A,
{
(guard.getter)(self)
}
}
fn drop_hook_children<T: ComponentData>(ctx: &mut State<T>, hook: &mut Box<dyn ReactiveHook<T>>) {
if let Some(invalid_hooks) = hook.drop_deps() {
for invalid_hook in invalid_hooks {
if let Some(mut hook) = ctx.hooks.remove(invalid_hook) {
drop_hook_children(ctx, &mut hook.0);
}
}
}
}
pub struct RenderCtx<'c, C> {
pub(crate) ctx: &'c mut State<C>,
pub(crate) render_state: RenderingState<'c>,
}
impl<C> Deref for RenderCtx<'_, C> {
type Target = C;
fn deref(&self) -> &Self::Target {
&self.ctx.data
}
}
impl<C: ComponentData> RenderCtx<'_, C> {
pub fn watch<T, F>(&mut self, func: F) -> T
where
F: Fn(&State<C>) -> T + 'static,
T: PartialEq + Clone + 'static,
{
let signal_state = self.ctx.pop_signals();
let result = func(self.ctx);
let hook = WatchState {
calc_value: Box::new(func),
last_value: result.clone(),
dep: self.render_state.parent_dep,
};
let me = self.ctx.insert_hook(Box::new(hook));
self.ctx.reg_dep(me);
self.render_state.hooks.push(me);
self.ctx.set_signals(signal_state);
result
}
pub fn get<F, A>(&self, guard: &Guard<F>) -> A
where
F: Fn(&State<C>) -> A,
{
self.ctx.get(guard)
}
}
struct WatchState<F, T> {
calc_value: F,
last_value: T,
dep: HookKey,
}
impl<C, F, T> ReactiveHook<C> for WatchState<F, T>
where
C: ComponentData,
T: PartialEq,
F: Fn(&State<C>) -> T,
{
fn update(&mut self, ctx: &mut State<C>, you: HookKey) -> UpdateResult {
ctx.clear();
let new_value = (self.calc_value)(ctx);
ctx.reg_dep(you);
if new_value == self.last_value {
UpdateResult::Nothing
} else {
UpdateResult::RunHook(self.dep)
}
}
}
#[cfg_attr(feature = "nightly", must_not_suspend)]
#[derive(Clone, Copy)]
pub struct Guard<F> {
getter: F,
}
#[macro_export]
macro_rules! guard_option {
($ctx:ident. $($getter:tt)+) => {
if $ctx.watch(move |ctx| ctx.$($getter)+.is_some()) {
Some(::natrix::macro_ref::Guard::new(
move |ctx: &::natrix::macro_ref::S<Self>| ctx.$($getter)+.expect("Guard used on None value"),
))
} else {
None
}
};
}
#[macro_export]
macro_rules! guard_result {
($ctx:ident. $($getter:tt)+) => {
if $ctx.watch(move |ctx| ctx.$($getter)+.is_ok()) {
Ok(::natrix::macro_ref::Guard::new(
move |ctx: &::natrix::macro_ref::S<Self>| ctx.$($getter)+.expect("Ok-Guard used on Err value"),
))
} else {
Err(::natrix::macro_ref::Guard::new(
move |ctx: &::natrix::macro_ref::S<Self>| ctx.$($getter)+.expect_err("Err-Guard used on Ok value"),
))
}
};
}
impl<F> Guard<F> {
#[doc(hidden)]
pub fn new(getter: F) -> Self {
Self { getter }
}
}
#[cfg(feature = "async")]
pub mod async_impl {
use std::cell::{RefCell, RefMut};
use std::marker::PhantomData;
use std::ops::{Deref, DerefMut};
use std::rc::{Rc, Weak};
use ouroboros::self_referencing;
use super::{ComponentData, State};
pub struct AsyncCtx<T> {
inner: Weak<RefCell<State<T>>>,
}
#[self_referencing]
struct AsyncRefInner<'p, T: 'static> {
rc: Rc<RefCell<State<T>>>,
lifetime: PhantomData<&'p ()>,
#[borrows(rc)]
#[covariant]
reference: RefMut<'this, State<T>>,
}
#[cfg_attr(feature = "nightly", must_not_suspend)]
pub struct AsyncRef<'p, T: ComponentData + 'static>(AsyncRefInner<'p, T>);
impl<T: ComponentData> AsyncCtx<T> {
pub fn borrow_mut(&mut self) -> Option<AsyncRef<'_, T>> {
crate::return_if_panic!(None);
let rc = self.inner.upgrade()?;
let mut borrow = AsyncRefInner::new(rc, PhantomData, |rc| rc.borrow_mut());
borrow.with_reference_mut(|ctx| ctx.clear());
Some(AsyncRef(borrow))
}
}
impl<T: ComponentData> Deref for AsyncRef<'_, T> {
type Target = State<T>;
fn deref(&self) -> &Self::Target {
self.0.borrow_reference()
}
}
impl<T: ComponentData> DerefMut for AsyncRef<'_, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.0.with_reference_mut(|cell| &mut **cell)
}
}
impl<T: ComponentData> Drop for AsyncRef<'_, T> {
fn drop(&mut self) {
self.0.with_reference_mut(|ctx| {
ctx.update();
});
}
}
impl<T: ComponentData> State<T> {
fn get_async_ctx(&mut self) -> AsyncCtx<T> {
AsyncCtx { inner: self.weak() }
}
pub fn use_async<C, F>(&mut self, func: C)
where
C: FnOnce(AsyncCtx<T>) -> F,
F: Future<Output = ()> + 'static,
{
wasm_bindgen_futures::spawn_local(func(self.get_async_ctx()));
}
}
}