use std::cell::{Cell, RefCell};
use std::collections::VecDeque;
use crate::Element;
const MAX_CACHE_SIZE: usize = 4096;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OffsetCacheKind {
Linear { offset: usize },
Quadratic,
}
#[derive(Debug, Clone)]
pub struct OffsetCache {
pub(crate) offsets: RefCell<VecDeque<(usize, Element)>>,
pub(crate) kind: Option<OffsetCacheKind>,
pub(crate) total_removed: Cell<usize>,
}
impl OffsetCache {
pub const fn new() -> Self {
Self { offsets: RefCell::new(VecDeque::new()), kind: None, total_removed: Cell::new(0) }
}
pub fn set_policy(&mut self, kind: impl Into<Option<OffsetCacheKind>>) {
self.kind = kind.into();
if self.kind.is_none() {
self.clear();
}
}
pub fn clear(&self) {
self.offsets.borrow_mut().clear();
self.total_removed.set(0);
}
pub fn cache_elem_if_needed(
&self, index: usize, elem: Element, elem_cnt: usize, affected_items: usize,
) {
debug_assert!(index <= elem_cnt);
debug_assert!(index + 1 >= affected_items);
let need_to_cache = match self.kind {
Some(OffsetCacheKind::Linear { offset }) => {
let last_cached_abs_index = self.offsets.borrow().back().map_or(0, |(idx, _)| *idx);
let last_cached_rel_index =
last_cached_abs_index.saturating_sub(self.total_removed.get());
Self::should_cache_linear(index, offset, last_cached_rel_index)
}
Some(OffsetCacheKind::Quadratic) => Self::should_cache_quadratic(index, affected_items),
None => false,
};
if !need_to_cache {
return;
}
let abs_index = index + self.total_removed.get();
let mut offsets = self.offsets.borrow_mut();
if let Some(&(last_cached_index, last_cached_elem)) = offsets.back() {
if last_cached_index >= abs_index {
if last_cached_index == abs_index {
debug_assert_eq!(last_cached_elem.pos, elem.pos);
debug_assert_eq!(last_cached_elem.len, elem.len);
}
return;
}
}
offsets.push_back((abs_index, elem));
if offsets.len() > MAX_CACHE_SIZE {
offsets.pop_front();
}
}
#[inline]
const fn should_cache_linear(index: usize, offset: usize, last_cached_index: usize) -> bool {
index.saturating_sub(last_cached_index) >= offset
}
#[inline]
fn should_cache_quadratic(index: usize, affected_items: usize) -> bool {
let x = (index as f64).sqrt() as usize;
x > 1 && (index + 1 - affected_items..=index).contains(&(x * x))
}
#[inline]
pub fn cached_offset_up_to(&self, i: usize) -> Option<(usize, Element)> {
let abs_i = i + self.total_removed.get();
let offsets = self.offsets.borrow();
let idx = offsets
.binary_search_by(|(idx, _)| idx.cmp(&abs_i))
.map_or_else(|i| i.checked_sub(1), Some)?;
let (abs_idx, elem) = offsets[idx];
Some((abs_idx.saturating_sub(self.total_removed.get()), elem))
}
pub fn drop_up_to(&self, n: usize) {
self.total_removed.set(self.total_removed.get() + n);
let mut offsets = self.offsets.borrow_mut();
while matches!(offsets.front(), Some((index, _)) if *index < self.total_removed.get()) {
offsets.pop_front();
}
}
}