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::distinct`](nami::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
33/// A type-erased container for metadata that can be associated with computation results.
34///
35/// `Metadata` allows attaching arbitrary typed information to computation results
36/// and passing it through the computation pipeline.
37#[derive(Debug, Default, Clone)]
38pub struct Metadata(Box<MetadataInner>);
39
40/// Internal implementation of the metadata storage system.
41///
42/// Uses a `BTreeMap` with `TypeId` as keys to store type-erased values.
43#[derive(Debug, Default, Clone)]
44struct MetadataInner(BTreeMap<TypeId, Rc<dyn Any>>);
45
46impl MetadataInner {
47    /// Attempts to retrieve a value of type `T` from the metadata store.
48    ///
49    /// Returns `None` if no value of the requested type is present.
50    #[allow(clippy::unwrap_used)]
51    pub fn try_get<T: 'static + Clone>(&self) -> Option<T> {
52        // Once `downcast_ref_unchecked` stabilized, we will use it here.
53        self.0
54            .get(&TypeId::of::<T>())
55            .map(|v| v.downcast_ref::<T>().unwrap())
56            .cloned()
57    }
58
59    /// Inserts a value of type `T` into the metadata store.
60    ///
61    /// If a value of the same type already exists, it will be replaced.
62    pub fn insert<T: 'static + Clone>(&mut self, value: T) {
63        // Value are always cheap to clone, for example, `Animation` may only within one machine word.
64        // However, we must erase the type, so we must make a choice between `Rc` and `Box`.
65        // Here we choose `Rc` to allow cheap cloning when retrieving the value.
66        self.0.insert(TypeId::of::<T>(), Rc::new(value));
67    }
68}
69
70/// Type alias for a reference-counted watcher function.
71pub type Watcher<T> = Rc<dyn Fn(Context<T>) + 'static>;
72
73/// Context passed to watchers containing the value and associated metadata.
74#[derive(Debug, Clone)]
75pub struct Context<T> {
76    /// The current value being watched.
77    value: T,
78    /// Associated metadata for this value change.
79    metadata: Metadata,
80}
81
82impl<T> Context<T> {
83    /// Creates a new context with the given value and metadata.
84    pub const fn new(value: T, metadata: Metadata) -> Self {
85        Self { value, metadata }
86    }
87
88    /// Adds additional metadata to this context.
89    #[must_use]
90    pub fn with<V: Clone + 'static>(mut self, value: V) -> Self {
91        self.metadata = self.metadata.with(value);
92        self
93    }
94
95    /// Consumes the context and returns the inner value.
96    pub fn into_value(self) -> T {
97        self.value
98    }
99
100    /// Returns a reference to the inner value.
101    pub const fn value(&self) -> &T {
102        &self.value
103    }
104
105    /// Returns a mutable reference to the inner value.
106    pub const fn value_mut(&mut self) -> &mut T {
107        &mut self.value
108    }
109
110    /// Returns a reference to the metadata.
111    pub const fn metadata(&self) -> &Metadata {
112        &self.metadata
113    }
114
115    /// Returns a mutable reference to the metadata.
116    pub const fn metadata_mut(&mut self) -> &mut Metadata {
117        &mut self.metadata
118    }
119
120    /// Maps the inner value to a new value.
121    pub fn map<U, F: FnOnce(T) -> U>(self, f: F) -> Context<U> {
122        Context::new(f(self.value), self.metadata)
123    }
124
125    /// Returns a new context with a reference to the inner value.
126    pub fn as_ref(&self) -> Context<&T> {
127        Context::new(&self.value, self.metadata.clone())
128    }
129
130    /// Returns a new context with a mutable reference to the inner value.
131    pub fn as_mut(&mut self) -> Context<&mut T> {
132        Context::new(&mut self.value, self.metadata.clone())
133    }
134
135    /// Returns a new context with a reference to the dereferenced inner value.
136    pub fn as_deref(&self) -> Context<&T::Target>
137    where
138        T: core::ops::Deref,
139    {
140        Context::new(&*self.value, self.metadata.clone())
141    }
142
143    /// Returns a new context with a mutable reference to the dereferenced inner value.
144    pub fn as_deref_mut(&mut self) -> Context<&mut T::Target>
145    where
146        T: core::ops::DerefMut,
147    {
148        Context::new(&mut *self.value, self.metadata.clone())
149    }
150}
151
152impl<T> From<T> for Context<T> {
153    fn from(value: T) -> Self {
154        Self::new(value, Metadata::new())
155    }
156}
157
158/// A guard that ensures proper cleanup of watchers when dropped.
159#[must_use]
160pub trait WatcherGuard: 'static {}
161
162impl<T: 'static> WatcherGuard for Option<T> {}
163
164impl<T: WatcherGuard, E: WatcherGuard> WatcherGuard for Result<T, E> {}
165
166impl WatcherGuard for () {}
167
168impl<T1: WatcherGuard, T2: WatcherGuard> WatcherGuard for (T1, T2) {}
169
170/// A utility struct that runs a cleanup function when dropped.
171#[derive(Debug)]
172pub struct OnDrop<F>(Option<F>)
173where
174    F: FnOnce();
175
176impl<F> OnDrop<F>
177where
178    F: FnOnce() + 'static,
179{
180    /// Creates a new `OnDrop` that will call the function when dropped.
181    pub const fn new(f: F) -> Self {
182        Self(Some(f))
183    }
184
185    /// Attaches a cleanup function to a guard.
186    #[allow(clippy::needless_pass_by_value)]
187    pub fn attach(guard: impl WatcherGuard, f: F) -> impl WatcherGuard {
188        OnDrop::new(move || {
189            let _ = guard;
190            f();
191        })
192    }
193}
194
195#[allow(clippy::unwrap_used)]
196impl<F> Drop for OnDrop<F>
197where
198    F: FnOnce(),
199{
200    fn drop(&mut self) {
201        (self.0.take().unwrap())();
202    }
203}
204
205/// Type alias for a boxed watcher guard.
206pub type BoxWatcherGuard = Box<dyn WatcherGuard>;
207
208impl<T: WatcherGuard + ?Sized> WatcherGuard for Box<T> {}
209impl<T: WatcherGuard + ?Sized> WatcherGuard for Rc<T> {}
210
211impl<F: FnOnce() + 'static> WatcherGuard for OnDrop<F> {}
212
213impl Metadata {
214    /// Creates a new, empty metadata container.
215    #[must_use]
216    pub fn new() -> Self {
217        Self::default()
218    }
219
220    /// Gets a value of type `T` from the metadata.
221    ///
222    /// # Panics
223    ///
224    /// Panics if no value of type `T` is present in the metadata.
225    #[must_use]
226    #[allow(clippy::expect_used)]
227    pub fn get<T: 'static + Clone>(&self) -> T {
228        self.try_get()
229            .expect("Value of requested type should be present in metadata")
230    }
231
232    /// Attempts to get a value of type `T` from the metadata.
233    ///
234    /// Returns `None` if no value of the requested type is present.
235    #[must_use]
236    pub fn try_get<T: 'static + Clone>(&self) -> Option<T> {
237        self.0.try_get()
238    }
239
240    /// Adds a value to the metadata and returns the updated metadata.
241    ///
242    /// This method is chainable for fluent API usage.
243    #[must_use]
244    pub fn with<T: 'static + Clone>(mut self, value: T) -> Self {
245        self.0.insert(value);
246        self
247    }
248
249    /// Checks if the metadata container is empty.
250    #[must_use]
251    pub fn is_empty(&self) -> bool {
252        self.0.0.is_empty()
253    }
254}
255
256/// A unique identifier for registered watchers.
257pub(crate) type WatcherId = NonZeroUsize;
258
259/// Manages a collection of watchers for a specific computation type.
260///
261/// Provides functionality to register, notify, and cancel watchers.
262#[derive(Debug)]
263pub struct WatcherManager<T> {
264    inner: Rc<RefCell<WatcherManagerInner<T>>>,
265}
266
267impl<T> Clone for WatcherManager<T> {
268    fn clone(&self) -> Self {
269        Self {
270            inner: self.inner.clone(),
271        }
272    }
273}
274
275impl<T> Default for WatcherManager<T> {
276    fn default() -> Self {
277        Self {
278            inner: Rc::default(),
279        }
280    }
281}
282
283impl<T: 'static> WatcherManager<T> {
284    /// Creates a new, empty watcher manager.
285    #[must_use]
286    pub fn new() -> Self {
287        Self::default()
288    }
289
290    /// Checks if the manager has any registered watchers.
291    #[must_use]
292    pub fn is_empty(&self) -> bool {
293        self.inner.borrow().is_empty()
294    }
295
296    /// Registers a new watcher and returns its unique identifier.
297    pub fn register(&self, watcher: impl Fn(Context<T>) + 'static) -> WatcherId {
298        self.inner.borrow_mut().register(watcher)
299    }
300
301    /// Registers a watcher and returns a guard that will unregister it when dropped.
302    pub fn register_as_guard(
303        &self,
304        watcher: impl Fn(Context<T>) + 'static,
305    ) -> WatcherManagerGuard<T> {
306        let id = self.register(watcher);
307        let this = self.clone();
308        WatcherManagerGuard { manager: this, id }
309    }
310
311    /// Notifies all registered watchers with a preconstructed context.
312    pub fn notify(&self, ctx: &Context<T>)
313    where
314        T: Clone,
315    {
316        let watchers = {
317            let inner = self.inner.borrow();
318            inner.watchers_snapshot()
319        };
320
321        if watchers.is_empty() {
322            return;
323        }
324
325        for watcher in watchers {
326            watcher(ctx.clone());
327        }
328    }
329
330    /// Cancels a previously registered watcher by its identifier.
331    pub fn cancel(&self, id: WatcherId) {
332        self.inner.borrow_mut().cancel(id);
333    }
334}
335
336/// A guard that ensures a watcher is unregistered when dropped.
337#[must_use]
338#[derive(Debug)]
339pub struct WatcherManagerGuard<T: 'static> {
340    manager: WatcherManager<T>,
341    id: WatcherId,
342}
343
344impl<T> WatcherGuard for WatcherManagerGuard<T> {}
345
346impl<T: 'static> Drop for WatcherManagerGuard<T> {
347    fn drop(&mut self) {
348        self.manager.cancel(self.id);
349    }
350}
351
352/// Internal implementation of the watcher manager.
353///
354/// Maintains the collection of watchers and handles identifier assignment.
355struct WatcherManagerInner<T> {
356    id: WatcherId,
357    map: BTreeMap<WatcherId, Watcher<T>>,
358}
359
360impl<T> Debug for WatcherManagerInner<T> {
361    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
362        f.write_str(type_name::<Self>())
363    }
364}
365
366impl<T> Default for WatcherManagerInner<T> {
367    fn default() -> Self {
368        Self {
369            id: WatcherId::MIN,
370            map: BTreeMap::new(),
371        }
372    }
373}
374
375impl<T: 'static> WatcherManagerInner<T> {
376    /// Checks if there are any registered watchers.
377    pub fn is_empty(&self) -> bool {
378        self.map.is_empty()
379    }
380
381    /// Assigns a new unique identifier for a watcher.
382    const fn assign(&mut self) -> WatcherId {
383        let id = self.id;
384        self.id = match self.id.checked_add(1) {
385            Some(id) => id,
386            None => panic!("`id` grows beyond `usize::MAX`"),
387        };
388        id
389    }
390
391    /// Registers a watcher and returns its unique identifier.
392    pub fn register(&mut self, watcher: impl Fn(Context<T>) + 'static) -> WatcherId {
393        let id = self.assign();
394        self.map.insert(id, Rc::new(watcher));
395        id
396    }
397
398    /// Creates a snapshot of the current watchers for notification.
399    fn watchers_snapshot(&self) -> Vec<Watcher<T>> {
400        self.map.values().cloned().collect()
401    }
402
403    /// Cancels a watcher registration by its identifier.
404    pub fn cancel(&mut self, id: WatcherId) {
405        self.map.remove(&id);
406    }
407}