use super::core::SignalName;
use super::signal::Signal;
use parking_lot::RwLock;
use std::any::{Any, TypeId};
use std::collections::HashMap;
pub(crate) struct SignalRegistry {
signals: RwLock<HashMap<(TypeId, String), Box<dyn Any + Send + Sync>>>,
}
impl SignalRegistry {
fn new() -> Self {
Self {
signals: RwLock::new(HashMap::new()),
}
}
pub(crate) fn get_or_create<T: Send + Sync + 'static>(&self, name: SignalName) -> Signal<T> {
let type_id = TypeId::of::<T>();
let key = (type_id, name.as_str().to_string());
let signals = self.signals.upgradable_read();
if let Some(signal_any) = signals.get(&key)
&& let Some(signal) = signal_any.downcast_ref::<Signal<T>>()
{
return signal.clone();
}
let signal = Signal::new(name);
let mut signals = parking_lot::RwLockUpgradableReadGuard::upgrade(signals);
signals.insert(key, Box::new(signal.clone()));
signal
}
#[doc(hidden)]
pub(crate) fn get_or_create_with_string<T: Send + Sync + 'static>(
&self,
name: impl Into<String>,
) -> Signal<T> {
let name_str = name.into();
let type_id = TypeId::of::<T>();
let key = (type_id, name_str.clone());
let signals = self.signals.upgradable_read();
if let Some(signal_any) = signals.get(&key)
&& let Some(signal) = signal_any.downcast_ref::<Signal<T>>()
{
return signal.clone();
}
let signal = Signal::new(SignalName::from_string(name_str.clone()));
let mut signals = parking_lot::RwLockUpgradableReadGuard::upgrade(signals);
signals.insert(key, Box::new(signal.clone()));
signal
}
}
static GLOBAL_REGISTRY: once_cell::sync::Lazy<SignalRegistry> =
once_cell::sync::Lazy::new(SignalRegistry::new);
pub fn get_signal<T: Send + Sync + 'static>(name: SignalName) -> Signal<T> {
GLOBAL_REGISTRY.get_or_create(name)
}
#[doc(hidden)]
pub fn get_signal_with_string<T: Send + Sync + 'static>(name: impl Into<String>) -> Signal<T> {
GLOBAL_REGISTRY.get_or_create_with_string(name)
}
#[cfg(test)]
mod tests {
use super::*;
use rstest::rstest;
#[rstest]
fn test_registry_get_or_create_with_static_name() {
let registry = SignalRegistry::new();
let signal1 = registry.get_or_create::<String>(SignalName::PRE_SAVE);
let signal2 = registry.get_or_create::<String>(SignalName::PRE_SAVE);
assert_eq!(signal1.receiver_count(), signal2.receiver_count());
}
#[rstest]
fn test_registry_get_or_create_with_string_no_leak() {
let registry = SignalRegistry::new();
for i in 0..100 {
let name = format!("dynamic_signal_{}", i);
let _signal = registry.get_or_create_with_string::<String>(name);
}
let signals = registry.signals.read();
let string_signal_count = signals
.keys()
.filter(|(_, name)| name.starts_with("dynamic_signal_"))
.count();
assert_eq!(string_signal_count, 100);
}
#[rstest]
fn test_registry_get_or_create_with_string_deduplication() {
let registry = SignalRegistry::new();
let signal1 = registry.get_or_create_with_string::<String>("dedup_test");
let signal2 = registry.get_or_create_with_string::<String>("dedup_test");
assert_eq!(signal1.receiver_count(), 0);
assert_eq!(signal2.receiver_count(), 0);
}
#[rstest]
fn test_registry_from_string_creates_arc_based_signal_name() {
let dynamic_name = format!("arc_signal_{}", 42);
let name = SignalName::from_string(dynamic_name);
assert_eq!(name.as_str(), "arc_signal_42");
}
}