use core::fmt;
use crate::{swap_apart, Duration, Instant};
#[derive(Debug, Clone, Copy)]
struct Entry {
network: u16,
solicited: Instant,
}
const NO_ENTRY: Entry = Entry {
network: 0,
solicited: Instant::ZERO,
};
#[derive(Clone)]
pub struct Discovery<const D: usize> {
entries: [Entry; D],
len: usize,
cancelled: bool,
}
impl<const D: usize> fmt::Debug for Discovery<D> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Discovery")
.field("entries", &self.stored())
.field("cancelled", &self.cancelled)
.finish()
}
}
impl<const D: usize> Default for Discovery<D> {
fn default() -> Self {
Self::new()
}
}
impl<const D: usize> Discovery<D> {
#[must_use]
pub const fn new() -> Self {
Self {
entries: [NO_ENTRY; D],
len: 0,
cancelled: false,
}
}
pub fn should_solicit(&mut self, network: u16, now: Instant, cooldown: Duration) -> bool {
if self.cancelled || network == 0 || network == 0xFFFF {
return false;
}
let entry = Entry {
network,
solicited: now,
};
match self.find(network) {
Ok(index) => {
let last = self.entries[index].solicited;
if now.saturating_duration_since(last) < cooldown {
return false;
}
self.entries[index] = entry;
}
Err(_) if self.len == D => return false,
Err(index) => {
self.entries[self.len] = entry;
for at in (index..self.len).rev() {
swap_apart(&mut self.entries, at, at + 1);
}
self.len += 1;
}
}
true
}
pub fn expire(
&mut self,
now: Instant,
max_age: Duration,
mut expired: impl FnMut(u16),
) -> usize {
let old = |entry: &Entry| now.saturating_duration_since(entry.solicited) > max_age;
for entry in self.stored().iter().filter(|entry| old(entry)) {
expired(entry.network);
}
let mut kept = 0;
for index in 0..self.len {
if !old(&self.entries[index]) {
swap_apart(&mut self.entries, kept, index);
kept += 1;
}
}
let removed = self.len - kept;
self.entries[kept..self.len].fill(NO_ENTRY);
self.len = kept;
removed
}
pub(crate) fn clear(&mut self) {
self.entries[..self.len].fill(NO_ENTRY);
self.len = 0;
self.cancelled = false;
}
pub fn cancel(&mut self) {
self.cancelled = true;
self.entries[..self.len].fill(NO_ENTRY);
self.len = 0;
}
#[must_use]
pub const fn is_cancelled(&self) -> bool {
self.cancelled
}
#[must_use]
pub fn last_solicited(&self, network: u16) -> Option<Instant> {
let index = self.find(network).ok()?;
Some(self.entries[index].solicited)
}
#[must_use]
pub const fn len(&self) -> usize {
self.len
}
#[must_use]
pub const fn is_empty(&self) -> bool {
self.len == 0
}
fn stored(&self) -> &[Entry] {
&self.entries[..self.len]
}
fn find(&self, network: u16) -> Result<usize, usize> {
self.stored()
.binary_search_by_key(&network, |entry| entry.network)
}
}