use std::{
any::{Any, TypeId},
cell::{Cell, RefCell},
collections::{HashMap, HashSet},
fmt,
rc::{Rc, Weak},
sync::atomic::{AtomicU64, Ordering},
};
static NEXT_CONSUMER: AtomicU64 = AtomicU64::new(1);
static NEXT_ENVIRONMENT: AtomicU64 = AtomicU64::new(1);
use crate::reactivity::{DependencySource, Subscription, TrackingGuard};
fn next_identity(counter: &AtomicU64, label: &'static str) -> u64 {
counter
.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |id| id.checked_add(1))
.unwrap_or_else(|_| panic!("{label} identity space exhausted"))
}
fn next_revision(revision: u64) -> u64 {
revision
.checked_add(1)
.expect("context dependency revision exhausted")
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
enum DependencyKey {
Environment { environment: u64, type_id: TypeId },
}
#[derive(Default)]
struct TrackerInner {
dependencies: RefCell<HashMap<ConsumerId, HashMap<u64, Subscription>>>,
sources: RefCell<HashMap<DependencyKey, DependencySource>>,
source_keys: RefCell<HashMap<u64, DependencyKey>>,
dirty: RefCell<HashSet<ConsumerId>>,
owners: RefCell<HashMap<ConsumerId, Weak<ConsumerOwner>>>,
}
impl TrackerInner {
fn owner(self: &Rc<Self>, consumer: ConsumerId) -> Rc<ConsumerOwner> {
let mut owners = self.owners.borrow_mut();
if let Some(owner) = owners.get(&consumer).and_then(Weak::upgrade) {
return owner;
}
let owner = Rc::new(ConsumerOwner {
tracker: Rc::downgrade(self),
consumer,
});
owners.insert(consumer, Rc::downgrade(&owner));
owner
}
fn record(self: &Rc<Self>, consumer: ConsumerId, dependency: DependencyKey) {
let source = self
.sources
.borrow_mut()
.entry(dependency)
.or_insert_with(|| {
let source = DependencySource::default();
self.source_keys
.borrow_mut()
.insert(source.id(), dependency);
source
})
.clone();
self.record_source(consumer, source);
}
fn record_source(self: &Rc<Self>, consumer: ConsumerId, source: DependencySource) {
let mut dependencies = self.dependencies.borrow_mut();
dependencies
.entry(consumer)
.or_default()
.entry(source.id())
.or_insert_with(|| {
let weak = Rc::downgrade(self);
source.subscribe(consumer, move || {
if let Some(tracker) = weak.upgrade() {
tracker.dirty.borrow_mut().insert(consumer);
}
})
});
}
fn clear(&self, consumer: ConsumerId) {
self.dirty.borrow_mut().remove(&consumer);
let Some(subscriptions) = self.dependencies.borrow_mut().remove(&consumer) else {
return;
};
let ids: Vec<u64> = subscriptions.keys().copied().collect();
drop(subscriptions);
let mut sources = self.sources.borrow_mut();
let mut keys = self.source_keys.borrow_mut();
for id in ids {
if let Some(key) = keys.get(&id).copied()
&& sources
.get(&key)
.is_some_and(|source| source.subscriber_count() == 0)
{
sources.remove(&key);
keys.remove(&id);
}
}
}
fn invalidate(&self, dependency: DependencyKey) {
let source = self.sources.borrow().get(&dependency).cloned();
if let Some(source) = source {
source.notify();
}
}
fn dependency_count(&self, consumer: ConsumerId) -> usize {
self.dependencies
.borrow()
.get(&consumer)
.map_or(0, HashMap::len)
}
fn is_dirty(&self, consumer: ConsumerId) -> bool {
self.dirty.borrow().contains(&consumer)
}
fn take_dirty(&self, consumer: ConsumerId) -> bool {
self.dirty.borrow_mut().remove(&consumer)
}
fn take_all_dirty(&self) -> Vec<ConsumerId> {
self.dirty.borrow_mut().drain().collect()
}
}
struct ConsumerOwner {
tracker: Weak<TrackerInner>,
consumer: ConsumerId,
}
impl Drop for ConsumerOwner {
fn drop(&mut self) {
if let Some(tracker) = self.tracker.upgrade() {
tracker.clear(self.consumer);
tracker.owners.borrow_mut().remove(&self.consumer);
}
}
}
struct Environment {
id: u64,
parent: Option<Rc<Environment>>,
values: RefCell<HashMap<TypeId, Rc<dyn Any>>>,
revision: Cell<u64>,
}
impl Environment {
fn root() -> Rc<Self> {
Rc::new(Self {
id: next_identity(&NEXT_ENVIRONMENT, "environment"),
parent: None,
values: RefCell::new(HashMap::new()),
revision: Cell::new(0),
})
}
fn child(parent: &Rc<Self>) -> Rc<Self> {
Rc::new(Self {
id: next_identity(&NEXT_ENVIRONMENT, "environment"),
parent: Some(parent.clone()),
values: RefCell::new(HashMap::new()),
revision: Cell::new(0),
})
}
fn lookup(&self, type_id: TypeId) -> Option<(u64, Rc<dyn Any>)> {
if let Some(value) = self.values.borrow().get(&type_id) {
return Some((self.id, value.clone()));
}
self.parent.as_deref()?.lookup(type_id)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct ConsumerId(u64);
impl ConsumerId {
#[must_use]
pub fn new() -> Self {
Self(next_identity(&NEXT_CONSUMER, "context consumer"))
}
#[must_use]
pub const fn from_raw(value: u64) -> Self {
Self(value)
}
#[must_use]
pub const fn raw(self) -> u64 {
self.0
}
}
impl Default for ConsumerId {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct DependencySnapshot {
count: usize,
dirty: bool,
}
impl DependencySnapshot {
#[must_use]
pub const fn count(self) -> usize {
self.count
}
#[must_use]
pub const fn is_empty(self) -> bool {
self.count == 0
}
#[must_use]
pub const fn is_dirty(self) -> bool {
self.dirty
}
}
struct ActiveScope {
tracker: Rc<TrackerInner>,
consumer: ConsumerId,
}
thread_local! {
static ACTIVE_SCOPES: RefCell<Vec<ActiveScope>> = const { RefCell::new(Vec::new()) };
}
#[derive(Clone)]
pub struct BuildContext {
_owner: Rc<ConsumerOwner>,
environment: Rc<Environment>,
tracker: Rc<TrackerInner>,
consumer: ConsumerId,
}
impl fmt::Debug for BuildContext {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("BuildContext")
.field("consumer", &self.consumer)
.field("dependency_count", &self.dependency_count())
.field("dirty", &self.is_dirty())
.finish()
}
}
impl Default for BuildContext {
fn default() -> Self {
Self::new()
}
}
impl BuildContext {
#[must_use]
pub fn new() -> Self {
Self::for_consumer(ConsumerId::new())
}
#[must_use]
pub fn for_consumer(consumer: ConsumerId) -> Self {
let tracker = Rc::new(TrackerInner::default());
Self {
_owner: tracker.owner(consumer),
environment: Environment::root(),
tracker,
consumer,
}
}
#[must_use]
pub fn for_related_consumer(&self, consumer: ConsumerId) -> Self {
Self {
_owner: self.tracker.owner(consumer),
environment: self.environment.clone(),
tracker: self.tracker.clone(),
consumer,
}
}
#[must_use]
pub fn boundary_for_consumer(&self, consumer: ConsumerId) -> Self {
Self {
_owner: self.tracker.owner(consumer),
environment: Environment::root(),
tracker: self.tracker.clone(),
consumer,
}
}
#[must_use]
pub const fn consumer_id(&self) -> ConsumerId {
self.consumer
}
#[must_use]
pub fn child(&self) -> Self {
Self {
_owner: self._owner.clone(),
environment: Environment::child(&self.environment),
tracker: self.tracker.clone(),
consumer: self.consumer,
}
}
#[must_use]
pub fn provide<T: Any>(&self, value: T) -> Self {
let child = self.child();
child.insert(value);
child
}
#[doc(hidden)]
#[must_use]
pub fn provide_erased(&self, type_id: TypeId, value: Rc<dyn Any>) -> Self {
let child = self.child();
child.environment.values.borrow_mut().insert(type_id, value);
child
}
#[doc(hidden)]
pub fn set_erased(&self, type_id: TypeId, value: Rc<dyn Any>) {
self.environment.values.borrow_mut().insert(type_id, value);
self.environment
.revision
.set(next_revision(self.environment.revision.get()));
self.tracker.invalidate(DependencyKey::Environment {
environment: self.environment.id,
type_id,
});
}
pub fn insert<T: Any>(&self, value: T) -> bool {
let type_id = TypeId::of::<T>();
self.environment
.values
.borrow_mut()
.insert(type_id, Rc::new(value));
self.environment
.revision
.set(next_revision(self.environment.revision.get()));
self.tracker.invalidate(DependencyKey::Environment {
environment: self.environment.id,
type_id,
});
true
}
pub fn set<T: Any + PartialEq>(&self, value: T) -> bool {
let type_id = TypeId::of::<T>();
let changed = self
.environment
.values
.borrow()
.get(&type_id)
.and_then(|old| old.downcast_ref::<T>())
.is_none_or(|old| old != &value);
if changed {
self.insert(value);
}
changed
}
pub fn remove<T: Any>(&self) -> bool {
let type_id = TypeId::of::<T>();
let removed = self
.environment
.values
.borrow_mut()
.remove(&type_id)
.is_some();
if removed {
self.environment
.revision
.set(next_revision(self.environment.revision.get()));
self.tracker.invalidate(DependencyKey::Environment {
environment: self.environment.id,
type_id,
});
}
removed
}
#[must_use]
pub fn contains<T: Any>(&self) -> bool {
self.lookup_value(TypeId::of::<T>()).is_some()
}
#[must_use]
pub fn revision(&self) -> u64 {
self.environment.revision.get()
}
fn lookup_value(&self, type_id: TypeId) -> Option<(u64, Rc<dyn Any>)> {
self.environment.lookup(type_id)
}
#[must_use]
pub fn read<T: Any + Clone>(&self) -> Option<T> {
self.read_shared::<T>().map(|value| (*value).clone())
}
#[must_use]
pub fn get<T: Any + Clone>(&self) -> Option<T> {
self.read()
}
#[must_use]
pub fn read_shared<T: Any>(&self) -> Option<Rc<T>> {
self.lookup_value(TypeId::of::<T>())
.and_then(|(_, value)| value.downcast::<T>().ok())
}
fn record_environment(&self, type_id: TypeId) {
let environment = self
.lookup_value(type_id)
.map_or(self.environment.id, |(environment, _)| environment);
with_active_scope(|scope| {
scope.tracker.record(
scope.consumer,
DependencyKey::Environment {
environment,
type_id,
},
);
})
.unwrap_or_else(|| {
self.tracker.record(
self.consumer,
DependencyKey::Environment {
environment,
type_id,
},
)
});
}
#[must_use]
pub fn depend<T: Any + Clone>(&self) -> Option<T> {
self.record_environment(TypeId::of::<T>());
self.read()
}
#[must_use]
pub fn depend_shared<T: Any>(&self) -> Option<Rc<T>> {
self.record_environment(TypeId::of::<T>());
self.read_shared()
}
#[must_use]
pub fn watch<T: Any + Clone>(&self) -> Option<T> {
self.depend()
}
#[must_use]
pub fn enter(&self) -> ContextGuard {
ACTIVE_SCOPES.with(|scopes| {
scopes.borrow_mut().push(ActiveScope {
tracker: self.tracker.clone(),
consumer: self.consumer,
});
});
let tracker = self.tracker.clone();
let consumer = self.consumer;
ContextGuard {
active: true,
_owner: self._owner.clone(),
_read_only: crate::reactivity::ReadOnlyGuard::enter(),
_tracking: TrackingGuard::enter(move |source| tracker.record_source(consumer, source)),
}
}
#[must_use]
pub fn scope(&self) -> ContextGuard {
self.enter()
}
pub fn build<R>(&self, callback: impl FnOnce(&BuildContext) -> R) -> R {
self.clear_dependencies();
let _guard = self.enter();
callback(self)
}
pub fn run<R>(&self, callback: impl FnOnce() -> R) -> R {
self.clear_dependencies();
let _guard = self.enter();
callback()
}
pub fn with_context<R>(&self, callback: impl FnOnce(&BuildContext) -> R) -> R {
self.build(callback)
}
pub fn clear_dependencies(&self) {
self.tracker.clear(self.consumer);
}
#[must_use]
pub fn dependency_count(&self) -> usize {
self.tracker.dependency_count(self.consumer)
}
#[must_use]
pub fn is_dirty(&self) -> bool {
self.tracker.is_dirty(self.consumer)
}
pub fn take_dirty(&self) -> bool {
self.tracker.take_dirty(self.consumer)
}
#[doc(hidden)]
pub fn take_dirty_consumers(&self) -> Vec<ConsumerId> {
self.tracker.take_all_dirty()
}
#[doc(hidden)]
#[must_use]
pub fn has_dirty_consumers(&self) -> bool {
!self.tracker.dirty.borrow().is_empty()
}
#[must_use]
pub fn dependencies(&self) -> DependencySnapshot {
DependencySnapshot {
count: self.dependency_count(),
dirty: self.is_dirty(),
}
}
}
pub struct ContextGuard {
active: bool,
_owner: Rc<ConsumerOwner>,
_tracking: TrackingGuard,
_read_only: crate::reactivity::ReadOnlyGuard,
}
impl Drop for ContextGuard {
fn drop(&mut self) {
if self.active {
ACTIVE_SCOPES.with(|scopes| {
let _ = scopes.borrow_mut().pop();
});
self.active = false;
}
}
}
fn with_active_scope<R>(callback: impl FnOnce(&ActiveScope) -> R) -> Option<R> {
ACTIVE_SCOPES.with(|scopes| scopes.borrow().last().map(callback))
}