use super::{Subscriber, SubscriptionId};
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CollectionChange<T> {
Added { index: usize, item: T },
Removed { index: usize, item: T },
Replaced {
index: usize,
old_item: T,
new_item: T,
},
Cleared,
Reset { items: Vec<T> },
}
pub struct ObservableCollection<T> {
inner: Arc<Mutex<CollectionInner<T>>>,
}
struct CollectionInner<T> {
items: Vec<T>,
subscribers: HashMap<usize, Subscriber<CollectionChange<T>>>,
next_id: usize,
}
impl<T: Clone> ObservableCollection<T> {
pub fn new() -> Self {
ObservableCollection {
inner: Arc::new(Mutex::new(CollectionInner {
items: Vec::new(),
subscribers: HashMap::new(),
next_id: 0,
})),
}
}
pub fn from_vec(items: Vec<T>) -> Self {
ObservableCollection {
inner: Arc::new(Mutex::new(CollectionInner {
items,
subscribers: HashMap::new(),
next_id: 0,
})),
}
}
pub fn len(&self) -> usize {
self.inner.lock().unwrap().items.len()
}
pub fn is_empty(&self) -> bool {
self.inner.lock().unwrap().items.is_empty()
}
pub fn get(&self, index: usize) -> Option<T> {
self.inner.lock().unwrap().items.get(index).cloned()
}
pub fn get_all(&self) -> Vec<T> {
self.inner.lock().unwrap().items.clone()
}
pub fn with<F, R>(&self, f: F) -> R
where
F: FnOnce(&[T]) -> R,
{
let inner = self.inner.lock().unwrap();
f(&inner.items)
}
pub fn push(&self, item: T) {
let mut inner = self.inner.lock().unwrap();
let index = inner.items.len();
inner.items.push(item.clone());
Self::notify_subscribers(
&inner.subscribers,
&CollectionChange::Added { index, item },
);
}
pub fn pop(&self) -> Option<T> {
let mut inner = self.inner.lock().unwrap();
if let Some(item) = inner.items.pop() {
let index = inner.items.len(); Self::notify_subscribers(
&inner.subscribers,
&CollectionChange::Removed {
index,
item: item.clone(),
},
);
Some(item)
} else {
None
}
}
pub fn insert(&self, index: usize, item: T) {
let mut inner = self.inner.lock().unwrap();
if index <= inner.items.len() {
inner.items.insert(index, item.clone());
Self::notify_subscribers(
&inner.subscribers,
&CollectionChange::Added { index, item },
);
}
}
pub fn remove(&self, index: usize) -> Option<T> {
let mut inner = self.inner.lock().unwrap();
if index < inner.items.len() {
let item = inner.items.remove(index);
Self::notify_subscribers(
&inner.subscribers,
&CollectionChange::Removed {
index,
item: item.clone(),
},
);
Some(item)
} else {
None
}
}
pub fn replace(&self, index: usize, new_item: T) -> Option<T> {
let mut inner = self.inner.lock().unwrap();
if index < inner.items.len() {
let old_item = std::mem::replace(&mut inner.items[index], new_item.clone());
Self::notify_subscribers(
&inner.subscribers,
&CollectionChange::Replaced {
index,
old_item: old_item.clone(),
new_item,
},
);
Some(old_item)
} else {
None
}
}
pub fn clear(&self) {
let mut inner = self.inner.lock().unwrap();
inner.items.clear();
Self::notify_subscribers(&inner.subscribers, &CollectionChange::Cleared);
}
pub fn reset(&self, items: Vec<T>) {
let mut inner = self.inner.lock().unwrap();
inner.items = items.clone();
Self::notify_subscribers(
&inner.subscribers,
&CollectionChange::Reset { items },
);
}
pub fn subscribe<F>(&self, f: F) -> SubscriptionId
where
F: FnMut(&CollectionChange<T>) + Send + 'static,
{
let mut inner = self.inner.lock().unwrap();
let id = inner.next_id;
inner.next_id += 1;
let subscriber = Arc::new(Mutex::new(f));
inner.subscribers.insert(id, subscriber);
SubscriptionId::new(id)
}
pub fn unsubscribe(&self, id: SubscriptionId) {
let mut inner = self.inner.lock().unwrap();
inner.subscribers.remove(&id.0);
}
pub fn subscriber_count(&self) -> usize {
self.inner.lock().unwrap().subscribers.len()
}
fn notify_subscribers(
subscribers: &HashMap<usize, Subscriber<CollectionChange<T>>>,
change: &CollectionChange<T>,
) {
for subscriber in subscribers.values() {
if let Ok(mut sub) = subscriber.lock() {
sub(change);
}
}
}
}
impl<T: Clone> Clone for ObservableCollection<T> {
fn clone(&self) -> Self {
ObservableCollection {
inner: self.inner.clone(),
}
}
}
impl<T: Clone> Default for ObservableCollection<T> {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_collection_push_pop() {
let col = ObservableCollection::new();
col.push(1);
col.push(2);
col.push(3);
assert_eq!(col.len(), 3);
assert_eq!(col.pop(), Some(3));
assert_eq!(col.len(), 2);
}
#[test]
fn test_collection_subscribe() {
let col = ObservableCollection::new();
let changes = Arc::new(Mutex::new(Vec::new()));
col.subscribe({
let changes = changes.clone();
move |change| {
changes.lock().unwrap().push(change.clone());
}
});
col.push("A".to_string());
col.push("B".to_string());
col.remove(0);
let recorded = changes.lock().unwrap();
assert_eq!(recorded.len(), 3);
assert!(matches!(recorded[0], CollectionChange::Added { .. }));
assert!(matches!(recorded[1], CollectionChange::Added { .. }));
assert!(matches!(recorded[2], CollectionChange::Removed { .. }));
}
#[test]
fn test_collection_clear() {
let col = ObservableCollection::new();
col.push(1);
col.push(2);
col.push(3);
assert_eq!(col.len(), 3);
col.clear();
assert_eq!(col.len(), 0);
assert!(col.is_empty());
}
}