1#[allow(dead_code)]
10#[derive(Debug, Clone, PartialEq)]
11pub enum EventKind {
12 TargetLoaded,
13 TargetUnloaded,
14 ParamChanged,
15 ExportStarted,
16 ExportFinished,
17 PluginRegistered,
18 Error,
19 Custom(String),
20}
21
22#[allow(dead_code)]
24#[derive(Debug, Clone)]
25pub struct Event {
26 pub kind: EventKind,
27 pub payload: String,
29 pub timestamp_ms: u64,
31}
32
33pub type EventHandler = Box<dyn Fn(&Event) + Send + Sync>;
35
36pub struct EventBus {
39 handlers: Vec<(EventKind, EventHandler)>,
40 history: Vec<Event>,
41}
42
43impl EventBus {
46 #[allow(dead_code)]
48 pub fn new() -> Self {
49 Self {
50 handlers: Vec::new(),
51 history: Vec::new(),
52 }
53 }
54
55 #[allow(dead_code)]
57 pub fn subscribe(&mut self, kind: EventKind, handler: EventHandler) {
58 self.handlers.push((kind, handler));
59 }
60
61 #[allow(dead_code)]
63 pub fn publish(&mut self, event: Event) {
64 for (kind, handler) in &self.handlers {
65 if *kind == event.kind {
66 handler(&event);
67 }
68 }
69 self.history.push(event);
70 }
71
72 #[allow(dead_code)]
74 pub fn history(&self) -> &[Event] {
75 &self.history
76 }
77
78 #[allow(dead_code)]
80 pub fn clear_history(&mut self) {
81 self.history.clear();
82 }
83
84 #[allow(dead_code)]
86 pub fn handler_count(&self) -> usize {
87 self.handlers.len()
88 }
89
90 #[allow(dead_code)]
92 pub fn event_count(&self) -> usize {
93 self.history.len()
94 }
95}
96
97impl Default for EventBus {
98 fn default() -> Self {
99 Self::new()
100 }
101}
102
103#[allow(dead_code)]
107pub fn make_param_changed_event(name: &str, value: f32) -> Event {
108 Event {
109 kind: EventKind::ParamChanged,
110 payload: format!(r#"{{"name":"{name}","value":{value}}}"#),
111 timestamp_ms: 0,
112 }
113}
114
115#[allow(dead_code)]
117pub fn make_export_event(path: &str, format: &str) -> Event {
118 Event {
119 kind: EventKind::ExportStarted,
120 payload: format!(r#"{{"path":"{path}","format":"{format}"}}"#),
121 timestamp_ms: 0,
122 }
123}
124
125#[allow(dead_code)]
127pub fn make_error_event(msg: &str) -> Event {
128 Event {
129 kind: EventKind::Error,
130 payload: format!(r#"{{"message":"{msg}"}}"#),
131 timestamp_ms: 0,
132 }
133}
134
135#[cfg(test)]
138mod tests {
139 use super::*;
140 use std::sync::{Arc, Mutex};
141
142 fn ts() -> u64 {
143 0
144 }
145
146 #[test]
147 fn test_subscribe_and_publish_triggers_handler() {
148 let counter = Arc::new(Mutex::new(0u32));
149 let c = Arc::clone(&counter);
150 let mut bus = EventBus::new();
151 bus.subscribe(
152 EventKind::TargetLoaded,
153 Box::new(move |_| {
154 *c.lock().expect("should succeed") += 1;
155 }),
156 );
157 bus.publish(Event {
158 kind: EventKind::TargetLoaded,
159 payload: "null".to_string(),
160 timestamp_ms: ts(),
161 });
162 assert_eq!(*counter.lock().expect("should succeed"), 1);
163 }
164
165 #[test]
166 fn test_wrong_kind_does_not_trigger() {
167 let counter = Arc::new(Mutex::new(0u32));
168 let c = Arc::clone(&counter);
169 let mut bus = EventBus::new();
170 bus.subscribe(
171 EventKind::ExportFinished,
172 Box::new(move |_| {
173 *c.lock().expect("should succeed") += 1;
174 }),
175 );
176 bus.publish(Event {
177 kind: EventKind::TargetLoaded,
178 payload: "null".to_string(),
179 timestamp_ms: ts(),
180 });
181 assert_eq!(*counter.lock().expect("should succeed"), 0);
182 }
183
184 #[test]
185 fn test_history_grows() {
186 let mut bus = EventBus::new();
187 for i in 0..5 {
188 bus.publish(Event {
189 kind: EventKind::ParamChanged,
190 payload: format!("{i}"),
191 timestamp_ms: ts(),
192 });
193 }
194 assert_eq!(bus.history().len(), 5);
195 }
196
197 #[test]
198 fn test_clear_history_empties() {
199 let mut bus = EventBus::new();
200 bus.publish(Event {
201 kind: EventKind::Error,
202 payload: "null".to_string(),
203 timestamp_ms: ts(),
204 });
205 assert!(!bus.history().is_empty());
206 bus.clear_history();
207 assert!(bus.history().is_empty());
208 }
209
210 #[test]
211 fn test_handler_count() {
212 let mut bus = EventBus::new();
213 bus.subscribe(EventKind::TargetLoaded, Box::new(|_| {}));
214 bus.subscribe(EventKind::TargetUnloaded, Box::new(|_| {}));
215 assert_eq!(bus.handler_count(), 2);
216 }
217
218 #[test]
219 fn test_event_count_matches_published() {
220 let mut bus = EventBus::new();
221 bus.publish(Event {
222 kind: EventKind::ExportStarted,
223 payload: "null".to_string(),
224 timestamp_ms: ts(),
225 });
226 bus.publish(Event {
227 kind: EventKind::ExportFinished,
228 payload: "null".to_string(),
229 timestamp_ms: ts(),
230 });
231 assert_eq!(bus.event_count(), 2);
232 }
233
234 #[test]
235 fn test_multiple_handlers_same_kind() {
236 let counter = Arc::new(Mutex::new(0u32));
237 let c1 = Arc::clone(&counter);
238 let c2 = Arc::clone(&counter);
239 let mut bus = EventBus::new();
240 bus.subscribe(
241 EventKind::Error,
242 Box::new(move |_| *c1.lock().expect("should succeed") += 1),
243 );
244 bus.subscribe(
245 EventKind::Error,
246 Box::new(move |_| *c2.lock().expect("should succeed") += 1),
247 );
248 bus.publish(Event {
249 kind: EventKind::Error,
250 payload: "null".to_string(),
251 timestamp_ms: ts(),
252 });
253 assert_eq!(*counter.lock().expect("should succeed"), 2);
254 }
255
256 #[test]
257 fn test_custom_variant_matching() {
258 let counter = Arc::new(Mutex::new(0u32));
259 let c = Arc::clone(&counter);
260 let mut bus = EventBus::new();
261 bus.subscribe(
262 EventKind::Custom("my_event".to_string()),
263 Box::new(move |_| *c.lock().expect("should succeed") += 1),
264 );
265 bus.publish(Event {
266 kind: EventKind::Custom("my_event".to_string()),
267 payload: "null".to_string(),
268 timestamp_ms: ts(),
269 });
270 bus.publish(Event {
271 kind: EventKind::Custom("other".to_string()),
272 payload: "null".to_string(),
273 timestamp_ms: ts(),
274 });
275 assert_eq!(*counter.lock().expect("should succeed"), 1);
276 }
277
278 #[test]
279 fn test_make_param_changed_event_kind() {
280 let ev = make_param_changed_event("height", 1.75);
281 assert_eq!(ev.kind, EventKind::ParamChanged);
282 assert!(ev.payload.contains("height"));
283 }
284
285 #[test]
286 fn test_make_export_event_kind() {
287 let ev = make_export_event("/tmp/out.glb", "glb");
288 assert_eq!(ev.kind, EventKind::ExportStarted);
289 assert!(ev.payload.contains("glb"));
290 }
291
292 #[test]
293 fn test_make_error_event_kind() {
294 let ev = make_error_event("something went wrong");
295 assert_eq!(ev.kind, EventKind::Error);
296 assert!(ev.payload.contains("went wrong"));
297 }
298
299 #[test]
300 fn test_new_bus_empty() {
301 let bus = EventBus::new();
302 assert_eq!(bus.handler_count(), 0);
303 assert_eq!(bus.event_count(), 0);
304 }
305
306 #[test]
307 fn test_plugin_registered_event() {
308 let mut bus = EventBus::new();
309 let hit = Arc::new(Mutex::new(false));
310 let h = Arc::clone(&hit);
311 bus.subscribe(
312 EventKind::PluginRegistered,
313 Box::new(move |_| *h.lock().expect("should succeed") = true),
314 );
315 bus.publish(Event {
316 kind: EventKind::PluginRegistered,
317 payload: r#"{"name":"my-plugin"}"#.to_string(),
318 timestamp_ms: ts(),
319 });
320 assert!(*hit.lock().expect("should succeed"));
321 }
322
323 #[test]
324 fn test_history_payload_preserved() {
325 let mut bus = EventBus::new();
326 bus.publish(Event {
327 kind: EventKind::ParamChanged,
328 payload: r#"{"x":42}"#.to_string(),
329 timestamp_ms: 100,
330 });
331 assert_eq!(bus.history()[0].payload, r#"{"x":42}"#);
332 assert_eq!(bus.history()[0].timestamp_ms, 100);
333 }
334}