gravel_ffi/
cache.rs

1use crate::ArcDynHit;
2use std::mem;
3use std::ops::DerefMut;
4use std::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
5use std::time::{Duration, Instant};
6
7/// Container for unchanging hits.
8pub struct StaticHitCache {
9	data: Box<[ArcDynHit]>,
10}
11
12impl StaticHitCache {
13	/// Creates a new cache from any iterator if hits.
14	pub fn new<H>(items: impl IntoIterator<Item = H>) -> Self
15	where
16		H: Into<ArcDynHit>,
17	{
18		Self {
19			data: items.into_iter().map(Into::into).collect(),
20		}
21	}
22
23	/// Gets a slice of the contained hits.
24	pub fn get(&self) -> &[ArcDynHit] {
25		&self.data
26	}
27}
28
29/// Container for hits than can be re-used.
30///
31/// By default it caches hits indefinitely, only allowing manual clearing.  
32/// See [`Self::max_age`].
33#[derive(Default, Debug)]
34pub struct HitCache {
35	data: RwLock<Data>,
36	strategy: Strategy,
37}
38
39impl HitCache {
40	/// Converts this cache to using the MaxAge strategy,
41	/// discarding the contents after the specified time has passed.
42	pub fn max_age(mut self, age: Duration) -> Self {
43		self.strategy = Strategy::MaxAge(age);
44		self
45	}
46
47	/// Clears the cache, setting it to empty.
48	pub fn clear(&self) {
49		let mut guard = self.write();
50		guard.hits.clear();
51		guard.created = None;
52	}
53
54	/// Gets the cached hits, if there are any.
55	pub fn get(&self) -> Option<Guard<'_>> {
56		let guard = self.read();
57
58		if self.is_stale(&guard) {
59			return None;
60		}
61
62		Some(Guard(guard))
63	}
64
65	/// Gets the cached hits or computes them from `f`, storing them in the cache.
66	pub fn get_or<I, H>(&self, f: impl FnOnce() -> I) -> Guard<'_>
67	where
68		I: Iterator<Item = H>,
69		H: Into<ArcDynHit>,
70	{
71		if let Some(a) = self.get() {
72			return a;
73		};
74
75		self.set(f().map(Into::into));
76		Guard(self.read())
77	}
78
79	/// Replaces any current contents with `hits`.
80	pub fn set(&self, hits: impl Iterator<Item = ArcDynHit>) {
81		let mut guard = self.write();
82		guard.hits.clear();
83		guard.hits.extend(hits);
84		guard.created = Some(Instant::now());
85	}
86
87	/// Private API, may change at any time.
88	#[doc(hidden)]
89	pub fn _is_poisoned(&self) -> bool {
90		self.data.is_poisoned()
91	}
92
93	fn is_stale(&self, data: &Data) -> bool {
94		match self.strategy {
95			Strategy::Lazy => data.created.is_none(),
96			Strategy::MaxAge(max_age) => data.created.map(|c| c.elapsed() > max_age).unwrap_or(true),
97		}
98	}
99
100	fn read(&self) -> RwLockReadGuard<'_, Data> {
101		match self.data.read() {
102			Ok(g) => g,
103			Err(e) => {
104				drop(e);
105
106				// write resets the contents to a known-good state and clears the poison
107				drop(self.write());
108				self.read()
109			}
110		}
111	}
112
113	fn write(&self) -> RwLockWriteGuard<'_, Data> {
114		self.data.write().unwrap_or_else(|e| {
115			log::error!("cache rwlock was poisoned! resetting cache and continuing");
116
117			let mut guard = e.into_inner();
118			mem::take(guard.deref_mut());
119			self.data.clear_poison();
120
121			guard
122		})
123	}
124}
125
126#[derive(Debug, Default)]
127enum Strategy {
128	#[default]
129	Lazy,
130	MaxAge(Duration),
131}
132
133#[derive(Default, Debug)]
134struct Data {
135	hits: Vec<ArcDynHit>,
136	created: Option<Instant>,
137}
138
139#[derive(Debug)]
140pub struct Guard<'a>(RwLockReadGuard<'a, Data>);
141
142impl Guard<'_> {
143	pub fn get(&self) -> &[ArcDynHit] {
144		&self.0.hits
145	}
146}