Krolyxon
Tag v0.2.0

ferreniumsrccache.rs

263 lines · 8.1 KB · Rust

1//! One byte-weighted in-memory cache for rendered and computed data.
2//!
3//! Keys are content-addressed (blob/tree/commit ids) and never need
4//! invalidation. Ref-dependent entries include the repository's
5//! `generation`, so a refresh retires them naturally.
6
7use std::sync::Arc;
8
9use crate::git::blame::BlameHunk;
10use crate::git::log::{LogPage, LogQuery};
11use crate::git::refs::RefList;
12use crate::git::tree::TreeEntry;
13use crate::git::{Mailmap, ObjectId};
14use crate::index::languages::Stats as LanguageStats;
15use crate::render::diff::{CommitDiff, Options as DiffOptions};
16
17#[derive(Debug, Clone, PartialEq, Eq, Hash)]
18pub enum Key {
19 /// Highlighted lines of a blob with a given syntax.
20 Highlight {
21 blob: ObjectId,
22 syntax: String,
23 },
24 /// Rendered Markdown; `context` is everything relative links depend on.
25 Markdown {
26 blob: ObjectId,
27 context: String,
28 },
29 Tree(ObjectId),
30 CommitCount(ObjectId),
31 Refs {
32 repo: String,
33 generation: u64,
34 },
35 Log {
36 tip: ObjectId,
37 query: LogQuery,
38 },
39 Diff {
40 commit: ObjectId,
41 options: DiffOptions,
42 },
43 Mailmap(ObjectId),
44 Blame {
45 commit: ObjectId,
46 path: Vec<u8>,
47 },
48 /// Every file path in a tree (the file finder).
49 Paths(ObjectId),
50 /// Language statistics of a tree.
51 Languages(ObjectId),
52}
53
54#[derive(Clone)]
55pub enum Value {
56 Lines(Arc<Vec<String>>),
57 Html(Arc<str>),
58 Tree(Arc<Vec<TreeEntry>>),
59 Count(u64),
60 Refs(Arc<RefList>),
61 Log(Arc<LogPage>),
62 Diff(Arc<CommitDiff>),
63 Mailmap(Arc<Mailmap>),
64 Blame(Arc<Vec<BlameHunk>>),
65 Paths(Arc<(Vec<String>, bool)>),
66 Languages(Arc<LanguageStats>),
67}
68
69impl Value {
70 fn weight(&self) -> usize {
71 match self {
72 Value::Lines(lines) => lines.iter().map(|l| l.len() + 24).sum(),
73 Value::Html(html) => html.len(),
74 Value::Tree(entries) => entries.iter().map(|e| e.name.len() + 64).sum(),
75 Value::Count(_) => 8,
76 Value::Refs(refs) => refs.weight(),
77 Value::Log(page) => page.weight(),
78 Value::Diff(diff) => diff.weight(),
79 Value::Mailmap(map) => map.weight(),
80 Value::Languages(stats) => stats.weight(),
81 Value::Paths(paths) => paths.0.iter().map(|p| p.len() + 24).sum(),
82 Value::Blame(hunks) => hunks
83 .iter()
84 .map(|h| 48 + h.source_path.as_ref().map_or(0, String::len))
85 .sum(),
86 }
87 }
88}
89
90pub struct Cache {
91 inner: moka::sync::Cache<Key, Value>,
92}
93
94impl Cache {
95 pub fn new(size_mb: u64) -> Self {
96 let inner = moka::sync::Cache::builder()
97 .max_capacity(size_mb.saturating_mul(1024 * 1024))
98 .weigher(|key: &Key, value: &Value| {
99 let key = match key {
100 Key::Highlight { syntax, .. } => syntax.len(),
101 Key::Markdown { context, .. } => context.len(),
102 Key::Refs { repo, .. } => repo.len(),
103 Key::Blame { path, .. } => path.len(),
104 Key::Log { query, .. } => {
105 query.path.len() + query.grep.as_ref().map_or(0, String::len)
106 }
107 _ => 0,
108 };
109 u32::try_from(64 + key + value.weight()).unwrap_or(u32::MAX)
110 })
111 .build();
112 Cache { inner }
113 }
114
115 /// Cached value for `key`, computing (and storing) it on a miss.
116 /// Errors are not cached.
117 fn get_or<E>(&self, key: Key, compute: impl FnOnce() -> Result<Value, E>) -> Result<Value, E> {
118 if let Some(value) = self.inner.get(&key) {
119 return Ok(value);
120 }
121 let value = compute()?;
122 self.inner.insert(key, value.clone());
123 Ok(value)
124 }
125
126 pub fn lines<E>(
127 &self,
128 key: Key,
129 compute: impl FnOnce() -> Result<Vec<String>, E>,
130 ) -> Result<Arc<Vec<String>>, E> {
131 match self.get_or(key, || compute().map(|v| Value::Lines(Arc::new(v))))? {
132 Value::Lines(v) => Ok(v),
133 _ => unreachable!("cache key/value kind mismatch"),
134 }
135 }
136
137 pub fn html<E>(
138 &self,
139 key: Key,
140 compute: impl FnOnce() -> Result<String, E>,
141 ) -> Result<Arc<str>, E> {
142 match self.get_or(key, || compute().map(|v| Value::Html(v.into())))? {
143 Value::Html(v) => Ok(v),
144 _ => unreachable!("cache key/value kind mismatch"),
145 }
146 }
147
148 pub fn tree<E>(
149 &self,
150 tree: ObjectId,
151 compute: impl FnOnce() -> Result<Vec<TreeEntry>, E>,
152 ) -> Result<Arc<Vec<TreeEntry>>, E> {
153 match self.get_or(Key::Tree(tree), || {
154 compute().map(|v| Value::Tree(Arc::new(v)))
155 })? {
156 Value::Tree(v) => Ok(v),
157 _ => unreachable!("cache key/value kind mismatch"),
158 }
159 }
160
161 pub fn count<E>(&self, key: Key, compute: impl FnOnce() -> Result<u64, E>) -> Result<u64, E> {
162 match self.get_or(key, || compute().map(Value::Count))? {
163 Value::Count(v) => Ok(v),
164 _ => unreachable!("cache key/value kind mismatch"),
165 }
166 }
167
168 pub fn refs<E>(
169 &self,
170 key: Key,
171 compute: impl FnOnce() -> Result<RefList, E>,
172 ) -> Result<Arc<RefList>, E> {
173 match self.get_or(key, || compute().map(|v| Value::Refs(Arc::new(v))))? {
174 Value::Refs(v) => Ok(v),
175 _ => unreachable!("cache key/value kind mismatch"),
176 }
177 }
178
179 pub fn log<E>(
180 &self,
181 key: Key,
182 compute: impl FnOnce() -> Result<LogPage, E>,
183 ) -> Result<Arc<LogPage>, E> {
184 match self.get_or(key, || compute().map(|v| Value::Log(Arc::new(v))))? {
185 Value::Log(v) => Ok(v),
186 _ => unreachable!("cache key/value kind mismatch"),
187 }
188 }
189
190 pub fn diff<E>(
191 &self,
192 key: Key,
193 compute: impl FnOnce() -> Result<CommitDiff, E>,
194 ) -> Result<Arc<CommitDiff>, E> {
195 match self.get_or(key, || compute().map(|v| Value::Diff(Arc::new(v))))? {
196 Value::Diff(v) => Ok(v),
197 _ => unreachable!("cache key/value kind mismatch"),
198 }
199 }
200
201 pub fn mailmap<E>(
202 &self,
203 blob: ObjectId,
204 compute: impl FnOnce() -> Result<Mailmap, E>,
205 ) -> Result<Arc<Mailmap>, E> {
206 match self.get_or(Key::Mailmap(blob), || {
207 compute().map(|v| Value::Mailmap(Arc::new(v)))
208 })? {
209 Value::Mailmap(v) => Ok(v),
210 _ => unreachable!("cache key/value kind mismatch"),
211 }
212 }
213
214 pub fn blame<E>(
215 &self,
216 key: Key,
217 compute: impl FnOnce() -> Result<Vec<BlameHunk>, E>,
218 ) -> Result<Arc<Vec<BlameHunk>>, E> {
219 match self.get_or(key, || compute().map(|v| Value::Blame(Arc::new(v))))? {
220 Value::Blame(v) => Ok(v),
221 _ => unreachable!("cache key/value kind mismatch"),
222 }
223 }
224
225 pub fn paths<E>(
226 &self,
227 tree: ObjectId,
228 compute: impl FnOnce() -> Result<(Vec<String>, bool), E>,
229 ) -> Result<Arc<(Vec<String>, bool)>, E> {
230 match self.get_or(Key::Paths(tree), || {
231 compute().map(|v| Value::Paths(Arc::new(v)))
232 })? {
233 Value::Paths(v) => Ok(v),
234 _ => unreachable!("cache key/value kind mismatch"),
235 }
236 }
237
238 pub fn languages<E>(
239 &self,
240 tree: ObjectId,
241 compute: impl FnOnce() -> Result<LanguageStats, E>,
242 ) -> Result<Arc<LanguageStats>, E> {
243 match self.get_or(Key::Languages(tree), || {
244 compute().map(|v| Value::Languages(Arc::new(v)))
245 })? {
246 Value::Languages(v) => Ok(v),
247 _ => unreachable!("cache key/value kind mismatch"),
248 }
249 }
250
251 /// Language statistics if they are cached; never computes.
252 pub fn peek_languages(&self, tree: ObjectId) -> Option<Arc<LanguageStats>> {
253 match self.inner.get(&Key::Languages(tree))? {
254 Value::Languages(v) => Some(v),
255 _ => None,
256 }
257 }
258
259 pub fn entry_count(&self) -> u64 {
260 self.inner.run_pending_tasks();
261 self.inner.entry_count()
262 }
263}