Skip to main content

sova_core/
events.rs

1//! In-process application events (`EventBus`).
2
3use std::any::{Any, TypeId};
4use std::collections::HashMap;
5use std::sync::{Arc, Mutex};
6
7/// Typed application event.
8pub trait Event: Send + Sync + 'static {
9    fn name(&self) -> &'static str;
10}
11
12type DynListener = Arc<dyn Fn(&dyn Any) + Send + Sync>;
13
14/// Sync event bus: listeners run in the dispatching task (order of registration).
15#[derive(Clone, Default)]
16pub struct EventBus {
17    inner: Arc<Mutex<HashMap<TypeId, Vec<DynListener>>>>,
18}
19
20impl EventBus {
21    pub fn new() -> Self {
22        Self::default()
23    }
24
25    /// Register a typed listener. Multiple listeners per event type are allowed.
26    pub fn listen<E, F>(&self, f: F)
27    where
28        E: Event,
29        F: Fn(&E) + Send + Sync + 'static,
30    {
31        let mut map = self.inner.lock().expect("EventBus");
32        map.entry(TypeId::of::<E>())
33            .or_default()
34            .push(Arc::new(move |any| {
35                if let Some(e) = any.downcast_ref::<E>() {
36                    f(e);
37                }
38            }));
39    }
40
41    /// Dispatch `event` to all listeners for `E` (registration order).
42    pub fn dispatch<E: Event>(&self, event: E) {
43        let listeners = {
44            let map = self.inner.lock().expect("EventBus");
45            map.get(&TypeId::of::<E>()).cloned().unwrap_or_default()
46        };
47        for listener in &listeners {
48            listener(&event);
49        }
50    }
51
52    /// Number of listeners registered for `E` (tests / diagnostics).
53    pub fn listener_count<E: Event>(&self) -> usize {
54        self.inner
55            .lock()
56            .expect("EventBus")
57            .get(&TypeId::of::<E>())
58            .map(|v| v.len())
59            .unwrap_or(0)
60    }
61}
62
63#[cfg(test)]
64mod tests {
65    use super::*;
66    use std::sync::atomic::{AtomicUsize, Ordering};
67
68    struct Ping;
69    impl Event for Ping {
70        fn name(&self) -> &'static str {
71            "ping"
72        }
73    }
74
75    #[test]
76    fn order_and_multiple() {
77        let bus = EventBus::new();
78        let log = Arc::new(Mutex::new(Vec::new()));
79        let a = Arc::clone(&log);
80        let b = Arc::clone(&log);
81        bus.listen::<Ping, _>(move |_| a.lock().unwrap().push(1));
82        bus.listen::<Ping, _>(move |_| b.lock().unwrap().push(2));
83        assert_eq!(bus.listener_count::<Ping>(), 2);
84        bus.dispatch(Ping);
85        assert_eq!(*log.lock().unwrap(), vec![1, 2]);
86    }
87
88    #[test]
89    fn no_listeners_ok() {
90        let bus = EventBus::new();
91        let n = AtomicUsize::new(0);
92        bus.dispatch(Ping);
93        assert_eq!(n.load(Ordering::SeqCst), 0);
94    }
95}