Skip to main content

tempo_precompiles/storage/types/
cache.rs

1use alloy_primitives::map::HashMap;
2use std::{cell::RefCell, hash::Hash};
3
4const CACHE_THRESHOLD: usize = 100;
5
6#[derive(Debug)]
7pub(crate) struct LinearCache<K, H> {
8    entries: Vec<(K, Box<H>)>,
9}
10
11impl<K, H> Default for LinearCache<K, H> {
12    #[inline]
13    fn default() -> Self {
14        Self {
15            entries: Vec::new(),
16        }
17    }
18}
19
20impl<K: Eq + Clone, H> LinearCache<K, H> {
21    #[inline]
22    fn len(&self) -> usize {
23        self.entries.len()
24    }
25
26    #[inline]
27    fn find(&self, key: &K) -> Option<*const H> {
28        self.entries
29            .iter()
30            .find(|(candidate, _)| candidate == key)
31            .map(|(_, boxed)| boxed.as_ref() as *const H)
32    }
33
34    #[inline]
35    fn find_mut(&mut self, key: &K) -> Option<*mut H> {
36        self.entries
37            .iter_mut()
38            .find(|(candidate, _)| candidate == key)
39            .map(|(_, boxed)| boxed.as_mut() as *mut H)
40    }
41
42    #[inline]
43    fn insert(&mut self, key: &K, f: impl FnOnce() -> H) -> *const H {
44        self.entries.push((key.clone(), Box::new(f())));
45        self.entries
46            .last()
47            .expect("just pushed handler cache entry")
48            .1
49            .as_ref() as *const H
50    }
51
52    #[inline]
53    fn insert_mut(&mut self, key: &K, f: impl FnOnce() -> H) -> *mut H {
54        self.entries.push((key.clone(), Box::new(f())));
55        self.entries
56            .last_mut()
57            .expect("just pushed handler cache entry")
58            .1
59            .as_mut() as *mut H
60    }
61
62    #[inline]
63    fn drain_into_map(&mut self, map: &mut MapCache<K, H>)
64    where
65        K: Hash,
66    {
67        for (key, value) in self.entries.drain(..) {
68            map.insert_boxed(key, value);
69        }
70    }
71}
72
73#[derive(Debug)]
74pub(crate) struct MapCache<K, H> {
75    entries: HashMap<K, Box<H>>,
76}
77
78impl<K, H> Default for MapCache<K, H> {
79    #[inline]
80    fn default() -> Self {
81        Self {
82            entries: HashMap::default(),
83        }
84    }
85}
86
87impl<K: Hash + Eq + Clone, H> MapCache<K, H> {
88    #[inline]
89    fn reserve(&mut self, additional: usize) {
90        self.entries.reserve(additional);
91    }
92
93    #[inline]
94    fn insert_boxed(&mut self, key: K, value: Box<H>) {
95        self.entries.insert(key, value);
96    }
97
98    #[inline]
99    fn get_or_insert(&mut self, key: &K, f: impl FnOnce() -> H) -> *const H {
100        if let Some(boxed) = self.entries.get(key) {
101            boxed.as_ref() as *const H
102        } else {
103            self.entries
104                .entry(key.clone())
105                .or_insert_with(|| Box::new(f()))
106                .as_ref() as *const H
107        }
108    }
109
110    #[inline]
111    fn get_or_insert_mut(&mut self, key: &K, f: impl FnOnce() -> H) -> *mut H {
112        if let Some(boxed) = self.entries.get_mut(key) {
113            boxed.as_mut() as *mut H
114        } else {
115            self.entries
116                .entry(key.clone())
117                .or_insert_with(|| Box::new(f()))
118                .as_mut() as *mut H
119        }
120    }
121}
122
123#[derive(Debug)]
124enum HandlerCacheState<K, H> {
125    Linear(LinearCache<K, H>),
126    Mapped(MapCache<K, H>),
127}
128
129/// Hybrid linear/map cache for lazily computed handlers with stable references.
130///
131/// Enables `Index` implementations on handlers by storing child handlers and
132/// returning references that remain valid across insertions.
133///
134/// Uses `RefCell` for interior mutability with runtime borrow checking.
135/// Re-entrant access will panic rather than cause undefined behavior.
136#[derive(Debug)]
137pub(crate) struct HandlerCache<K, H, const THRESHOLD: usize = CACHE_THRESHOLD> {
138    inner: RefCell<HandlerCacheState<K, H>>,
139}
140
141impl<K, H, const THRESHOLD: usize> HandlerCache<K, H, THRESHOLD> {
142    /// Creates a new empty handler cache.
143    #[inline]
144    pub(super) fn new() -> Self {
145        Self {
146            inner: RefCell::new(HandlerCacheState::Linear(LinearCache::default())),
147        }
148    }
149}
150
151impl<K, H, const THRESHOLD: usize> HandlerCache<K, H, THRESHOLD>
152where
153    K: Eq + Hash + Clone,
154{
155    #[inline]
156    fn promote_to_map(linear: &mut LinearCache<K, H>) -> MapCache<K, H> {
157        let mut map = MapCache::default();
158        map.reserve(THRESHOLD * 2);
159        linear.drain_into_map(&mut map);
160        map
161    }
162
163    /// Returns a reference to a lazily initialized handler for the given key.
164    #[inline]
165    pub(super) fn get_or_insert(&self, key: &K, f: impl FnOnce() -> H) -> &H {
166        let mut cache = self.inner.borrow_mut();
167        let ptr = match &mut *cache {
168            HandlerCacheState::Linear(linear) => {
169                if let Some(ptr) = linear.find(key) {
170                    ptr
171                } else if linear.len() < THRESHOLD {
172                    linear.insert(key, f)
173                } else {
174                    let map = Self::promote_to_map(linear);
175                    *cache = HandlerCacheState::Mapped(map);
176                    match &mut *cache {
177                        HandlerCacheState::Mapped(map) => map.get_or_insert(key, f),
178                        HandlerCacheState::Linear(_) => unreachable!("handler cache was promoted"),
179                    }
180                }
181            }
182            HandlerCacheState::Mapped(map) => map.get_or_insert(key, f),
183        };
184        // SAFETY: Box provides stable heap address. Cache is append-only.
185        unsafe { &*ptr }
186    }
187
188    /// Returns a mutable reference to a lazily initialized handler for the given key.
189    #[inline]
190    pub(super) fn get_or_insert_mut(&mut self, key: &K, f: impl FnOnce() -> H) -> &mut H {
191        let mut cache = self.inner.borrow_mut();
192        let ptr = match &mut *cache {
193            HandlerCacheState::Linear(linear) => {
194                if let Some(ptr) = linear.find_mut(key) {
195                    ptr
196                } else if linear.len() < THRESHOLD {
197                    linear.insert_mut(key, f)
198                } else {
199                    let map = Self::promote_to_map(linear);
200                    *cache = HandlerCacheState::Mapped(map);
201                    match &mut *cache {
202                        HandlerCacheState::Mapped(map) => map.get_or_insert_mut(key, f),
203                        HandlerCacheState::Linear(_) => unreachable!("handler cache was promoted"),
204                    }
205                }
206            }
207            HandlerCacheState::Mapped(map) => map.get_or_insert_mut(key, f),
208        };
209        // SAFETY: Box provides stable heap address. Cache is append-only. `&mut self` ensures exclusive access.
210        unsafe { &mut *ptr }
211    }
212}
213
214impl<K, H, const THRESHOLD: usize> Clone for HandlerCache<K, H, THRESHOLD> {
215    /// Creates a new empty cache (cached handlers are not cloned).
216    fn clone(&self) -> Self {
217        Self::new()
218    }
219}