use std::collections::VecDeque;
use std::sync::Arc;
use std::time::{Duration, Instant};
use bacnet_types::enums::{ErrorClass, ErrorCode, PropertyIdentifier};
use bacnet_types::error::Error;
use bacnet_types::primitives::PropertyValue;
use super::{
Event, PendingWrite, SaveTicket, SaveWait, SaveWriter, StageStep, STAGED_WRITE_LIFETIME,
};
pub(crate) struct Step<N> {
write: PendingWrite,
pub(crate) next: N,
}
pub(crate) fn steps<N>(
writes: &[PendingWrite],
mut next: impl FnMut(&[Step<N>], &PendingWrite) -> Result<Option<N>, Error>,
) -> Vec<Step<N>> {
let mut steps = Vec::new();
for write in writes {
match next(&steps, write) {
Ok(Some(state)) => steps.push(Step {
write: write.clone(),
next: state,
}),
Ok(None) => {}
Err(error) if relinquishes_nothing(&write.value, &error) => {}
Err(_) => break,
}
}
steps
}
fn relinquishes_nothing(value: &PropertyValue, error: &Error) -> bool {
let (Error::Protocol { class, code } | Error::Structured { class, code, .. }) = error else {
return false;
};
is_null(value)
&& *class == ErrorClass::PROPERTY.to_raw() as u32
&& *code == ErrorCode::INVALID_DATA_TYPE.to_raw() as u32
}
fn is_null(value: &PropertyValue) -> bool {
match value {
PropertyValue::Null => true,
PropertyValue::ApplicationData(octets) => octets.as_slice() == [0x00],
PropertyValue::List(values) => matches!(values.as_slice(), [only] if is_null(only)),
_ => false,
}
}
struct StagedWrite<S, N> {
steps: VecDeque<Step<N>>,
base: u64,
taken: u64,
ticket: SaveTicket,
served: S,
released: Arc<Event>,
finished_seen_at: Option<Duration>,
}
pub(crate) struct StagedSaves<S: Send + 'static, N> {
writer: SaveWriter<S>,
staged: Option<StagedWrite<S, N>>,
correction: Option<S>,
}
impl<S: Send + 'static, N> StagedSaves<S, N> {
pub(crate) fn new(writer: SaveWriter<S>) -> Self {
Self {
writer,
staged: None,
correction: None,
}
}
pub(crate) fn busy(&mut self) -> Option<SaveWait> {
self.busy_at(Instant::now())
}
pub(crate) fn busy_at(&mut self, now: Instant) -> Option<SaveWait> {
let staged = self.staged.as_ref()?;
if !staged.ticket.outlived_at(now) {
return Some(SaveWait::new(Arc::clone(&staged.released)));
}
self.drop_staged();
None
}
pub(crate) fn stage(
&mut self,
steps: Vec<Step<N>>,
base: u64,
served: S,
apply: impl Fn(&mut S, &N),
) -> StageStep
where
S: Clone,
{
if steps.is_empty() {
return StageStep::Skip;
}
let mut snapshot = served.clone();
for step in &steps {
apply(&mut snapshot, &step.next);
}
if let Some(correction) = self.correction.take() {
self.writer.submit_coalescing(correction);
}
let ticket = self.writer.submit(snapshot);
let wait = ticket.wait();
self.staged = Some(StagedWrite {
steps: steps.into(),
base,
taken: 0,
ticket,
served,
released: Arc::default(),
finished_seen_at: None,
});
StageStep::Staged(wait)
}
pub(crate) fn expire(&mut self, now: Duration) {
let Some(staged) = self.staged.as_mut() else {
return;
};
if !staged.ticket.is_done() {
return;
}
let seen = match staged.finished_seen_at {
Some(seen) if seen <= now => seen,
_ => *staged.finished_seen_at.insert(now),
};
if now - seen >= STAGED_WRITE_LIFETIME {
self.drop_staged();
}
}
pub(crate) fn claim(
&mut self,
property: PropertyIdentifier,
array_index: Option<u32>,
value: &PropertyValue,
base: u64,
) -> Option<Result<N, Error>> {
let staged = self.staged.as_mut()?;
let next = &staged.steps.front()?.write;
if next.property != property
|| next.array_index != array_index
|| staged.base.wrapping_add(staged.taken) != base
|| next.value != *value
{
return None;
}
if let Err(error) = staged.ticket.take_outcome() {
self.staged.take().expect("matched above").released.set();
return Some(Err(error));
}
let step = staged.steps.pop_front().expect("matched above");
staged.taken = staged.taken.wrapping_add(1);
if staged.steps.is_empty() {
self.staged.take().expect("matched above").released.set();
}
Some(Ok(step.next))
}
pub(crate) fn is_staged(&self) -> bool {
self.staged.is_some()
}
pub(crate) fn save_now(&mut self, snapshot: S) -> Result<(), Error> {
self.writer.submit(snapshot).take_outcome()
}
pub(crate) fn submit_coalescing(&mut self, snapshot: S) {
self.writer.submit_coalescing(snapshot);
}
pub(crate) fn release(&mut self, wait: &SaveWait) {
if self
.staged
.as_ref()
.is_some_and(|staged| staged.ticket.issued(wait))
{
self.drop_staged();
}
}
pub(crate) fn drop_staged(&mut self) {
let Some(staged) = self.staged.take() else {
return;
};
staged.released.set();
if staged.ticket.succeeded() != Some(false) {
self.correction = Some(staged.served);
}
}
pub(crate) fn take_correction(&mut self) -> bool {
self.correction.take().is_some()
}
pub(crate) fn correct(&mut self, served: impl FnOnce() -> S) {
if self.take_correction() {
self.writer.submit_coalescing(served());
} else if let Some(staged) = self.staged.as_mut() {
staged.served = served();
}
}
pub(crate) fn drop_forgotten(&mut self, served: impl FnOnce() -> S) -> SaveWait {
self.drop_staged();
self.correct(served);
self.writer.idle()
}
pub(crate) fn wait_idle(&self) {
self.writer.wait_idle();
}
}
impl<S: Send + 'static, N> Drop for StagedSaves<S, N> {
fn drop(&mut self) {
self.drop_staged();
if let Some(served) = self.correction.take() {
self.writer.put_back(served);
}
}
}