use alloc::sync::Arc;
use core::sync::atomic::{AtomicBool, Ordering};
use crate::sync::AtomicU64;
use bacnet_types::error::Error;
use bacnet_types::primitives::ObjectIdentifier;
use super::persistence::{ColorOutputPersistence, ColorOutputSnapshot, ColorOutputValue};
use crate::durable::{SaveWait, SaveWriter};
#[derive(Default)]
pub(super) struct OutputStorage(Option<OutputStore>);
impl Clone for OutputStorage {
fn clone(&self) -> Self {
Self(None)
}
}
#[derive(Clone, Debug, Default)]
pub struct ColorOutputSaveCounters {
failed_saves: Arc<AtomicU64>,
}
impl ColorOutputSaveCounters {
pub fn failed_saves(&self) -> u64 {
self.failed_saves.load(Ordering::Relaxed)
}
fn record_failure(&self) {
let _ = self
.failed_saves
.try_update(Ordering::Relaxed, Ordering::Relaxed, |n| {
Some(n.saturating_add(1))
});
}
}
struct OutputStore {
writer: SaveWriter<ColorOutputSnapshot>,
counters: ColorOutputSaveCounters,
queued: Option<ColorOutputValue>,
current: ColorOutputValue,
last_failed: Arc<AtomicBool>,
}
impl OutputStorage {
pub(super) fn open(
&mut self,
oid: ObjectIdentifier,
current: ColorOutputValue,
persistence: Arc<dyn ColorOutputPersistence>,
) -> Result<Option<ColorOutputValue>, Error> {
let loaded = persistence.load(oid)?.map(|saved| saved.present_value);
let last_failed = Arc::new(AtomicBool::new(false));
let failed = Arc::clone(&last_failed);
let counters = ColorOutputSaveCounters::default();
let counted = counters.clone();
let writer = SaveWriter::new(
format!(
"bacnet-color-{}-{}-save",
oid.object_type().to_raw(),
oid.instance_number()
),
move |snapshot: &ColorOutputSnapshot| persistence.save(oid, snapshot),
move |_, result| {
failed.store(result.is_err(), Ordering::Release);
if let Err(error) = result {
counted.record_failure();
log_warn!(
object = %oid,
%error,
"Failed to save a colour Present_Value; the next change or stop tries again"
);
}
},
);
self.0 = Some(OutputStore {
writer,
counters,
queued: loaded,
current: loaded.unwrap_or(current),
last_failed,
});
Ok(loaded)
}
pub(super) fn counters(&self) -> Option<ColorOutputSaveCounters> {
self.0.as_ref().map(|store| store.counters.clone())
}
pub(super) fn is_some(&self) -> bool {
self.0.is_some()
}
pub(super) fn note(&mut self, value: ColorOutputValue) {
let Some(store) = &mut self.0 else {
return;
};
store.current = value;
if store.queued != Some(value) || store.last_failed.load(Ordering::Acquire) {
store.queue();
}
}
pub(super) fn settle(&mut self) -> Option<SaveWait> {
let store = self.0.as_mut()?;
store.retry_failed();
Some(store.writer.idle())
}
pub(super) fn wait_for_saves(&self) {
if let Some(store) = &self.0 {
store.writer.wait_idle();
}
}
}
impl OutputStore {
fn queue(&mut self) {
self.writer.submit_coalescing(ColorOutputSnapshot {
present_value: self.current,
});
self.queued = Some(self.current);
}
fn retry_failed(&mut self) {
if self.last_failed.load(Ordering::Acquire) {
self.queue();
}
}
}
impl Drop for OutputStore {
fn drop(&mut self) {
self.retry_failed();
}
}
macro_rules! impl_color_durable {
($object:ty) => {
impl $crate::durable::DurableWrites for $object {
fn stage_write(
&mut self,
property: bacnet_types::enums::PropertyIdentifier,
array_index: Option<u32>,
value: &bacnet_types::primitives::PropertyValue,
) -> $crate::durable::StageStep {
self.profile.stage_write(property, array_index, value)
}
fn stage_writes(
&mut self,
writes: &[$crate::durable::PendingWrite],
) -> $crate::durable::StageStep {
self.profile.stage_writes(writes)
}
fn release_staged_write(&mut self, staged: &$crate::durable::SaveWait) {
self.profile.release_staged_write(staged);
}
fn has_staged_write(&self) -> bool {
self.profile.has_staged_write()
}
fn settle_forgotten_writes(&mut self) -> Option<$crate::durable::SaveWait> {
let tags = self.profile.settle_forgotten_writes();
let output = self.output.settle();
match (tags, output) {
(Some(tags), Some(output)) => Some(tags.and(output)),
(tags, output) => tags.or(output),
}
}
}
};
}
pub(super) use impl_color_durable;