1use std::cell::RefCell;
9use std::collections::HashMap;
10use std::rc::Rc;
11
12#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
22pub enum LazyLayoutKey {
23 User(u64),
25 Index(usize),
27}
28
29impl LazyLayoutKey {
30 const USER_TAG: u64 = 0b00 << 62;
32 const INDEX_TAG: u64 = 0b01 << 62;
34 const VALUE_MASK: u64 = (1u64 << 62) - 1;
37
38 #[inline]
54 pub fn to_slot_id(self) -> u64 {
55 match self {
56 LazyLayoutKey::User(k) => {
58 let value = Self::normalize_value(k, "User");
59 Self::USER_TAG | value
60 }
61 LazyLayoutKey::Index(i) => {
62 let value = Self::normalize_value(i as u64, "Index");
63 Self::INDEX_TAG | value
64 }
65 }
66 }
67
68 #[inline]
69 fn normalize_value(value: u64, kind: &'static str) -> u64 {
70 if value <= Self::VALUE_MASK {
71 value
72 } else {
73 log::warn!(
74 "LazyList {} key {:#018x} exceeds 62 bits; mixing to 62 bits to avoid overflow",
75 kind,
76 value
77 );
78 Self::mix_to_value_bits(value)
79 }
80 }
81
82 #[inline]
83 fn mix_to_value_bits(mut value: u64) -> u64 {
84 value ^= value >> 33;
85 value = value.wrapping_mul(0xff51afd7ed558ccd);
86 value ^= value >> 33;
87 value = value.wrapping_mul(0xc4ceb9fe1a85ec53);
88 value ^= value >> 33;
89 value & Self::VALUE_MASK
90 }
91
92 #[inline]
94 pub fn is_user_key(self) -> bool {
95 matches!(self, LazyLayoutKey::User(_))
96 }
97
98 #[inline]
100 pub fn is_index_key(self) -> bool {
101 matches!(self, LazyLayoutKey::Index(_))
102 }
103}
104
105#[doc(hidden)]
107pub struct LazyScopeMarker;
108
109pub trait LazyListScope {
130 fn item<F>(&mut self, key: Option<u64>, content_type: Option<u64>, content: F)
137 where
138 F: Fn() + 'static;
139
140 fn items<K, C, F>(
148 &mut self,
149 count: usize,
150 key: Option<K>,
151 content_type: Option<C>,
152 item_content: F,
153 ) where
154 K: Fn(usize) -> u64 + 'static,
155 C: Fn(usize) -> u64 + 'static,
156 F: Fn(usize) + 'static;
157}
158
159pub struct LazyListInterval {
164 pub start_index: usize,
166
167 pub count: usize,
169
170 pub key: Option<Rc<dyn Fn(usize) -> u64>>,
173
174 pub content_type: Option<Rc<dyn Fn(usize) -> u64>>,
177
178 pub content: Rc<dyn Fn(usize)>,
181}
182
183impl std::fmt::Debug for LazyListInterval {
184 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
185 f.debug_struct("LazyListInterval")
186 .field("start_index", &self.start_index)
187 .field("count", &self.count)
188 .finish_non_exhaustive()
189 }
190}
191
192pub struct LazyListIntervalContent {
196 intervals: Vec<LazyListInterval>,
197 total_count: usize,
198 key_cache: RefCell<Option<HashMap<u64, usize>>>,
201}
202
203impl LazyListIntervalContent {
204 pub fn new() -> Self {
206 Self {
207 intervals: Vec::new(),
208 total_count: 0,
209 key_cache: RefCell::new(None),
210 }
211 }
212
213 fn invalidate_cache(&self) {
215 *self.key_cache.borrow_mut() = None;
216 }
217
218 fn ensure_cache(&self) {
224 let mut cache = self.key_cache.borrow_mut();
225 if cache.is_some() {
226 return; }
228
229 let mut map = HashMap::with_capacity(self.total_count);
230 for index in 0..self.total_count {
231 let slot_id = self.get_key(index).to_slot_id();
232 map.insert(slot_id, index);
233 }
234 *cache = Some(map);
235 }
236
237 pub fn item_count(&self) -> usize {
240 self.total_count
241 }
242
243 pub fn intervals(&self) -> &[LazyListInterval] {
245 &self.intervals
246 }
247
248 pub fn get_key(&self, index: usize) -> LazyLayoutKey {
255 if let Some((interval, local_index)) = self.find_interval(index) {
256 if let Some(key_fn) = &interval.key {
257 return LazyLayoutKey::User(key_fn(local_index));
258 }
259 }
260 LazyLayoutKey::Index(index)
262 }
263
264 pub fn get_content_type(&self, index: usize) -> Option<u64> {
267 if let Some((interval, local_index)) = self.find_interval(index) {
268 if let Some(type_fn) = &interval.content_type {
269 return Some(type_fn(local_index));
270 }
271 }
272 None
273 }
274
275 pub fn invoke_content(&self, index: usize) {
280 if let Some((interval, local_index)) = self.find_interval(index) {
281 (interval.content)(local_index);
282 }
283 }
284
285 pub fn with_interval<T, F>(&self, global_index: usize, block: F) -> Option<T>
288 where
289 F: FnOnce(usize, &LazyListInterval) -> T,
290 {
291 self.find_interval(global_index)
292 .map(|(interval, local_index)| block(local_index, interval))
293 }
294
295 #[must_use]
305 pub fn get_index_by_key(&self, key: LazyLayoutKey) -> Option<usize> {
306 let slot_id = key.to_slot_id();
308 self.get_index_by_slot_id(slot_id)
309 }
310
311 pub fn get_index_by_key_in_range(
314 &self,
315 key: LazyLayoutKey,
316 range: std::ops::Range<usize>,
317 ) -> Option<usize> {
318 let start = range.start.min(self.total_count);
319 let end = range.end.min(self.total_count);
320 (start..end).find(|&index| self.get_key(index) == key)
321 }
322
323 const CACHE_THRESHOLD: usize = 64;
325
326 #[must_use]
335 pub fn get_index_by_slot_id(&self, slot_id: u64) -> Option<usize> {
336 if self.total_count <= Self::CACHE_THRESHOLD {
338 return (0..self.total_count)
339 .find(|&index| self.get_key(index).to_slot_id() == slot_id);
340 }
341
342 self.ensure_cache();
344 if let Some(cache) = self.key_cache.borrow().as_ref() {
345 return cache.get(&slot_id).copied();
346 }
347
348 log::warn!(
350 "get_index_by_slot_id: cache unexpectedly missing ({} items), using linear search",
351 self.total_count
352 );
353 (0..self.total_count).find(|&index| self.get_key(index).to_slot_id() == slot_id)
354 }
355
356 pub fn get_index_by_slot_id_in_range(
358 &self,
359 slot_id: u64,
360 range: std::ops::Range<usize>,
361 ) -> Option<usize> {
362 let start = range.start.min(self.total_count);
363 let end = range.end.min(self.total_count);
364 (start..end).find(|&index| self.get_key(index).to_slot_id() == slot_id)
365 }
366
367 fn find_interval(&self, index: usize) -> Option<(&LazyListInterval, usize)> {
371 if self.intervals.is_empty() || index >= self.total_count {
372 return None;
373 }
374
375 let pos = self
377 .intervals
378 .partition_point(|interval| interval.start_index + interval.count <= index);
379
380 if pos < self.intervals.len() {
381 let interval = &self.intervals[pos];
382 if index >= interval.start_index && index < interval.start_index + interval.count {
383 let local_index = index - interval.start_index;
384 return Some((interval, local_index));
385 }
386 }
387 None
388 }
389}
390
391impl Default for LazyListIntervalContent {
392 fn default() -> Self {
393 Self::new()
394 }
395}
396
397impl LazyListScope for LazyListIntervalContent {
398 fn item<F>(&mut self, key: Option<u64>, content_type: Option<u64>, content: F)
399 where
400 F: Fn() + 'static,
401 {
402 self.invalidate_cache(); let start_index = self.total_count;
404 self.intervals.push(LazyListInterval {
405 start_index,
406 count: 1,
407 key: key.map(|k| Rc::new(move |_| k) as Rc<dyn Fn(usize) -> u64>),
408 content_type: content_type.map(|t| Rc::new(move |_| t) as Rc<dyn Fn(usize) -> u64>),
409 content: Rc::new(move |_| content()),
410 });
411 self.total_count += 1;
412 }
413
414 fn items<K, C, F>(
415 &mut self,
416 count: usize,
417 key: Option<K>,
418 content_type: Option<C>,
419 item_content: F,
420 ) where
421 K: Fn(usize) -> u64 + 'static,
422 C: Fn(usize) -> u64 + 'static,
423 F: Fn(usize) + 'static,
424 {
425 if count == 0 {
426 return;
427 }
428
429 self.invalidate_cache(); let start_index = self.total_count;
431 self.intervals.push(LazyListInterval {
432 start_index,
433 count,
434 key: key.map(|k| Rc::new(k) as Rc<dyn Fn(usize) -> u64>),
435 content_type: content_type.map(|c| Rc::new(c) as Rc<dyn Fn(usize) -> u64>),
436 content: Rc::new(item_content),
437 });
438 self.total_count += count;
439 }
440}
441
442use crate::lazy::item_provider::LazyLayoutItemProvider;
443
444impl LazyLayoutItemProvider for LazyListIntervalContent {
448 fn item_count(&self) -> usize {
449 self.total_count
450 }
451
452 fn get_key(&self, index: usize) -> u64 {
453 LazyListIntervalContent::get_key(self, index).to_slot_id()
455 }
456
457 fn get_content_type(&self, index: usize) -> Option<u64> {
458 LazyListIntervalContent::get_content_type(self, index)
460 }
461
462 fn get_index(&self, key: u64) -> Option<usize> {
463 self.get_index_by_slot_id(key)
465 }
466}
467
468pub trait LazyListScopeExt: LazyListScope {
478 fn items_slice<T, F>(&mut self, items: &[T], item_content: F)
506 where
507 T: Clone + 'static,
508 F: Fn(&T) + 'static,
509 {
510 let items_rc: Rc<[T]> = items.to_vec().into();
513 self.items(
514 items.len(),
515 None::<fn(usize) -> u64>,
516 None::<fn(usize) -> u64>,
517 move |index| {
518 if let Some(item) = items_rc.get(index) {
519 item_content(item);
520 }
521 },
522 );
523 }
524
525 fn items_vec<T, F>(&mut self, items: Vec<T>, item_content: F)
540 where
541 T: 'static,
542 F: Fn(&T) + 'static,
543 {
544 let len = items.len();
545 let items_rc: Rc<[T]> = Rc::from(items);
546 self.items(
547 len,
548 None::<fn(usize) -> u64>,
549 None::<fn(usize) -> u64>,
550 move |index| {
551 if let Some(item) = items_rc.get(index) {
552 item_content(item);
553 }
554 },
555 );
556 }
557
558 fn items_indexed<T, L, F>(&mut self, items: L, item_content: F)
582 where
583 T: 'static,
584 L: Into<Rc<[T]>>,
585 F: Fn(usize, &T) + 'static,
586 {
587 let items_rc: Rc<[T]> = items.into();
588 self.items(
589 items_rc.len(),
590 None::<fn(usize) -> u64>,
591 None::<fn(usize) -> u64>,
592 move |index| {
593 if let Some(item) = items_rc.get(index) {
594 item_content(index, item);
595 }
596 },
597 );
598 }
599
600 fn items_slice_rc<T, F>(&mut self, items: Rc<[T]>, item_content: F)
614 where
615 T: 'static,
616 F: Fn(&T) + 'static,
617 {
618 let len = items.len();
619 self.items(
620 len,
621 None::<fn(usize) -> u64>,
622 None::<fn(usize) -> u64>,
623 move |index| {
624 if let Some(item) = items.get(index) {
625 item_content(item);
626 }
627 },
628 );
629 }
630
631 fn items_indexed_rc<T, F>(&mut self, items: Rc<[T]>, item_content: F)
645 where
646 T: 'static,
647 F: Fn(usize, &T) + 'static,
648 {
649 let len = items.len();
650 self.items(
651 len,
652 None::<fn(usize) -> u64>,
653 None::<fn(usize) -> u64>,
654 move |index| {
655 if let Some(item) = items.get(index) {
656 item_content(index, item);
657 }
658 },
659 );
660 }
661
662 fn items_with_provider<T, P, F>(&mut self, count: usize, provider: P, item_content: F)
684 where
685 T: 'static,
686 P: Fn(usize) -> Option<T> + 'static,
687 F: Fn(T) + 'static,
688 {
689 self.items(
690 count,
691 None::<fn(usize) -> u64>,
692 None::<fn(usize) -> u64>,
693 move |index| {
694 if let Some(item) = provider(index) {
695 item_content(item);
696 }
697 },
698 );
699 }
700
701 fn items_indexed_with_provider<T, P, F>(&mut self, count: usize, provider: P, item_content: F)
720 where
721 T: 'static,
722 P: Fn(usize) -> Option<T> + 'static,
723 F: Fn(usize, T) + 'static,
724 {
725 self.items(
726 count,
727 None::<fn(usize) -> u64>,
728 None::<fn(usize) -> u64>,
729 move |index| {
730 if let Some(item) = provider(index) {
731 item_content(index, item);
732 }
733 },
734 );
735 }
736}
737
738impl<T: LazyListScope + ?Sized> LazyListScopeExt for T {}
739
740#[cfg(test)]
741mod tests {
742 use super::*;
743 use std::cell::Cell;
744
745 #[test]
746 fn key_overflow_warning_suppression_has_no_process_global_state() {
747 let source = include_str!("lazy_list_scope.rs");
748 let user_logged = ["USER_OVERFLOW", "_LOGGED"].concat();
749 let index_logged = ["INDEX_OVERFLOW", "_LOGGED"].concat();
750 let atomic_bool = ["Atomic", "Bool"].concat();
751
752 assert!(
753 !source.contains(&user_logged)
754 && !source.contains(&index_logged)
755 && !source.contains(&atomic_bool),
756 "lazy-list key overflow diagnostics must not use process-global suppression state"
757 );
758 }
759
760 #[test]
761 fn test_single_item() {
762 let mut content = LazyListIntervalContent::new();
763 let called = Rc::new(Cell::new(false));
764 let called_clone = Rc::clone(&called);
765
766 content.item(Some(42), None, move || {
767 called_clone.set(true);
768 });
769
770 assert_eq!(content.item_count(), 1);
771 assert_eq!(content.get_key(0), LazyLayoutKey::User(42));
772
773 content.invoke_content(0);
774 assert!(called.get());
775 }
776
777 #[test]
778 fn test_multiple_items() {
779 let mut content = LazyListIntervalContent::new();
780
781 content.items(
782 5,
783 Some(|i| (i * 10) as u64),
784 None::<fn(usize) -> u64>,
785 |_i| {},
786 );
787
788 assert_eq!(content.item_count(), 5);
789 assert_eq!(content.get_key(0), LazyLayoutKey::User(0));
790 assert_eq!(content.get_key(1), LazyLayoutKey::User(10));
791 assert_eq!(content.get_key(4), LazyLayoutKey::User(40));
792 }
793
794 #[test]
795 fn test_mixed_intervals() {
796 let mut content = LazyListIntervalContent::new();
797
798 content.item(Some(100), None, || {});
800
801 content.items(3, Some(|i| i as u64), None::<fn(usize) -> u64>, |_| {});
803
804 content.item(Some(200), None, || {});
806
807 assert_eq!(content.item_count(), 5);
808 assert_eq!(content.get_key(0), LazyLayoutKey::User(100)); assert_eq!(content.get_key(1), LazyLayoutKey::User(0)); assert_eq!(content.get_key(2), LazyLayoutKey::User(1)); assert_eq!(content.get_key(3), LazyLayoutKey::User(2)); assert_eq!(content.get_key(4), LazyLayoutKey::User(200)); }
814
815 #[test]
816 fn test_with_interval() {
817 let mut content = LazyListIntervalContent::new();
818 content.items(
819 5,
820 None::<fn(usize) -> u64>,
821 None::<fn(usize) -> u64>,
822 |_| {},
823 );
824
825 let result = content.with_interval(3, |local_idx, interval| (local_idx, interval.count));
826
827 assert_eq!(result, Some((3, 5)));
828 }
829
830 #[test]
831 fn test_user_keys_dont_collide_with_default_keys() {
832 let mut content = LazyListIntervalContent::new();
833
834 content.item(Some(0), None, || {});
836 content.item(None, None, || {});
838 content.item(Some(1), None, || {});
840
841 assert_eq!(content.get_key(0), LazyLayoutKey::User(0));
843 assert_eq!(content.get_key(1), LazyLayoutKey::Index(1));
844 assert_eq!(content.get_key(2), LazyLayoutKey::User(1));
845
846 assert_ne!(content.get_key(0), content.get_key(1));
848 assert_ne!(content.get_key(2), content.get_key(1));
849
850 assert_ne!(
852 content.get_key(0).to_slot_id(),
853 content.get_key(1).to_slot_id()
854 );
855 }
856
857 #[test]
858 fn test_slot_id_collision_prevention() {
859 let user_key = LazyLayoutKey::User(0);
861 let index_key = LazyLayoutKey::Index(0);
862
863 assert_ne!(user_key.to_slot_id(), index_key.to_slot_id());
864
865 assert_eq!(user_key.to_slot_id(), 0); assert_eq!(index_key.to_slot_id(), 1u64 << 62); assert!(user_key.to_slot_id() < (1u64 << 62));
873 assert!(index_key.to_slot_id() >= (1u64 << 62));
874 assert!(index_key.to_slot_id() < (2u64 << 62));
875
876 let user_max = LazyLayoutKey::User((1u64 << 62) - 1);
878 assert!(
879 user_max.to_slot_id() < (1u64 << 62),
880 "User keys stay in user range"
881 );
882 assert_eq!(user_max.to_slot_id(), (1u64 << 62) - 1); let index_large = LazyLayoutKey::Index(((1u64 << 62) - 1) as usize);
886 assert!(
887 index_large.to_slot_id() >= (1u64 << 62),
888 "Index keys stay in index range"
889 );
890 assert!(
891 index_large.to_slot_id() < (2u64 << 62),
892 "Index keys below reserved range"
893 );
894
895 }
897
898 #[test]
899 fn test_user_key_overflow_is_stable_and_tagged() {
900 let user_max = LazyLayoutKey::User(u64::MAX);
901 let slot = user_max.to_slot_id();
902 assert_eq!(slot, user_max.to_slot_id());
903 assert!(slot < (1u64 << 62));
904 }
905
906 #[test]
907 fn test_index_key_overflow_is_stable_and_tagged() {
908 let index_max = LazyLayoutKey::Index(usize::MAX);
909 let slot = index_max.to_slot_id();
910 assert_eq!(slot, index_max.to_slot_id());
911 assert!(slot >= (1u64 << 62));
912 assert!(slot < (2u64 << 62));
913 }
914
915 #[test]
916 fn test_user_key_high_bits_influence_slot_id() {
917 let key_low = LazyLayoutKey::User(0x0000_0000_0000_0001);
918 let key_high = LazyLayoutKey::User(0x4000_0000_0000_0001); assert_ne!(
920 key_low.to_slot_id(),
921 key_high.to_slot_id(),
922 "High bits are mixed into the slot id to avoid truncation collisions"
923 );
924 }
925
926 #[test]
931 fn test_items_slice() {
932 let mut content = LazyListIntervalContent::new();
933 let data = vec!["Apple", "Banana", "Cherry"];
934 let items_visited = Rc::new(RefCell::new(Vec::new()));
935 let items_clone = items_visited.clone();
936
937 content.items_slice(&data, move |item: &&str| {
938 items_clone.borrow_mut().push((*item).to_string());
939 });
940
941 assert_eq!(content.item_count(), 3);
942
943 for i in 0..3 {
945 content.invoke_content(i);
946 }
947
948 let visited = items_visited.borrow();
949 assert_eq!(*visited, vec!["Apple", "Banana", "Cherry"]);
950 }
951
952 #[test]
953 fn test_items_indexed() {
954 let mut content = LazyListIntervalContent::new();
955 let data = vec![
957 "Apple".to_string(),
958 "Banana".to_string(),
959 "Cherry".to_string(),
960 ];
961 let items_visited = Rc::new(RefCell::new(Vec::new()));
962 let items_clone = items_visited.clone();
963
964 content.items_indexed(data, move |index, item: &String| {
965 items_clone.borrow_mut().push((index, item.clone()));
966 });
967
968 assert_eq!(content.item_count(), 3);
969
970 for i in 0..3 {
971 content.invoke_content(i);
972 }
973
974 let visited = items_visited.borrow();
975 assert_eq!(
976 *visited,
977 vec![
978 (0, "Apple".to_string()),
979 (1, "Banana".to_string()),
980 (2, "Cherry".to_string())
981 ]
982 );
983 }
984
985 #[test]
986 fn test_items_indexed_slice() {
987 let mut content = LazyListIntervalContent::new();
988 let data = vec!["Apple", "Banana", "Cherry"];
990 let items_visited = Rc::new(RefCell::new(Vec::new()));
991 let items_clone = items_visited.clone();
992
993 content.items_indexed(data.as_slice(), move |index, item: &&str| {
995 items_clone.borrow_mut().push((index, (*item).to_string()));
996 });
997
998 assert_eq!(content.item_count(), 3);
999
1000 for i in 0..3 {
1001 content.invoke_content(i);
1002 }
1003
1004 let visited = items_visited.borrow();
1005 assert_eq!(
1006 *visited,
1007 vec![
1008 (0, "Apple".to_string()),
1009 (1, "Banana".to_string()),
1010 (2, "Cherry".to_string())
1011 ]
1012 );
1013 }
1014
1015 #[test]
1016 fn test_items_slice_rc() {
1017 let mut content = LazyListIntervalContent::new();
1018 let data: Rc<[String]> = Rc::from(vec!["Apple".into(), "Banana".into()]);
1019 let items_visited = Rc::new(RefCell::new(Vec::new()));
1020 let items_clone = items_visited.clone();
1021
1022 content.items_slice_rc(Rc::clone(&data), move |item: &String| {
1023 items_clone.borrow_mut().push(item.clone());
1024 });
1025
1026 assert_eq!(content.item_count(), 2);
1027
1028 for i in 0..2 {
1029 content.invoke_content(i);
1030 }
1031
1032 let visited = items_visited.borrow();
1033 assert_eq!(*visited, vec!["Apple", "Banana"]);
1034 }
1035
1036 #[test]
1037 fn test_items_indexed_rc() {
1038 let mut content = LazyListIntervalContent::new();
1039 let data: Rc<[String]> = Rc::from(vec!["Apple".into(), "Banana".into()]);
1040 let items_visited = Rc::new(RefCell::new(Vec::new()));
1041 let items_clone = items_visited.clone();
1042
1043 content.items_indexed_rc(Rc::clone(&data), move |index, item: &String| {
1044 items_clone.borrow_mut().push((index, item.clone()));
1045 });
1046
1047 assert_eq!(content.item_count(), 2);
1048
1049 for i in 0..2 {
1050 content.invoke_content(i);
1051 }
1052
1053 let visited = items_visited.borrow();
1054 assert_eq!(
1055 *visited,
1056 vec![(0, "Apple".to_string()), (1, "Banana".to_string())]
1057 );
1058 }
1059
1060 #[test]
1061 fn test_items_with_provider() {
1062 let mut content = LazyListIntervalContent::new();
1063 let data = ["Apple", "Banana", "Cherry"];
1064 let items_visited = Rc::new(RefCell::new(Vec::new()));
1065 let items_clone = items_visited.clone();
1066
1067 content.items_with_provider(
1068 data.len(),
1069 move |index| data.get(index).copied(),
1070 move |item: &str| {
1071 items_clone.borrow_mut().push(item.to_string());
1072 },
1073 );
1074
1075 assert_eq!(content.item_count(), 3);
1076
1077 for i in 0..3 {
1078 content.invoke_content(i);
1079 }
1080
1081 let visited = items_visited.borrow();
1082 assert_eq!(*visited, vec!["Apple", "Banana", "Cherry"]);
1083 }
1084
1085 #[test]
1086 fn test_items_indexed_with_provider() {
1087 let mut content = LazyListIntervalContent::new();
1088 let data = ["Apple", "Banana", "Cherry"];
1089 let items_visited = Rc::new(RefCell::new(Vec::new()));
1090 let items_clone = items_visited.clone();
1091
1092 content.items_indexed_with_provider(
1093 data.len(),
1094 move |index| data.get(index).copied(),
1095 move |index, item: &str| {
1096 items_clone.borrow_mut().push((index, item.to_string()));
1097 },
1098 );
1099
1100 assert_eq!(content.item_count(), 3);
1101
1102 for i in 0..3 {
1103 content.invoke_content(i);
1104 }
1105
1106 let visited = items_visited.borrow();
1107 assert_eq!(
1108 *visited,
1109 vec![
1110 (0, "Apple".to_string()),
1111 (1, "Banana".to_string()),
1112 (2, "Cherry".to_string())
1113 ]
1114 );
1115 }
1116
1117 #[test]
1118 fn test_large_list_cache_works() {
1119 let mut content = LazyListIntervalContent::new();
1122
1123 content.items(
1125 20_000,
1126 Some(|i| (i * 7) as u64), None::<fn(usize) -> u64>,
1128 |_| {},
1129 );
1130
1131 let key_19999 = content.get_key(19999);
1133 assert_eq!(key_19999, LazyLayoutKey::User(19999 * 7));
1134
1135 let slot_id = key_19999.to_slot_id();
1137 let found_index = content.get_index_by_slot_id(slot_id);
1138 assert_eq!(found_index, Some(19999));
1139
1140 let key_10000 = content.get_key(10000);
1142 let slot_id_mid = key_10000.to_slot_id();
1143 let found_mid = content.get_index_by_slot_id(slot_id_mid);
1144 assert_eq!(found_mid, Some(10000));
1145 }
1146}