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