use bacnet_objects::clock::ClockFrame;
use bacnet_objects::database::ObjectDatabase;
use bacnet_objects::traits::BACnetObject;
use bacnet_types::enums::PropertyIdentifier;
use bacnet_types::error::Error;
use bacnet_types::primitives::{ObjectIdentifier, PropertyValue};
use super::multiple_reads::MultipleReads;
use super::timed::{TimedChange, TimedStore};
use super::{
CovNotificationKind, CovSubscriptionKey, CovSubscriptionSnapshot, CovSubscriptionTable,
};
use crate::handlers::{WriteCommitObserver, WriteTarget};
use crate::life_safety_cov::{LifeSafetyCovChange, LifeSafetyCovSnapshots, is_life_safety_object};
pub(crate) struct TimedCapture {
store: TimedStore,
refs: Vec<CovSubscriptionSnapshot>,
force: bool,
frame: Option<ClockFrame>,
}
fn exact_selects(sub: &CovSubscriptionSnapshot, change: &LifeSafetyCovChange) -> bool {
sub.monitored_object_identifier == change.object_identifier
&& (change
.changed_properties
.contains(&PropertyIdentifier::STATUS_FLAGS)
|| sub
.monitored_property
.is_some_and(|property| change.changed_properties.contains(&property)))
}
impl CovSubscriptionTable {
fn timed_refs(&self, select: impl Fn(&CovSubscriptionSnapshot) -> bool) -> TimedCapture {
TimedCapture {
store: self.timed.clone(),
refs: self
.subs
.values()
.filter(|sub| {
sub.timestamped
&& sub.notification_kind == CovNotificationKind::Multiple
&& select(sub)
&& self.is_current(sub)
})
.cloned()
.collect(),
force: false,
frame: None,
}
}
pub(crate) fn timed_capture(&self, oid: ObjectIdentifier) -> TimedCapture {
let ordinary = !is_life_safety_object(oid);
self.timed_refs(|sub| ordinary && sub.monitored_object_identifier == oid)
}
pub(crate) fn timed_capture_exact(&self, changes: &[LifeSafetyCovChange]) -> TimedCapture {
self.timed_refs(|sub| changes.iter().any(|change| exact_selects(sub, change)))
}
pub(crate) fn timed_capture_all(&self) -> TimedCapture {
self.timed_refs(|_| true)
}
pub(crate) fn initial_timed_capture(
&self,
snapshots: &[CovSubscriptionSnapshot],
frame: ClockFrame,
) -> TimedCapture {
TimedCapture {
store: self.timed.clone(),
refs: snapshots
.iter()
.filter(|sub| sub.timestamped && self.is_current(sub))
.cloned()
.collect(),
force: true,
frame: Some(frame),
}
}
}
impl TimedCapture {
fn select(&self, keep: impl Fn(&CovSubscriptionSnapshot) -> bool) -> Self {
Self {
store: self.store.clone(),
refs: self.refs.iter().filter(|sub| keep(sub)).cloned().collect(),
force: self.force,
frame: self.frame,
}
}
fn object(&self, oid: ObjectIdentifier) -> Self {
let ordinary = !is_life_safety_object(oid);
self.select(|sub| ordinary && sub.monitored_object_identifier == oid)
}
fn exact(&self, change: &LifeSafetyCovChange) -> Self {
self.select(|sub| exact_selects(sub, change))
}
fn watches(&self, oid: ObjectIdentifier) -> bool {
self.refs
.iter()
.any(|sub| sub.monitored_object_identifier == oid)
}
pub(crate) fn run(self, db: &ObjectDatabase) -> Vec<CovSubscriptionKey> {
if self.refs.is_empty() {
return Vec::new();
}
let Some(frame) = self.frame.or_else(|| {
db.clock_frame()
.filter(|frame| frame.is_valid_actual_datetime())
}) else {
return Vec::new();
};
self.run_from_source(frame, |oid| db.get(&oid))
}
pub(crate) fn run_from_snapshot(
self,
snapshot: &dyn BACnetObject,
frame: Option<ClockFrame>,
) -> Vec<CovSubscriptionKey> {
let Some(frame) = frame.filter(|frame| frame.is_valid_actual_datetime()) else {
return Vec::new();
};
self.run_from_source(frame, |oid| {
(snapshot.object_identifier() == oid).then_some(snapshot)
})
}
fn run_from_source<'a>(
self,
frame: ClockFrame,
source: impl Fn(ObjectIdentifier) -> Option<&'a dyn BACnetObject>,
) -> Vec<CovSubscriptionKey> {
let mut reads = MultipleReads::default();
for sub in &self.refs {
if let Some(object) = source(sub.monitored_object_identifier) {
reads.capture_source(object, sub);
}
}
let current: Vec<_> = self
.refs
.iter()
.filter_map(|sub| {
let object = source(sub.monitored_object_identifier)?;
Some((sub, reads.read(object, sub)?))
})
.collect();
let mut timed = self.store.lock();
let mut queued = Vec::new();
for (sub, prepared) in current {
let key = sub.key();
let generation = sub.generation();
let baseline = timed
.baseline(key, generation)
.or(sub.last_notified_observation.as_ref())
.cloned();
if !self.force && !reads.reports(&prepared, baseline.as_ref()) {
timed.note_field(key, generation, &prepared.values, frame);
continue;
}
let values =
reads.with_flags_companion(&sub.monitored_object_identifier, prepared.values);
if timed.push(
key,
generation,
TimedChange::new(frame, values, prepared.observation),
) {
queued.push(key.clone());
}
}
queued
}
}
pub(crate) struct TimedWriteCapture<'a> {
inner: Option<&'a mut dyn WriteCommitObserver>,
capture: TimedCapture,
life_safety: Option<LifeSafetyCovSnapshots>,
life_safety_queued: Vec<CovSubscriptionKey>,
}
impl<'a> TimedWriteCapture<'a> {
pub(crate) fn new(
capture: TimedCapture,
inner: Option<&'a mut dyn WriteCommitObserver>,
) -> Self {
Self {
inner,
capture,
life_safety: None,
life_safety_queued: Vec::new(),
}
}
pub(crate) fn life_safety_queued(&self) -> &[CovSubscriptionKey] {
&self.life_safety_queued
}
}
impl WriteCommitObserver for TimedWriteCapture<'_> {
fn before(&mut self, db: &ObjectDatabase, write: WriteTarget<'_>) {
self.life_safety = (is_life_safety_object(write.oid) && self.capture.watches(write.oid))
.then(|| LifeSafetyCovSnapshots::capture_oid(db, write.oid));
if let Some(inner) = self.inner.as_deref_mut() {
inner.before(db, write);
}
}
fn commit_policy(
&mut self,
db: &mut ObjectDatabase,
write: WriteTarget<'_>,
value: &PropertyValue,
) -> Option<Result<(), Error>> {
self.inner.as_deref_mut()?.commit_policy(db, write, value)
}
fn committed(&mut self, db: &mut ObjectDatabase) {
if let Some(inner) = self.inner.as_deref_mut() {
inner.committed(db);
}
}
fn failed(&mut self, db: &mut ObjectDatabase, error: &Error) {
self.life_safety = None;
if let Some(inner) = self.inner.as_deref_mut() {
inner.failed(db, error);
}
}
fn applied(&mut self, db: &ObjectDatabase, oid: ObjectIdentifier) {
if let Some(inner) = self.inner.as_deref_mut() {
inner.applied(db, oid);
}
if !is_life_safety_object(oid) {
self.capture.object(oid).run(db);
return;
}
if let Some(before) = self.life_safety.take() {
for change in before.changes(db, std::slice::from_ref(&oid)) {
for key in self.capture.exact(&change).run(db) {
if !self.life_safety_queued.contains(&key) {
self.life_safety_queued.push(key);
}
}
}
}
}
}