1use std::sync::Arc;
7
8use dashmap::DashMap;
9use tokio::sync::broadcast;
10
11use crate::error::WsError;
12
13pub struct Room {
15 pub id: String,
17 tx: broadcast::Sender<String>,
18 participants: DashMap<String, ()>,
19}
20
21impl std::fmt::Debug for Room {
22 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
23 f.debug_struct("Room")
24 .field("id", &self.id)
25 .field("participants", &self.participant_count())
26 .field("receiver_count", &self.receiver_count())
27 .finish()
28 }
29}
30
31impl Room {
32 pub fn new(id: impl Into<String>, capacity: usize) -> Self {
34 let (tx, _) = broadcast::channel(capacity);
35 Self {
36 id: id.into(),
37 tx,
38 participants: DashMap::new(),
39 }
40 }
41
42 pub fn subscribe(&self) -> broadcast::Receiver<String> {
44 self.tx.subscribe()
45 }
46
47 pub fn broadcast(&self, msg: String) -> Result<usize, WsError> {
49 self.tx.send(msg).map_err(|_| WsError::BroadcastFull)
50 }
51
52 pub fn join(&self, participant_id: impl Into<String>) {
54 self.participants.insert(participant_id.into(), ());
55 }
56
57 pub fn leave(&self, participant_id: &str) -> bool {
59 self.participants.remove(participant_id).is_some()
60 }
61
62 pub fn contains(&self, participant_id: &str) -> bool {
64 self.participants.contains_key(participant_id)
65 }
66
67 pub fn participant_count(&self) -> usize {
69 self.participants.len()
70 }
71
72 pub fn receiver_count(&self) -> usize {
74 self.tx.receiver_count()
75 }
76
77 pub fn is_empty(&self) -> bool {
79 self.participants.is_empty() && self.tx.receiver_count() == 0
80 }
81
82 pub fn sender(&self) -> &broadcast::Sender<String> {
84 &self.tx
85 }
86}
87
88#[derive(Debug, Default)]
90pub struct RoomManager {
91 rooms: DashMap<String, Arc<Room>>,
92}
93
94impl RoomManager {
95 pub fn new() -> Self {
97 Self {
98 rooms: DashMap::new(),
99 }
100 }
101
102 pub fn get_or_create(&self, room_id: &str) -> Arc<Room> {
104 if let Some(r) = self.rooms.get(room_id) {
105 return Arc::clone(&r);
106 }
107 let room = Arc::new(Room::new(room_id, 1024));
108 let entry = self.rooms.entry(room_id.to_string()).or_insert_with(|| Arc::clone(&room));
110 Arc::clone(entry.value())
111 }
112
113 pub fn get_or_create_with_capacity(&self, room_id: &str, capacity: usize) -> Arc<Room> {
115 if let Some(r) = self.rooms.get(room_id) {
116 return Arc::clone(&r);
117 }
118 let room = Arc::new(Room::new(room_id, capacity));
119 let entry = self.rooms.entry(room_id.to_string()).or_insert_with(|| Arc::clone(&room));
120 Arc::clone(entry.value())
121 }
122
123 pub fn get(&self, room_id: &str) -> Option<Arc<Room>> {
125 self.rooms.get(room_id).map(|r| Arc::clone(&r))
126 }
127
128 pub fn remove(&self, room_id: &str) -> Option<Arc<Room>> {
130 self.rooms.remove(room_id).map(|(_, v)| v)
131 }
132
133 pub fn room_count(&self) -> usize {
135 self.rooms.len()
136 }
137
138 pub fn cleanup(&self) -> usize {
140 let mut to_remove = Vec::new();
141 for entry in self.rooms.iter() {
142 if entry.value().is_empty() {
143 to_remove.push(entry.key().clone());
144 }
145 }
146 let n = to_remove.len();
147 for k in to_remove {
148 self.rooms.remove(&k);
149 }
150 n
151 }
152
153 pub fn room_ids(&self) -> Vec<String> {
155 self.rooms.iter().map(|e| e.key().clone()).collect()
156 }
157}
158
159#[cfg(test)]
160mod tests {
161 use super::*;
162
163 #[test]
164 fn room_join_leave() {
165 let room = Room::new("lobby", 16);
166 assert_eq!(room.participant_count(), 0);
167 room.join("alice");
168 room.join("bob");
169 assert_eq!(room.participant_count(), 2);
170 assert!(room.contains("alice"));
171 assert!(room.leave("alice"));
172 assert!(!room.contains("alice"));
173 assert_eq!(room.participant_count(), 1);
174 }
175
176 #[tokio::test]
177 async fn room_broadcast() {
178 let room = Room::new("chat", 16);
179 let mut rx = room.subscribe();
180 let n = room.broadcast("hello".to_string()).unwrap();
181 assert_eq!(n, 1);
182 assert_eq!(rx.recv().await.unwrap(), "hello");
183 }
184
185 #[tokio::test]
186 async fn room_broadcast_no_receiver() {
187 let room = Room::new("empty", 16);
188 let err = room.broadcast("hi".to_string()).unwrap_err();
189 assert_eq!(err, WsError::BroadcastFull);
190 }
191
192 #[test]
193 fn manager_get_or_create_idempotent() {
194 let m = RoomManager::new();
195 let r1 = m.get_or_create("room1");
196 let r2 = m.get_or_create("room1");
197 assert!(Arc::ptr_eq(&r1, &r2));
198 assert_eq!(m.room_count(), 1);
199 }
200
201 #[test]
202 fn manager_cleanup_removes_empty() {
203 let m = RoomManager::new();
204 let _r = m.get_or_create("a");
205 assert_eq!(m.cleanup(), 1);
210 assert_eq!(m.room_count(), 0);
211
212 let r = m.get_or_create("b");
213 r.join("user1");
214 assert_eq!(m.cleanup(), 0);
215 assert_eq!(m.room_count(), 1);
216 }
217
218 #[test]
219 fn manager_get_and_remove() {
220 let m = RoomManager::new();
221 assert!(m.get("nonexistent").is_none());
222 let r = m.get_or_create("x");
223 assert!(m.get("x").is_some());
224 let removed = m.remove("x").unwrap();
225 assert!(Arc::ptr_eq(&r, &removed));
226 assert!(m.get("x").is_none());
227 }
228
229 #[tokio::test]
230 async fn manager_broadcast_isolation() {
231 let m = RoomManager::new();
232 let r1 = m.get_or_create("r1");
233 let r2 = m.get_or_create("r2");
234 let mut rx1 = r1.subscribe();
235 let mut rx2 = r2.subscribe();
236 r1.broadcast("for r1".to_string()).unwrap();
237 assert_eq!(rx1.recv().await.unwrap(), "for r1");
238 use tokio::time::{timeout, Duration};
240 let res = timeout(Duration::from_millis(10), rx2.recv()).await;
241 assert!(res.is_err());
242 }
243}