1use std::sync::Arc;
8
9use dashmap::DashMap;
10use tokio::sync::broadcast;
11
12use crate::config::WsConfig;
13use crate::error::WsError;
14
15pub struct Room {
17 pub id: String,
19 tx: broadcast::Sender<String>,
20 participants: DashMap<u32, String>,
22}
23
24impl std::fmt::Debug for Room {
25 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26 f.debug_struct("Room")
27 .field("id", &self.id)
28 .field("participants", &self.participant_count())
29 .field("receiver_count", &self.receiver_count())
30 .finish()
31 }
32}
33
34impl Room {
35 pub fn new(id: impl Into<String>, capacity: usize) -> Self {
37 let (tx, _) = broadcast::channel(capacity);
38 Self {
39 id: id.into(),
40 tx,
41 participants: DashMap::new(),
42 }
43 }
44
45 pub fn subscribe(&self) -> broadcast::Receiver<String> {
47 self.tx.subscribe()
48 }
49
50 pub fn broadcast(&self, msg: String) -> Result<usize, WsError> {
52 self.tx.send(msg).map_err(|_| WsError::BroadcastFull)
53 }
54
55 pub fn join(&self, participant_id: u32, name: String) {
57 self.participants.insert(participant_id, name);
58 }
59
60 pub fn leave(&self, participant_id: u32) -> bool {
62 self.participants.remove(&participant_id).is_some()
63 }
64
65 pub fn contains(&self, participant_id: u32) -> bool {
67 self.participants.contains_key(&participant_id)
68 }
69
70 pub fn participants(&self) -> Vec<(u32, String)> {
72 self.participants
73 .iter()
74 .map(|r| (*r.key(), r.value().clone()))
75 .collect()
76 }
77
78 pub fn participant_names(&self) -> Vec<String> {
80 self.participants
81 .iter()
82 .map(|r| r.value().clone())
83 .collect()
84 }
85
86 pub fn participant_count(&self) -> usize {
88 self.participants.len()
89 }
90
91 pub fn receiver_count(&self) -> usize {
93 self.tx.receiver_count()
94 }
95
96 pub fn is_empty(&self) -> bool {
98 self.participants.is_empty() && self.tx.receiver_count() == 0
99 }
100
101 pub fn sender(&self) -> &broadcast::Sender<String> {
103 &self.tx
104 }
105}
106
107#[derive(Debug)]
109pub struct RoomManager {
110 rooms: DashMap<String, Arc<Room>>,
111 default_capacity: usize,
112}
113
114impl Default for RoomManager {
115 fn default() -> Self {
116 Self::new()
117 }
118}
119
120impl RoomManager {
121 pub fn new() -> Self {
123 Self {
124 rooms: DashMap::new(),
125 default_capacity: 1024,
126 }
127 }
128
129 pub fn with_config(config: &WsConfig) -> Self {
131 Self {
132 rooms: DashMap::new(),
133 default_capacity: config.broadcast_capacity,
134 }
135 }
136
137 pub fn get_or_create(&self, room_id: &str) -> Arc<Room> {
139 self.get_or_create_with_capacity(room_id, self.default_capacity)
140 }
141
142 pub fn get_or_create_with_capacity(&self, room_id: &str, capacity: usize) -> Arc<Room> {
144 if let Some(r) = self.rooms.get(room_id) {
145 return Arc::clone(&r);
146 }
147 let room = Arc::new(Room::new(room_id, capacity));
148 let entry = self
150 .rooms
151 .entry(room_id.to_string())
152 .or_insert_with(|| Arc::clone(&room));
153 Arc::clone(entry.value())
154 }
155
156 pub fn get(&self, room_id: &str) -> Option<Arc<Room>> {
158 self.rooms.get(room_id).map(|r| Arc::clone(&r))
159 }
160
161 pub fn remove(&self, room_id: &str) -> Option<Arc<Room>> {
163 self.rooms.remove(room_id).map(|(_, v)| v)
164 }
165
166 pub fn room_count(&self) -> usize {
168 self.rooms.len()
169 }
170
171 pub fn total_participants(&self) -> usize {
173 self.rooms
174 .iter()
175 .map(|e| e.value().participant_count())
176 .sum()
177 }
178
179 pub fn cleanup(&self) -> usize {
181 let mut to_remove = Vec::new();
182 for entry in self.rooms.iter() {
183 if entry.value().is_empty() {
184 to_remove.push(entry.key().clone());
185 }
186 }
187 let n = to_remove.len();
188 for k in to_remove {
189 self.rooms.remove(&k);
190 }
191 n
192 }
193
194 pub fn cleanup_empty(&self) -> usize {
197 let to_remove: Vec<String> = self
198 .rooms
199 .iter()
200 .filter(|e| e.value().participant_count() == 0)
201 .map(|e| e.key().clone())
202 .collect();
203 let n = to_remove.len();
204 for k in to_remove {
205 self.rooms.remove(&k);
206 }
207 n
208 }
209
210 pub fn room_ids(&self) -> Vec<String> {
212 self.rooms.iter().map(|e| e.key().clone()).collect()
213 }
214}
215
216#[cfg(test)]
217mod tests {
218 use super::*;
219
220 fn name(s: &str) -> String {
221 s.to_string()
222 }
223
224 #[test]
225 fn room_join_leave() {
226 let room = Room::new("lobby", 16);
227 assert_eq!(room.participant_count(), 0);
228 room.join(1, name("alice"));
229 room.join(2, name("bob"));
230 assert_eq!(room.participant_count(), 2);
231 assert!(room.contains(1));
232 assert!(room.leave(1));
233 assert!(!room.contains(1));
234 assert_eq!(room.participant_count(), 1);
235 }
236
237 #[test]
238 fn room_rejoin_updates_name() {
239 let room = Room::new("lobby", 16);
240 room.join(1, name("alice"));
241 room.join(1, name("alice2"));
242 assert_eq!(room.participant_count(), 1);
243 assert_eq!(room.participant_names(), vec!["alice2".to_string()]);
244 }
245
246 #[test]
247 fn room_participant_snapshots() {
248 let room = Room::new("lobby", 16);
249 room.join(7, name("alice"));
250 room.join(3, name("bob"));
251 let mut pairs = room.participants();
252 pairs.sort();
253 assert_eq!(
254 pairs,
255 vec![(3, "bob".to_string()), (7, "alice".to_string())]
256 );
257 let mut names = room.participant_names();
258 names.sort();
259 assert_eq!(names, vec!["alice".to_string(), "bob".to_string()]);
260 assert_eq!(room.participant_count(), 2);
261 }
262
263 #[tokio::test]
264 async fn room_broadcast() {
265 let room = Room::new("chat", 16);
266 let mut rx = room.subscribe();
267 let n = room.broadcast("hello".to_string()).unwrap();
268 assert_eq!(n, 1);
269 assert_eq!(rx.recv().await.unwrap(), "hello");
270 }
271
272 #[tokio::test]
273 async fn room_broadcast_no_receiver() {
274 let room = Room::new("empty", 16);
275 let err = room.broadcast("hi".to_string()).unwrap_err();
276 assert_eq!(err, WsError::BroadcastFull);
277 }
278
279 #[test]
280 fn manager_get_or_create_idempotent() {
281 let m = RoomManager::new();
282 let r1 = m.get_or_create("room1");
283 let r2 = m.get_or_create("room1");
284 assert!(Arc::ptr_eq(&r1, &r2));
285 assert_eq!(m.room_count(), 1);
286 }
287
288 #[test]
289 fn manager_with_config_capacity() {
290 let cfg = WsConfig::builder().broadcast_capacity(2048).build();
291 let m = RoomManager::with_config(&cfg);
292 let r = m.get_or_create("room1");
293 let r2 = m.get_or_create("room1");
295 assert!(Arc::ptr_eq(&r, &r2));
296 assert_eq!(m.room_count(), 1);
297 }
298
299 #[test]
300 fn manager_cleanup_removes_empty() {
301 let m = RoomManager::new();
302 let _r = m.get_or_create("a");
303 assert_eq!(m.cleanup(), 1);
305 assert_eq!(m.room_count(), 0);
306
307 let r = m.get_or_create("b");
308 r.join(1, name("user1"));
309 assert_eq!(m.cleanup(), 0);
310 assert_eq!(m.room_count(), 1);
311 }
312
313 #[test]
314 fn manager_cleanup_empty_removes_zero_participant_rooms() {
315 let m = RoomManager::new();
316 let _a = m.get_or_create("a");
317 assert_eq!(m.cleanup_empty(), 1);
319 assert_eq!(m.room_count(), 0);
320
321 let a = m.get_or_create("a");
323 a.join(1, name("alice"));
324 assert_eq!(m.cleanup_empty(), 0);
325 assert_eq!(m.room_count(), 1);
326 let kept = m.get_or_create("a");
327 assert!(Arc::ptr_eq(&a, &kept));
328
329 a.leave(1);
330 assert_eq!(m.cleanup_empty(), 1);
331 assert_eq!(m.room_count(), 0);
332 }
333
334 #[test]
335 fn manager_total_participants() {
336 let m = RoomManager::new();
337 let r1 = m.get_or_create("r1");
338 let r2 = m.get_or_create("r2");
339 assert_eq!(m.total_participants(), 0);
340 r1.join(1, name("alice"));
341 r1.join(2, name("bob"));
342 r2.join(3, name("carol"));
343 assert_eq!(m.total_participants(), 3);
344 r2.leave(3);
345 assert_eq!(m.total_participants(), 2);
346 }
347
348 #[test]
349 fn manager_get_and_remove() {
350 let m = RoomManager::new();
351 assert!(m.get("nonexistent").is_none());
352 let r = m.get_or_create("x");
353 assert!(m.get("x").is_some());
354 let removed = m.remove("x").unwrap();
355 assert!(Arc::ptr_eq(&r, &removed));
356 assert!(m.get("x").is_none());
357 }
358
359 #[tokio::test]
360 async fn manager_broadcast_isolation() {
361 let m = RoomManager::new();
362 let r1 = m.get_or_create("r1");
363 let r2 = m.get_or_create("r2");
364 let mut rx1 = r1.subscribe();
365 let mut rx2 = r2.subscribe();
366 r1.broadcast("for r1".to_string()).unwrap();
367 assert_eq!(rx1.recv().await.unwrap(), "for r1");
368 use tokio::time::{timeout, Duration};
370 let res = timeout(Duration::from_millis(10), rx2.recv()).await;
371 assert!(res.is_err());
372 }
373}