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: Clone + '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, 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
93/// A type-erased wrapper for any collection that implements `Collection`.
94///
95/// This allows storing collections of different concrete types in the same container
96/// while preserving the ability to observe them through the `Collection` interface.
97/// Items are returned as `Box<dyn Any>` to allow runtime type checking.
98pub struct AnyCollection<T> {
99    inner: Box<dyn AnyCollectionImpl<Output = T>>,
100}
101
102impl<T> core::fmt::Debug for AnyCollection<T> {
103    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
104        f.debug_struct("AnyCollection").finish()
105    }
106}
107impl<T> Clone for AnyCollection<T> {
108    fn clone(&self) -> Self {
109        Self {
110            inner: self.inner.clone(),
111        }
112    }
113}
114
115/// A boxed collection watcher.
116pub type BoxCollectionWatcher<T> = Box<dyn for<'a> Fn(Context<&'a [T]>) + 'static>;
117
118/// Internal trait for type-erased collection operations.
119trait AnyCollectionImpl {
120    type Output;
121    fn get(&self, index: usize) -> Option<Self::Output>;
122    fn len(&self) -> usize;
123    fn is_empty(&self) -> bool;
124    fn watch(
125        &self,
126        range: (Bound<usize>, Bound<usize>),
127        watcher: BoxCollectionWatcher<Self::Output>,
128    ) -> BoxWatcherGuard;
129    fn clone(&self) -> Box<dyn AnyCollectionImpl<Output = Self::Output>>;
130}
131
132impl<T> AnyCollectionImpl for T
133where
134    T: Collection,
135{
136    type Output = T::Item;
137    fn get(&self, index: usize) -> Option<Self::Output> {
138        <T as Collection>::get(self, index)
139    }
140
141    fn len(&self) -> usize {
142        <T as Collection>::len(self)
143    }
144
145    fn is_empty(&self) -> bool {
146        <T as Collection>::is_empty(self)
147    }
148
149    fn watch(
150        &self,
151        range: (Bound<usize>, Bound<usize>),
152        watcher: Box<dyn for<'a> Fn(Context<&'a [Self::Output]>) + 'static>,
153    ) -> BoxWatcherGuard {
154        Box::new(<T as Collection>::watch(self, range, watcher))
155    }
156
157    fn clone(&self) -> Box<dyn AnyCollectionImpl<Output = Self::Output>> {
158        Box::new(self.clone())
159    }
160}
161
162impl<T> AnyCollection<T> {
163    /// Creates a new `AnyCollection` from any type that implements `Collection`.
164    pub fn new<C>(collection: C) -> Self
165    where
166        C: Collection<Item = T>,
167    {
168        Self {
169            inner: Box::new(collection),
170        }
171    }
172
173    /// Gets an item from the collection at the specified index.
174    ///
175    /// Returns `None` if the index is out of bounds.
176    /// The returned item is type-erased as `Box<dyn Any>`.
177    #[must_use]
178    pub fn get(&self, index: usize) -> Option<T> {
179        self.inner.get(index)
180    }
181
182    /// Returns the number of items in the collection.
183    #[must_use]
184    pub fn len(&self) -> usize {
185        self.inner.len()
186    }
187
188    /// Returns `true` if the collection contains no elements.
189    #[must_use]
190    pub fn is_empty(&self) -> bool {
191        self.inner.is_empty()
192    }
193
194    /// Registers a watcher for changes in the specified range of the collection.
195    ///
196    /// The watcher receives a `Vec<Box<dyn Any>>` of items.
197    /// Returns a type-erased guard that will unregister the watcher when dropped.
198    pub fn watch(
199        &self,
200        range: impl RangeBounds<usize>,
201        watcher: impl for<'a> Fn(Context<&'a [T]>) + 'static,
202    ) -> BoxWatcherGuard {
203        let start_bound = match range.start_bound() {
204            Bound::Included(&n) => Bound::Included(n),
205            Bound::Excluded(&n) => Bound::Excluded(n),
206            Bound::Unbounded => Bound::Unbounded,
207        };
208        let end_bound = match range.end_bound() {
209            Bound::Included(&n) => Bound::Included(n),
210            Bound::Excluded(&n) => Bound::Excluded(n),
211            Bound::Unbounded => Bound::Unbounded,
212        };
213
214        self.inner
215            .watch((start_bound, end_bound), Box::new(watcher))
216    }
217}