use std::collections::HashMap;
use std::hash::Hash;
use derive_more::{Deref, DerefMut};
use educe::Educe;
use slotmap::dense::DenseSlotMap;
use tor_circmgr::isolation::Isolation;
#[derive(Debug, Educe)]
#[educe(Default)]
pub(crate) struct MultikeyIsolatedMap<I, K1, K2, V>
where
I: slotmap::Key,
K1: Hash + Eq,
K2: Eq,
{
index: HashMap<K1, Vec<I>>,
table: DenseSlotMap<I, Record<K2, V>>,
}
#[derive(Debug, Deref, DerefMut)]
pub(crate) struct Record<K2, V>
where
K2: Eq,
{
k2: K2,
isolation: Box<dyn Isolation>,
#[deref]
#[deref_mut]
value: V,
}
impl<K2, V> Record<K2, V>
where
K2: Eq,
{
#[allow(dead_code)] pub(crate) fn k2(&self) -> &K2 {
&self.k2
}
#[allow(dead_code)] pub(crate) fn isolation(&self) -> &dyn Isolation {
&*self.isolation
}
}
impl<I, K1, K2, V> MultikeyIsolatedMap<I, K1, K2, V>
where
I: slotmap::Key,
K1: Hash + Eq,
K2: Eq,
{
pub(crate) fn index_or_insert_with(
&mut self,
k1: &K1,
k2: &K2,
isolation: Box<dyn Isolation>,
create: impl FnOnce() -> V,
) -> I
where
K1: Clone,
K2: Clone,
{
let indices = self.index.entry(k1.clone()).or_default();
match indices.iter().find_map(|&t_index| {
let Record {
k2: t_k2,
isolation: t_isolation,
value: _,
} = self.table.get(t_index)
?;
(t_k2 == k2).then_some(())?;
let new_isolation = t_isolation.join(&*isolation)?;
Some((t_index, new_isolation))
}) {
Some((t_index, new_isolation)) => {
self.table
.get_mut(t_index)
.expect("table entry disappeared")
.isolation = new_isolation;
t_index
}
None => {
let value = create();
let record = Record {
k2: k2.clone(),
isolation,
value,
};
let table_index = self.table.insert(record);
indices.push(table_index);
table_index
}
}
}
#[allow(dead_code)] pub(crate) fn by_index(&self, t_index: I) -> Option<&Record<K2, V>> {
self.table.get(t_index)
}
pub(crate) fn by_index_mut(&mut self, t_index: I) -> Option<&mut Record<K2, V>> {
self.table.get_mut(t_index)
}
#[allow(dead_code)] pub(crate) fn retain(&mut self, mut test: impl FnMut(&K1, &Record<K2, V>, I) -> bool) {
self.index.retain(|k1, indices| {
indices.retain(|&t_index| {
let record = match self.table.get(t_index) {
Some(record) => record,
None => return false, };
let keep = test(k1, record, t_index);
if !keep {
self.table.remove(t_index);
}
keep
});
!indices.is_empty()
});
}
#[cfg(test)]
fn check_or_panic(&self) {
let mut referenced = slotmap::SecondaryMap::default();
for indices in self.index.values() {
assert!(!indices.is_empty(), "empty Vec not GC'd");
for (vi1, &ti1) in indices.iter().enumerate() {
let rec1 = self.table.get(ti1).expect("dangling index");
match referenced.entry(ti1) {
Some(slotmap::secondary::Entry::Vacant(ve)) => ve.insert(()),
_ => panic!("colliding references or something {ti1:?}"),
};
for &ti2 in &indices[vi1 + 1..] {
let rec2 = &self.table[ti2];
assert!(
!(rec1.k2 == rec2.k2 && rec1.isolation.compatible(&*rec2.isolation)),
"Vec contains entries that should have been merged",
);
}
}
}
for ti in self.table.keys() {
let () = referenced.get(ti).expect("unreferenced entry");
}
}
}
#[cfg(test)]
mod test {
#![allow(clippy::bool_assert_comparison)]
#![allow(clippy::clone_on_copy)]
#![allow(clippy::dbg_macro)]
#![allow(clippy::print_stderr)]
#![allow(clippy::print_stdout)]
#![allow(clippy::single_char_pattern)]
#![allow(clippy::unwrap_used)]
#![allow(clippy::unchecked_duration_subtraction)]
use super::*;
slotmap::new_key_type! {
struct Idx;
}
use crate::state::test::NarrowableIsolation;
fn mk_isol(s: impl Into<String>) -> Box<dyn Isolation> {
NarrowableIsolation(s.into()).into()
}
fn mk() -> MultikeyIsolatedMap<Idx, u32, u16, String> {
let mut out = MultikeyIsolatedMap::<Idx, u32, u16, String>::default();
let ti = out.index_or_insert_with(&1, &22, mk_isol("a"), || "hi".into());
assert_eq!(out.by_index(ti).unwrap().k2(), &22);
out.check_or_panic();
out
}
#[test]
fn simple() {
mk();
}
#[test]
fn retain() {
let mut m = mk();
m.index_or_insert_with(&2, &22, mk_isol("ab"), || "22".into());
m.check_or_panic();
m.index_or_insert_with(&2, &23, mk_isol("ac"), || "23".into());
m.check_or_panic();
m.index_or_insert_with(&2, &24, mk_isol("dd"), || "24".into());
m.check_or_panic();
dbg!(&m);
m.retain(|_k1, rec, _ti| (rec.k2 % 2) == 1);
dbg!(&m);
m.check_or_panic();
}
}