use std::any::Any;
use std::cell::RefCell;
use std::marker::PhantomData;
use std::rc::Rc;
use super::effect::Effect;
use super::memo::Memo;
use super::node::{eq_any, NodeKind};
use super::runtime::{self, with_rt, RawId};
use super::signal::Signal;
#[derive(Copy, Clone, PartialEq, Eq)]
pub struct Scope {
pub(crate) id: RawId,
}
impl std::fmt::Debug for Scope {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Scope({}, gen {})",
self.id.key.index, self.id.key.generation
)
}
}
impl Scope {
pub fn signal<T: 'static>(self, value: T) -> Signal<T> {
let id = with_rt(|rt| {
rt.check_thread(self.id);
let key = rt.create_node(
Some(self.id.key),
NodeKind::Signal {
value: Rc::new(RefCell::new(Box::new(value) as Box<dyn Any>)),
},
);
RawId { key, rt: rt.rt_id }
});
Signal {
id,
_marker: PhantomData,
}
}
pub fn memo<T, F>(self, f: F) -> Memo<T>
where
T: PartialEq + 'static,
F: Fn() -> T + 'static,
{
let id = with_rt(|rt| {
rt.check_thread(self.id);
let compute: Rc<dyn Fn() -> Box<dyn Any>> =
Rc::new(move || Box::new(f()) as Box<dyn Any>);
let key = rt.create_node(
Some(self.id.key),
NodeKind::Memo {
value: Rc::new(RefCell::new(None)),
compute,
eq: eq_any::<T>,
},
);
RawId { key, rt: rt.rt_id }
});
Memo {
id,
_marker: PhantomData,
}
}
pub fn effect(self, f: impl FnMut() + 'static) -> Effect {
Effect::new(self, None, f)
}
pub fn effect_labeled(self, label: &'static str, f: impl FnMut() + 'static) -> Effect {
Effect::new(self, Some(label), f)
}
pub fn child(self) -> Scope {
let id = with_rt(|rt| {
rt.check_thread(self.id);
let key = rt.create_node(Some(self.id.key), NodeKind::Scope);
RawId { key, rt: rt.rt_id }
});
Scope { id }
}
pub fn on_cleanup(self, f: impl FnOnce() + 'static) {
with_rt(|rt| {
rt.check_thread(self.id);
let Some(node) = rt.graph.get_mut(self.id.key) else {
panic!("abstracttui reactive: on_cleanup on a disposed scope");
};
node.cleanups.push(Box::new(f));
});
}
pub fn run<R>(self, f: impl FnOnce() -> R) -> R {
let prev = with_rt(|rt| {
rt.check_thread(self.id);
if !rt.graph.contains(self.id.key) {
panic!("abstracttui reactive: run on a disposed scope");
}
rt.current_owner.replace(self.id.key)
});
struct OwnerGuard(Option<super::arena::Key>);
impl Drop for OwnerGuard {
fn drop(&mut self) {
runtime::restore_owner(self.0);
}
}
let _guard = OwnerGuard(prev);
f()
}
pub fn dispose(self) {
runtime::dispose_node(self.id);
}
pub fn is_alive(self) -> bool {
with_rt(|rt| rt.rt_id == self.id.rt && rt.graph.contains(self.id.key))
}
pub fn provide_context<T: Clone + 'static>(self, value: T) {
use std::any::TypeId;
with_rt(|rt| {
rt.check_thread(self.id);
if !rt.graph.contains(self.id.key) {
panic!("abstracttui reactive: provide_context on a disposed scope");
}
let entry = rt.contexts.entry(self.id.key).or_default();
let boxed: Rc<dyn Any> = Rc::new(value);
match entry.iter_mut().find(|(t, _)| *t == TypeId::of::<T>()) {
Some((_, slot)) => *slot = boxed,
None => entry.push((TypeId::of::<T>(), boxed)),
}
});
}
pub fn use_context<T: Clone + 'static>(self) -> Option<T> {
use std::any::TypeId;
with_rt(|rt| {
rt.check_thread(self.id);
let mut cur = Some(self.id.key);
while let Some(key) = cur {
if let Some(entry) = rt.contexts.get(&key) {
if let Some((_, v)) = entry.iter().find(|(t, _)| *t == TypeId::of::<T>()) {
return v.downcast_ref::<T>().cloned();
}
}
cur = rt.graph.get(key).and_then(|n| n.parent);
}
None
})
}
}
pub struct RootScope {
scope: Scope,
}
impl RootScope {
pub fn scope(&self) -> Scope {
self.scope
}
pub fn dispose(self) {
}
}
impl Drop for RootScope {
fn drop(&mut self) {
if self.scope.is_alive() {
runtime::dispose_node(self.scope.id);
}
}
}
pub fn create_root<R>(f: impl FnOnce(Scope) -> R) -> (RootScope, R) {
let scope = {
let id = with_rt(|rt| {
let key = rt.create_node(None, NodeKind::Scope);
RawId { key, rt: rt.rt_id }
});
Scope { id }
};
let result = scope.run(|| f(scope));
(RootScope { scope }, result)
}