Krolyxon
Commit 4e844bb

ferreniumsrccache.rs

198 lines · 5.9 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::log::{LogPage, LogQuery};
10use crate::git::refs::RefList;
11use crate::git::tree::TreeEntry;
12use crate::git::{Mailmap, ObjectId};
13use crate::render::diff::{CommitDiff, Options as DiffOptions};
14
15#[derive(Debug, Clone, PartialEq, Eq, Hash)]
16pub enum Key {
17 /// Highlighted lines of a blob with a given syntax.
18 Highlight {
19 blob: ObjectId,
20 syntax: String,
21 },
22 /// Rendered Markdown; `context` is everything relative links depend on.
23 Markdown {
24 blob: ObjectId,
25 context: String,
26 },
27 Tree(ObjectId),
28 CommitCount(ObjectId),
29 Refs {
30 repo: String,
31 generation: u64,
32 },
33 Log {
34 tip: ObjectId,
35 query: LogQuery,
36 },
37 Diff {
38 commit: ObjectId,
39 options: DiffOptions,
40 },
41 Mailmap(ObjectId),
42}
43
44#[derive(Clone)]
45pub enum Value {
46 Lines(Arc<Vec<String>>),
47 Html(Arc<str>),
48 Tree(Arc<Vec<TreeEntry>>),
49 Count(u64),
50 Refs(Arc<RefList>),
51 Log(Arc<LogPage>),
52 Diff(Arc<CommitDiff>),
53 Mailmap(Arc<Mailmap>),
54}
55
56impl Value {
57 fn weight(&self) -> usize {
58 match self {
59 Value::Lines(lines) => lines.iter().map(|l| l.len() + 24).sum(),
60 Value::Html(html) => html.len(),
61 Value::Tree(entries) => entries.iter().map(|e| e.name.len() + 64).sum(),
62 Value::Count(_) => 8,
63 Value::Refs(refs) => refs.weight(),
64 Value::Log(page) => page.weight(),
65 Value::Diff(diff) => diff.weight(),
66 Value::Mailmap(map) => map.weight(),
67 }
68 }
69}
70
71pub struct Cache {
72 inner: moka::sync::Cache<Key, Value>,
73}
74
75impl Cache {
76 pub fn new(size_mb: u64) -> Self {
77 let inner = moka::sync::Cache::builder()
78 .max_capacity(size_mb.saturating_mul(1024 * 1024))
79 .weigher(|key: &Key, value: &Value| {
80 let key = match key {
81 Key::Highlight { syntax, .. } => syntax.len(),
82 Key::Markdown { context, .. } => context.len(),
83 Key::Refs { repo, .. } => repo.len(),
84 Key::Log { query, .. } => {
85 query.path.len() + query.grep.as_ref().map_or(0, String::len)
86 }
87 _ => 0,
88 };
89 u32::try_from(64 + key + value.weight()).unwrap_or(u32::MAX)
90 })
91 .build();
92 Cache { inner }
93 }
94
95 /// Cached value for `key`, computing (and storing) it on a miss.
96 /// Errors are not cached.
97 fn get_or<E>(&self, key: Key, compute: impl FnOnce() -> Result<Value, E>) -> Result<Value, E> {
98 if let Some(value) = self.inner.get(&key) {
99 return Ok(value);
100 }
101 let value = compute()?;
102 self.inner.insert(key, value.clone());
103 Ok(value)
104 }
105
106 pub fn lines<E>(
107 &self,
108 key: Key,
109 compute: impl FnOnce() -> Result<Vec<String>, E>,
110 ) -> Result<Arc<Vec<String>>, E> {
111 match self.get_or(key, || compute().map(|v| Value::Lines(Arc::new(v))))? {
112 Value::Lines(v) => Ok(v),
113 _ => unreachable!("cache key/value kind mismatch"),
114 }
115 }
116
117 pub fn html<E>(
118 &self,
119 key: Key,
120 compute: impl FnOnce() -> Result<String, E>,
121 ) -> Result<Arc<str>, E> {
122 match self.get_or(key, || compute().map(|v| Value::Html(v.into())))? {
123 Value::Html(v) => Ok(v),
124 _ => unreachable!("cache key/value kind mismatch"),
125 }
126 }
127
128 pub fn tree<E>(
129 &self,
130 tree: ObjectId,
131 compute: impl FnOnce() -> Result<Vec<TreeEntry>, E>,
132 ) -> Result<Arc<Vec<TreeEntry>>, E> {
133 match self.get_or(Key::Tree(tree), || {
134 compute().map(|v| Value::Tree(Arc::new(v)))
135 })? {
136 Value::Tree(v) => Ok(v),
137 _ => unreachable!("cache key/value kind mismatch"),
138 }
139 }
140
141 pub fn count<E>(&self, key: Key, compute: impl FnOnce() -> Result<u64, E>) -> Result<u64, E> {
142 match self.get_or(key, || compute().map(Value::Count))? {
143 Value::Count(v) => Ok(v),
144 _ => unreachable!("cache key/value kind mismatch"),
145 }
146 }
147
148 pub fn refs<E>(
149 &self,
150 key: Key,
151 compute: impl FnOnce() -> Result<RefList, E>,
152 ) -> Result<Arc<RefList>, E> {
153 match self.get_or(key, || compute().map(|v| Value::Refs(Arc::new(v))))? {
154 Value::Refs(v) => Ok(v),
155 _ => unreachable!("cache key/value kind mismatch"),
156 }
157 }
158
159 pub fn log<E>(
160 &self,
161 key: Key,
162 compute: impl FnOnce() -> Result<LogPage, E>,
163 ) -> Result<Arc<LogPage>, E> {
164 match self.get_or(key, || compute().map(|v| Value::Log(Arc::new(v))))? {
165 Value::Log(v) => Ok(v),
166 _ => unreachable!("cache key/value kind mismatch"),
167 }
168 }
169
170 pub fn diff<E>(
171 &self,
172 key: Key,
173 compute: impl FnOnce() -> Result<CommitDiff, E>,
174 ) -> Result<Arc<CommitDiff>, E> {
175 match self.get_or(key, || compute().map(|v| Value::Diff(Arc::new(v))))? {
176 Value::Diff(v) => Ok(v),
177 _ => unreachable!("cache key/value kind mismatch"),
178 }
179 }
180
181 pub fn mailmap<E>(
182 &self,
183 blob: ObjectId,
184 compute: impl FnOnce() -> Result<Mailmap, E>,
185 ) -> Result<Arc<Mailmap>, E> {
186 match self.get_or(Key::Mailmap(blob), || {
187 compute().map(|v| Value::Mailmap(Arc::new(v)))
188 })? {
189 Value::Mailmap(v) => Ok(v),
190 _ => unreachable!("cache key/value kind mismatch"),
191 }
192 }
193
194 pub fn entry_count(&self) -> u64 {
195 self.inner.run_pending_tasks();
196 self.inner.entry_count()
197 }
198}