Krolyxon
Commit 55c1225

ferreniumsrccache.rs

143 lines · 4.2 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::ObjectId;
10use crate::git::refs::RefList;
11use crate::git::tree::TreeEntry;
12
13#[derive(Debug, Clone, PartialEq, Eq, Hash)]
14pub enum Key {
15 /// Highlighted lines of a blob with a given syntax.
16 Highlight {
17 blob: ObjectId,
18 syntax: String,
19 },
20 /// Rendered Markdown; `context` is everything relative links depend on.
21 Markdown {
22 blob: ObjectId,
23 context: String,
24 },
25 Tree(ObjectId),
26 CommitCount(ObjectId),
27 Refs {
28 repo: String,
29 generation: u64,
30 },
31}
32
33#[derive(Clone)]
34pub enum Value {
35 Lines(Arc<Vec<String>>),
36 Html(Arc<str>),
37 Tree(Arc<Vec<TreeEntry>>),
38 Count(u64),
39 Refs(Arc<RefList>),
40}
41
42impl Value {
43 fn weight(&self) -> usize {
44 match self {
45 Value::Lines(lines) => lines.iter().map(|l| l.len() + 24).sum(),
46 Value::Html(html) => html.len(),
47 Value::Tree(entries) => entries.iter().map(|e| e.name.len() + 64).sum(),
48 Value::Count(_) => 8,
49 Value::Refs(refs) => refs.weight(),
50 }
51 }
52}
53
54pub struct Cache {
55 inner: moka::sync::Cache<Key, Value>,
56}
57
58impl Cache {
59 pub fn new(size_mb: u64) -> Self {
60 let inner = moka::sync::Cache::builder()
61 .max_capacity(size_mb.saturating_mul(1024 * 1024))
62 .weigher(|key: &Key, value: &Value| {
63 let key = match key {
64 Key::Highlight { syntax, .. } => syntax.len(),
65 Key::Markdown { context, .. } => context.len(),
66 Key::Refs { repo, .. } => repo.len(),
67 _ => 0,
68 };
69 u32::try_from(64 + key + value.weight()).unwrap_or(u32::MAX)
70 })
71 .build();
72 Cache { inner }
73 }
74
75 /// Cached value for `key`, computing (and storing) it on a miss.
76 /// Errors are not cached.
77 fn get_or<E>(&self, key: Key, compute: impl FnOnce() -> Result<Value, E>) -> Result<Value, E> {
78 if let Some(value) = self.inner.get(&key) {
79 return Ok(value);
80 }
81 let value = compute()?;
82 self.inner.insert(key, value.clone());
83 Ok(value)
84 }
85
86 pub fn lines<E>(
87 &self,
88 key: Key,
89 compute: impl FnOnce() -> Result<Vec<String>, E>,
90 ) -> Result<Arc<Vec<String>>, E> {
91 match self.get_or(key, || compute().map(|v| Value::Lines(Arc::new(v))))? {
92 Value::Lines(v) => Ok(v),
93 _ => unreachable!("cache key/value kind mismatch"),
94 }
95 }
96
97 pub fn html<E>(
98 &self,
99 key: Key,
100 compute: impl FnOnce() -> Result<String, E>,
101 ) -> Result<Arc<str>, E> {
102 match self.get_or(key, || compute().map(|v| Value::Html(v.into())))? {
103 Value::Html(v) => Ok(v),
104 _ => unreachable!("cache key/value kind mismatch"),
105 }
106 }
107
108 pub fn tree<E>(
109 &self,
110 tree: ObjectId,
111 compute: impl FnOnce() -> Result<Vec<TreeEntry>, E>,
112 ) -> Result<Arc<Vec<TreeEntry>>, E> {
113 match self.get_or(Key::Tree(tree), || {
114 compute().map(|v| Value::Tree(Arc::new(v)))
115 })? {
116 Value::Tree(v) => Ok(v),
117 _ => unreachable!("cache key/value kind mismatch"),
118 }
119 }
120
121 pub fn count<E>(&self, key: Key, compute: impl FnOnce() -> Result<u64, E>) -> Result<u64, E> {
122 match self.get_or(key, || compute().map(Value::Count))? {
123 Value::Count(v) => Ok(v),
124 _ => unreachable!("cache key/value kind mismatch"),
125 }
126 }
127
128 pub fn refs<E>(
129 &self,
130 key: Key,
131 compute: impl FnOnce() -> Result<RefList, E>,
132 ) -> Result<Arc<RefList>, E> {
133 match self.get_or(key, || compute().map(|v| Value::Refs(Arc::new(v))))? {
134 Value::Refs(v) => Ok(v),
135 _ => unreachable!("cache key/value kind mismatch"),
136 }
137 }
138
139 pub fn entry_count(&self) -> u64 {
140 self.inner.run_pending_tasks();
141 self.inner.entry_count()
142 }
143}