use std::{
any,
collections::{HashMap, HashSet},
fmt::Debug,
hash::Hash,
sync::Arc,
};
use parking_lot::Mutex;
use tracing::warn;
use super::Project;
pub trait Queryable: Sized {
type Input: Debug + Hash + Eq + Clone;
type Backref: Debug + Hash + Eq + Clone;
type Ctx: Default;
fn compute(input: Self::Input, ctx: &Self::Ctx, proj: &Project) -> Arc<Self>;
fn invalidate_backref(&self, backref: Self::Backref, proj: &Project);
fn invalidate(&self, input: &Self::Input, ctx: &Self::Ctx, proj: &Project);
}
struct QuerryEntry<Q: Queryable> {
data: Arc<Q>,
backrefs: HashSet<Q::Backref>,
}
pub struct Querry<Q: Queryable> {
cache: Mutex<HashMap<Q::Input, QuerryEntry<Q>>>,
ctx: Q::Ctx,
}
impl<Q: Queryable> Querry<Q> {
pub fn new() -> Self {
Self {
cache: Mutex::new(HashMap::new()),
ctx: Default::default(),
}
}
fn get_inner(&self, proj: &Project, i: Q::Input, backref: Option<Q::Backref>) -> Arc<Q> {
if let Some(entry) = self.cache.lock().get_mut(&i) {
backref.map(|b| entry.backrefs.insert(b));
return entry.data.clone();
}
let computed = Q::compute(i.clone(), &self.ctx, proj);
let mut cache = self.cache.lock();
let entry = cache.entry(i).or_insert_with(|| QuerryEntry {
data: computed,
backrefs: HashSet::new(),
});
backref.map(|b| entry.backrefs.insert(b));
entry.data.clone()
}
pub fn get(&self, proj: &Project, i: Q::Input) -> Arc<Q> {
self.get_inner(proj, i, None)
}
pub fn get_as(&self, proj: &Project, i: Q::Input, backref: Q::Backref) -> Arc<Q> {
self.get_inner(proj, i, Some(backref))
}
pub fn invalidate(&self, proj: &Project, input: &Q::Input) {
let cached = self.cache.lock().remove(input);
if let Some(mut entry) = cached {
entry
.backrefs
.into_iter()
.for_each(|b| entry.data.invalidate_backref(b, proj));
entry.data.invalidate(input, &self.ctx, proj);
if Arc::get_mut(&mut entry.data).is_none() {
warn!(
"Querry '{}' with input '{:?}' still has references after invalidation: {:?}",
any::type_name::<Q>(),
input,
(
Arc::strong_count(&entry.data) - 1,
Arc::weak_count(&entry.data)
)
)
}
}
}
pub fn clear(&self, proj: &Project) {
for i in self.cache.lock().keys() {
self.invalidate(proj, i);
}
}
}
impl<Q: Queryable> Default for Querry<Q> {
fn default() -> Self {
Self::new()
}
}