use epics_base_rs::types::DbFieldType;
use std::fmt;
use std::net::SocketAddr;
use std::sync::atomic::{AtomicU32, Ordering};
pub(crate) fn alloc_nonzero_probe(
counter: &AtomicU32,
mut is_live: impl FnMut(u32) -> bool,
) -> u32 {
loop {
let v = counter.fetch_add(1, Ordering::Relaxed);
if v == 0 {
tracing::warn!("CA ID allocator wrapped through 2^32; skipping 0");
continue;
}
if is_live(v) {
tracing::warn!(
id = v,
"CA ID allocator wrapped onto a live id; skipping to avoid collision"
);
continue;
}
return v;
}
}
#[derive(Debug, Clone, Copy)]
pub struct AccessRights {
pub read: bool,
pub write: bool,
}
impl AccessRights {
pub fn from_u32(v: u32) -> Self {
Self {
read: v & 1 != 0,
write: v & 2 != 0,
}
}
}
impl fmt::Display for AccessRights {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match (self.read, self.write) {
(true, true) => write!(f, "read/write"),
(true, false) => write!(f, "read-only"),
(false, true) => write!(f, "write-only"),
(false, false) => write!(f, "no access"),
}
}
}
#[derive(Debug)]
pub struct ChannelInfo {
pub pv_name: String,
pub server_addr: SocketAddr,
pub native_type: DbFieldType,
pub element_count: u32,
pub access_rights: AccessRights,
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashSet;
#[test]
fn alloc_nonzero_probe_skips_zero_on_wrap() {
let counter = AtomicU32::new(0);
assert_eq!(alloc_nonzero_probe(&counter, |_| false), 1);
}
#[test]
fn alloc_nonzero_probe_skips_live_ids() {
let counter = AtomicU32::new(5);
let live: HashSet<u32> = [5, 6].into_iter().collect();
assert_eq!(alloc_nonzero_probe(&counter, |v| live.contains(&v)), 7);
}
#[test]
fn alloc_nonzero_probe_returns_free_id_directly() {
let counter = AtomicU32::new(42);
assert_eq!(alloc_nonzero_probe(&counter, |_| false), 42);
assert_eq!(alloc_nonzero_probe(&counter, |_| false), 43);
}
}