use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
use bacnet_types::enums::ObjectType;
use bacnet_types::primitives::ObjectIdentifier;
use super::objects::ListRow;
use super::watch::Screen;
use super::{App, Link};
use crate::tui::format::clean_name;
use crate::tui::message::Command;
use crate::tui::point::{NameAsk, ReadName, ReqId};
pub(crate) const MAX_NAMES: usize = 4_096;
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum Name {
Known(String),
Unavailable,
Unreachable,
}
#[derive(Debug)]
struct Entry {
address: String,
name: Name,
used: u64,
}
#[derive(Debug, Default)]
struct Ask {
req: ReqId,
address: String,
objects: HashSet<ObjectIdentifier>,
sent: bool,
retry: bool,
retried: bool,
}
#[derive(Debug, Default)]
pub(crate) struct NameCache {
names: HashMap<(u32, ObjectIdentifier), Vec<Entry>>,
by_use: BTreeMap<u64, (u32, ObjectIdentifier)>,
clock: u64,
stalled: HashMap<u32, usize>,
changes: u64,
device_changes: u64,
asked: BTreeMap<u32, Ask>,
}
impl NameCache {
pub(crate) fn get(
&self,
device: u32,
address: &str,
object: ObjectIdentifier,
) -> Option<&Name> {
self.names
.get(&(device, object))?
.iter()
.find(|entry| entry.address == address)
.map(|entry| &entry.name)
}
pub(crate) fn known(
&self,
device: u32,
address: &str,
object: ObjectIdentifier,
) -> Option<&str> {
match self.get(device, address, object)? {
Name::Known(name) => Some(name),
Name::Unavailable | Name::Unreachable => None,
}
}
pub(crate) fn changes(&self) -> u64 {
self.changes
}
pub(crate) fn device_changes(&self) -> u64 {
self.device_changes
}
fn changed(&mut self, object: ObjectIdentifier) {
self.changes += 1;
if object.object_type() == ObjectType::DEVICE {
self.device_changes += 1;
}
}
#[cfg(test)]
pub(crate) fn len(&self) -> usize {
self.by_use.len()
}
pub(crate) fn learn_name(
&mut self,
device: u32,
address: &str,
object: ObjectIdentifier,
name: String,
) {
self.learn(device, address, object, ReadName::Named(name));
}
pub(crate) fn learn(
&mut self,
device: u32,
address: &str,
object: ObjectIdentifier,
read: ReadName,
) {
if let Some(ask) = self.asked.get_mut(&device).filter(|a| a.address == address) {
ask.objects.remove(&object);
}
let known = self.get(device, address, object);
let name = match read {
ReadName::Named(name) => Name::Known(clean_name(&name)),
ReadName::Refused if !matches!(known, Some(Name::Known(_))) => Name::Unavailable,
ReadName::Unreachable if known.is_none() => Name::Unreachable,
ReadName::Refused | ReadName::Unreachable => return,
};
self.put(device, address, object, name);
}
fn touch(&mut self, device: u32, address: &str, object: ObjectIdentifier) {
self.clock += 1;
let now = self.clock;
let Some(entry) = self
.names
.get_mut(&(device, object))
.and_then(|entries| entries.iter_mut().find(|e| e.address == address))
else {
return;
};
self.by_use.remove(&entry.used);
entry.used = now;
self.by_use.insert(now, (device, object));
}
fn put(&mut self, device: u32, address: &str, object: ObjectIdentifier, name: Name) {
self.clock += 1;
let now = self.clock;
let unreachable = name == Name::Unreachable;
let entries = self.names.entry((device, object)).or_default();
let old = match entries.iter_mut().find(|e| e.address == address) {
Some(entry) => {
self.by_use.remove(&entry.used);
entry.used = now;
Some(std::mem::replace(&mut entry.name, name.clone()))
}
None => {
entries.push(Entry {
address: address.to_string(),
name: name.clone(),
used: now,
});
None
}
};
self.by_use.insert(now, (device, object));
let was_unreachable = old.as_ref() == Some(&Name::Unreachable);
if unreachable && !was_unreachable {
*self.stalled.entry(device).or_default() += 1;
} else if was_unreachable && !unreachable {
self.unstall(device);
}
if matches!(name, Name::Known(_)) || matches!(old, Some(Name::Known(_))) {
self.changed(object);
}
while self.by_use.len() > MAX_NAMES {
self.evict_oldest();
}
}
fn unstall(&mut self, device: u32) {
if let Some(count) = self.stalled.get_mut(&device) {
*count -= 1;
if *count == 0 {
self.stalled.remove(&device);
}
}
}
fn evict_oldest(&mut self) {
let Some((used, key)) = self.by_use.pop_first() else {
return;
};
let Some(entries) = self.names.get_mut(&key) else {
return;
};
let entry = entries
.iter()
.position(|e| e.used == used)
.map(|at| entries.swap_remove(at));
if entries.is_empty() {
self.names.remove(&key);
}
if let Some(entry) = entry {
self.forgotten(key, &entry.name);
}
}
fn forgotten(&mut self, (device, object): (u32, ObjectIdentifier), name: &Name) {
match name {
Name::Unreachable => self.unstall(device),
Name::Known(_) => self.changed(object),
Name::Unavailable => {}
}
}
fn forget_where(&mut self, device: u32, address: &str, drop: impl Fn(&Name) -> bool) -> bool {
let keys: Vec<(u32, ObjectIdentifier)> = self
.names
.keys()
.filter(|(of, _)| *of == device)
.copied()
.collect();
let mut any = false;
for key in keys {
let Some(entries) = self.names.get_mut(&key) else {
continue;
};
let Some(at) = entries
.iter()
.position(|e| e.address == address && drop(&e.name))
else {
continue;
};
let entry = entries.swap_remove(at);
if entries.is_empty() {
self.names.remove(&key);
}
self.by_use.remove(&entry.used);
self.forgotten(key, &entry.name);
any = true;
}
any
}
pub(crate) fn answered(&mut self, device: u32, address: &str) {
if !self.stalled.contains_key(&device) {
return;
}
if self.forget_where(device, address, |name| *name == Name::Unreachable) {
self.asked.entry(device).or_default().retry = true;
}
}
pub(crate) fn forget_failures(&mut self, device: u32, address: &str) {
self.forget_where(device, address, |name| !matches!(name, Name::Known(_)));
}
pub(crate) fn refused(&mut self, req: ReqId) -> bool {
let mut found = false;
for ask in self
.asked
.values_mut()
.filter(|ask| ask.req == req && ask.sent)
{
ask.sent = false;
ask.retry |= ask.retried;
found = true;
}
found
}
#[cfg(test)]
pub(crate) fn asked_devices(&self) -> usize {
self.asked.len()
}
}
#[derive(Clone, Copy)]
pub(crate) struct NameLookup<'a> {
cache: &'a NameCache,
device: u32,
address: &'a str,
}
impl<'a> NameLookup<'a> {
pub(crate) fn new(cache: &'a NameCache, device: u32, address: &'a str) -> Self {
Self {
cache,
device,
address,
}
}
pub(crate) fn name(&self, object: ObjectIdentifier) -> Option<&'a str> {
self.cache.known(self.device, self.address, object)
}
pub(crate) fn changes(&self) -> u64 {
self.cache.changes()
}
}
pub(crate) fn device_object(instance: u32) -> Option<ObjectIdentifier> {
ObjectIdentifier::new(ObjectType::DEVICE, instance).ok()
}
impl App {
pub(crate) fn names_live(&self) -> bool {
matches!(self.link, Link::Up { .. }) && !self.safety.passive
}
pub(crate) fn device_address(&self, instance: u32) -> &str {
self.devices
.get(instance)
.map_or("", |row| row.address.as_str())
}
pub(crate) fn name_of(&self, device: u32, object: ObjectIdentifier) -> Option<&Name> {
self.names.get(device, self.device_address(device), object)
}
fn rows_on_screen(&self) -> Vec<(u32, ObjectIdentifier)> {
let mut rows = Vec::new();
let devices = &self.devices;
if self.screen == Screen::Main && devices.named.get() {
let shown = devices.visible_window(devices.offset.get(), devices.page.get());
rows.extend(
shown.filter_map(|row| device_object(row.instance).map(|o| (row.instance, o))),
);
}
if let Some(browser) = self.shown_browser().filter(|b| b.objects.named.get()) {
let objects = &browser.objects;
let shown = objects.rows().iter().skip(objects.offset.get());
for row in shown.take(objects.page.get()) {
if let ListRow::Object(object) = row {
rows.push((browser.device, *object));
}
}
}
rows
}
pub(super) fn ask_names(&mut self, out: &mut Vec<Command>) {
let rows = if self.names_live() {
self.rows_on_screen()
} else {
Vec::new()
};
let devices = &self.devices;
let names = &mut self.names;
names
.asked
.retain(|_, ask| !(ask.sent && ask.objects.is_empty() && !ask.retry));
let mut wanted: BTreeMap<u32, Vec<ObjectIdentifier>> = BTreeMap::new();
for (device, object) in rows {
let address = devices.get(device).map_or("", |row| row.address.as_str());
if names.get(device, address, object).is_some() {
names.touch(device, address, object);
} else {
wanted.entry(device).or_default().push(object);
}
}
let all: BTreeSet<u32> = names.asked.keys().chain(wanted.keys()).copied().collect();
let mut asks = Vec::new();
for device in all {
let now = wanted.get(&device).map_or(&[][..], Vec::as_slice);
let row = devices.get(device);
let address = row.map_or("", |row| row.address.as_str());
let unchanged = match names.asked.get(&device) {
Some(ask) => {
ask.sent
&& !ask.retry
&& ask.address == address
&& ask.objects.len() == now.len()
&& now.iter().all(|object| ask.objects.contains(object))
}
None => now.is_empty(),
};
if !unchanged {
let retry = names.asked.get(&device).is_some_and(|ask| ask.retry);
asks.push(NameAsk {
device,
address: address.to_string(),
max_apdu: row.map(|row| row.max_apdu),
objects: now.to_vec(),
retry,
});
}
}
if asks.is_empty() {
return;
}
let req = self.next_req();
for ask in &asks {
let entry = Ask {
req,
address: ask.address.clone(),
objects: ask.objects.iter().copied().collect(),
sent: true,
retry: false,
retried: ask.retry,
};
self.names.asked.insert(ask.device, entry);
}
out.push(Command::Names { req, asks });
}
}