leptos_windowing/
cache.rs

1use leptos::prelude::*;
2use reactive_stores::{Store, StoreFieldIterator};
3use std::{
4    ops::{Index, Range},
5    sync::Arc,
6};
7
8use crate::{LoadedItems, item_state::ItemState};
9
10/// This is a cache for items used internally to track
11/// which items are already loaded, which are still loading and which are missing.
12#[derive(Store, Debug)]
13pub struct Cache<T>
14where
15    T: Send + Sync + 'static,
16{
17    items: Vec<ItemState<T>>,
18    item_count: Option<usize>,
19}
20
21impl<T: Send + Sync + 'static> Default for Cache<T> {
22    fn default() -> Self {
23        Self {
24            items: Vec::new(),
25            item_count: None,
26        }
27    }
28}
29
30impl<T: Send + Sync + 'static> Cache<T> {
31    /// Create a new store of the cache.
32    pub fn new_store() -> Store<Self> {
33        Store::new(Self::default())
34    }
35
36    #[inline]
37    /// Length of the cache.
38    pub fn len(&self) -> usize {
39        self.items.len()
40    }
41
42    pub fn is_empty(&self) -> bool {
43        self.items.is_empty()
44    }
45
46    #[inline]
47    /// Resize the cache to the specified length.
48    pub fn resize(&mut self, len: usize) {
49        self.items.resize(len, ItemState::Placeholder);
50    }
51
52    /// Grow the cache size to the specified length.
53    pub fn grow(&mut self, len: usize) {
54        if self.items.len() < len {
55            self.items.resize(len, ItemState::Placeholder);
56        }
57    }
58
59    /// Marks the specified range of items as loading.
60    pub fn write_loading(this_store: Store<Self>, range: Range<usize>) {
61        if range.end > this_store.items().read().len() {
62            this_store
63                .items()
64                .write()
65                .resize(range.end, ItemState::Placeholder);
66        }
67
68        for row in &mut this_store
69            .items()
70            .iter_unkeyed()
71            .skip(range.start)
72            .take(range.len())
73        {
74            if let Some(mut row) = row.try_write() {
75                *row = ItemState::Loading;
76            }
77        }
78    }
79
80    /// Called after the loader has finished loading items.
81    ///
82    /// This will update the respective range of items with the loaded data (or errors).
83    pub fn write_loaded(
84        this_store: Store<Self>,
85        loading_result: Result<LoadedItems<T>, String>,
86        requested_load_range: Range<usize>,
87    ) {
88        match loading_result {
89            Ok(LoadedItems { items, range }) => {
90                #[cfg(debug_assertions)]
91                let _z = leptos::reactive::diagnostics::SpecialNonReactiveZone::enter();
92
93                if range.end > this_store.items().read_untracked().len()
94                    && let Some(mut writer) = this_store.items().try_write()
95                {
96                    writer.resize(range.end, ItemState::Placeholder);
97                }
98
99                for (self_row, loaded_row) in this_store
100                    .items()
101                    .iter_unkeyed()
102                    .skip(range.start)
103                    .zip(items)
104                {
105                    if let Some(mut writer) = self_row.try_write() {
106                        *writer = ItemState::Loaded(Arc::new(loaded_row));
107                    }
108                }
109            }
110            Err(error) => {
111                let range = requested_load_range.start
112                    ..requested_load_range
113                        .end
114                        .min(this_store.items().read().len());
115                if range.start >= range.end {
116                    return;
117                }
118
119                for row in this_store.items().iter_unkeyed() {
120                    if let Some(mut writer) = row.try_write() {
121                        *writer = ItemState::Error(error.clone());
122                    }
123                }
124            }
125        }
126    }
127
128    #[inline]
129    /// Returns the range of items that are missing from the cache inside the given range.
130    ///
131    /// Used to know what items should be loaded and which ones are already loaded or in the process of being loaded.
132    /// Errored items are not considered missing here.
133    pub fn missing_range(&self, range_to_load: Range<usize>) -> Option<Range<usize>> {
134        let do_load_predicate = |item: &ItemState<T>| matches!(item, &ItemState::Placeholder);
135
136        if range_to_load.end <= range_to_load.start {
137            return None;
138        }
139
140        if range_to_load.start >= self.items.len() {
141            return Some(range_to_load);
142        }
143
144        let existing_range_end = self.items.len().min(range_to_load.end);
145
146        let slice = &self.items[range_to_load.start..existing_range_end];
147
148        let start = slice
149            .iter()
150            .position(do_load_predicate)
151            .unwrap_or(slice.len());
152        let start = start + range_to_load.start;
153
154        let mut end = if range_to_load.end >= self.items.len() {
155            range_to_load.end
156        } else {
157            slice.iter().rposition(do_load_predicate)? + range_to_load.start + 1
158        };
159
160        if let Some(item_count) = self.item_count {
161            end = end.min(item_count);
162        }
163
164        if end <= start {
165            return None;
166        }
167
168        Some(
169            start
170                ..end
171                    .max(range_to_load.end)
172                    .min(self.item_count.unwrap_or(usize::MAX)),
173        )
174    }
175
176    #[inline]
177    /// Sets all items in the cache to the placeholder state.
178    pub fn clear(this_store: Store<Self>) {
179        this_store.items().write().fill(ItemState::Placeholder);
180        this_store.item_count().set(None);
181    }
182}
183
184impl<T: Sync + Send> Index<Range<usize>> for Cache<T> {
185    type Output = [ItemState<T>];
186
187    #[inline]
188    fn index(&self, index: Range<usize>) -> &Self::Output {
189        &self.items[index]
190    }
191}
192
193impl<T: Send + Sync> Index<usize> for Cache<T> {
194    type Output = ItemState<T>;
195
196    #[inline]
197    fn index(&self, index: usize) -> &Self::Output {
198        &self.items[index]
199    }
200}
201
202#[cfg(test)]
203mod tests {
204    use super::*;
205
206    #[test]
207    fn test_missing_range() {
208        let cache = Cache::<i32>::new_store();
209
210        assert_eq!(cache.read_untracked().missing_range(0..10), Some(0..10));
211        assert_eq!(cache.read_untracked().missing_range(5..10), Some(5..10));
212
213        Cache::write_loaded(
214            cache,
215            Ok(LoadedItems {
216                items: (0..5).collect::<Vec<_>>(),
217                range: 0..5,
218            }),
219            0..5,
220        );
221
222        assert_eq!(cache.read_untracked().missing_range(0..10), Some(5..10));
223        assert_eq!(cache.read_untracked().missing_range(5..10), Some(5..10));
224        assert_eq!(cache.read_untracked().missing_range(5..20), Some(5..20));
225
226        Cache::write_loading(cache, 5..9);
227
228        assert_eq!(cache.read_untracked().missing_range(0..10), Some(9..10));
229        assert_eq!(cache.read_untracked().missing_range(5..10), Some(9..10));
230        assert_eq!(cache.read_untracked().missing_range(5..20), Some(9..20));
231    }
232}