use std::cell::RefCell;
pub struct PagedData<T, F>
where
F: Fn(usize, usize) -> Vec<T>,
{
total: usize,
page_size: usize,
pages: RefCell<Vec<Option<Vec<T>>>>,
loader: F,
loading: RefCell<Vec<bool>>,
}
impl<T, F> PagedData<T, F>
where
F: Fn(usize, usize) -> Vec<T>,
{
pub fn new(total: usize, page_size: usize, loader: F) -> Self {
let num_pages = total.div_ceil(page_size);
let pages: Vec<Option<Vec<T>>> = (0..num_pages).map(|_| None).collect();
let loading: Vec<bool> = vec![false; num_pages];
Self {
total,
page_size,
pages: RefCell::new(pages),
loader,
loading: RefCell::new(loading),
}
}
pub fn total(&self) -> usize {
self.total
}
pub fn page_size(&self) -> usize {
self.page_size
}
pub fn page_count(&self) -> usize {
self.total.div_ceil(self.page_size)
}
pub fn is_page_loaded(&self, page: usize) -> bool {
self.pages
.borrow()
.get(page)
.map(|p| p.is_some())
.unwrap_or(false)
}
pub fn get(&self, index: usize) -> Option<std::cell::Ref<'_, T>> {
if index >= self.total {
return None;
}
let page = index / self.page_size;
let offset = index % self.page_size;
self.ensure_page_loaded(page);
let pages = self.pages.borrow();
if let Some(Some(items)) = pages.get(page) {
if offset < items.len() {
return Some(std::cell::Ref::map(pages, |p| {
&p[page].as_ref().unwrap()[offset]
}));
}
}
None
}
pub fn get_range(&self, start: usize, end: usize) -> Vec<std::cell::Ref<'_, T>> {
let mut result = Vec::new();
for i in start..end.min(self.total) {
if let Some(item) = self.get(i) {
result.push(item);
}
}
result
}
pub fn prefetch(&self, start_index: usize, count: usize) {
let start_page = start_index / self.page_size;
let end_page = (start_index + count) / self.page_size;
for page in start_page..=end_page.min(self.page_count().saturating_sub(1)) {
self.ensure_page_loaded(page);
}
}
pub fn invalidate_page(&self, page: usize) {
if let Some(slot) = self.pages.borrow_mut().get_mut(page) {
*slot = None;
}
}
pub fn invalidate_all(&self) {
for slot in self.pages.borrow_mut().iter_mut() {
*slot = None;
}
}
fn ensure_page_loaded(&self, page: usize) {
if page >= self.page_count() {
return;
}
{
let pages = self.pages.borrow();
if pages.get(page).map(|p| p.is_some()).unwrap_or(false) {
return;
}
}
{
let mut loading = self.loading.borrow_mut();
if loading.get(page).copied().unwrap_or(false) {
return; }
if let Some(slot) = loading.get_mut(page) {
*slot = true;
}
}
let items = (self.loader)(page, self.page_size);
if let Some(slot) = self.pages.borrow_mut().get_mut(page) {
*slot = Some(items);
}
if let Some(slot) = self.loading.borrow_mut().get_mut(page) {
*slot = false;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn create_test_paged() -> PagedData<usize, impl Fn(usize, usize) -> Vec<usize>> {
PagedData::new(100, 10, |page, size| {
(page * size..(page + 1) * size).collect()
})
}
#[test]
fn test_paged_data_new() {
let paged = create_test_paged();
assert_eq!(paged.total(), 100);
assert_eq!(paged.page_size(), 10);
assert_eq!(paged.page_count(), 10);
}
#[test]
fn test_paged_data_total() {
let paged: PagedData<i32, _> = PagedData::new(50, 10, |_, _| vec![]);
assert_eq!(paged.total(), 50);
}
#[test]
fn test_paged_data_page_size() {
let paged: PagedData<i32, _> = PagedData::new(100, 25, |_, _| vec![]);
assert_eq!(paged.page_size(), 25);
}
#[test]
fn test_paged_data_page_count() {
let paged: PagedData<i32, _> = PagedData::new(100, 10, |_, _| vec![]);
assert_eq!(paged.page_count(), 10);
let paged2: PagedData<i32, _> = PagedData::new(95, 10, |_, _| vec![]);
assert_eq!(paged2.page_count(), 10); }
#[test]
fn test_paged_data_is_page_loaded() {
let paged = create_test_paged();
assert!(!paged.is_page_loaded(0));
paged.get(0);
assert!(paged.is_page_loaded(0));
}
#[test]
fn test_paged_data_get() {
let paged = create_test_paged();
let item = paged.get(5);
assert!(item.is_some());
assert_eq!(*item.unwrap(), 5);
}
#[test]
fn test_paged_data_get_out_of_bounds() {
let paged = create_test_paged();
let item = paged.get(100);
assert!(item.is_none());
}
#[test]
fn test_paged_data_get_range() {
let paged = create_test_paged();
let range = paged.get_range(5, 10);
assert_eq!(range.len(), 5);
assert_eq!(*range[0], 5);
assert_eq!(*range[4], 9);
}
#[test]
fn test_paged_data_prefetch() {
let paged = create_test_paged();
paged.prefetch(0, 25);
assert!(paged.is_page_loaded(0));
assert!(paged.is_page_loaded(1));
assert!(paged.is_page_loaded(2));
}
#[test]
fn test_paged_data_invalidate_page() {
let paged = create_test_paged();
paged.get(0);
assert!(paged.is_page_loaded(0));
paged.invalidate_page(0);
assert!(!paged.is_page_loaded(0));
}
#[test]
fn test_paged_data_invalidate_all() {
let paged = create_test_paged();
paged.prefetch(0, 50);
assert!(paged.is_page_loaded(0));
assert!(paged.is_page_loaded(1));
paged.invalidate_all();
assert!(!paged.is_page_loaded(0));
assert!(!paged.is_page_loaded(1));
}
#[test]
fn test_paged_data_with_strings() {
let paged = PagedData::new(20, 5, |page, size| {
(0..size)
.map(|i| format!("item_{}", page * size + i))
.collect()
});
let item = paged.get(7);
assert!(item.is_some());
assert_eq!(*item.unwrap(), "item_7");
}
}