Skip to main content

santui_core/
event.rs

1use std::collections::VecDeque;
2
3use crate::theme::Theme;
4
5/// Events that can be published through the application's [`EventBus`].
6#[derive(Clone, Debug, PartialEq)]
7pub enum Event {
8    /// The active theme changed (plugins should refresh their colours).
9    ThemeChanged(Theme),
10    /// Plugin-to-plugin message.  `from` and `to` are plugin ids.
11    PluginMessage {
12        from: String,
13        to: String,
14        action: String,
15        data: String,
16    },
17}
18
19/// A read-only observer registered via [`EventBus::subscribe`].
20pub type EventSubscriber = Box<dyn FnMut(&Event) + Send>;
21
22/// A simple in-app event bus for decoupling components.
23///
24/// Components emit events via [`EventBus::emit`] and the main loop drains the
25/// pending queue via [`EventBus::drain`] once per frame, forwarding them to
26/// [`PluginManager::process_events`](crate::app::plugin_manager::PluginManager).
27///
28/// External code can register read-only observers with [`EventBus::subscribe`]
29/// to react to events without consuming them (e.g. event logging, metrics).
30///
31/// The pending queue is capped at [`MAX_PENDING`] entries.  If full, the oldest
32/// event is dropped to make room for the newest, ensuring the bus never grows
33/// without bound.
34pub struct EventBus {
35    pending: VecDeque<Event>,
36    subscribers: Vec<EventSubscriber>,
37}
38
39const MAX_PENDING: usize = 1024;
40
41impl EventBus {
42    pub fn new() -> Self {
43        Self {
44            pending: VecDeque::new(),
45            subscribers: Vec::new(),
46        }
47    }
48
49    /// Register a read-only observer that is called for every emitted event.
50    ///
51    /// Subscribers are invoked synchronously inside [`EventBus::emit`] after the
52    /// event is pushed to the pending queue. They receive a shared reference and
53    /// cannot modify or consume the event.
54    pub fn subscribe(&mut self, f: EventSubscriber) {
55        self.subscribers.push(f);
56    }
57
58    /// Push an event onto the pending queue and notify all subscribers.
59    ///
60    /// If the queue is at capacity the oldest event is dropped.
61    pub fn emit(&mut self, event: Event) {
62        for sub in &mut self.subscribers {
63            sub(&event);
64        }
65        if self.pending.len() >= MAX_PENDING {
66            self.pending.pop_front();
67        }
68        self.pending.push_back(event);
69    }
70
71    /// Drain all pending events.
72    pub fn drain(&mut self) -> Vec<Event> {
73        std::mem::take(&mut self.pending).into_iter().collect()
74    }
75}
76
77impl std::fmt::Debug for EventBus {
78    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
79        f.debug_struct("EventBus")
80            .field("pending", &self.pending)
81            .field(
82                "subscribers",
83                &format_args!("{} subscribers", self.subscribers.len()),
84            )
85            .finish()
86    }
87}
88
89impl Default for EventBus {
90    fn default() -> Self {
91        Self::new()
92    }
93}
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98    use std::sync::Mutex;
99
100    fn msg(from: &str, to: &str) -> Event {
101        Event::PluginMessage {
102            from: from.into(),
103            to: to.into(),
104            action: "test".into(),
105            data: "{}".into(),
106        }
107    }
108
109    #[test]
110    fn new_creates_empty_bus() {
111        let mut bus = EventBus::new();
112        assert!(bus.drain().is_empty());
113    }
114
115    #[test]
116    fn default_creates_empty_bus() {
117        let mut bus = EventBus::default();
118        assert!(bus.drain().is_empty());
119    }
120
121    #[test]
122    fn emit_and_drain_returns_event() {
123        let mut bus = EventBus::new();
124        bus.emit(msg("a", "b"));
125        let drained = bus.drain();
126        assert_eq!(drained.len(), 1);
127        assert_eq!(drained[0], msg("a", "b"));
128    }
129
130    #[test]
131    fn drain_returns_in_order() {
132        let mut bus = EventBus::new();
133        bus.emit(msg("a", "b"));
134        bus.emit(msg("c", "d"));
135        let drained = bus.drain();
136        assert_eq!(drained.len(), 2);
137        assert_eq!(drained[0], msg("a", "b"));
138        assert_eq!(drained[1], msg("c", "d"));
139    }
140
141    #[test]
142    fn drain_clears_queue() {
143        let mut bus = EventBus::new();
144        bus.emit(msg("a", "b"));
145        let first = bus.drain();
146        assert_eq!(first.len(), 1);
147        let second = bus.drain();
148        assert!(second.is_empty());
149    }
150
151    #[test]
152    fn drain_empty_returns_empty() {
153        let mut bus = EventBus::new();
154        assert!(bus.drain().is_empty());
155        assert!(bus.drain().is_empty());
156    }
157
158    #[test]
159    fn subscriber_receives_event() {
160        let mut bus = EventBus::new();
161        let count = std::sync::Arc::new(Mutex::new(0usize));
162        let c = count.clone();
163        bus.subscribe(Box::new(move |_| {
164            *c.lock().unwrap() += 1;
165        }));
166        bus.emit(msg("a", "b"));
167        assert_eq!(*count.lock().unwrap(), 1);
168        bus.emit(msg("c", "d"));
169        assert_eq!(*count.lock().unwrap(), 2);
170    }
171
172    #[test]
173    fn multiple_subscribers_all_called() {
174        let mut bus = EventBus::new();
175        let c1 = std::sync::Arc::new(Mutex::new(0usize));
176        let c2 = std::sync::Arc::new(Mutex::new(0usize));
177        let a1 = c1.clone();
178        let a2 = c2.clone();
179        bus.subscribe(Box::new(move |_| {
180            *a1.lock().unwrap() += 1;
181        }));
182        bus.subscribe(Box::new(move |_| {
183            *a2.lock().unwrap() += 1;
184        }));
185        bus.emit(msg("a", "b"));
186        assert_eq!(*c1.lock().unwrap(), 1);
187        assert_eq!(*c2.lock().unwrap(), 1);
188    }
189
190    #[test]
191    fn subscriber_receives_correct_event() {
192        let mut bus = EventBus::new();
193        let seen = std::sync::Arc::new(Mutex::new(String::new()));
194        let s = seen.clone();
195        bus.subscribe(Box::new(move |e| {
196            if let Event::PluginMessage { from, .. } = e {
197                *s.lock().unwrap() = from.clone();
198            }
199        }));
200        bus.emit(msg("hello", "world"));
201        assert_eq!(*seen.lock().unwrap(), "hello");
202    }
203
204    #[test]
205    fn max_pending_drops_oldest() {
206        let mut bus = EventBus::new();
207        for i in 0..super::MAX_PENDING {
208            bus.emit(msg(&format!("s{i}"), "t"));
209        }
210        bus.emit(msg("last", "t"));
211        let drained = bus.drain();
212        assert_eq!(drained.len(), super::MAX_PENDING);
213        if let Event::PluginMessage { from, .. } = &drained[0] {
214            assert_eq!(from, "s1");
215        } else {
216            panic!("expected PluginMessage");
217        }
218        if let Event::PluginMessage { from, .. } = &drained[drained.len() - 1] {
219            assert_eq!(from, "last");
220        } else {
221            panic!("expected PluginMessage");
222        }
223    }
224
225    #[test]
226    fn subscriber_called_before_queue_drain() {
227        let mut bus = EventBus::new();
228        let flag = std::sync::Arc::new(Mutex::new(false));
229        let f = flag.clone();
230        bus.subscribe(Box::new(move |_| {
231            *f.lock().unwrap() = true;
232        }));
233        bus.emit(msg("a", "b"));
234        assert!(*flag.lock().unwrap());
235    }
236
237    #[test]
238    fn debug_format() {
239        let mut bus = EventBus::new();
240        bus.subscribe(Box::new(|_| {}));
241        let s = format!("{:?}", bus);
242        assert!(s.contains("EventBus"));
243        assert!(s.contains("1 subscriber"));
244    }
245}