use super::{ConnectionHandle, GenericSignal, Signal1};
use crate::compat::Box;
use crate::widget::capability::CapabilityValue;
use alloc::sync::Arc;
pub type EventSlot = dyn FnMut(&CapabilityValue) + Send + Sync;
pub struct EventSignalRef {
name: &'static str,
#[allow(clippy::type_complexity)]
subscribe: Box<dyn Fn(Box<EventSlot>) -> ConnectionHandle + Send + Sync>,
#[allow(clippy::type_complexity)]
disconnect: Box<dyn Fn(ConnectionHandle) -> bool + Send + Sync>,
slot_count: Box<dyn Fn() -> usize + Send + Sync>,
}
impl EventSignalRef {
pub fn unit(name: &'static str, signal: &GenericSignal) -> Self {
let for_subscribe = signal.clone();
let for_count = signal.clone();
let for_disconnect = signal.clone();
Self {
name,
subscribe: Box::new(move |mut slot| {
for_subscribe.connect(move || slot(&CapabilityValue::Null))
}),
disconnect: Box::new(move |handle| for_disconnect.disconnect(handle)),
slot_count: Box::new(move || for_count.slot_count()),
}
}
pub fn mapped<T, F>(name: &'static str, signal: &Signal1<T>, convert: F) -> Self
where
T: Clone + Send + 'static,
F: Fn(&T) -> CapabilityValue + Send + Sync + Clone + 'static,
{
let source = signal.clone();
let for_count = signal.clone();
let for_disconnect = signal.clone();
Self {
name,
subscribe: Box::new(move |mut slot| {
let convert = convert.clone();
source.connect(move |value: Arc<T>| slot(&convert(&value)))
}),
disconnect: Box::new(move |handle| for_disconnect.disconnect(handle)),
slot_count: Box::new(move || for_count.slot_count()),
}
}
pub fn name(&self) -> &'static str {
self.name
}
pub fn subscribe(&self, slot: Box<EventSlot>) -> ConnectionHandle {
(self.subscribe)(slot)
}
pub fn slot_count(&self) -> usize {
(self.slot_count)()
}
pub fn disconnect(&self, handle: ConnectionHandle) -> bool {
(self.disconnect)(handle)
}
}
impl core::fmt::Debug for EventSignalRef {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("EventSignalRef")
.field("name", &self.name)
.field("slots", &self.slot_count())
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::compat::{lock, Mutex, Vec};
use core::sync::atomic::{AtomicUsize, Ordering};
#[test]
fn a_unit_signal_delivers_null() {
let signal = GenericSignal::new();
let reference = EventSignalRef::unit("clicked", &signal);
let seen = Arc::new(Mutex::new(Vec::<CapabilityValue>::new()));
let recorder = Arc::clone(&seen);
let _handle = reference.subscribe(Box::new(move |value| {
lock(&recorder).push(value.clone());
}));
signal.emit();
assert_eq!(*lock(&seen), vec![CapabilityValue::Null]);
}
#[test]
fn a_mapped_signal_delivers_its_converted_payload() {
let signal: Signal1<i32> = Signal1::new();
let reference = EventSignalRef::mapped("value_changed", &signal, |value| {
CapabilityValue::Int(*value as i64)
});
let seen = Arc::new(Mutex::new(Vec::<CapabilityValue>::new()));
let recorder = Arc::clone(&seen);
let _handle = reference.subscribe(Box::new(move |value| {
lock(&recorder).push(value.clone());
}));
signal.emit(42);
assert_eq!(*lock(&seen), vec![CapabilityValue::Int(42)]);
}
#[test]
fn the_slot_count_tracks_subscriptions() {
let signal = GenericSignal::new();
let reference = EventSignalRef::unit("clicked", &signal);
assert_eq!(reference.slot_count(), 0, "a fresh reference has no subscribers");
let handle = reference.subscribe(Box::new(|_| {}));
assert_eq!(reference.slot_count(), 1, "a subscription must be visible in the count");
signal.disconnect(handle);
assert_eq!(reference.slot_count(), 0, "a removed subscription must not be counted");
}
#[test]
fn a_reference_accepts_several_subscriptions() {
let signal = GenericSignal::new();
let reference = EventSignalRef::unit("clicked", &signal);
let calls = Arc::new(AtomicUsize::new(0));
let first = Arc::clone(&calls);
let second = Arc::clone(&calls);
let _a = reference.subscribe(Box::new(move |_| {
first.fetch_add(1, Ordering::SeqCst);
}));
let _b = reference.subscribe(Box::new(move |_| {
second.fetch_add(1, Ordering::SeqCst);
}));
assert_eq!(reference.slot_count(), 2);
signal.emit();
assert_eq!(calls.load(Ordering::SeqCst), 2, "both wires must receive the event");
}
#[test]
fn a_self_disconnecting_subscriber_is_honoured() {
let signal = GenericSignal::new();
let reference = EventSignalRef::unit("clicked", &signal);
let calls = Arc::new(AtomicUsize::new(0));
let counter = Arc::clone(&calls);
let holder = Arc::new(Mutex::new(None::<ConnectionHandle>));
let slot_holder = Arc::clone(&holder);
let slot_signal = signal.clone();
let handle = reference.subscribe(Box::new(move |_value| {
counter.fetch_add(1, Ordering::SeqCst);
if let Some(own) = lock(&slot_holder).take() {
slot_signal.disconnect(own);
}
}));
*lock(&holder) = Some(handle);
signal.emit();
signal.emit();
assert_eq!(
calls.load(Ordering::SeqCst),
1,
"a subscriber that removed itself must not be called again"
);
}
}