use std::{borrow::Borrow, collections::HashMap, hash::Hash};
use eyeball_im::{ObservableVector, Vector, VectorDiff};
use futures_util::Stream;
#[derive(Debug)]
pub(crate) struct ObservableMap<K, V>
where
V: Clone + 'static,
{
mapping: HashMap<K, usize>,
values: ObservableVector<V>,
}
impl<K, V> ObservableMap<K, V>
where
K: Hash + Eq,
V: Clone + 'static,
{
pub(crate) fn new() -> Self {
Self { mapping: HashMap::new(), values: ObservableVector::new() }
}
pub(crate) fn insert(&mut self, key: K, value: V) -> usize {
match self.mapping.get(&key) {
Some(position) => {
self.values.set(*position, value);
*position
}
None => {
let position = self.values.len();
self.values.push_back(value);
self.mapping.insert(key, position);
position
}
}
}
pub(crate) fn get<L>(&self, key: &L) -> Option<&V>
where
K: Borrow<L>,
L: Hash + Eq + ?Sized,
{
self.mapping.get(key).and_then(|position| self.values.get(*position))
}
pub(crate) fn get_or_create<L, F>(&mut self, key: &L, default: F) -> &V
where
K: Borrow<L>,
L: Hash + Eq + ?Sized + ToOwned<Owned = K>,
F: FnOnce() -> V,
{
let position = match self.mapping.get(key) {
Some(position) => *position,
None => {
let value = default();
let position = self.values.len();
self.values.push_back(value);
self.mapping.insert(key.to_owned(), position);
position
}
};
self.values
.get(position)
.expect("Value should be present or has just been inserted, but it's missing")
}
pub(crate) fn iter(&self) -> impl Iterator<Item = &V> {
self.values.iter()
}
pub(crate) fn stream(&self) -> (Vector<V>, impl Stream<Item = Vec<VectorDiff<V>>> + use<K, V>) {
self.values.subscribe().into_values_and_batched_stream()
}
pub(crate) fn remove<L>(&mut self, key: &L) -> Option<V>
where
K: Borrow<L>,
L: Hash + Eq + ?Sized,
{
let position = self.mapping.remove(key)?;
for mapped_pos in self.mapping.values_mut().filter(|pos| **pos > position) {
*mapped_pos = mapped_pos.saturating_sub(1);
}
Some(self.values.remove(position))
}
}
#[cfg(test)]
mod tests {
use eyeball_im::VectorDiff;
use stream_assert::{assert_closed, assert_next_eq, assert_pending};
use super::ObservableMap;
#[test]
fn test_insert_and_get() {
let mut map = ObservableMap::<char, char>::new();
assert!(map.get(&'a').is_none());
assert!(map.get(&'b').is_none());
assert!(map.get(&'c').is_none());
map.insert('a', 'e');
map.insert('b', 'f');
assert_eq!(map.get(&'a'), Some(&'e'));
assert_eq!(map.get(&'b'), Some(&'f'));
assert!(map.get(&'c').is_none());
map.insert('c', 'g');
assert_eq!(map.get(&'a'), Some(&'e'));
assert_eq!(map.get(&'b'), Some(&'f'));
assert_eq!(map.get(&'c'), Some(&'g'));
map.insert('b', 'F');
assert_eq!(map.get(&'a'), Some(&'e'));
assert_eq!(map.get(&'b'), Some(&'F'));
assert_eq!(map.get(&'c'), Some(&'g'));
}
#[test]
fn test_get_or_create() {
let mut map = ObservableMap::<char, char>::new();
map.insert('b', 'f');
assert_eq!(map.get_or_create(&'a', || 'E'), &'E');
assert_eq!(map.get_or_create(&'b', || 'F'), &'f'); assert_eq!(map.get_or_create(&'c', || 'G'), &'G');
assert_eq!(map.get(&'a'), Some(&'E'));
assert_eq!(map.get(&'b'), Some(&'f'));
assert_eq!(map.get(&'c'), Some(&'G'));
assert_eq!(map.remove(&'b'), Some('f'));
assert_eq!(map.get_or_create(&'c', || 'G'), &'G');
}
#[test]
fn test_remove() {
let mut map = ObservableMap::<char, char>::new();
assert!(map.get(&'a').is_none());
assert!(map.get(&'b').is_none());
assert!(map.get(&'c').is_none());
map.insert('a', 'e');
map.insert('b', 'f');
map.insert('c', 'g');
assert_eq!(map.get(&'a'), Some(&'e'));
assert_eq!(map.get(&'b'), Some(&'f'));
assert_eq!(map.get(&'c'), Some(&'g'));
assert!(map.get(&'d').is_none());
assert_eq!(map.remove(&'c'), Some('g'));
assert_eq!(map.get(&'a'), Some(&'e'));
assert_eq!(map.get(&'b'), Some(&'f'));
assert_eq!(map.get(&'c'), None);
assert_eq!(map.remove(&'c'), None);
assert_eq!(map.remove(&'a'), Some('e'));
assert_eq!(map.get(&'b'), Some(&'f'));
}
#[test]
fn test_iter() {
let mut map = ObservableMap::<char, char>::new();
map.insert('a', 'e');
map.insert('b', 'f');
map.insert('c', 'g');
assert_eq!(
map.iter().map(|c| c.to_ascii_uppercase()).collect::<Vec<_>>(),
&['E', 'F', 'G']
);
}
#[test]
fn test_stream() {
let mut map = ObservableMap::<char, char>::new();
map.insert('b', 'f');
let (initial_values, mut stream) = map.stream();
assert_eq!(initial_values.iter().copied().collect::<Vec<_>>(), &['f']);
assert_pending!(stream);
map.insert('c', 'g');
map.insert('a', 'e');
assert_next_eq!(
stream,
vec![VectorDiff::PushBack { value: 'g' }, VectorDiff::PushBack { value: 'e' }]
);
assert_pending!(stream);
map.insert('b', 'F');
assert_next_eq!(stream, vec![VectorDiff::Set { index: 0, value: 'F' }]);
assert_pending!(stream);
map.remove(&'b');
assert_next_eq!(stream, vec![VectorDiff::Remove { index: 0 }]);
assert_pending!(stream);
drop(map);
assert_closed!(stream);
}
}