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