use std::collections::HashMap;
use std::collections::hash_map::Entry;
use std::sync::{Arc, Mutex, OnceLock};
use crate::error::{AsynError, AsynResult};
use crate::port_handle::PortHandle;
use crate::trace::TraceManager;
#[derive(Clone)]
pub struct PortEntry {
pub handle: PortHandle,
}
#[derive(Clone)]
pub struct PortRegistry {
inner: Arc<Mutex<HashMap<String, PortEntry>>>,
}
impl Default for PortRegistry {
fn default() -> Self {
Self::new()
}
}
impl PortRegistry {
pub fn new() -> Self {
Self {
inner: Arc::new(Mutex::new(HashMap::new())),
}
}
pub fn register(&self, name: &str, handle: PortHandle) -> AsynResult<()> {
let mut reg = self.inner.lock().unwrap();
match reg.entry(name.to_string()) {
Entry::Occupied(_) => Err(AsynError::PortAlreadyRegistered(name.to_string())),
Entry::Vacant(slot) => {
handle.trace().register_port(name, handle.is_multi_device());
slot.insert(PortEntry { handle });
Ok(())
}
}
}
pub fn get(&self, name: &str) -> Option<PortEntry> {
let reg = self.inner.lock().ok()?;
reg.get(name).cloned()
}
pub fn names(&self) -> Vec<String> {
match self.inner.lock() {
Ok(reg) => reg.keys().cloned().collect(),
Err(_) => Vec::new(),
}
}
pub fn remove(&self, name: &str) {
if let Ok(mut reg) = self.inner.lock() {
reg.remove(name);
}
}
}
static GLOBAL_PORT_REGISTRY: OnceLock<PortRegistry> = OnceLock::new();
fn global_registry() -> &'static PortRegistry {
GLOBAL_PORT_REGISTRY.get_or_init(PortRegistry::new)
}
pub fn register_port(name: &str, handle: PortHandle) -> AsynResult<()> {
global_registry().register(name, handle)?;
arm_boot_flush();
Ok(())
}
fn arm_boot_flush() {
static ARMED: OnceLock<()> = OnceLock::new();
ARMED.get_or_init(|| {
epics_libcom_rs::runtime::interrupt_accept::on_interrupts_accepted(|| {
let _ = std::thread::Builder::new()
.name("asyn-boot-flush".into())
.spawn(flush_all_ports_once);
});
});
}
fn flush_all_ports_once() {
for name in port_names() {
let Some(entry) = get_port(&name) else {
continue;
};
for addr in 0..entry.handle.max_addr().max(1) {
let _ = entry.handle.call_param_callbacks_blocking(addr);
}
}
}
pub fn get_port(name: &str) -> Option<PortEntry> {
global_registry().get(name)
}
pub fn port_names() -> Vec<String> {
global_registry().names()
}
pub fn unregister_port(name: &str) {
global_registry().remove(name);
}
#[cfg(test)]
mod tests {
use super::*;
use crate::interrupt::InterruptManager;
fn dummy_handle(name: &str) -> PortHandle {
let (tx, _rx) = tokio::sync::mpsc::channel(1);
PortHandle::new(
tx,
name.to_string(),
Arc::new(InterruptManager::new(4)),
crate::port_actor::ActorId::new(),
Arc::new(TraceManager::new()),
)
}
#[test]
fn register_rejects_duplicate_name() {
let reg = PortRegistry::new();
reg.register("regdup", dummy_handle("regdup")).unwrap();
match reg.register("regdup", dummy_handle("regdup")) {
Err(AsynError::PortAlreadyRegistered(name)) => assert_eq!(name, "regdup"),
other => panic!("expected PortAlreadyRegistered, got {other:?}"),
}
assert!(reg.get("regdup").is_some());
}
#[test]
fn removed_name_can_be_reregistered() {
let reg = PortRegistry::new();
reg.register("regrecycle", dummy_handle("regrecycle"))
.unwrap();
reg.remove("regrecycle");
assert!(
reg.register("regrecycle", dummy_handle("regrecycle"))
.is_ok(),
"re-register after remove must succeed"
);
}
}