solverforge_maps/routing/
cache.rs1use std::collections::HashMap;
4use std::mem::size_of;
5use std::ops::Deref;
6use std::sync::atomic::{AtomicU64, Ordering};
7use std::sync::Arc;
8use std::sync::OnceLock;
9
10use serde::{Deserialize, Serialize};
11use tokio::sync::{Mutex, RwLock, RwLockReadGuard};
12
13use super::bbox::BoundingBox;
14use super::network::RoadNetwork;
15
16pub const CACHE_VERSION: u32 = 5;
17
18static NETWORK_CACHE: OnceLock<RwLock<HashMap<String, RoadNetwork>>> = OnceLock::new();
19static IN_FLIGHT_LOADS: OnceLock<Mutex<HashMap<String, Arc<Mutex<()>>>>> = OnceLock::new();
20static LOAD_REQUESTS: AtomicU64 = AtomicU64::new(0);
21static MEMORY_HITS: AtomicU64 = AtomicU64::new(0);
22static DISK_HITS: AtomicU64 = AtomicU64::new(0);
23static NETWORK_FETCHES: AtomicU64 = AtomicU64::new(0);
24static IN_FLIGHT_WAITS: AtomicU64 = AtomicU64::new(0);
25
26pub(crate) fn cache() -> &'static RwLock<HashMap<String, RoadNetwork>> {
27 NETWORK_CACHE.get_or_init(|| RwLock::new(HashMap::new()))
28}
29
30pub(crate) fn in_flight_loads() -> &'static Mutex<HashMap<String, Arc<Mutex<()>>>> {
31 IN_FLIGHT_LOADS.get_or_init(|| Mutex::new(HashMap::new()))
32}
33
34pub(crate) fn record_load_request() {
35 LOAD_REQUESTS.fetch_add(1, Ordering::Relaxed);
36}
37
38pub(crate) fn record_memory_hit() {
39 MEMORY_HITS.fetch_add(1, Ordering::Relaxed);
40}
41
42pub(crate) fn record_disk_hit() {
43 DISK_HITS.fetch_add(1, Ordering::Relaxed);
44}
45
46pub(crate) fn record_network_fetch() {
47 NETWORK_FETCHES.fetch_add(1, Ordering::Relaxed);
48}
49
50pub(crate) fn record_in_flight_wait() {
51 IN_FLIGHT_WAITS.fetch_add(1, Ordering::Relaxed);
52}
53
54#[cfg(test)]
55pub(crate) fn reset_cache_metrics() {
56 LOAD_REQUESTS.store(0, Ordering::Relaxed);
57 MEMORY_HITS.store(0, Ordering::Relaxed);
58 DISK_HITS.store(0, Ordering::Relaxed);
59 NETWORK_FETCHES.store(0, Ordering::Relaxed);
60 IN_FLIGHT_WAITS.store(0, Ordering::Relaxed);
61}
62
63#[derive(Debug, Clone)]
64pub struct CacheStats {
65 pub networks_cached: usize,
66 pub total_nodes: usize,
67 pub total_edges: usize,
68 pub memory_bytes: usize,
69 pub load_requests: u64,
70 pub memory_hits: u64,
71 pub disk_hits: u64,
72 pub network_fetches: u64,
73 pub in_flight_waits: u64,
74}
75
76pub struct NetworkRef {
78 guard: RwLockReadGuard<'static, HashMap<String, RoadNetwork>>,
79 key: String,
80}
81
82impl Deref for NetworkRef {
83 type Target = RoadNetwork;
84
85 fn deref(&self) -> &RoadNetwork {
86 self.guard
87 .get(&self.key)
88 .expect("cached network disappeared")
89 }
90}
91
92impl NetworkRef {
93 pub(crate) fn new(
94 guard: RwLockReadGuard<'static, HashMap<String, RoadNetwork>>,
95 key: String,
96 ) -> Self {
97 debug_assert!(
98 guard.contains_key(&key),
99 "NetworkRef created for missing key"
100 );
101 Self { guard, key }
102 }
103
104 pub fn cache_key(&self) -> &str {
105 &self.key
106 }
107}
108
109#[derive(Debug, Serialize, Deserialize)]
110pub struct CachedNetwork {
111 pub version: u32,
112 pub nodes: Vec<CachedNode>,
113 pub edges: Vec<CachedEdge>,
114}
115
116#[derive(Debug, Serialize, Deserialize)]
117pub struct CachedNode {
118 pub lat: f64,
119 pub lng: f64,
120}
121
122#[derive(Debug, Serialize, Deserialize)]
123pub struct CachedEdge {
124 pub from: usize,
125 pub to: usize,
126 pub travel_time_s: f64,
127 pub distance_m: f64,
128}
129
130impl RoadNetwork {
131 pub async fn cache_stats() -> CacheStats {
132 let guard = cache().read().await;
133
134 let mut total_nodes = 0usize;
135 let mut total_edges = 0usize;
136 let mut memory_bytes = 0usize;
137
138 for network in guard.values() {
139 let nodes = network.node_count();
140 let edges = network.edge_count();
141 total_nodes += nodes;
142 total_edges += edges;
143
144 memory_bytes += nodes * (size_of::<f64>() * 2 + size_of::<usize>() * 2);
146 memory_bytes += edges * (size_of::<f64>() * 2 + size_of::<usize>() * 2);
147 }
148
149 CacheStats {
150 networks_cached: guard.len(),
151 total_nodes,
152 total_edges,
153 memory_bytes,
154 load_requests: LOAD_REQUESTS.load(Ordering::Relaxed),
155 memory_hits: MEMORY_HITS.load(Ordering::Relaxed),
156 disk_hits: DISK_HITS.load(Ordering::Relaxed),
157 network_fetches: NETWORK_FETCHES.load(Ordering::Relaxed),
158 in_flight_waits: IN_FLIGHT_WAITS.load(Ordering::Relaxed),
159 }
160 }
161
162 pub async fn clear_cache() {
163 let mut guard = cache().write().await;
164 guard.clear();
165 }
166
167 pub async fn evict(bbox: &BoundingBox) -> bool {
168 let cache_key = bbox.cache_key();
169 let mut guard = cache().write().await;
170 guard.remove(&cache_key).is_some()
171 }
172
173 pub async fn cached_regions() -> Vec<BoundingBox> {
174 let guard = cache().read().await;
175 guard
176 .keys()
177 .filter_map(|key| parse_cache_key(key))
178 .collect()
179 }
180}
181
182fn parse_cache_key(key: &str) -> Option<BoundingBox> {
183 let parts: Vec<&str> = key.split('_').collect();
184 if parts.len() != 4 {
185 return None;
186 }
187
188 let min_lat: f64 = parts[0].parse().ok()?;
189 let min_lng: f64 = parts[1].parse().ok()?;
190 let max_lat: f64 = parts[2].parse().ok()?;
191 let max_lng: f64 = parts[3].parse().ok()?;
192
193 BoundingBox::try_new(min_lat, min_lng, max_lat, max_lng).ok()
194}