use std::collections::{HashMap, VecDeque};
use std::sync::{Arc, Mutex, MutexGuard, PoisonError};
use crate::error::Error;
use crate::transform::Transformer;
fn lock_inner(mutex: &Mutex<LruInner>) -> MutexGuard<'_, LruInner> {
mutex.lock().unwrap_or_else(PoisonError::into_inner)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct TransformerKey {
pub src_epsg: u32,
pub dst_epsg: u32,
}
impl TransformerKey {
pub fn new(src_epsg: u32, dst_epsg: u32) -> Self {
Self { src_epsg, dst_epsg }
}
}
struct LruInner {
map: HashMap<TransformerKey, Arc<Transformer>>,
order: VecDeque<TransformerKey>,
}
impl LruInner {
fn touch(&mut self, key: &TransformerKey) {
if let Some(pos) = self.order.iter().position(|k| k == key) {
self.order.remove(pos);
}
self.order.push_front(*key);
}
}
#[derive(Clone)]
pub struct TransformerCache {
inner: Arc<Mutex<LruInner>>,
capacity: usize,
}
impl TransformerCache {
pub fn new(capacity: usize) -> Self {
let capacity = capacity.max(1);
Self {
inner: Arc::new(Mutex::new(LruInner {
map: HashMap::with_capacity(capacity),
order: VecDeque::with_capacity(capacity),
})),
capacity,
}
}
pub fn capacity(&self) -> usize {
self.capacity
}
pub fn len(&self) -> usize {
let guard = lock_inner(&self.inner);
guard.map.len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn contains(&self, src_epsg: u32, dst_epsg: u32) -> bool {
let guard = lock_inner(&self.inner);
guard
.map
.contains_key(&TransformerKey::new(src_epsg, dst_epsg))
}
pub fn clear(&self) {
let mut guard = lock_inner(&self.inner);
guard.map.clear();
guard.order.clear();
}
pub fn cached_keys(&self) -> Vec<TransformerKey> {
let guard = lock_inner(&self.inner);
guard.order.iter().copied().collect()
}
pub fn get_or_build(&self, src_epsg: u32, dst_epsg: u32) -> Result<Arc<Transformer>, Error> {
let key = TransformerKey::new(src_epsg, dst_epsg);
{
let mut guard = lock_inner(&self.inner);
if let Some(arc) = guard.map.get(&key).cloned() {
guard.touch(&key);
return Ok(arc);
}
}
let transformer = Arc::new(Transformer::from_epsg(src_epsg, dst_epsg)?);
let mut guard = lock_inner(&self.inner);
if let Some(arc) = guard.map.get(&key).cloned() {
guard.touch(&key);
return Ok(arc);
}
while guard.map.len() >= self.capacity {
match guard.order.pop_back() {
Some(oldest) => {
guard.map.remove(&oldest);
}
None => break,
}
}
guard.map.insert(key, transformer.clone());
guard.order.push_front(key);
Ok(transformer)
}
}
impl std::fmt::Debug for TransformerCache {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let len = {
let guard = lock_inner(&self.inner);
guard.map.len()
};
f.debug_struct("TransformerCache")
.field("len", &len)
.field("capacity", &self.capacity)
.finish()
}
}
#[cfg(test)]
#[allow(clippy::expect_used)]
mod tests {
use super::*;
#[test]
fn test_key_equality_and_hash() {
let k1 = TransformerKey::new(4326, 3857);
let k2 = TransformerKey::new(4326, 3857);
let k3 = TransformerKey::new(3857, 4326);
assert_eq!(k1, k2);
assert_ne!(k1, k3);
let mut map: HashMap<TransformerKey, i32> = HashMap::new();
map.insert(k1, 42);
assert_eq!(map.get(&k2), Some(&42));
assert_eq!(map.get(&k3), None);
}
#[test]
fn test_capacity_clamp() {
let c0 = TransformerCache::new(0);
let c5 = TransformerCache::new(5);
assert_eq!(c0.capacity(), 1);
assert_eq!(c5.capacity(), 5);
}
#[test]
fn test_debug_format() {
let cache = TransformerCache::new(4);
let s = format!("{:?}", cache);
assert!(s.contains("TransformerCache"));
assert!(s.contains("len"));
assert!(s.contains("capacity"));
}
}