use std::fmt;
use std::sync::Arc;
use bacnet_objects::command::CommandRun;
use bacnet_objects::database::ObjectDatabase;
use bacnet_types::primitives::ObjectIdentifier;
use tokio::sync::RwLock;
use tracing::debug;
use super::{Unfinished, when_free};
#[derive(Default)]
#[must_use = "runs dropped unstarted end as unsuccessful"]
pub(crate) struct TakenRuns {
runs: Vec<CommandRun>,
database: Option<Arc<RwLock<ObjectDatabase>>>,
}
impl TakenRuns {
pub(crate) fn take(
database: &Arc<RwLock<ObjectDatabase>>,
db: &mut ObjectDatabase,
oids: &[ObjectIdentifier],
) -> Self {
let runs = super::take_queued(db, oids);
let database = (!runs.is_empty()).then(|| Arc::clone(database));
Self { runs, database }
}
pub(crate) fn is_empty(&self) -> bool {
self.runs.is_empty()
}
pub(crate) fn iter(&self) -> impl Iterator<Item = &CommandRun> {
self.runs.iter()
}
pub(crate) fn iter_mut(&mut self) -> impl Iterator<Item = &mut CommandRun> {
self.runs.iter_mut()
}
pub(crate) fn extend(&mut self, mut other: Self) {
if other.runs.is_empty() {
return;
}
debug_assert!(
self.database
.as_ref()
.zip(other.database.as_ref())
.is_none_or(|(ours, theirs)| Arc::ptr_eq(ours, theirs))
);
if self.database.is_none() {
self.database = other.database.take();
}
self.runs.append(&mut other.runs);
}
pub(crate) fn split_off(&mut self, mut split: impl FnMut(&CommandRun) -> bool) -> Self {
let (picked, kept) = std::mem::take(&mut self.runs)
.into_iter()
.partition(|run| split(run));
self.runs = kept;
Self {
runs: picked,
database: self.database.clone(),
}
}
pub(crate) fn hand_over(mut self, mut owner: impl FnMut(CommandRun)) {
self.runs.reverse();
while let Some(run) = self.runs.pop() {
owner(run);
}
}
pub(crate) fn end(mut self, db: &mut ObjectDatabase) -> Vec<ObjectIdentifier> {
self.runs
.drain(..)
.map(|run| {
Unfinished::start(&run).end(db);
run.source
})
.collect()
}
}
impl Drop for TakenRuns {
fn drop(&mut self) {
let Some(database) = self.database.take() else {
debug_assert!(self.runs.is_empty(), "taken runs without a database");
return;
};
if self.runs.is_empty() {
return;
}
let left: Vec<_> = self
.runs
.drain(..)
.map(|run| {
debug!(source = %run.source, "Ending a run let go of before it started");
Unfinished::start(&run)
})
.collect();
when_free(&database, move |db| {
for left in left {
left.end(db);
}
});
}
}
impl fmt::Debug for TakenRuns {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_list().entries(&self.runs).finish()
}
}
#[cfg(test)]
#[path = "taken_tests.rs"]
mod tests;