1use std::collections::HashMap;
10use std::sync::{Arc, Mutex};
11
12use serde::{Deserialize, Serialize};
13
14use super::mps::{MuxEvent, WpsClient, EVENT_SYS_INFO, EVENT_BLOCK_STATS};
15
16pub const WS_EVENT_RPC: &str = "rpc";
19
20pub const WS_EVENT_MUX_OBJ_BATCHED_UPDATES: &str = "waveobj:batchedupdates";
25
26#[derive(Clone, Copy, Debug, PartialEq, Eq)]
34pub enum Lane {
35 Priority,
36 Background,
37}
38
39const PRIORITY_LANE_CAPACITY: usize = 8192;
49const BACKGROUND_LANE_CAPACITY: usize = 256;
53
54pub struct WsReceivers {
58 pub priority: tokio::sync::mpsc::Receiver<serde_json::Value>,
59 pub background: tokio::sync::mpsc::Receiver<serde_json::Value>,
60}
61
62#[derive(Debug, Clone, Serialize, Deserialize)]
65pub struct WSEventType {
66 pub eventtype: String,
67 #[serde(skip_serializing_if = "String::is_empty", default)]
68 pub oref: String,
69 #[serde(skip_serializing_if = "Option::is_none")]
70 pub data: Option<serde_json::Value>,
71}
72
73struct WindowWatchData {
74 priority: tokio::sync::mpsc::Sender<serde_json::Value>,
76 background: tokio::sync::mpsc::Sender<serde_json::Value>,
78 #[allow(dead_code)]
79 tab_id: String,
80}
81
82impl WindowWatchData {
83 fn sender(&self, lane: Lane) -> &tokio::sync::mpsc::Sender<serde_json::Value> {
84 match lane {
85 Lane::Priority => &self.priority,
86 Lane::Background => &self.background,
87 }
88 }
89}
90
91pub struct EventBus {
93 watches: Mutex<HashMap<String, WindowWatchData>>,
94}
95
96impl EventBus {
97 pub fn new() -> Self {
98 Self {
99 watches: Mutex::new(HashMap::new()),
100 }
101 }
102
103 pub fn register_ws(&self, conn_id: &str, tab_id: &str) -> WsReceivers {
106 let (priority_tx, priority_rx) = tokio::sync::mpsc::channel(PRIORITY_LANE_CAPACITY);
107 let (background_tx, background_rx) = tokio::sync::mpsc::channel(BACKGROUND_LANE_CAPACITY);
108 let mut watches = self.watches.lock().unwrap();
109 watches.insert(
110 conn_id.to_string(),
111 WindowWatchData {
112 priority: priority_tx,
113 background: background_tx,
114 tab_id: tab_id.to_string(),
115 },
116 );
117 WsReceivers {
118 priority: priority_rx,
119 background: background_rx,
120 }
121 }
122
123 pub fn unregister_ws(&self, conn_id: &str) {
125 let mut watches = self.watches.lock().unwrap();
126 watches.remove(conn_id);
127 }
128
129 #[allow(dead_code)]
131 pub fn has_connections_for(&self, tab_id: &str) -> bool {
132 let watches = self.watches.lock().unwrap();
133 watches.values().any(|w| w.tab_id == tab_id)
134 }
135
136 #[allow(dead_code)]
138 pub async fn wait_for_connection(
139 &self,
140 tab_id: &str,
141 timeout: std::time::Duration,
142 ) -> bool {
143 let deadline = tokio::time::Instant::now() + timeout;
144 loop {
145 if self.has_connections_for(tab_id) {
146 return true;
147 }
148 if tokio::time::Instant::now() >= deadline {
149 return false;
150 }
151 tokio::time::sleep(std::time::Duration::from_millis(20)).await;
152 }
153 }
154
155 #[cfg(test)]
158 pub fn send_to_conn_lane(&self, conn_id: &str, event: &WSEventType, lane: Lane) {
159 let data = match serde_json::to_value(event) {
160 Ok(v) => v,
161 Err(e) => {
162 tracing::error!("cannot marshal event: {}", e);
163 return;
164 }
165 };
166 self.send_value_to_conn_lane(conn_id, data, lane);
167 }
168
169 pub fn send_value_to_conn_lane(&self, conn_id: &str, data: serde_json::Value, lane: Lane) {
173 let watches = self.watches.lock().unwrap();
174 if let Some(watch) = watches.get(conn_id) {
175 Self::try_send_lane(conn_id, watch.sender(lane), data);
176 }
177 }
178
179 pub fn broadcast_event(&self, event: &WSEventType) {
181 self.broadcast_event_lane(event, Lane::Priority);
182 }
183
184 pub fn typed_ws_event<E: super::mps_events::Event>(payload: &E::Payload, oref: impl Into<String>) -> WSEventType {
188 WSEventType {
190 eventtype: E::NAME.to_string(),
191 oref: oref.into(),
192 data: super::mps_events::payload_data(payload),
193 }
194 }
195
196 pub fn typed_frame<E: super::mps_events::Event>(payload: &E::Payload) -> serde_json::Value {
199 serde_json::json!({
200 "eventtype": WS_EVENT_RPC,
201 "data": { "command": "eventrecv", "data": { "event": E::NAME, "data": super::mps_events::payload_data(payload) } },
203 })
204 }
205
206 pub fn broadcast_typed<E: super::mps_events::Event>(&self, payload: &E::Payload) {
210 let event = MuxEvent {
212 event: E::NAME.to_string(),
213 scopes: vec![],
214 sender: String::new(),
215 persist: 0,
216 data: super::mps_events::payload_data(payload),
217 };
218 self.broadcast_event(&WSEventType {
219 eventtype: WS_EVENT_RPC.to_string(),
220 oref: String::new(),
221 data: Some(serde_json::json!({ "command": "eventrecv", "data": event })),
223 });
224 }
225
226 pub fn broadcast_mux_obj_updates(&self, updates: &[super::obj::MuxObjUpdate]) {
242 if updates.is_empty() {
243 return;
244 }
245 let batch = super::obj::MuxObjBatch(updates.to_vec());
246 self.broadcast_event(&Self::typed_ws_event::<super::mps_events::MuxObjBatchedUpdates>(&batch, String::new()));
247 }
248
249 pub fn broadcast_event_lane(&self, event: &WSEventType, lane: Lane) {
251 let data = match serde_json::to_value(event) {
252 Ok(v) => v,
253 Err(e) => {
254 tracing::error!("cannot marshal event: {}", e);
255 return;
256 }
257 };
258 self.broadcast_value_lane(data, lane);
259 }
260
261 pub fn broadcast_value_lane(&self, data: serde_json::Value, lane: Lane) {
263 let watches = self.watches.lock().unwrap();
264 for (conn_id, watch) in watches.iter() {
265 Self::try_send_lane(conn_id, watch.sender(lane), data.clone());
266 }
267 }
268
269 fn try_send_lane(
274 conn_id: &str,
275 sender: &tokio::sync::mpsc::Sender<serde_json::Value>,
276 data: serde_json::Value,
277 ) {
278 match sender.try_send(data) {
279 Ok(()) => {}
280 Err(tokio::sync::mpsc::error::TrySendError::Full(_)) => {
281 tracing::warn!(
282 "ws egress lane full for conn {} — consumer stalled, dropping event",
283 conn_id
284 );
285 }
286 Err(tokio::sync::mpsc::error::TrySendError::Closed(_)) => {
287 tracing::warn!("failed to send event to conn {}", conn_id);
288 }
289 }
290 }
291
292 #[allow(dead_code)]
294 pub fn send_to_tab(&self, tab_id: &str, event: &WSEventType) {
295 let data = match serde_json::to_value(event) {
296 Ok(v) => v,
297 Err(e) => {
298 tracing::error!("cannot marshal event: {}", e);
299 return;
300 }
301 };
302 let watches = self.watches.lock().unwrap();
303 for (conn_id, watch) in watches.iter() {
304 if watch.tab_id == tab_id {
305 Self::try_send_lane(conn_id, watch.sender(Lane::Priority), data.clone());
306 }
307 }
308 }
309
310 pub fn connection_count(&self) -> usize {
312 self.watches.lock().unwrap().len()
313 }
314}
315
316impl Default for EventBus {
317 fn default() -> Self {
318 Self::new()
319 }
320}
321
322pub struct EventBusBridge {
325 event_bus: Arc<EventBus>,
326}
327
328impl EventBusBridge {
329 pub fn new(event_bus: Arc<EventBus>) -> Self {
330 Self { event_bus }
331 }
332}
333
334impl WpsClient for EventBusBridge {
335 fn send_event(&self, route_id: &str, event: MuxEvent) {
336 let lane = match event.event.as_str() {
342 EVENT_SYS_INFO | EVENT_BLOCK_STATS => Lane::Background,
343 _ => Lane::Priority,
344 };
345 let Some(data) = rpc_eventrecv_value(&event) else {
354 return;
355 };
356 if route_id == "ws-main" {
360 self.event_bus.broadcast_value_lane(data, lane);
361 } else {
362 self.event_bus.send_value_to_conn_lane(route_id, data, lane);
363 }
364 }
365}
366
367fn rpc_eventrecv_value(event: &MuxEvent) -> Option<serde_json::Value> {
370 let payload = match serde_json::to_value(event) {
371 Ok(v) => v,
372 Err(e) => {
373 tracing::error!("cannot marshal event: {}", e);
374 return None;
375 }
376 };
377 let mut inner = serde_json::Map::new();
378 inner.insert("command".to_string(), serde_json::Value::String("eventrecv".to_string()));
379 inner.insert("data".to_string(), payload);
380 let mut outer = serde_json::Map::new();
381 outer.insert("eventtype".to_string(), serde_json::Value::String(WS_EVENT_RPC.to_string()));
382 outer.insert("data".to_string(), serde_json::Value::Object(inner));
383 Some(serde_json::Value::Object(outer))
384}
385
386#[cfg(test)]
391mod tests {
392 use super::*;
393 use crate::backend::mps::EVENT_BLOCK_FILE;
394
395 #[test]
396 fn test_register_unregister() {
397 let bus = EventBus::new();
398 let _rx = bus.register_ws("conn-1", "tab-1");
399 assert_eq!(bus.connection_count(), 1);
400 assert!(bus.has_connections_for("tab-1"));
401 assert!(!bus.has_connections_for("tab-2"));
402
403 bus.unregister_ws("conn-1");
404 assert_eq!(bus.connection_count(), 0);
405 assert!(!bus.has_connections_for("tab-1"));
406 }
407
408 #[test]
409 fn test_broadcast_event() {
410 let bus = EventBus::new();
411 let mut rx1 = bus.register_ws("conn-1", "tab-1");
412 let mut rx2 = bus.register_ws("conn-2", "tab-2");
413
414 let event = WSEventType {
415 eventtype: WS_EVENT_RPC.to_string(),
416 oref: String::new(),
417 data: Some(serde_json::json!({"test": true})),
418 };
419 bus.broadcast_event(&event);
420
421 assert!(rx1.priority.try_recv().is_ok());
423 assert!(rx2.priority.try_recv().is_ok());
424 assert!(rx1.background.try_recv().is_err());
425 }
426
427 #[test]
428 fn test_send_to_tab() {
429 let bus = EventBus::new();
430 let mut rx1 = bus.register_ws("conn-1", "tab-1");
431 let mut rx2 = bus.register_ws("conn-2", "tab-2");
432
433 let event = WSEventType {
434 eventtype: WS_EVENT_RPC.to_string(),
435 oref: String::new(),
436 data: None,
437 };
438 bus.send_to_tab("tab-1", &event);
439
440 assert!(rx1.priority.try_recv().is_ok());
441 assert!(rx2.priority.try_recv().is_err()); }
443
444 #[test]
449 fn test_broadcast_mux_obj_updates_single_batched_frame() {
450 use crate::backend::obj::MuxObjUpdate;
451
452 let bus = EventBus::new();
453 let mut rx = bus.register_ws("conn-1", "tab-1");
454
455 let updates = vec![
456 MuxObjUpdate {
457 updatetype: "update".into(),
458 otype: "workspace".into(),
459 oid: "ws-1".into(),
460 obj: Some(serde_json::json!({"oid": "ws-1"})),
461 },
462 MuxObjUpdate {
463 updatetype: "delete".into(),
464 otype: "tab".into(),
465 oid: "tab-9".into(),
466 obj: None,
467 },
468 ];
469 bus.broadcast_mux_obj_updates(&updates);
470
471 let msg = rx.priority.try_recv().expect("one frame expected");
472 assert_eq!(
473 msg.get("eventtype").and_then(|v| v.as_str()),
474 Some(WS_EVENT_MUX_OBJ_BATCHED_UPDATES),
475 );
476 let arr = msg.get("data").and_then(|d| d.as_array()).expect("array payload");
477 assert_eq!(arr.len(), 2);
478 assert_eq!(arr[0].get("otype").and_then(|v| v.as_str()), Some("workspace"));
479 assert_eq!(arr[1].get("updatetype").and_then(|v| v.as_str()), Some("delete"));
480 assert!(rx.priority.try_recv().is_err());
482
483 bus.broadcast_mux_obj_updates(&[]);
485 assert!(rx.priority.try_recv().is_err());
486 }
487
488 #[test]
489 fn test_lane_separation() {
490 let bus = EventBus::new();
494 let mut rx = bus.register_ws("conn-1", "tab-1");
495
496 let event = WSEventType {
497 eventtype: WS_EVENT_RPC.to_string(),
498 oref: String::new(),
499 data: None,
500 };
501 bus.send_to_conn_lane("conn-1", &event, Lane::Background);
502 assert!(rx.priority.try_recv().is_err()); assert!(rx.background.try_recv().is_ok()); bus.send_to_conn_lane("conn-1", &event, Lane::Priority);
506 assert!(rx.background.try_recv().is_err()); assert!(rx.priority.try_recv().is_ok()); }
509
510 #[test]
511 fn test_stalled_consumer_drops_instead_of_growing_unbounded() {
512 let bus = EventBus::new();
519 let mut rx = bus.register_ws("conn-1", "tab-1"); let event = WSEventType {
521 eventtype: WS_EVENT_RPC.to_string(),
522 oref: String::new(),
523 data: None,
524 };
525 let sent = BACKGROUND_LANE_CAPACITY * 4;
526 for _ in 0..sent {
527 bus.send_to_conn_lane("conn-1", &event, Lane::Background);
528 }
529 let mut received = 0;
530 while rx.background.try_recv().is_ok() {
531 received += 1;
532 }
533 assert!(
534 received <= BACKGROUND_LANE_CAPACITY,
535 "received {received} events from a lane capped at {BACKGROUND_LANE_CAPACITY} — sends were not bounded"
536 );
537 assert!(received < sent, "no events were dropped despite flooding well past capacity");
538 }
539
540 #[test]
541 fn test_bridge_routes_telemetry_to_background() {
542 let bus = Arc::new(EventBus::new());
545 let mut rx = bus.register_ws("conn-1", "tab-1");
546 let bridge = EventBusBridge::new(bus.clone());
547
548 let telemetry = |event: &str| MuxEvent {
549 event: event.to_string(),
550 scopes: vec![],
551 sender: String::new(),
552 persist: 0,
553 data: None,
554 };
555
556 bridge.send_event("conn-1", telemetry(EVENT_SYS_INFO));
557 bridge.send_event("conn-1", telemetry(EVENT_BLOCK_STATS));
558 assert!(rx.priority.try_recv().is_err()); assert!(rx.background.try_recv().is_ok()); assert!(rx.background.try_recv().is_ok()); bridge.send_event("conn-1", telemetry(EVENT_BLOCK_FILE));
564 assert!(rx.priority.try_recv().is_ok());
565 assert!(rx.background.try_recv().is_err());
566 }
567
568 #[test]
569 fn test_ws_event_serialization() {
570 let event = WSEventType {
571 eventtype: "test".to_string(),
572 oref: String::new(),
573 data: Some(serde_json::json!(42)),
574 };
575 let json = serde_json::to_string(&event).unwrap();
576 assert!(json.contains("\"eventtype\":\"test\""));
577 assert!(!json.contains("\"oref\""));
579 }
580
581 #[test]
584 fn rpc_eventrecv_value_matches_wsevent_shape() {
585 let event = MuxEvent {
586 event: "blockfile".to_string(),
587 scopes: vec!["block:abc".to_string()],
588 sender: String::new(),
589 persist: 0,
590 data: Some(serde_json::json!({ "data64": "aGVsbG8=", "n": 3 })),
591 };
592 let old = serde_json::to_value(WSEventType {
593 eventtype: WS_EVENT_RPC.to_string(),
594 oref: String::new(),
595 data: Some(serde_json::json!({ "command": "eventrecv", "data": event })),
596 })
597 .unwrap();
598 assert_eq!(rpc_eventrecv_value(&event).unwrap(), old);
599 }
600
601 #[test]
602 fn send_event_delivers_the_eventrecv_envelope_to_the_subscribed_conn() {
603 let bus = Arc::new(EventBus::new());
604 let mut rx = bus.register_ws("conn-1", "tab-1");
605 let bridge = EventBusBridge::new(Arc::clone(&bus));
606 let event = MuxEvent {
607 event: "blockfile".to_string(),
608 scopes: vec![],
609 sender: String::new(),
610 persist: 0,
611 data: Some(serde_json::json!({ "k": "v" })),
612 };
613 bridge.send_event("conn-1", event.clone());
614 let got = rx.priority.try_recv().expect("delivered on the priority lane");
615 assert_eq!(got, rpc_eventrecv_value(&event).unwrap());
616 assert_eq!(got["eventtype"], "rpc");
617 assert_eq!(got["data"]["command"], "eventrecv");
618 assert_eq!(got["data"]["data"]["data"]["k"], "v");
619 }
620}