use std::future::Future;
use std::sync::Arc;
use bacnet_encoding::primitives::encode_property_value;
use bacnet_objects::command::CommandRun;
use bacnet_objects::database::ObjectDatabase;
use bacnet_services::write_property::WritePropertyRequest;
use bacnet_types::constructed::{BACnetActionCommand, BACnetDeviceObjectPropertyReference};
use bacnet_types::error::Error;
use bacnet_types::primitives::{ObjectIdentifier, PropertyValue};
use bytes::BytesMut;
use futures_util::StreamExt;
use futures_util::stream::FuturesUnordered;
use tokio::sync::{RwLock, mpsc};
use super::{RunHost, TakenRuns, Unfinished, end_when_free, execute};
use crate::server::RemoteRequestError;
pub(crate) fn run_unattached(
db: &Arc<RwLock<ObjectDatabase>>,
runs: TakenRuns,
) -> impl Future<Output = ()> + Send + '_ {
let (started, queued) = mpsc::unbounded_channel();
runs.hand_over(|run| {
let _ = started.send(run);
});
let mut queue = Queue { db, queued };
async move {
let host = Unattached { db, started };
let mut running = FuturesUnordered::new();
loop {
while let Ok(run) = queue.queued.try_recv() {
running.push(execute(&host, run));
}
if running.is_empty() {
break;
}
tokio::select! {
biased;
Some(run) = queue.queued.recv() => running.push(execute(&host, run)),
Some(()) = running.next() => {}
else => break,
}
}
}
}
struct Queue<'a> {
db: &'a Arc<RwLock<ObjectDatabase>>,
queued: mpsc::UnboundedReceiver<CommandRun>,
}
impl Drop for Queue<'_> {
fn drop(&mut self) {
self.queued.close();
while let Ok(run) = self.queued.try_recv() {
end_when_free(self.db, Unfinished::start(&run));
}
}
}
struct Unattached<'a> {
db: &'a Arc<RwLock<ObjectDatabase>>,
started: mpsc::UnboundedSender<CommandRun>,
}
impl RunHost for Unattached<'_> {
fn database(&self) -> &Arc<RwLock<ObjectDatabase>> {
self.db
}
async fn write(&self, run: &CommandRun, command: &BACnetActionCommand) -> Result<(), Error> {
let request = request(command)?;
let runs = {
let mut db = self.db.write().await;
let origin = crate::command_source::resolve_local(
&db,
crate::LocalCommandSource::Object(run.source),
)
.ok();
let (target, _) = crate::handlers::handle_write_property_observed(
&mut db,
&request,
None,
None,
origin.as_ref(),
)?;
TakenRuns::take(self.db, &mut db, &[target])
};
super::admit(self, run, runs, |runs| {
runs.hand_over(|run| {
if let Err(refused) = self.started.send(run) {
end_when_free(self.db, Unfinished::start(&refused.0));
}
});
})
.await
}
async fn write_remote(
&self,
_device: ObjectIdentifier,
_command: &BACnetActionCommand,
) -> Result<(), RemoteRequestError> {
Err(RemoteRequestError::NoNetwork)
}
async fn read_remote(
&self,
_device: ObjectIdentifier,
_reference: &BACnetDeviceObjectPropertyReference,
) -> Result<PropertyValue, RemoteRequestError> {
Err(RemoteRequestError::NoNetwork)
}
fn remote_slots(&self) -> Option<&super::RemoteSlots> {
None
}
async fn committed(&self, _db: &ObjectDatabase, _source: ObjectIdentifier) {}
async fn report(&self, _source: ObjectIdentifier) {}
fn abandoned(&self, left: Unfinished) {
end_when_free(self.db, left);
}
}
fn request(command: &BACnetActionCommand) -> Result<Vec<u8>, Error> {
let mut value = BytesMut::new();
encode_property_value(&mut value, &command.property_value)?;
let mut request = BytesMut::new();
WritePropertyRequest {
object_identifier: command.object_identifier,
property_identifier: command.property_identifier,
property_array_index: command.property_array_index,
property_value: value.to_vec(),
priority: command.priority,
}
.encode(&mut request)?;
Ok(request.to_vec())
}
#[cfg(test)]
#[path = "unattached_tests.rs"]
mod tests;