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