nami_core/
collection.rs

1/// A trait for collections that can be observed for changes.
2///
3/// This trait provides a common interface for collections that support
4/// reactive programming patterns through watchers.
5pub trait Collection: 'static {
6    /// The type of items stored in the collection.
7    type Item: 'static;
8    /// The type of guard returned when registering a watcher.
9    type Guard: WatcherGuard;
10
11    /// Gets an item from the collection at the specified index.
12    fn get(&self, index: usize) -> Option<Self::Item>;
13    /// Returns the number of items in the collection.
14    fn len(&self) -> usize;
15
16    /// Returns `true` if the collection contains no elements.
17    fn is_empty(&self) -> bool {
18        self.len() == 0
19    }
20
21    /// Registers a watcher for changes in the specified range of the collection.
22    ///
23    /// Returns a guard that will unregister the watcher when dropped.
24    fn watch(
25        &self,
26        range: impl RangeBounds<usize>,
27        watcher: impl for<'a> Fn(Context<&'a [Self::Item]>) + 'static, // watcher will receive a slice of items, its range is decided by the range parameter
28    ) -> Self::Guard;
29}
30
31use core::ops::{Bound, RangeBounds};
32
33use alloc::{boxed::Box, rc::Rc, vec::Vec};
34
35use crate::watcher::{BoxWatcherGuard, Context, WatcherGuard};
36
37impl<T: Clone + 'static> Collection for Vec<T> {
38    type Item = T;
39    type Guard = ();
40
41    fn get(&self, index: usize) -> Option<Self::Item> {
42        self.as_slice().get(index).cloned()
43    }
44    fn len(&self) -> usize {
45        <[T]>::len(self)
46    }
47    fn watch(
48        &self,
49        _range: impl RangeBounds<usize>,
50        _watcher: impl for<'a> Fn(Context<&'a [Self::Item]>) + 'static,
51    ) -> Self::Guard {
52        // Vec is static - no reactivity, so watch is a no-op
53    }
54}
55
56impl<T: Clone + 'static> Collection for &'static [T] {
57    type Item = T;
58    type Guard = ();
59
60    fn get(&self, index: usize) -> Option<Self::Item> {
61        (*self).get(index).cloned()
62    }
63    fn len(&self) -> usize {
64        <[T]>::len(self)
65    }
66    fn watch(
67        &self,
68        _range: impl RangeBounds<usize>,
69        _watcher: impl for<'a> Fn(Context<&'a [Self::Item]>) + 'static,
70    ) -> Self::Guard {
71        // Slices are static - no reactivity, so watch is a no-op
72    }
73}
74
75impl<T: Clone + 'static, const N: usize> Collection for [T; N] {
76    type Item = T;
77    type Guard = ();
78
79    fn get(&self, index: usize) -> Option<Self::Item> {
80        self.as_slice().get(index).cloned()
81    }
82    fn len(&self) -> usize {
83        N
84    }
85    fn watch(
86        &self,
87        _range: impl RangeBounds<usize>,
88        _watcher: impl for<'a> Fn(Context<&'a [Self::Item]>) + 'static,
89    ) -> Self::Guard {
90    }
91}
92
93impl<T: Clone + 'static> Collection for alloc::rc::Rc<[T]> {
94    type Item = T;
95    type Guard = ();
96
97    fn get(&self, index: usize) -> Option<Self::Item> {
98        self.as_ref().get(index).cloned()
99    }
100    fn len(&self) -> usize {
101        self.as_ref().len()
102    }
103    fn watch(
104        &self,
105        _range: impl RangeBounds<usize>,
106        _watcher: impl for<'a> Fn(Context<&'a [Self::Item]>) + 'static,
107    ) -> Self::Guard {
108    }
109}
110
111impl<C> Collection for Box<C>
112where
113    C: Collection,
114{
115    type Item = C::Item;
116    type Guard = C::Guard;
117
118    fn get(&self, index: usize) -> Option<Self::Item> {
119        (**self).get(index)
120    }
121    fn len(&self) -> usize {
122        (**self).len()
123    }
124    fn watch(
125        &self,
126        range: impl RangeBounds<usize>,
127        watcher: impl for<'a> Fn(Context<&'a [Self::Item]>) + 'static,
128    ) -> Self::Guard {
129        (**self).watch(range, watcher)
130    }
131}
132
133impl<C> Collection for Rc<C>
134where
135    C: Collection,
136{
137    type Item = C::Item;
138    type Guard = C::Guard;
139
140    fn get(&self, index: usize) -> Option<Self::Item> {
141        (**self).get(index)
142    }
143    fn len(&self) -> usize {
144        (**self).len()
145    }
146    fn watch(
147        &self,
148        range: impl RangeBounds<usize>,
149        watcher: impl for<'a> Fn(Context<&'a [Self::Item]>) + 'static,
150    ) -> Self::Guard {
151        (**self).watch(range, watcher)
152    }
153}
154
155/// A type-erased wrapper for any collection that implements `Collection`.
156///
157/// This allows storing collections of different concrete types in the same container
158/// while preserving the ability to observe them through the `Collection` interface.
159/// Items are returned as `Box<dyn Any>` to allow runtime type checking.
160pub struct AnyCollection<T> {
161    inner: Box<dyn AnyCollectionImpl<Output = T>>,
162}
163
164impl<T> core::fmt::Debug for AnyCollection<T> {
165    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
166        f.debug_struct("AnyCollection").finish()
167    }
168}
169
170/// A boxed collection watcher.
171pub type BoxCollectionWatcher<T> = Box<dyn for<'a> Fn(Context<&'a [T]>) + 'static>;
172
173/// Internal trait for type-erased collection operations.
174trait AnyCollectionImpl {
175    type Output;
176    fn get(&self, index: usize) -> Option<Self::Output>;
177    fn len(&self) -> usize;
178    fn is_empty(&self) -> bool;
179    fn watch(
180        &self,
181        range: (Bound<usize>, Bound<usize>),
182        watcher: BoxCollectionWatcher<Self::Output>,
183    ) -> BoxWatcherGuard;
184}
185
186impl<T> AnyCollectionImpl for T
187where
188    T: Collection,
189{
190    type Output = T::Item;
191    fn get(&self, index: usize) -> Option<Self::Output> {
192        <T as Collection>::get(self, index)
193    }
194
195    fn len(&self) -> usize {
196        <T as Collection>::len(self)
197    }
198
199    fn is_empty(&self) -> bool {
200        <T as Collection>::is_empty(self)
201    }
202
203    fn watch(
204        &self,
205        range: (Bound<usize>, Bound<usize>),
206        watcher: Box<dyn for<'a> Fn(Context<&'a [Self::Output]>) + 'static>,
207    ) -> BoxWatcherGuard {
208        Box::new(<T as Collection>::watch(self, range, watcher))
209    }
210}
211
212impl<T> AnyCollection<T> {
213    /// Creates a new `AnyCollection` from any type that implements `Collection`.
214    pub fn new<C>(collection: C) -> Self
215    where
216        C: Collection<Item = T>,
217    {
218        Self {
219            inner: Box::new(collection),
220        }
221    }
222
223    /// Gets an item from the collection at the specified index.
224    ///
225    /// Returns `None` if the index is out of bounds.
226    /// The returned item is type-erased as `Box<dyn Any>`.
227    #[must_use]
228    pub fn get(&self, index: usize) -> Option<T> {
229        self.inner.get(index)
230    }
231
232    /// Returns the number of items in the collection.
233    #[must_use]
234    pub fn len(&self) -> usize {
235        self.inner.len()
236    }
237
238    /// Returns `true` if the collection contains no elements.
239    #[must_use]
240    pub fn is_empty(&self) -> bool {
241        self.inner.is_empty()
242    }
243
244    /// Registers a watcher for changes in the specified range of the collection.
245    ///
246    /// The watcher receives a `Vec<Box<dyn Any>>` of items.
247    /// Returns a type-erased guard that will unregister the watcher when dropped.
248    pub fn watch(
249        &self,
250        range: impl RangeBounds<usize>,
251        watcher: impl for<'a> Fn(Context<&'a [T]>) + 'static,
252    ) -> BoxWatcherGuard {
253        let start_bound = match range.start_bound() {
254            Bound::Included(&n) => Bound::Included(n),
255            Bound::Excluded(&n) => Bound::Excluded(n),
256            Bound::Unbounded => Bound::Unbounded,
257        };
258        let end_bound = match range.end_bound() {
259            Bound::Included(&n) => Bound::Included(n),
260            Bound::Excluded(&n) => Bound::Excluded(n),
261            Bound::Unbounded => Bound::Unbounded,
262        };
263
264        self.inner
265            .watch((start_bound, end_bound), Box::new(watcher))
266    }
267}