Skip to main content

cranpose_foundation/lazy/
prefetch.rs

1//! Prefetch scheduler for lazy layouts.
2//!
3//! Pre-composes items before they become visible to reduce jank during scrolling.
4//! Inspired by Jetpack Compose's `LazyListPrefetchStrategy`.
5
6use std::collections::VecDeque;
7
8/// Strategy for prefetching items in a lazy list.
9#[derive(Clone, Debug)]
10pub struct PrefetchStrategy {
11    /// Number of items to prefetch beyond the visible area.
12    /// Default is 2, matching JC's default.
13    pub prefetch_count: usize,
14
15    /// Whether prefetching is enabled.
16    pub enabled: bool,
17}
18
19impl Default for PrefetchStrategy {
20    fn default() -> Self {
21        Self {
22            prefetch_count: 2,
23            enabled: true,
24        }
25    }
26}
27
28impl PrefetchStrategy {
29    /// Creates a new prefetch strategy with the specified count.
30    pub fn new(prefetch_count: usize) -> Self {
31        Self {
32            prefetch_count,
33            enabled: true,
34        }
35    }
36
37    /// Disables prefetching.
38    pub fn disabled() -> Self {
39        Self {
40            prefetch_count: 0,
41            enabled: false,
42        }
43    }
44}
45
46/// Scheduler that tracks which items should be prefetched.
47///
48/// Based on scroll direction and velocity, determines which items
49/// to pre-compose before they become visible.
50#[derive(Debug, Default)]
51pub struct PrefetchScheduler {
52    prefetch_queue: VecDeque<usize>,
53}
54
55impl PrefetchScheduler {
56    /// Creates a new prefetch scheduler.
57    pub fn new() -> Self {
58        Self::default()
59    }
60
61    /// Updates the prefetch queue based on current scroll state.
62    ///
63    /// # Arguments
64    /// * `first_visible_index` - Index of the first visible item
65    /// * `last_visible_index` - Index of the last visible item  
66    /// * `total_items` - Total number of items in the list
67    /// * `scroll_direction` - Current scroll direction (positive = forward)
68    /// * `strategy` - Prefetch strategy to use
69    pub fn update(
70        &mut self,
71        first_visible_index: usize,
72        last_visible_index: usize,
73        total_items: usize,
74        scroll_direction: f32,
75        strategy: &PrefetchStrategy,
76    ) {
77        if !strategy.enabled {
78            self.prefetch_queue.clear();
79            return;
80        }
81
82        self.prefetch_queue.clear();
83
84        let prefetch_count = strategy.prefetch_count;
85
86        if scroll_direction >= 0.0 {
87            for i in 1..=prefetch_count {
88                let index = last_visible_index.saturating_add(i);
89                if index < total_items {
90                    self.prefetch_queue.push_back(index);
91                }
92            }
93        } else {
94            for i in 1..=prefetch_count {
95                if first_visible_index >= i {
96                    let index = first_visible_index - i;
97                    self.prefetch_queue.push_back(index);
98                }
99            }
100        }
101    }
102
103    /// Returns the next item index to prefetch, if any.
104    pub fn next_prefetch(&mut self) -> Option<usize> {
105        self.prefetch_queue.pop_front()
106    }
107}
108
109#[cfg(test)]
110mod tests {
111    use super::*;
112
113    #[test]
114    fn test_prefetch_forward_scroll() {
115        let mut scheduler = PrefetchScheduler::new();
116        let strategy = PrefetchStrategy::new(2);
117
118        scheduler.update(5, 10, 100, 1.0, &strategy);
119
120        assert_eq!(scheduler.next_prefetch(), Some(11));
121        assert_eq!(scheduler.next_prefetch(), Some(12));
122        assert_eq!(scheduler.next_prefetch(), None);
123    }
124
125    #[test]
126    fn test_prefetch_backward_scroll() {
127        let mut scheduler = PrefetchScheduler::new();
128        let strategy = PrefetchStrategy::new(2);
129
130        scheduler.update(5, 10, 100, -1.0, &strategy);
131
132        assert_eq!(scheduler.next_prefetch(), Some(4));
133        assert_eq!(scheduler.next_prefetch(), Some(3));
134        assert_eq!(scheduler.next_prefetch(), None);
135    }
136
137    #[test]
138    fn test_prefetch_at_end() {
139        let mut scheduler = PrefetchScheduler::new();
140        let strategy = PrefetchStrategy::new(2);
141
142        scheduler.update(95, 99, 100, 1.0, &strategy);
143
144        assert_eq!(scheduler.next_prefetch(), None);
145    }
146
147    #[test]
148    fn test_prefetch_disabled() {
149        let mut scheduler = PrefetchScheduler::new();
150        let strategy = PrefetchStrategy::disabled();
151
152        scheduler.update(5, 10, 100, 1.0, &strategy);
153
154        assert_eq!(scheduler.next_prefetch(), None);
155    }
156}