//! Thread-safe observable key/value storage. use crate::{DictionaryEventAction, Error}; use libremetaverse_types::compat::{DictionaryEntry, IEnumerator, Object}; use std::collections::HashMap; use std::hash::Hash; use std::sync::{Arc, Mutex}; type Callback = Arc; pub struct ObservableDictionary { values: Mutex>>, delegates: Mutex>>, } impl ObservableDictionary where TKey: Clone + Eq + Hash + Into, TValue: Clone + PartialEq + Into, { pub fn new_with_constructor() -> Result { Self::new_with_capacity(0) } pub fn new_with_int32(capacity: i32) -> Result { let capacity = usize::try_from(capacity).map_err(|_| Error::Argument)?; Self::new_with_capacity(capacity) } fn new_with_capacity(capacity: usize) -> Result { Ok(Self { values: Mutex::new(HashMap::with_capacity(capacity)), delegates: Mutex::new(HashMap::new()), }) } pub fn add(&self, key: TKey, value: Option) -> Result<(), Error> { self.values .lock() .unwrap_or_else(std::sync::PoisonError::into_inner) .insert(key.clone(), value.clone()); self.fire(DictionaryEventAction::Add, key, value); Ok(()) } pub fn add_delegate( &self, action: DictionaryEventAction, callback: Callback, ) -> Result<(), Error> { self.delegates .lock() .unwrap_or_else(std::sync::PoisonError::into_inner) .entry(action) .or_default() .push(callback); Ok(()) } pub fn remove_delegate( &self, action: DictionaryEventAction, callback: Callback, ) -> Result<(), Error> { if let Some(callbacks) = self .delegates .lock() .unwrap_or_else(std::sync::PoisonError::into_inner) .get_mut(&action) { callbacks.retain(|registered| !Arc::ptr_eq(registered, &callback)); } Ok(()) } pub fn clear(&self) -> Result<(), Error> { let removed: Vec<_> = self .values .lock() .unwrap_or_else(std::sync::PoisonError::into_inner) .drain() .collect(); for (key, value) in removed { self.fire(DictionaryEventAction::Remove, key, value); } Ok(()) } pub fn contains_key(&self, key: TKey) -> Result { Ok(self .values .lock() .unwrap_or_else(std::sync::PoisonError::into_inner) .contains_key(&key)) } pub fn contains_value(&self, value: Option) -> Result { Ok(self .values .lock() .unwrap_or_else(std::sync::PoisonError::into_inner) .values() .any(|candidate| candidate == &value)) } pub fn find( &self, predicate: Box) -> bool + Send + Sync>, ) -> Result, Error> { Ok(self .values .lock() .unwrap_or_else(std::sync::PoisonError::into_inner) .values() .find(|value| predicate(value)) .cloned() .flatten()) } pub fn find_all_with_predicate( &self, predicate: Box bool + Send + Sync>, ) -> Result, Error> { Ok(self .values .lock() .unwrap_or_else(std::sync::PoisonError::into_inner) .keys() .filter(|key| predicate(key)) .cloned() .collect()) } pub fn find_all_with_predicate_6d98ccd2( &self, predicate: Box) -> bool + Send + Sync>, ) -> Result>, Error> { Ok(self .values .lock() .unwrap_or_else(std::sync::PoisonError::into_inner) .values() .filter(|value| predicate(value)) .cloned() .collect()) } pub fn get_enumerator(&self) -> Result { Ok(IEnumerator) } pub fn remove(&self, key: TKey) -> Result { let removed = self .values .lock() .unwrap_or_else(std::sync::PoisonError::into_inner) .remove(&key); if let Some(value) = removed { self.fire(DictionaryEventAction::Remove, key, value); Ok(true) } else { Ok(false) } } #[must_use] pub fn try_get_value(&self, key: TKey, value: &mut Option) -> bool { let found = self .values .lock() .unwrap_or_else(std::sync::PoisonError::into_inner) .get(&key) .cloned(); match found { Some(found) => { *value = found; true } None => false, } } #[must_use] pub fn count(&self) -> i32 { i32::try_from( self.values .lock() .unwrap_or_else(std::sync::PoisonError::into_inner) .len(), ) .unwrap_or(i32::MAX) } #[must_use] pub fn item(&self, key: Option) -> Option { let key = key?; self.values .lock() .unwrap_or_else(std::sync::PoisonError::into_inner) .get(&key) .cloned() .flatten() } pub fn set_item(&mut self, key: Option, value: Option) { let Some(key) = key else { return }; let action = if self .values .lock() .unwrap_or_else(std::sync::PoisonError::into_inner) .insert(key.clone(), value.clone()) .is_some() { DictionaryEventAction::Change } else { DictionaryEventAction::Add }; self.fire(action, key, value); } fn fire(&self, action: DictionaryEventAction, key: TKey, value: Option) { let callbacks = self .delegates .lock() .unwrap_or_else(std::sync::PoisonError::into_inner) .get(&action) .cloned() .unwrap_or_default(); let entry = DictionaryEntry(key.into(), value.map_or(Object::Undefined, Into::into)); for callback in callbacks { callback(action, DictionaryEntry(entry.0.clone(), entry.1.clone())); } } }