Files
MetaCrate/crates/libremetaverse/src/observable_dictionary.rs
Chili Palmer c9a1170a27
Some checks failed
API and SemVer surface / api-surface (push) Failing after 1m34s
Native code generation / deterministic (push) Has been cancelled
Concurrency and resource soak audit / soak (push) Has been cancelled
Documentation / documentation (push) Has been cancelled
performance evidence / audit (push) Has been cancelled
First release candidate / non-fuzz-release-gate (push) Has been cancelled
Release platform and feature matrix / audit (push) Has been cancelled
Release platform and feature matrix / matrix (false, linux-stable-minimal, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (false, macos-stable-portable, x86_64-apple-darwin, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (false, windows-stable-portable, x86_64-pc-windows-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-msrv-portable, x86_64-unknown-linux-gnu, 1.96.0) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-stable-default, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-stable-features, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-stable-release-surface, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Dependency and supply-chain audit / audit (push) Has been cancelled
Native release artifact audit / audit (push) Has been cancelled
Imaging and meshing gate / native (push) Failing after 53s
JPEG 2000 feature / linux (push) Successful in 2m49s
Native Rust workspace compile / compile (push) Failing after 59s
Skia feature / linux (push) Has been cancelled
Complete first release candidate audit (#107)
2026-08-12 14:44:28 +00:00

235 lines
6.7 KiB
Rust

//! 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<dyn Fn(DictionaryEventAction, DictionaryEntry) + Send + Sync>;
pub struct ObservableDictionary<TKey, TValue> {
values: Mutex<HashMap<TKey, Option<TValue>>>,
delegates: Mutex<HashMap<DictionaryEventAction, Vec<Callback>>>,
}
impl<TKey, TValue> ObservableDictionary<TKey, TValue>
where
TKey: Clone + Eq + Hash + Into<Object>,
TValue: Clone + PartialEq + Into<Object>,
{
pub fn new_with_constructor() -> Result<Self, Error> {
Self::new_with_capacity(0)
}
pub fn new_with_int32(capacity: i32) -> Result<Self, Error> {
let capacity = usize::try_from(capacity).map_err(|_| Error::Argument)?;
Self::new_with_capacity(capacity)
}
fn new_with_capacity(capacity: usize) -> Result<Self, Error> {
Ok(Self {
values: Mutex::new(HashMap::with_capacity(capacity)),
delegates: Mutex::new(HashMap::new()),
})
}
pub fn add(&self, key: TKey, value: Option<TValue>) -> 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<bool, Error> {
Ok(self
.values
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.contains_key(&key))
}
pub fn contains_value(&self, value: Option<TValue>) -> Result<bool, Error> {
Ok(self
.values
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.values()
.any(|candidate| candidate == &value))
}
pub fn find(
&self,
predicate: Box<dyn Fn(&Option<TValue>) -> bool + Send + Sync>,
) -> Result<Option<TValue>, 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<dyn Fn(&TKey) -> bool + Send + Sync>,
) -> Result<Vec<TKey>, 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<dyn Fn(&Option<TValue>) -> bool + Send + Sync>,
) -> Result<Vec<Option<TValue>>, 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<IEnumerator, Error> {
Ok(IEnumerator)
}
pub fn remove(&self, key: TKey) -> Result<bool, Error> {
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<TValue>) -> 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<TKey>) -> Option<TValue> {
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<TKey>, value: Option<TValue>) {
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<TValue>) {
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()));
}
}
}