use std::sync::Arc;
use dashmap::DashMap;
use super::property::{BACnetValue, PropertyId};
use super::traits::{ArcObject, BACnetObject};
use super::types::{ObjectId, ObjectType};
pub struct ObjectRegistry {
objects: DashMap<ObjectId, ArcObject>,
names: DashMap<String, ObjectId>,
type_counts: DashMap<ObjectType, usize>,
}
impl ObjectRegistry {
pub fn new() -> Self {
Self {
objects: DashMap::new(),
names: DashMap::new(),
type_counts: DashMap::new(),
}
}
pub fn with_capacity(capacity: usize) -> Self {
Self {
objects: DashMap::with_capacity(capacity),
names: DashMap::with_capacity(capacity),
type_counts: DashMap::new(),
}
}
pub fn register(&self, object: ArcObject) -> Option<ArcObject> {
let id = object.object_identifier();
let name = object.object_name().to_string();
let object_type = id.object_type;
if let Some((_, old)) = self.objects.remove(&id) {
self.names.remove(old.object_name());
self.type_counts
.entry(object_type)
.and_modify(|c| *c = c.saturating_sub(1));
}
self.names.insert(name, id);
self.type_counts
.entry(object_type)
.and_modify(|c| *c += 1)
.or_insert(1);
self.objects.insert(id, object)
}
pub fn register_boxed(&self, object: Box<dyn BACnetObject>) -> Option<ArcObject> {
self.register(Arc::from(object))
}
pub fn unregister(&self, id: &ObjectId) -> Option<ArcObject> {
if let Some((_, object)) = self.objects.remove(id) {
self.names.remove(object.object_name());
self.type_counts
.entry(id.object_type)
.and_modify(|c| *c = c.saturating_sub(1));
Some(object)
} else {
None
}
}
pub fn get(&self, id: &ObjectId) -> Option<ArcObject> {
self.objects.get(id).map(|r| r.clone())
}
pub fn get_by_name(&self, name: &str) -> Option<ArcObject> {
self.names
.get(name)
.and_then(|id| self.objects.get(&id).map(|r| r.clone()))
}
pub fn contains(&self, id: &ObjectId) -> bool {
self.objects.contains_key(id)
}
pub fn contains_name(&self, name: &str) -> bool {
self.names.contains_key(name)
}
pub fn len(&self) -> usize {
self.objects.len()
}
pub fn is_empty(&self) -> bool {
self.objects.is_empty()
}
pub fn object_ids(&self) -> Vec<ObjectId> {
self.objects.iter().map(|r| *r.key()).collect()
}
pub fn object_ids_by_type(&self, object_type: ObjectType) -> Vec<ObjectId> {
self.objects
.iter()
.filter(|r| r.key().object_type == object_type)
.map(|r| *r.key())
.collect()
}
pub fn count_by_type(&self, object_type: ObjectType) -> usize {
self.type_counts.get(&object_type).map(|r| *r).unwrap_or(0)
}
pub fn object_names(&self) -> Vec<String> {
self.names.iter().map(|r| r.key().clone()).collect()
}
pub fn read_property(
&self,
object_id: &ObjectId,
property_id: PropertyId,
) -> Result<BACnetValue, RegistryError> {
let object = self
.get(object_id)
.ok_or(RegistryError::ObjectNotFound(*object_id))?;
object
.read_property(property_id)
.map_err(|e| RegistryError::PropertyError(e.to_string()))
}
pub fn write_property(
&self,
object_id: &ObjectId,
property_id: PropertyId,
value: BACnetValue,
) -> Result<(), RegistryError> {
let object = self
.get(object_id)
.ok_or(RegistryError::ObjectNotFound(*object_id))?;
object
.write_property(property_id, value)
.map_err(|e| RegistryError::PropertyError(e.to_string()))
}
pub fn iter(&self) -> impl Iterator<Item = ArcObject> + '_ {
self.objects.iter().map(|r| r.clone())
}
pub fn iter_by_type(&self, object_type: ObjectType) -> impl Iterator<Item = ArcObject> + '_ {
self.objects
.iter()
.filter(move |r| r.key().object_type == object_type)
.map(|r| r.clone())
}
pub fn clear(&self) {
self.objects.clear();
self.names.clear();
self.type_counts.clear();
}
pub fn populate_standard_objects(
&self,
descriptors: &[ObjectTypeDescriptor],
count_per_type: usize,
) {
for desc in descriptors {
for i in 0..count_per_type {
let instance = i as u32;
let name = format!("{}_{}", desc.prefix, instance);
let object = (desc.create)(instance, name);
self.register(object);
}
}
}
pub fn next_available_instance(&self, object_type: ObjectType) -> Option<u32> {
let max = self
.objects
.iter()
.filter(|r| r.key().object_type == object_type)
.map(|r| r.key().instance)
.max();
let next = match max {
Some(m) => m.checked_add(1)?,
None => 0,
};
if next > ObjectId::MAX_INSTANCE {
None
} else {
Some(next)
}
}
pub fn statistics(&self) -> RegistryStatistics {
let mut type_counts = Vec::new();
for entry in self.type_counts.iter() {
type_counts.push((*entry.key(), *entry.value()));
}
type_counts.sort_by_key(|(t, _)| *t as u16);
RegistryStatistics {
total_objects: self.objects.len(),
type_counts,
}
}
}
impl Default for ObjectRegistry {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct RegistryStatistics {
pub total_objects: usize,
pub type_counts: Vec<(ObjectType, usize)>,
}
pub struct ObjectTypeDescriptor {
pub prefix: &'static str,
pub create: fn(u32, String) -> ArcObject,
}
pub fn default_object_descriptors() -> Vec<ObjectTypeDescriptor> {
use super::standard::{AnalogInput, AnalogOutput, BinaryInput, BinaryOutput};
vec![
ObjectTypeDescriptor {
prefix: "AI",
create: |instance, name| Arc::new(AnalogInput::new(instance, name)),
},
ObjectTypeDescriptor {
prefix: "AO",
create: |instance, name| Arc::new(AnalogOutput::new(instance, name)),
},
ObjectTypeDescriptor {
prefix: "BI",
create: |instance, name| Arc::new(BinaryInput::new(instance, name)),
},
ObjectTypeDescriptor {
prefix: "BO",
create: |instance, name| Arc::new(BinaryOutput::new(instance, name)),
},
]
}
#[derive(Debug, thiserror::Error)]
pub enum RegistryError {
#[error("Object not found: {0}")]
ObjectNotFound(ObjectId),
#[error("Object name already exists: {0}")]
NameExists(String),
#[error("Property error: {0}")]
PropertyError(String),
}
#[cfg(test)]
mod tests {
use super::super::standard::{AnalogInput, AnalogOutput, BinaryOutput};
use super::*;
#[test]
fn test_registry_basic_operations() {
let registry = ObjectRegistry::new();
let ai = Arc::new(AnalogInput::new(1, "Temperature"));
let bo = Arc::new(BinaryOutput::new(1, "Fan"));
registry.register(ai.clone());
registry.register(bo.clone());
assert_eq!(registry.len(), 2);
assert!(registry.contains(&ObjectId::new(ObjectType::AnalogInput, 1)));
assert!(registry.contains(&ObjectId::new(ObjectType::BinaryOutput, 1)));
}
#[test]
fn test_registry_lookup_by_name() {
let registry = ObjectRegistry::new();
let ai = Arc::new(AnalogInput::new(1, "Zone Temperature"));
registry.register(ai);
let found = registry.get_by_name("Zone Temperature");
assert!(found.is_some());
assert_eq!(found.unwrap().object_name(), "Zone Temperature");
assert!(registry.get_by_name("Nonexistent").is_none());
}
#[test]
fn test_registry_type_counts() {
let registry = ObjectRegistry::new();
registry.register(Arc::new(AnalogInput::new(1, "AI1")));
registry.register(Arc::new(AnalogInput::new(2, "AI2")));
registry.register(Arc::new(BinaryOutput::new(1, "BO1")));
assert_eq!(registry.count_by_type(ObjectType::AnalogInput), 2);
assert_eq!(registry.count_by_type(ObjectType::BinaryOutput), 1);
assert_eq!(registry.count_by_type(ObjectType::AnalogValue), 0);
}
#[test]
fn test_registry_unregister() {
let registry = ObjectRegistry::new();
let ai = Arc::new(AnalogInput::new(1, "Temperature"));
let id = ai.object_identifier();
registry.register(ai);
assert!(registry.contains(&id));
let removed = registry.unregister(&id);
assert!(removed.is_some());
assert!(!registry.contains(&id));
assert!(!registry.contains_name("Temperature"));
}
#[test]
fn test_registry_property_access() {
let registry = ObjectRegistry::new();
let ai = Arc::new(AnalogInput::new(1, "Temperature"));
ai.set_value(25.0);
let id = ai.object_identifier();
registry.register(ai);
let value = registry
.read_property(&id, PropertyId::PresentValue)
.unwrap();
assert_eq!(value.as_real(), Some(25.0));
}
#[test]
fn test_populate_standard_objects_starts_at_instance_0() {
let registry = ObjectRegistry::new();
let descriptors = default_object_descriptors();
registry.populate_standard_objects(&descriptors, 3);
assert!(registry.contains(&ObjectId::new(ObjectType::AnalogInput, 0)));
assert!(registry.contains(&ObjectId::new(ObjectType::AnalogOutput, 0)));
assert!(registry.contains(&ObjectId::new(ObjectType::BinaryInput, 0)));
assert!(registry.contains(&ObjectId::new(ObjectType::BinaryOutput, 0)));
assert!(registry.contains(&ObjectId::new(ObjectType::AnalogInput, 2)));
assert!(!registry.contains(&ObjectId::new(ObjectType::AnalogInput, 3)));
assert!(registry.get_by_name("AI_0").is_some());
assert!(registry.get_by_name("BO_2").is_some());
assert!(registry.get_by_name("AI_3").is_none());
assert_eq!(registry.len(), 12);
assert_eq!(registry.count_by_type(ObjectType::AnalogInput), 3);
assert_eq!(registry.count_by_type(ObjectType::BinaryOutput), 3);
}
#[test]
fn test_populate_extensibility_custom_descriptors() {
let registry = ObjectRegistry::new();
let descriptors = vec![
ObjectTypeDescriptor {
prefix: "AI",
create: |instance, name| Arc::new(AnalogInput::new(instance, name)),
},
ObjectTypeDescriptor {
prefix: "AO",
create: |instance, name| Arc::new(AnalogOutput::new(instance, name)),
},
];
registry.populate_standard_objects(&descriptors, 5);
assert_eq!(registry.len(), 10);
assert_eq!(registry.count_by_type(ObjectType::AnalogInput), 5);
assert_eq!(registry.count_by_type(ObjectType::AnalogOutput), 5);
assert_eq!(registry.count_by_type(ObjectType::BinaryInput), 0);
}
}