use std::{cell::Cell, collections::hash_map::Entry, hash::Hash, rc::Rc};
use crate::{Computed, ToComputed, fast_hash::FastMap, struct_mut::ValueMut};
pub struct KeyedListItem<K, V> {
pub key: K,
pub value: V,
}
impl<K: Clone, V: Clone> Clone for KeyedListItem<K, V> {
fn clone(&self) -> Self {
KeyedListItem {
key: self.key.clone(),
value: self.value.clone(),
}
}
}
impl<K: PartialEq, V: PartialEq> PartialEq for KeyedListItem<K, V> {
fn eq(&self, other: &Self) -> bool {
self.key == other.key && self.value == other.value
}
}
type RowCache<K, T> = FastMap<K, (u64, Computed<T>)>;
pub fn keyed_computed_list<T, K>(
items: impl ToComputed<Vec<T>>,
get_key: impl Fn(&T) -> K + 'static,
) -> Computed<Vec<KeyedListItem<K, Computed<T>>>>
where
T: Clone + PartialEq + 'static,
K: Clone + Eq + Hash + 'static,
{
let items = items.to_computed();
let indexed = Computed::from({
move |ctx| {
let items = items.get(ctx);
let mut order = Vec::with_capacity(items.len());
let mut by_key = FastMap::with_capacity_and_hasher(items.len(), Default::default());
for item in items {
let key = get_key(&item);
match by_key.entry(key.clone()) {
Entry::Occupied(_) => {
log::error!(
"keyed_computed_list: duplicate key; keeping the first occurrence"
);
}
Entry::Vacant(slot) => {
slot.insert(item);
order.push(key);
}
}
}
(Rc::new(order), Rc::new(by_key))
}
});
let by_key = Computed::from({
let indexed = indexed.clone();
move |ctx| indexed.get(ctx).1
});
let cache: Rc<ValueMut<RowCache<K, T>>> = Rc::new(ValueMut::new(FastMap::default()));
let pass = Rc::new(Cell::new(0u64));
Computed::from({
move |ctx| {
let (order, by_key_now) = indexed.get(ctx);
let stamp = pass.get().wrapping_add(1);
pass.set(stamp);
let mut result_list = Vec::with_capacity(order.len());
cache.change(|cache| {
for key in order.iter() {
let value = match cache.get_mut(key) {
Some(entry) => {
entry.0 = stamp;
entry.1.clone()
}
None => {
let Some(item) = by_key_now.get(key) else {
continue;
};
let value = row_computed(
key.clone(),
&by_key,
by_key_now.clone(),
item.clone(),
);
cache.insert(key.clone(), (stamp, value.clone()));
value
}
};
result_list.push(KeyedListItem {
key: key.clone(),
value,
});
}
cache.retain(|_, (seen, _)| *seen == stamp);
});
result_list
}
})
}
fn row_computed<T, K>(
key: K,
by_key: &Computed<Rc<FastMap<K, T>>>,
initial: Rc<FastMap<K, T>>,
seed: T,
) -> Computed<T>
where
T: Clone + PartialEq + 'static,
K: Clone + Eq + Hash + 'static,
{
let by_key = by_key.clone();
let last = Rc::new(ValueMut::new(initial));
let departed = Cell::new(false);
Computed::from(move |ctx| {
let current = by_key.get(ctx);
if let Some(value) = current.get(&key) {
let value = value.clone();
last.set(current);
departed.set(false);
return value;
}
let value = last
.map(|last| last.get(&key).cloned())
.unwrap_or_else(|| seed.clone());
if departed.replace(true) {
log::error!(
"keyed_computed_list: item Computed was read after its key left the source list; returning last value"
);
}
last.change(|last| {
if last.len() > 1 {
*last = Rc::new(FastMap::from_iter([(key.clone(), value.clone())]));
}
});
value
})
}