Skip to main content

nami_core/
watcher.rs

1//! # Watcher Management
2//!
3//! This module provides the infrastructure for managing reactive value watchers,
4//! including metadata handling and notification systems.
5//!
6//! # When watcher would be called?
7//!
8//! > Watchers are called when the value may have changed.
9//!
10//! This means that even if the new value is equal to the old value, watchers may will still be called.
11//!
12//! # Why not only when value actually changed?
13//!
14//! Since we does not require `Eq` or `PartialEq` bounds on the watched values, so we cannot determine
15//! whether the value actually changed or not.
16//!
17//! # So if upstream user calls `notify`, my watcher will always be called?
18//!
19//! The answer is: Maybe yes, maybe no.
20//!
21//! We preserve the freedom to discard notifications in certain scenarios, as a performance optimization.
22//!
23//! Also, you can use `nami::SignalExt::distinct` to create a distinct signal that only notifies when the value changes manually.
24
25use alloc::{boxed::Box, collections::BTreeMap, rc::Rc, vec::Vec};
26use core::{
27    any::{Any, TypeId, type_name},
28    cell::RefCell,
29    fmt::Debug,
30    num::NonZeroUsize,
31};
32
33use crate::observe::{self, Origin};
34
35/// A type-erased container for metadata that can be associated with computation results.
36///
37/// `Metadata` allows attaching arbitrary typed information to computation results
38/// and passing it through the computation pipeline.
39#[derive(Debug, Default, Clone)]
40pub struct Metadata(Box<MetadataInner>);
41
42/// Internal implementation of the metadata storage system.
43///
44/// Uses a `BTreeMap` with `TypeId` as keys to store type-erased values.
45#[derive(Debug, Default, Clone)]
46struct MetadataInner(BTreeMap<TypeId, Rc<dyn Any>>);
47
48impl MetadataInner {
49    /// Attempts to retrieve a value of type `T` from the metadata store.
50    ///
51    /// Returns `None` if no value of the requested type is present.
52    pub fn try_get<T: 'static + Clone>(&self) -> Option<T> {
53        // Once `downcast_ref_unchecked` stabilized, we will use it here.
54        self.0
55            .get(&TypeId::of::<T>())
56            .and_then(|value| value.downcast_ref::<T>())
57            .cloned()
58    }
59
60    /// Inserts a value of type `T` into the metadata store.
61    ///
62    /// If a value of the same type already exists, it will be replaced.
63    pub fn insert<T: 'static + Clone>(&mut self, value: T) {
64        // Value are always cheap to clone, for example, `Animation` may only within one machine word.
65        // However, we must erase the type, so we must make a choice between `Rc` and `Box`.
66        // Here we choose `Rc` to allow cheap cloning when retrieving the value.
67        self.0.insert(TypeId::of::<T>(), Rc::new(value));
68    }
69}
70
71/// Type alias for a reference-counted watcher function.
72pub type Watcher<T> = Rc<dyn Fn(Context<T>) + 'static>;
73
74/// Context passed to watchers containing the value and associated metadata.
75#[derive(Debug, Clone)]
76pub struct Context<T> {
77    /// The current value being watched.
78    value: T,
79    /// Associated metadata for this value change.
80    metadata: Metadata,
81}
82
83impl<T> Context<T> {
84    /// Creates a new context with the given value and metadata.
85    pub const fn new(value: T, metadata: Metadata) -> Self {
86        Self { value, metadata }
87    }
88
89    /// Adds additional metadata to this context.
90    #[must_use]
91    pub fn with<V: Clone + 'static>(mut self, value: V) -> Self {
92        self.metadata = self.metadata.with(value);
93        self
94    }
95
96    /// Consumes the context and returns the inner value.
97    pub fn into_value(self) -> T {
98        self.value
99    }
100
101    /// Returns a reference to the inner value.
102    pub const fn value(&self) -> &T {
103        &self.value
104    }
105
106    /// Returns a mutable reference to the inner value.
107    pub const fn value_mut(&mut self) -> &mut T {
108        &mut self.value
109    }
110
111    /// Returns a reference to the metadata.
112    pub const fn metadata(&self) -> &Metadata {
113        &self.metadata
114    }
115
116    /// Returns a mutable reference to the metadata.
117    pub const fn metadata_mut(&mut self) -> &mut Metadata {
118        &mut self.metadata
119    }
120
121    /// Maps the inner value to a new value.
122    pub fn map<U, F: FnOnce(T) -> U>(self, f: F) -> Context<U> {
123        Context::new(f(self.value), self.metadata)
124    }
125
126    /// Returns a new context with a reference to the inner value.
127    pub fn as_ref(&self) -> Context<&T> {
128        Context::new(&self.value, self.metadata.clone())
129    }
130
131    /// Returns a new context with a mutable reference to the inner value.
132    pub fn as_mut(&mut self) -> Context<&mut T> {
133        Context::new(&mut self.value, self.metadata.clone())
134    }
135
136    /// Returns a new context with a reference to the dereferenced inner value.
137    pub fn as_deref(&self) -> Context<&T::Target>
138    where
139        T: core::ops::Deref,
140    {
141        Context::new(&*self.value, self.metadata.clone())
142    }
143
144    /// Returns a new context with a mutable reference to the dereferenced inner value.
145    pub fn as_deref_mut(&mut self) -> Context<&mut T::Target>
146    where
147        T: core::ops::DerefMut,
148    {
149        Context::new(&mut *self.value, self.metadata.clone())
150    }
151}
152
153impl<T> From<T> for Context<T> {
154    fn from(value: T) -> Self {
155        Self::new(value, Metadata::new())
156    }
157}
158
159/// A guard that ensures proper cleanup of watchers when dropped.
160#[must_use]
161pub trait WatcherGuard: 'static {}
162
163impl<T: 'static> WatcherGuard for Option<T> {}
164
165impl<T: WatcherGuard, E: WatcherGuard> WatcherGuard for Result<T, E> {}
166
167impl WatcherGuard for () {}
168
169impl<T1: WatcherGuard, T2: WatcherGuard> WatcherGuard for (T1, T2) {}
170
171/// A utility struct that runs a cleanup function when dropped.
172#[derive(Debug)]
173pub struct OnDrop<F>(Option<F>)
174where
175    F: FnOnce();
176
177impl<F> OnDrop<F>
178where
179    F: FnOnce() + 'static,
180{
181    /// Creates a new `OnDrop` that will call the function when dropped.
182    pub const fn new(f: F) -> Self {
183        Self(Some(f))
184    }
185
186    /// Attaches a cleanup function to a guard.
187    #[allow(clippy::needless_pass_by_value)]
188    pub fn attach(guard: impl WatcherGuard, f: F) -> impl WatcherGuard {
189        OnDrop::new(move || {
190            let _ = guard;
191            f();
192        })
193    }
194}
195
196impl<F> Drop for OnDrop<F>
197where
198    F: FnOnce(),
199{
200    fn drop(&mut self) {
201        let Some(callback) = self.0.take() else {
202            panic!("OnDrop::drop called with missing callback");
203        };
204        callback();
205    }
206}
207
208/// Type alias for a boxed watcher guard.
209pub type BoxWatcherGuard = Box<dyn WatcherGuard>;
210
211impl<T: WatcherGuard + ?Sized> WatcherGuard for Box<T> {}
212impl<T: WatcherGuard + ?Sized> WatcherGuard for Rc<T> {}
213
214impl<F: FnOnce() + 'static> WatcherGuard for OnDrop<F> {}
215
216impl Metadata {
217    /// Creates a new, empty metadata container.
218    #[must_use]
219    pub fn new() -> Self {
220        Self::default()
221    }
222
223    /// Gets a value of type `T` from the metadata.
224    ///
225    /// # Panics
226    ///
227    /// Panics if no value of type `T` is present in the metadata.
228    #[must_use]
229    pub fn get<T: 'static + Clone>(&self) -> T {
230        self.try_get()
231            .unwrap_or_else(|| panic!("Metadata::get missing value for requested type"))
232    }
233
234    /// Attempts to get a value of type `T` from the metadata.
235    ///
236    /// Returns `None` if no value of the requested type is present.
237    #[must_use]
238    pub fn try_get<T: 'static + Clone>(&self) -> Option<T> {
239        self.0.try_get()
240    }
241
242    /// Adds a value to the metadata and returns the updated metadata.
243    ///
244    /// This method is chainable for fluent API usage.
245    #[must_use]
246    pub fn with<T: 'static + Clone>(mut self, value: T) -> Self {
247        self.0.insert(value);
248        self
249    }
250
251    /// Checks if the metadata container is empty.
252    #[must_use]
253    pub fn is_empty(&self) -> bool {
254        self.0.0.is_empty()
255    }
256}
257
258/// A unique identifier for registered watchers.
259pub(crate) type WatcherId = NonZeroUsize;
260
261/// Manages a collection of watchers for a specific computation type.
262///
263/// Provides functionality to register, notify, and cancel watchers.
264#[derive(Debug)]
265pub struct WatcherManager<T> {
266    inner: Rc<RefCell<WatcherManagerInner<T>>>,
267}
268
269impl<T> Clone for WatcherManager<T> {
270    fn clone(&self) -> Self {
271        Self {
272            inner: self.inner.clone(),
273        }
274    }
275}
276
277impl<T> Default for WatcherManager<T> {
278    fn default() -> Self {
279        Self {
280            inner: Rc::default(),
281        }
282    }
283}
284
285impl<T: 'static> WatcherManager<T> {
286    /// Creates a new, empty watcher manager with no provenance.
287    #[must_use]
288    pub fn new() -> Self {
289        Self::default()
290    }
291
292    /// Creates a watcher manager attributed to a state-owning signal node.
293    ///
294    /// State owners should use this rather than [`Self::new`] so development
295    /// tooling can attribute subscriptions and notifications to the node and to
296    /// the source location that created it. [`Origin`] is zero-sized unless the
297    /// `observability` feature is enabled.
298    #[must_use]
299    pub fn with_origin(origin: Origin) -> Self {
300        observe::on_create(origin);
301        Self {
302            inner: Rc::new(RefCell::new(WatcherManagerInner::with_origin(origin))),
303        }
304    }
305
306    /// Checks if the manager has any registered watchers.
307    #[must_use]
308    pub fn is_empty(&self) -> bool {
309        self.inner.borrow().is_empty()
310    }
311
312    /// Registers a new watcher and returns its unique identifier.
313    pub fn register(&self, watcher: impl Fn(Context<T>) + 'static) -> WatcherId {
314        let (id, origin, subscribers) = {
315            let mut inner = self.inner.borrow_mut();
316            let id = inner.register(watcher);
317            (id, inner.origin, inner.len())
318        };
319        observe::on_subscribe(origin, subscribers);
320        id
321    }
322
323    /// Registers a watcher and returns a guard that will unregister it when dropped.
324    pub fn register_as_guard(
325        &self,
326        watcher: impl Fn(Context<T>) + 'static,
327    ) -> WatcherManagerGuard<T> {
328        let id = self.register(watcher);
329        let this = self.clone();
330        WatcherManagerGuard { manager: this, id }
331    }
332
333    /// Notifies all registered watchers with a preconstructed context.
334    pub fn notify(&self, ctx: &Context<T>)
335    where
336        T: Clone,
337    {
338        let (watchers, origin) = {
339            let inner = self.inner.borrow();
340            (inner.watchers_snapshot(), inner.origin)
341        };
342
343        if watchers.is_empty() {
344            return;
345        }
346
347        observe::on_notify(origin, watchers.len());
348
349        for watcher in watchers {
350            watcher(ctx.clone());
351        }
352    }
353
354    /// Cancels a previously registered watcher by its identifier.
355    pub fn cancel(&self, id: WatcherId) {
356        let (origin, subscribers) = {
357            let mut inner = self.inner.borrow_mut();
358            inner.cancel(id);
359            (inner.origin, inner.len())
360        };
361        observe::on_unsubscribe(origin, subscribers);
362    }
363}
364
365/// A guard that ensures a watcher is unregistered when dropped.
366#[must_use]
367#[derive(Debug)]
368pub struct WatcherManagerGuard<T: 'static> {
369    manager: WatcherManager<T>,
370    id: WatcherId,
371}
372
373impl<T> WatcherGuard for WatcherManagerGuard<T> {}
374
375impl<T: 'static> Drop for WatcherManagerGuard<T> {
376    fn drop(&mut self) {
377        self.manager.cancel(self.id);
378    }
379}
380
381/// Internal implementation of the watcher manager.
382///
383/// Maintains the collection of watchers and handles identifier assignment.
384struct WatcherManagerInner<T> {
385    id: WatcherId,
386    map: BTreeMap<WatcherId, Watcher<T>>,
387    origin: Origin,
388}
389
390impl<T> Debug for WatcherManagerInner<T> {
391    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
392        f.write_str(type_name::<Self>())
393    }
394}
395
396impl<T> Default for WatcherManagerInner<T> {
397    fn default() -> Self {
398        Self {
399            id: WatcherId::MIN,
400            map: BTreeMap::new(),
401            origin: Origin::default(),
402        }
403    }
404}
405
406impl<T> Drop for WatcherManagerInner<T> {
407    fn drop(&mut self) {
408        observe::on_drop(self.origin);
409    }
410}
411
412impl<T: 'static> WatcherManagerInner<T> {
413    /// Creates an inner manager attributed to a signal node.
414    fn with_origin(origin: Origin) -> Self {
415        Self {
416            id: WatcherId::MIN,
417            map: BTreeMap::new(),
418            origin,
419        }
420    }
421
422    /// Checks if there are any registered watchers.
423    pub fn is_empty(&self) -> bool {
424        self.map.is_empty()
425    }
426
427    /// Number of currently registered watchers.
428    fn len(&self) -> usize {
429        self.map.len()
430    }
431
432    /// Assigns a new unique identifier for a watcher.
433    const fn assign(&mut self) -> WatcherId {
434        let id = self.id;
435        self.id = match self.id.checked_add(1) {
436            Some(id) => id,
437            None => panic!("`id` grows beyond `usize::MAX`"),
438        };
439        id
440    }
441
442    /// Registers a watcher and returns its unique identifier.
443    pub fn register(&mut self, watcher: impl Fn(Context<T>) + 'static) -> WatcherId {
444        let id = self.assign();
445        self.map.insert(id, Rc::new(watcher));
446        id
447    }
448
449    /// Creates a snapshot of the current watchers for notification.
450    fn watchers_snapshot(&self) -> Vec<Watcher<T>> {
451        self.map.values().cloned().collect()
452    }
453
454    /// Cancels a watcher registration by its identifier.
455    pub fn cancel(&mut self, id: WatcherId) {
456        self.map.remove(&id);
457    }
458}