use crate::ported::lib::monotonic::monotonic;
use serde_json::{Map, Value};
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
pub fn default_cache_key(kwargs: &Map<String, Value>) -> String {
let mut pairs: Vec<(String, String)> = kwargs
.iter()
.map(|(k, v)| (k.clone(), v.to_string()))
.collect();
pairs.sort();
pairs
.into_iter()
.map(|(k, v)| format!("{}={}", k, v))
.collect::<Vec<_>>()
.join("\0")
}
#[derive(Clone)]
pub struct CacheEntry {
pub result: Value,
pub time: f64,
}
#[allow(non_camel_case_types)]
pub struct memoize {
pub timeout: f64,
pub cache_key: fn(&Map<String, Value>) -> String,
pub cache: Arc<Mutex<HashMap<String, CacheEntry>>>,
}
impl memoize {
pub fn new(timeout: f64) -> Self {
Self {
timeout, cache_key: default_cache_key, cache: Arc::new(Mutex::new(HashMap::new())), }
}
pub fn get_or_compute<F>(&self, kwargs: &Map<String, Value>, compute: F) -> Value
where
F: FnOnce(&Map<String, Value>) -> Value,
{
self.decorated_function(kwargs, compute)
}
pub fn decorated_function<F>(&self, kwargs: &Map<String, Value>, compute: F) -> Value
where
F: FnOnce(&Map<String, Value>) -> Value,
{
let key = (self.cache_key)(kwargs);
let now = monotonic();
let cache = self.cache.lock().unwrap();
if let Some(cached) = cache.get(&key) {
if cached.time < now && now < cached.time + self.timeout {
return cached.result.clone();
}
}
drop(cache);
let result = compute(kwargs);
let mut cache = self.cache.lock().unwrap();
cache.insert(
key,
CacheEntry {
result: result.clone(),
time: now,
},
);
result
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
use std::sync::atomic::{AtomicI32, Ordering};
use std::sync::Arc as StdArc;
#[test]
fn default_cache_key_is_stable_across_iteration_order() {
let mut m1 = Map::new();
m1.insert("a".into(), json!(1));
m1.insert("b".into(), json!(2));
let mut m2 = Map::new();
m2.insert("b".into(), json!(2));
m2.insert("a".into(), json!(1));
assert_eq!(default_cache_key(&m1), default_cache_key(&m2));
}
#[test]
fn memoize_caches_within_timeout() {
let m = memoize::new(60.0);
let counter = StdArc::new(AtomicI32::new(0));
let mut kwargs = Map::new();
kwargs.insert("x".into(), json!(42));
for _ in 0..3 {
let c = counter.clone();
m.get_or_compute(&kwargs, move |_| {
c.fetch_add(1, Ordering::SeqCst);
json!("computed")
});
}
assert_eq!(counter.load(Ordering::SeqCst), 1);
}
#[test]
fn memoize_recomputes_on_different_key() {
let m = memoize::new(60.0);
let counter = StdArc::new(AtomicI32::new(0));
for x in [1, 2, 3] {
let c = counter.clone();
let mut kwargs = Map::new();
kwargs.insert("x".into(), json!(x));
m.get_or_compute(&kwargs, move |_| {
c.fetch_add(1, Ordering::SeqCst);
json!(x)
});
}
assert_eq!(counter.load(Ordering::SeqCst), 3);
}
#[test]
fn memoize_recomputes_after_timeout() {
let m = memoize::new(0.01); let counter = StdArc::new(AtomicI32::new(0));
let mut kwargs = Map::new();
kwargs.insert("x".into(), json!(42));
for _ in 0..3 {
let c = counter.clone();
m.get_or_compute(&kwargs, move |_| {
c.fetch_add(1, Ordering::SeqCst);
json!("computed")
});
std::thread::sleep(std::time::Duration::from_millis(15));
}
assert_eq!(counter.load(Ordering::SeqCst), 3);
}
}