use std::{
any::Any,
cell::{Cell, Ref, RefCell, RefMut},
collections::BTreeMap,
marker::PhantomData,
rc::{Rc, Weak},
sync::atomic::{AtomicU64, Ordering},
};
static NEXT_ID: AtomicU64 = AtomicU64::new(1);
fn next_id() -> u64 {
NEXT_ID
.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |id| id.checked_add(1))
.expect("reactive identity exhausted")
}
struct Observer {
active: Cell<bool>,
metadata: Box<dyn Any>,
invalidate: Box<dyn Fn()>,
}
struct SourceInner {
id: u64,
observers: RefCell<BTreeMap<u64, Weak<Observer>>>,
}
#[derive(Clone)]
pub struct DependencySource(Rc<SourceInner>);
impl Default for DependencySource {
fn default() -> Self {
Self(Rc::new(SourceInner {
id: next_id(),
observers: RefCell::new(BTreeMap::new()),
}))
}
}
impl DependencySource {
#[must_use]
pub fn id(&self) -> u64 {
self.0.id
}
pub fn subscribe(&self, metadata: impl Any, invalidate: impl Fn() + 'static) -> Subscription {
let id = next_id();
let observer = Rc::new(Observer {
active: Cell::new(true),
metadata: Box::new(metadata),
invalidate: Box::new(invalidate),
});
self.0
.observers
.borrow_mut()
.insert(id, Rc::downgrade(&observer));
Subscription {
id,
source: Rc::downgrade(&self.0),
observer,
}
}
pub fn notify(&self) {
let observers: Vec<_> = self
.0
.observers
.borrow()
.values()
.filter_map(Weak::upgrade)
.collect();
for observer in observers {
if observer.active.get() {
(observer.invalidate)();
}
}
}
pub fn subscriber_count(&self) -> usize {
self.0.observers.borrow().len()
}
pub fn subscribers<T: Any + Clone>(&self) -> Vec<T> {
let observers: Vec<_> = self
.0
.observers
.borrow()
.values()
.filter_map(Weak::upgrade)
.collect();
observers
.into_iter()
.filter(|observer| observer.active.get())
.filter_map(|observer| observer.metadata.downcast_ref::<T>().cloned())
.collect()
}
pub fn track(&self) -> bool {
let callback = TRACKING.with(|stack| stack.borrow().last().cloned());
if let Some(callback) = callback {
callback(self.clone());
true
} else {
false
}
}
}
#[must_use = "keep the subscription alive to observe changes"]
pub struct Subscription {
id: u64,
source: Weak<SourceInner>,
observer: Rc<Observer>,
}
impl Drop for Subscription {
fn drop(&mut self) {
self.observer.active.set(false);
if let Some(source) = self.source.upgrade() {
source.observers.borrow_mut().remove(&self.id);
}
}
}
type Collector = Rc<dyn Fn(DependencySource)>;
thread_local! { static TRACKING: RefCell<Vec<Collector>> = const { RefCell::new(Vec::new()) }; }
thread_local! { static READ_ONLY: Cell<usize> = const { Cell::new(0) }; }
pub struct ReadOnlyGuard(PhantomData<Rc<()>>);
impl ReadOnlyGuard {
#[must_use]
pub fn enter() -> Self {
READ_ONLY.with(|depth| depth.set(depth.get() + 1));
Self(PhantomData)
}
}
impl Drop for ReadOnlyGuard {
fn drop(&mut self) {
READ_ONLY.with(|depth| depth.set(depth.get() - 1));
}
}
pub fn assert_mutation_allowed() {
assert!(
READ_ONLY.with(Cell::get) == 0,
"reactive mutation during a pure build or memo evaluation"
);
}
pub struct TrackingGuard(PhantomData<Rc<()>>);
impl TrackingGuard {
#[must_use]
pub fn enter(collect: impl Fn(DependencySource) + 'static) -> Self {
TRACKING.with(|stack| stack.borrow_mut().push(Rc::new(collect)));
Self(PhantomData)
}
}
impl Drop for TrackingGuard {
fn drop(&mut self) {
TRACKING.with(|stack| {
stack.borrow_mut().pop();
});
}
}
pub struct ReactiveCell<T> {
value: RefCell<T>,
source: DependencySource,
}
impl<T> ReactiveCell<T> {
pub fn new(value: T) -> Self {
Self {
value: RefCell::new(value),
source: DependencySource::default(),
}
}
pub fn borrow(&self) -> Ref<'_, T> {
self.value.borrow()
}
pub fn borrow_mut(&self) -> RefMut<'_, T> {
self.value.borrow_mut()
}
pub fn source(&self) -> &DependencySource {
&self.source
}
}