forked from maplibre/martin
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcache.rs
More file actions
128 lines (112 loc) · 3.64 KB
/
cache.rs
File metadata and controls
128 lines (112 loc) · 3.64 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
use std::sync::Arc;
use std::time::Duration;
use martin_tile_utils::TileCoord;
use moka::future::Cache;
use tracing::{info, trace};
use crate::tiles::Tile;
/// Tile cache for storing rendered tile data.
#[derive(Clone, Debug)]
pub struct TileCache(Cache<TileCacheKey, Tile>);
impl TileCache {
/// Creates a new tile cache with the specified maximum size in bytes.
#[must_use]
pub fn new(
max_size_bytes: u64,
expiry: Option<Duration>,
idle_timeout: Option<Duration>,
) -> Self {
let mut builder = Cache::builder()
.name("tile_cache")
.weigher(|_key: &TileCacheKey, value: &Tile| -> u32 {
value.data.len().try_into().unwrap_or(u32::MAX)
})
.max_capacity(max_size_bytes)
.support_invalidation_closures();
if let Some(ttl) = expiry {
builder = builder.time_to_live(ttl);
}
if let Some(tti) = idle_timeout {
builder = builder.time_to_idle(tti);
}
Self(builder.build())
}
/// Gets a tile from cache or computes it using the provided function.
pub async fn get_or_insert<F, Fut, E>(
&self,
source_id: String,
xyz: TileCoord,
query: Option<String>,
compute: F,
) -> Result<Tile, Arc<E>>
where
F: FnOnce() -> Fut,
Fut: Future<Output = Result<Tile, E>>,
E: Send + Sync + 'static,
{
let key = TileCacheKey::new(source_id, xyz, query);
let entry = self
.0
.entry(key.clone())
.or_try_insert_with(async move { compute().await })
.await?;
if entry.is_fresh() {
hotpath::gauge!("tile_cache_misses").inc(1.0);
trace!("Tile cache MISS for {key:?}");
} else {
hotpath::gauge!("tile_cache_hits").inc(1.0);
trace!(
"Tile cache HIT for {key:?} (entries={entries}, size={size}B)",
entries = self.0.entry_count(),
size = self.0.weighted_size()
);
}
Ok(entry.into_value())
}
/// Invalidates all cached tiles for a specific source.
pub fn invalidate_source(&self, source_id: &str) {
let source_id_owned = source_id.to_string();
self.0
.invalidate_entries_if(move |key, _| key.source_id == source_id_owned)
.expect("invalidate_entries_if predicate should not error");
info!("Invalidated tile cache for source: {source_id}");
}
/// Invalidates all cached tiles.
pub fn invalidate_all(&self) {
self.0.invalidate_all();
info!("Invalidated all tile cache entries");
}
/// Returns the number of cached entries.
#[must_use]
pub fn entry_count(&self) -> u64 {
self.0.entry_count()
}
/// Returns the total size of cached data in bytes.
#[must_use]
pub fn weighted_size(&self) -> u64 {
self.0.weighted_size()
}
/// Runs pending maintenance tasks (e.g. processing invalidation predicates).
pub async fn run_pending_tasks(&self) {
self.0.run_pending_tasks().await;
}
}
/// Optional wrapper for `TileCache`.
pub type OptTileCache = Option<TileCache>;
/// Constant representing no tile cache configuration.
pub const NO_TILE_CACHE: OptTileCache = None;
/// Cache key for tile data.
#[derive(Debug, Hash, PartialEq, Eq, Clone)]
struct TileCacheKey {
source_id: String,
xyz: TileCoord,
query: Option<String>,
}
impl TileCacheKey {
fn new(source_id: String, xyz: TileCoord, query: Option<String>) -> Self {
Self {
source_id,
xyz,
query,
}
}
}