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,
pub trace: Arc<TraceManager>,
}
#[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,
trace: Arc<TraceManager>,
) -> 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) => {
slot.insert(PortEntry { handle, trace });
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, trace: Arc<TraceManager>) -> AsynResult<()> {
global_registry().register(name, handle, trace)
}
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);
}
pub fn asyn_record_factory() -> (&'static str, epics_base_rs::server::RecordFactory) {
("asyn", Box::new(|| Box::new(super::AsynRecord::default())))
}
pub fn register_asyn_record_type() {
epics_base_rs::server::db_loader::register_record_type(
"asyn",
Box::new(|| Box::new(super::AsynRecord::default())),
);
}
#[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(),
)
}
#[test]
fn register_rejects_duplicate_name() {
let reg = PortRegistry::new();
reg.register(
"regdup",
dummy_handle("regdup"),
Arc::new(TraceManager::new()),
)
.unwrap();
match reg.register(
"regdup",
dummy_handle("regdup"),
Arc::new(TraceManager::new()),
) {
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"),
Arc::new(TraceManager::new()),
)
.unwrap();
reg.remove("regrecycle");
assert!(
reg.register(
"regrecycle",
dummy_handle("regrecycle"),
Arc::new(TraceManager::new())
)
.is_ok(),
"re-register after remove must succeed"
);
}
}