1use bytes::Bytes;
2use std::collections::HashMap;
3use std::sync::Arc;
4use tokio::sync::{broadcast, watch, RwLock};
5
6#[derive(Debug, Clone)]
8pub struct BusMessage {
9 pub row_scope: Option<crate::row_scope::RowScope>,
11 pub key: String,
12 pub entity: String,
13 pub payload: Arc<Bytes>,
14}
15
16#[derive(Debug, Clone, Default)]
23pub struct StateUpdate {
24 pub published: u64,
25 pub replaced: u64,
30 pub payload: Arc<Bytes>,
31}
32
33fn replaces_entity(frame: &[u8]) -> bool {
36 #[derive(serde::Deserialize)]
37 struct Frame<'a> {
38 #[serde(borrow)]
39 op: std::borrow::Cow<'a, str>,
40 }
41 serde_json::from_slice::<Frame<'_>>(frame)
42 .is_ok_and(|frame| matches!(frame.op.as_ref(), "upsert" | "delete"))
43}
44
45#[derive(Clone)]
46#[allow(clippy::type_complexity)]
47pub struct BusManager {
48 state_buses: Arc<RwLock<HashMap<(String, String), watch::Sender<StateUpdate>>>>,
49 list_buses: Arc<RwLock<HashMap<String, broadcast::Sender<Arc<BusMessage>>>>>,
50 broadcast_capacity: usize,
51}
52
53impl BusManager {
54 pub fn new() -> Self {
55 Self::with_capacity(1000)
56 }
57
58 pub fn with_capacity(capacity: usize) -> Self {
59 Self {
60 state_buses: Arc::new(RwLock::new(HashMap::new())),
61 list_buses: Arc::new(RwLock::new(HashMap::new())),
62 broadcast_capacity: capacity,
63 }
64 }
65
66 pub async fn get_or_create_state_bus(
69 &self,
70 view_id: &str,
71 key: &str,
72 ) -> watch::Receiver<StateUpdate> {
73 let mut buses = self.state_buses.write().await;
74 let entry = (view_id.to_string(), key.to_string());
75
76 let tx = buses
77 .entry(entry)
78 .or_insert_with(|| watch::channel(StateUpdate::default()).0)
79 .clone();
80
81 tx.subscribe()
82 }
83
84 pub async fn get_or_create_list_bus(
85 &self,
86 view_id: &str,
87 ) -> broadcast::Receiver<Arc<BusMessage>> {
88 let mut buses = self.list_buses.write().await;
89
90 let tx = buses
91 .entry(view_id.to_string())
92 .or_insert_with(|| broadcast::channel(self.broadcast_capacity).0)
93 .clone();
94
95 tx.subscribe()
96 }
97
98 pub async fn publish_state(&self, view_id: &str, key: &str, frame: Arc<Bytes>) {
100 let buses = self.state_buses.read().await;
101 if let Some(tx) = buses.get(&(view_id.to_string(), key.to_string())) {
102 let replaces = replaces_entity(&frame);
103 tx.send_modify(|update| {
104 update.published += 1;
105 if replaces {
106 update.replaced = update.published;
107 }
108 update.payload = frame;
109 });
110 }
111 }
112
113 pub async fn publish_list(&self, view_id: &str, message: Arc<BusMessage>) {
114 let buses = self.list_buses.read().await;
115 if let Some(tx) = buses.get(view_id) {
116 let _ = tx.send(message);
117 }
118 }
119
120 pub async fn cleanup_stale_state_buses(&self) -> usize {
121 let mut buses = self.state_buses.write().await;
122 let before = buses.len();
123
124 buses.retain(|_, tx| tx.receiver_count() > 0);
125
126 before - buses.len()
127 }
128
129 pub async fn cleanup_stale_list_buses(&self) -> usize {
130 let mut buses = self.list_buses.write().await;
131 let before = buses.len();
132
133 buses.retain(|_, tx| tx.receiver_count() > 0);
134
135 before - buses.len()
136 }
137
138 pub async fn bus_counts(&self) -> (usize, usize) {
139 let state_count = self.state_buses.read().await.len();
140 let list_count = self.list_buses.read().await.len();
141 (state_count, list_count)
142 }
143}
144
145impl Default for BusManager {
146 fn default() -> Self {
147 Self::new()
148 }
149}
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154
155 fn frame(op: &str) -> Arc<Bytes> {
156 Arc::new(Bytes::from(format!(
157 r#"{{"mode":"state","entity":"Round/state","op":"{op}","key":"7","data":{{}}}}"#
158 )))
159 }
160
161 #[tokio::test]
165 async fn a_state_bus_records_its_latest_replacing_frame() {
166 let bus = BusManager::new();
167 let receiver = bus.get_or_create_state_bus("Round/state", "7").await;
168 bus.publish_state("Round/state", "7", frame("patch")).await;
169 assert_eq!(receiver.borrow().replaced, 0);
170 bus.publish_state("Round/state", "7", frame("upsert")).await;
171 bus.publish_state("Round/state", "7", frame("patch")).await;
172 assert_eq!(receiver.borrow().replaced, 2);
173 bus.publish_state("Round/state", "7", frame("delete")).await;
174 assert_eq!(receiver.borrow().replaced, 4);
175 assert_eq!(receiver.borrow().published, 4);
176 bus.publish_state(
177 "Round/state",
178 "7",
179 Arc::new(Bytes::from_static(b"not json")),
180 )
181 .await;
182 assert_eq!(receiver.borrow().replaced, 4, "unparsed frames are patches");
183 }
184}