1use std::cell::{Cell, RefCell};
9use std::collections::VecDeque;
10
11use crate::Element;
12
13const MAX_CACHE_SIZE: usize = 4096;
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum OffsetCacheKind {
19 Linear { offset: usize },
24 Quadratic,
29}
30
31#[derive(Debug, Clone)]
33pub struct OffsetCache {
34 pub(crate) offsets: RefCell<VecDeque<(usize, Element)>>,
36 pub(crate) kind: Option<OffsetCacheKind>,
38 pub(crate) total_removed: Cell<usize>,
40}
41
42impl OffsetCache {
43 pub const fn new() -> Self {
45 Self { offsets: RefCell::new(VecDeque::new()), kind: None, total_removed: Cell::new(0) }
46 }
47
48 pub fn set_policy(&mut self, kind: impl Into<Option<OffsetCacheKind>>) {
52 self.kind = kind.into();
53
54 if self.kind.is_none() {
55 self.clear();
56 }
57 }
58
59 pub fn clear(&self) {
61 self.offsets.borrow_mut().clear();
62 self.total_removed.set(0);
63 }
64
65 pub fn cache_elem_if_needed(
69 &self, index: usize, elem: Element, elem_cnt: usize, affected_items: usize,
70 ) {
71 debug_assert!(index <= elem_cnt);
72 debug_assert!(index + 1 >= affected_items);
73
74 let need_to_cache = match self.kind {
75 Some(OffsetCacheKind::Linear { offset }) => {
76 let last_cached_abs_index = self.offsets.borrow().back().map_or(0, |(idx, _)| *idx);
77 let last_cached_rel_index =
78 last_cached_abs_index.saturating_sub(self.total_removed.get());
79 Self::should_cache_linear(index, offset, last_cached_rel_index)
80 }
81 Some(OffsetCacheKind::Quadratic) => Self::should_cache_quadratic(index, affected_items),
82 None => false,
83 };
84
85 if !need_to_cache {
86 return;
87 }
88
89 let abs_index = index + self.total_removed.get();
90 let mut offsets = self.offsets.borrow_mut();
91
92 if let Some(&(last_cached_index, last_cached_elem)) = offsets.back() {
93 if last_cached_index >= abs_index {
94 if last_cached_index == abs_index {
95 debug_assert_eq!(last_cached_elem.pos, elem.pos);
96 debug_assert_eq!(last_cached_elem.len, elem.len);
97 }
98
99 return;
100 }
101 }
102
103 offsets.push_back((abs_index, elem));
104
105 if offsets.len() > MAX_CACHE_SIZE {
106 offsets.pop_front();
107 }
108 }
109
110 #[inline]
111 const fn should_cache_linear(index: usize, offset: usize, last_cached_index: usize) -> bool {
112 index.saturating_sub(last_cached_index) >= offset
113 }
114
115 #[inline]
116 fn should_cache_quadratic(index: usize, affected_items: usize) -> bool {
117 let x = (index as f64).sqrt() as usize;
118 x > 1 && (index + 1 - affected_items..=index).contains(&(x * x))
119 }
120
121 #[inline]
125 pub fn cached_offset_up_to(&self, i: usize) -> Option<(usize, Element)> {
126 let abs_i = i + self.total_removed.get();
127 let offsets = self.offsets.borrow();
128 let idx = offsets
129 .binary_search_by(|(idx, _)| idx.cmp(&abs_i))
130 .map_or_else(|i| i.checked_sub(1), Some)?;
131
132 let (abs_idx, elem) = offsets[idx];
133 Some((abs_idx.saturating_sub(self.total_removed.get()), elem))
134 }
135
136 pub fn drop_up_to(&self, n: usize) {
140 self.total_removed.set(self.total_removed.get() + n);
141 let mut offsets = self.offsets.borrow_mut();
142 while matches!(offsets.front(), Some((index, _)) if *index < self.total_removed.get()) {
143 offsets.pop_front();
144 }
145 }
146}