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 CACHE_HITS: AtomicU64 = AtomicU64::new(0);
21static CACHE_MISSES: AtomicU64 = AtomicU64::new(0);
22
23pub(crate) fn cache() -> &'static RwLock<HashMap<String, RoadNetwork>> {
24 NETWORK_CACHE.get_or_init(|| RwLock::new(HashMap::new()))
25}
26
27pub(crate) fn in_flight_loads() -> &'static Mutex<HashMap<String, Arc<Mutex<()>>>> {
28 IN_FLIGHT_LOADS.get_or_init(|| Mutex::new(HashMap::new()))
29}
30
31pub(crate) fn record_hit() {
32 CACHE_HITS.fetch_add(1, Ordering::Relaxed);
33}
34
35pub(crate) fn record_miss() {
36 CACHE_MISSES.fetch_add(1, Ordering::Relaxed);
37}
38
39#[derive(Debug, Clone)]
40pub struct CacheStats {
41 pub networks_cached: usize,
42 pub total_nodes: usize,
43 pub total_edges: usize,
44 pub memory_bytes: usize,
45 pub hits: u64,
46 pub misses: u64,
47}
48
49impl CacheStats {
50 pub fn hit_ratio(&self) -> f64 {
51 let total = self.hits + self.misses;
52 if total == 0 {
53 0.0
54 } else {
55 self.hits as f64 / total as f64
56 }
57 }
58}
59
60pub struct NetworkRef {
62 guard: RwLockReadGuard<'static, HashMap<String, RoadNetwork>>,
63 key: String,
64}
65
66impl Deref for NetworkRef {
67 type Target = RoadNetwork;
68
69 fn deref(&self) -> &RoadNetwork {
70 self.guard
71 .get(&self.key)
72 .expect("cached network disappeared")
73 }
74}
75
76impl NetworkRef {
77 pub(crate) fn new(
78 guard: RwLockReadGuard<'static, HashMap<String, RoadNetwork>>,
79 key: String,
80 ) -> Self {
81 debug_assert!(
82 guard.contains_key(&key),
83 "NetworkRef created for missing key"
84 );
85 Self { guard, key }
86 }
87
88 pub fn cache_key(&self) -> &str {
89 &self.key
90 }
91}
92
93#[derive(Debug, Serialize, Deserialize)]
94pub struct CachedNetwork {
95 pub version: u32,
96 pub nodes: Vec<CachedNode>,
97 pub edges: Vec<CachedEdge>,
98}
99
100#[derive(Debug, Serialize, Deserialize)]
101pub struct CachedNode {
102 pub lat: f64,
103 pub lng: f64,
104}
105
106#[derive(Debug, Serialize, Deserialize)]
107pub struct CachedEdge {
108 pub from: usize,
109 pub to: usize,
110 pub travel_time_s: f64,
111 pub distance_m: f64,
112}
113
114impl RoadNetwork {
115 pub async fn cache_stats() -> CacheStats {
116 let guard = cache().read().await;
117
118 let mut total_nodes = 0usize;
119 let mut total_edges = 0usize;
120 let mut memory_bytes = 0usize;
121
122 for network in guard.values() {
123 let nodes = network.node_count();
124 let edges = network.edge_count();
125 total_nodes += nodes;
126 total_edges += edges;
127
128 memory_bytes += nodes * (size_of::<f64>() * 2 + size_of::<usize>() * 2);
130 memory_bytes += edges * (size_of::<f64>() * 2 + size_of::<usize>() * 2);
131 }
132
133 CacheStats {
134 networks_cached: guard.len(),
135 total_nodes,
136 total_edges,
137 memory_bytes,
138 hits: CACHE_HITS.load(Ordering::Relaxed),
139 misses: CACHE_MISSES.load(Ordering::Relaxed),
140 }
141 }
142
143 pub async fn clear_cache() {
144 let mut guard = cache().write().await;
145 guard.clear();
146 }
147
148 pub async fn evict(bbox: &BoundingBox) -> bool {
149 let cache_key = bbox.cache_key();
150 let mut guard = cache().write().await;
151 guard.remove(&cache_key).is_some()
152 }
153
154 pub async fn cached_regions() -> Vec<BoundingBox> {
155 let guard = cache().read().await;
156 guard
157 .keys()
158 .filter_map(|key| parse_cache_key(key))
159 .collect()
160 }
161}
162
163fn parse_cache_key(key: &str) -> Option<BoundingBox> {
164 let parts: Vec<&str> = key.split('_').collect();
165 if parts.len() != 4 {
166 return None;
167 }
168
169 let min_lat: f64 = parts[0].parse().ok()?;
170 let min_lng: f64 = parts[1].parse().ok()?;
171 let max_lat: f64 = parts[2].parse().ok()?;
172 let max_lng: f64 = parts[3].parse().ok()?;
173
174 BoundingBox::try_new(min_lat, min_lng, max_lat, max_lng).ok()
175}