use std::{
cell::{Cell, OnceCell},
hash::{Hash, Hasher},
rc::{Rc, Weak},
};
use crate::{
OwnedMutableState, RuntimeHandle, composer_context,
snapshot_state_observer::SnapshotStateObserver,
};
pub struct ComputedState<T: Clone + PartialEq + 'static> {
inner: Rc<ComputedInner<T>>,
}
impl<T: Clone + PartialEq + 'static> Clone for ComputedState<T> {
fn clone(&self) -> Self {
Self {
inner: Rc::clone(&self.inner),
}
}
}
#[expect(non_snake_case)]
pub fn computedStateOf<T: Clone + PartialEq + 'static>(
calculation: impl Fn() -> T + 'static,
) -> ComputedState<T> {
ComputedState {
inner: Rc::new_cyclic(|this| ComputedInner {
calculation: Box::new(calculation),
this: this.clone(),
read: OnceCell::new(),
refresh_queued: Cell::new(false),
}),
}
}
impl<T: Clone + PartialEq + 'static> ComputedState<T> {
pub fn value(&self) -> T {
let Some(runtime) = composer_context::try_with_composer(crate::Composer::runtime_handle)
else {
return (self.inner.calculation)();
};
let value = self.inner.observed();
let (read, _) = self.inner.read.get_or_init(|| {
let read =
OwnedMutableState::with_runtime_structural_eq(value.clone(), runtime.clone());
(read, runtime)
});
read.set_value(value.clone());
read.as_state().read(|_| ());
value
}
pub fn get(&self) -> T {
self.value()
}
}
struct ComputedInner<T: Clone + PartialEq + 'static> {
calculation: Box<dyn Fn() -> T>,
this: Weak<ComputedInner<T>>,
read: OnceCell<(OwnedMutableState<T>, RuntimeHandle)>,
refresh_queued: Cell<bool>,
}
impl<T: Clone + PartialEq + 'static> ComputedInner<T> {
fn key(&self) -> ComputedKey {
ComputedKey {
id: self.id(),
target: self.this.clone(),
}
}
fn id(&self) -> usize {
std::ptr::from_ref(self).addr()
}
fn observed(&self) -> T {
with_observer(|observer| {
observer.observe_reads(self.key(), ComputedKey::changed, || {
composer_context::without_composer(|| (self.calculation)())
})
})
}
}
trait ComputedRefresh {
fn queue_refresh(&self);
}
impl<T: Clone + PartialEq + 'static> ComputedRefresh for ComputedInner<T> {
fn queue_refresh(&self) {
let Some((_, runtime)) = self.read.get() else {
return;
};
if self.refresh_queued.replace(true) {
return;
}
let this = self.this.clone();
runtime.enqueue_ui_task(Box::new(move || {
if let Some(inner) = this.upgrade() {
inner.refresh_queued.set(false);
let value = inner.observed();
if let Some((read, _)) = inner.read.get() {
read.set_value(value);
}
}
}));
}
}
impl<T: Clone + PartialEq + 'static> Drop for ComputedInner<T> {
fn drop(&mut self) {
let key = ComputedKey {
id: self.id(),
target: Weak::<Released>::new(),
};
try_with_observer(|observer| observer.clear(&key));
}
}
#[derive(Clone)]
struct ComputedKey {
id: usize,
target: Weak<dyn ComputedRefresh>,
}
impl ComputedKey {
fn changed(&self) {
if let Some(target) = self.target.upgrade() {
target.queue_refresh();
}
}
}
impl PartialEq for ComputedKey {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl Eq for ComputedKey {}
impl Hash for ComputedKey {
fn hash<H: Hasher>(&self, state: &mut H) {
self.id.hash(state);
}
}
struct Released;
impl ComputedRefresh for Released {
fn queue_refresh(&self) {}
}
fn with_observer<R>(f: impl FnOnce(&SnapshotStateObserver) -> R) -> R {
let observer = crate::thread_state::runtimes(|state| {
state
.computed_observer
.borrow_mut()
.get_or_insert_with(|| {
let observer = SnapshotStateObserver::new(|change| change());
observer.start();
observer
})
.clone()
});
f(&observer)
}
fn try_with_observer(f: impl FnOnce(&SnapshotStateObserver)) {
let observer = crate::thread_state::try_runtimes(|state| {
state
.computed_observer
.try_borrow()
.ok()
.and_then(|observer| observer.clone())
})
.flatten();
if let Some(observer) = observer {
f(&observer);
}
}