use std::any::TypeId;
use std::cell::{Cell, RefCell};
use std::rc::{Rc, Weak};
pub(crate) mod runtime;
use runtime::{CurrentOwnerGuard, EffectCell, MemoData, NodeId, OwnerData, OwnerState, SignalData};
pub fn init() {
runtime::with_runtime(|_| {});
}
pub fn flush() -> bool {
runtime::flush()
}
pub fn set_wake_callback(callback: impl Fn() + 'static) {
let scope = runtime::with_runtime(|runtime| runtime.current_scope());
scope.wake.replace(Some(Rc::new(callback)));
}
pub mod prelude {
pub use super::{Get, Read, Set, Update, With, provide_context, signal_local, use_context};
}
pub trait With<T: ?Sized + 'static> {
fn with<U>(&self, fun: impl FnOnce(&T) -> U) -> U;
}
pub trait Read<T: ?Sized + 'static> {
fn read<U>(&self, fun: impl FnOnce(&T) -> U) -> U;
}
pub trait Get<T: Clone + 'static>: With<T> {
fn get(&self) -> T {
self.with(T::clone)
}
}
impl<S, T> Get<T> for S
where
S: With<T> + ?Sized,
T: Clone + 'static,
{
}
pub trait Update<T: 'static> {
fn update<U>(&self, fun: impl FnOnce(&mut T) -> U) -> U;
}
pub trait Set<T: 'static>: Update<T> {
fn set(&self, value: T) {
self.update(|slot| *slot = value);
}
}
impl<S, T> Set<T> for S
where
S: Update<T> + ?Sized,
T: 'static,
{
}
pub struct RwSignal<T> {
data: Rc<SignalData<T>>,
}
impl<T: 'static> RwSignal<T> {
pub fn new(value: T) -> RwSignal<T> {
let node = runtime::with_runtime(runtime::Runtime::new_signal);
RwSignal {
data: Rc::new(SignalData {
value: RefCell::new(value),
node: Cell::new(node),
}),
}
}
}
impl<T: 'static> Clone for RwSignal<T> {
fn clone(&self) -> RwSignal<T> {
RwSignal {
data: Rc::clone(&self.data),
}
}
}
impl<T: 'static> Clone for ReadSignal<T> {
fn clone(&self) -> ReadSignal<T> {
ReadSignal {
data: Rc::clone(&self.data),
}
}
}
impl<T: 'static> Clone for WriteSignal<T> {
fn clone(&self) -> WriteSignal<T> {
WriteSignal {
data: Rc::clone(&self.data),
}
}
}
impl<T: 'static> Clone for Memo<T> {
fn clone(&self) -> Memo<T> {
Memo {
data: Rc::clone(&self.data),
}
}
}
impl<T: 'static> With<T> for RwSignal<T> {
fn with<U>(&self, fun: impl FnOnce(&T) -> U) -> U {
runtime::track_read(self.data.node.get());
fun(&self.data.value.borrow())
}
}
impl<T: 'static> Read<T> for RwSignal<T> {
fn read<U>(&self, fun: impl FnOnce(&T) -> U) -> U {
fun(&self.data.value.borrow())
}
}
impl<T: 'static> Update<T> for RwSignal<T> {
fn update<U>(&self, fun: impl FnOnce(&mut T) -> U) -> U {
let result = fun(&mut self.data.value.borrow_mut());
let wake = runtime::with_runtime(|runtime| runtime.notify(self.data.node.get()));
runtime::fire_wakes(wake);
result
}
}
pub struct ReadSignal<T> {
data: Rc<SignalData<T>>,
}
impl<T: 'static> With<T> for ReadSignal<T> {
fn with<U>(&self, fun: impl FnOnce(&T) -> U) -> U {
runtime::track_read(self.data.node.get());
fun(&self.data.value.borrow())
}
}
impl<T: 'static> Read<T> for ReadSignal<T> {
fn read<U>(&self, fun: impl FnOnce(&T) -> U) -> U {
fun(&self.data.value.borrow())
}
}
pub struct WriteSignal<T> {
data: Rc<SignalData<T>>,
}
impl<T: 'static> Update<T> for WriteSignal<T> {
fn update<U>(&self, fun: impl FnOnce(&mut T) -> U) -> U {
let result = fun(&mut self.data.value.borrow_mut());
let wake = runtime::with_runtime(|runtime| runtime.notify(self.data.node.get()));
runtime::fire_wakes(wake);
result
}
}
pub fn signal_local<T: 'static>(value: T) -> (ReadSignal<T>, WriteSignal<T>) {
let node = runtime::with_runtime(runtime::Runtime::new_signal);
let data = Rc::new(SignalData {
value: RefCell::new(value),
node: Cell::new(node),
});
(
ReadSignal {
data: Rc::clone(&data),
},
WriteSignal { data },
)
}
pub struct Memo<T> {
data: Rc<MemoData<T>>,
}
impl<T: 'static> Memo<T> {
pub fn new(compute: impl Fn() -> T + 'static) -> Memo<T>
where
T: PartialEq,
{
let data = Rc::new(MemoData {
value: RefCell::new(None),
node: Cell::new(NodeId::default()),
});
let stored = Rc::clone(&data);
let run: Rc<dyn Fn()> = Rc::new(move || {
let value = compute();
let changed = stored
.value
.borrow()
.as_ref()
.is_none_or(|old| *old != value);
if changed {
*stored.value.borrow_mut() = Some(value);
let wake = runtime::with_runtime(|runtime| runtime.notify(stored.node.get()));
runtime::fire_wakes(wake);
}
});
let node = runtime::with_runtime(|runtime| runtime.new_memo(run));
data.node.set(node);
runtime::compute_new_memo(node);
Memo { data }
}
}
impl<T: 'static> With<T> for Memo<T> {
fn with<U>(&self, fun: impl FnOnce(&T) -> U) -> U {
runtime::update_memo(self.data.node.get());
runtime::track_read(self.data.node.get());
let value = self.data.value.borrow();
fun(value.as_ref().expect(
"a memo holds a value once its first computation finished, so a \
read sees one unless the memo reads itself",
))
}
}
impl<T: 'static> Read<T> for Memo<T> {
fn read<U>(&self, fun: impl FnOnce(&T) -> U) -> U {
runtime::update_memo(self.data.node.get());
let value = self.data.value.borrow();
fun(value.as_ref().expect(
"a memo holds a value once its first computation finished, so a \
read sees one unless the memo reads itself",
))
}
}
pub struct RenderEffect {
node: NodeId,
owned: bool,
}
impl RenderEffect {
pub fn new<T>(mut run: impl FnMut() -> T + 'static) -> RenderEffect {
let owned = runtime::with_runtime(|runtime| runtime.current_owner().is_some());
let scope = runtime::with_runtime(|runtime| runtime.current_scope());
let cell = Rc::new(EffectCell {
run: RefCell::new(Some(Box::new(move || {
run();
}))),
});
let node = runtime::with_runtime(|runtime| runtime.new_effect(Rc::clone(&cell), scope));
let mut guard = runtime::DisposeOnUnwind {
node,
armed: !owned,
};
runtime::run_body(&cell, node);
guard.armed = false;
RenderEffect { node, owned }
}
}
impl Drop for RenderEffect {
fn drop(&mut self) {
if !self.owned {
runtime::dispose(self.node);
}
}
}
#[derive(Clone)]
pub struct Owner {
inner: Rc<OwnerData>,
}
impl Default for Owner {
fn default() -> Owner {
Owner::new()
}
}
impl Owner {
pub fn new() -> Owner {
let parent = runtime::with_runtime(|runtime| runtime.current_owner());
let scope = runtime::with_runtime(|runtime| runtime.owner_scope(parent.as_deref()));
let inner = Rc::new(OwnerData {
parent: parent.as_ref().map(Rc::downgrade),
scope,
cleaned: Cell::new(false),
state: RefCell::new(OwnerState::default()),
timer_book: RefCell::new(None),
clipboard: RefCell::new(None),
});
if let Some(parent) = parent {
parent.adopt(&inner);
}
Owner { inner }
}
pub fn child(&self) -> Owner {
self.inner.child()
}
pub fn set(&self) {
runtime::with_runtime(|runtime| runtime.set_current(&self.inner));
}
pub fn with<T>(&self, fun: impl FnOnce() -> T) -> T {
let _guard = CurrentOwnerGuard::enter(&self.inner);
fun()
}
pub fn cleanup(&self) {
self.inner.cleanup();
}
pub fn set_wake_callback(&self, callback: impl Fn() + 'static) {
self.inner.scope.wake.replace(Some(Rc::new(callback)));
}
pub fn on_cleanup(fun: impl FnOnce() + 'static) {
let Some(owner) = Owner::current() else {
return;
};
owner.state.borrow_mut().cleanups.push(Box::new(fun));
}
fn current() -> Option<Rc<OwnerData>> {
runtime::with_runtime(|runtime| runtime.current_owner())
}
pub(crate) fn wake(&self) {
let callback = self.inner.scope.wake.borrow().clone();
if let Some(callback) = callback {
callback();
}
}
pub(crate) fn data(&self) -> &Rc<OwnerData> {
&self.inner
}
}
impl Drop for Owner {
fn drop(&mut self) {
if Rc::strong_count(&self.inner) == 1 {
self.inner.cleanup();
}
}
}
pub fn provide_context<T: 'static>(value: T) {
let Some(owner) = Owner::current() else {
return;
};
let key = TypeId::of::<T>();
let value: Rc<dyn std::any::Any> = Rc::new(value);
let mut state = owner.state.borrow_mut();
match state
.contexts
.iter_mut()
.find(|(existing, _)| *existing == key)
{
Some((_, slot)) => *slot = value,
None => state.contexts.push((key, value)),
}
}
pub fn use_context<T: Clone + 'static>() -> Option<T> {
let key = TypeId::of::<T>();
let mut current = Owner::current();
while let Some(owner) = current {
let found = owner
.state
.borrow()
.contexts
.iter()
.find(|(existing, _)| *existing == key)
.map(|(_, value)| Rc::clone(value));
if let Some(value) = found
&& let Ok(value) = value.downcast::<T>()
{
return Some(T::clone(&value));
}
current = owner.parent.as_ref().and_then(Weak::upgrade);
}
None
}