use super::{ConnectionHandle, ConnectionScope, GenericSignal, Priority};
use crate::compat::HashMap;
use crate::compat::Mutex;
pub struct CustomSignalHub {
signals: Mutex<HashMap<String, GenericSignal>>,
}
impl CustomSignalHub {
pub fn new() -> Self {
Self { signals: Mutex::new(HashMap::new()) }
}
pub fn define(&self, name: impl Into<String>) {
self.signals.lock().unwrap_or_else(|e| e.into_inner()).entry(name.into()).or_default();
}
pub fn emit(&self, name: &str) {
let signal = {
let signals = self.signals.lock().unwrap_or_else(|e| e.into_inner());
signals.get(name).cloned()
};
if let Some(signal) = signal {
signal.emit();
}
}
pub fn connect<F>(&self, name: impl Into<String>, slot: F) -> ConnectionHandle
where
F: FnMut() + Send + Sync + 'static,
{
self.signals
.lock()
.unwrap_or_else(|e| e.into_inner())
.entry(name.into())
.or_default()
.connect(slot)
}
pub fn disconnect(&self, name: &str, handle: ConnectionHandle) -> bool {
let signals = self.signals.lock().unwrap_or_else(|e| e.into_inner());
if let Some(signal) = signals.get(name) {
signal.disconnect(handle)
} else {
false
}
}
pub fn disconnect_all(&self, name: &str) {
if let Some(signal) = self.signals.lock().unwrap_or_else(|e| e.into_inner()).get(name) {
signal.disconnect_all();
}
}
pub fn remove(&self, name: &str) -> bool {
self.signals.lock().unwrap_or_else(|e| e.into_inner()).remove(name).is_some()
}
pub fn contains(&self, name: &str) -> bool {
self.signals.lock().unwrap_or_else(|e| e.into_inner()).contains_key(name)
}
pub fn signal_count(&self) -> usize {
self.signals.lock().unwrap_or_else(|e| e.into_inner()).len()
}
pub fn is_empty(&self) -> bool {
self.signals.lock().unwrap_or_else(|e| e.into_inner()).is_empty()
}
pub fn clear(&self) {
self.signals.lock().unwrap_or_else(|e| e.into_inner()).clear();
}
pub fn block(&self, name: &str, handle: ConnectionHandle) -> bool {
self.signals
.lock()
.unwrap_or_else(|e| e.into_inner())
.get(name)
.map(|s| s.block(handle))
.unwrap_or(false)
}
pub fn unblock(&self, name: &str, handle: ConnectionHandle) -> bool {
self.signals
.lock()
.unwrap_or_else(|e| e.into_inner())
.get(name)
.map(|s| s.unblock(handle))
.unwrap_or(false)
}
pub fn is_blocked(&self, name: &str, handle: ConnectionHandle) -> Option<bool> {
self.signals
.lock()
.unwrap_or_else(|e| e.into_inner())
.get(name)
.and_then(|s| s.is_blocked(handle))
}
pub fn set_priority(&self, name: &str, handle: ConnectionHandle, priority: Priority) -> bool {
self.signals
.lock()
.unwrap_or_else(|e| e.into_inner())
.get(name)
.map(|s| s.set_priority(handle, priority))
.unwrap_or(false)
}
pub fn connect_once<F>(&self, name: impl Into<String>, slot: F) -> ConnectionHandle
where
F: FnMut() + Send + Sync + 'static,
{
self.signals
.lock()
.unwrap_or_else(|e| e.into_inner())
.entry(name.into())
.or_default()
.connect_once(slot)
}
pub fn connect_with_priority<F>(
&self,
name: impl Into<String>,
slot: F,
priority: Priority,
) -> ConnectionHandle
where
F: FnMut() + Send + Sync + 'static,
{
self.signals
.lock()
.unwrap_or_else(|e| e.into_inner())
.entry(name.into())
.or_default()
.connect_with_priority(slot, priority)
}
pub fn connect_scoped<F>(
&self,
name: impl Into<String>,
owner: &ConnectionScope,
slot: F,
) -> ConnectionHandle
where
F: FnMut() + Send + Sync + 'static,
{
self.signals
.lock()
.unwrap_or_else(|e| e.into_inner())
.entry(name.into())
.or_default()
.connect_scoped(owner, slot)
}
pub fn connect_once_scoped<F>(
&self,
name: impl Into<String>,
owner: &ConnectionScope,
slot: F,
) -> ConnectionHandle
where
F: FnMut() + Send + Sync + 'static,
{
self.signals
.lock()
.unwrap_or_else(|e| e.into_inner())
.entry(name.into())
.or_default()
.connect_once_scoped(owner, slot)
}
pub fn slot_count(&self, name: &str) -> usize {
self.signals
.lock()
.unwrap_or_else(|e| e.into_inner())
.get(name)
.map(|s| s.slot_count())
.unwrap_or(0)
}
}
crate::impl_default_via_new!(CustomSignalHub);
#[cfg(test)]
mod tests {
use super::*;
use alloc::sync::Arc;
use core::sync::atomic::{AtomicUsize, Ordering};
#[test]
fn a_re_entrant_hub_emit_does_not_deadlock() {
let hub = Arc::new(CustomSignalHub::new());
let calls = Arc::new(AtomicUsize::new(0));
let hub_for_reentry = Arc::clone(&hub);
let calls_outer = Arc::clone(&calls);
hub.connect("changed", move || {
calls_outer.fetch_add(1, Ordering::SeqCst);
if calls_outer.load(Ordering::SeqCst) == 1 {
hub_for_reentry.emit("changed");
}
});
hub.emit("changed");
assert_eq!(
calls.load(Ordering::SeqCst),
1,
"the re-entrant emit must skip the slot already on the stack, so it runs once"
);
}
#[test]
fn emitting_an_unknown_name_is_a_no_op() {
let hub = CustomSignalHub::new();
hub.emit("nobody-listens");
assert!(!hub.contains("nobody-listens"));
}
}