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.iter().map(|r| r.value().clone()).collect()
81 }
82
83 pub fn participant_count(&self) -> usize {
85 self.participants.len()
86 }
87
88 pub fn receiver_count(&self) -> usize {
90 self.tx.receiver_count()
91 }
92
93 pub fn is_empty(&self) -> bool {
95 self.participants.is_empty() && self.tx.receiver_count() == 0
96 }
97
98 pub fn sender(&self) -> &broadcast::Sender<String> {
100 &self.tx
101 }
102}
103
104#[derive(Debug)]
106pub struct RoomManager {
107 rooms: DashMap<String, Arc<Room>>,
108 default_capacity: usize,
109}
110
111impl Default for RoomManager {
112 fn default() -> Self {
113 Self::new()
114 }
115}
116
117impl RoomManager {
118 pub fn new() -> Self {
120 Self {
121 rooms: DashMap::new(),
122 default_capacity: 1024,
123 }
124 }
125
126 pub fn with_config(config: &WsConfig) -> Self {
128 Self {
129 rooms: DashMap::new(),
130 default_capacity: config.broadcast_capacity,
131 }
132 }
133
134 pub fn get_or_create(&self, room_id: &str) -> Arc<Room> {
136 self.get_or_create_with_capacity(room_id, self.default_capacity)
137 }
138
139 pub fn get_or_create_with_capacity(&self, room_id: &str, capacity: usize) -> Arc<Room> {
141 if let Some(r) = self.rooms.get(room_id) {
142 return Arc::clone(&r);
143 }
144 let room = Arc::new(Room::new(room_id, capacity));
145 let entry = self
147 .rooms
148 .entry(room_id.to_string())
149 .or_insert_with(|| Arc::clone(&room));
150 Arc::clone(entry.value())
151 }
152
153 pub fn get(&self, room_id: &str) -> Option<Arc<Room>> {
155 self.rooms.get(room_id).map(|r| Arc::clone(&r))
156 }
157
158 pub fn remove(&self, room_id: &str) -> Option<Arc<Room>> {
160 self.rooms.remove(room_id).map(|(_, v)| v)
161 }
162
163 pub fn room_count(&self) -> usize {
165 self.rooms.len()
166 }
167
168 pub fn total_participants(&self) -> usize {
170 self.rooms.iter().map(|e| e.value().participant_count()).sum()
171 }
172
173 pub fn cleanup(&self) -> usize {
175 let mut to_remove = Vec::new();
176 for entry in self.rooms.iter() {
177 if entry.value().is_empty() {
178 to_remove.push(entry.key().clone());
179 }
180 }
181 let n = to_remove.len();
182 for k in to_remove {
183 self.rooms.remove(&k);
184 }
185 n
186 }
187
188 pub fn cleanup_empty(&self) -> usize {
191 let to_remove: Vec<String> = self
192 .rooms
193 .iter()
194 .filter(|e| e.value().participant_count() == 0)
195 .map(|e| e.key().clone())
196 .collect();
197 let n = to_remove.len();
198 for k in to_remove {
199 self.rooms.remove(&k);
200 }
201 n
202 }
203
204 pub fn room_ids(&self) -> Vec<String> {
206 self.rooms.iter().map(|e| e.key().clone()).collect()
207 }
208}
209
210#[cfg(test)]
211mod tests {
212 use super::*;
213
214 fn name(s: &str) -> String {
215 s.to_string()
216 }
217
218 #[test]
219 fn room_join_leave() {
220 let room = Room::new("lobby", 16);
221 assert_eq!(room.participant_count(), 0);
222 room.join(1, name("alice"));
223 room.join(2, name("bob"));
224 assert_eq!(room.participant_count(), 2);
225 assert!(room.contains(1));
226 assert!(room.leave(1));
227 assert!(!room.contains(1));
228 assert_eq!(room.participant_count(), 1);
229 }
230
231 #[test]
232 fn room_rejoin_updates_name() {
233 let room = Room::new("lobby", 16);
234 room.join(1, name("alice"));
235 room.join(1, name("alice2"));
236 assert_eq!(room.participant_count(), 1);
237 assert_eq!(room.participant_names(), vec!["alice2".to_string()]);
238 }
239
240 #[test]
241 fn room_participant_snapshots() {
242 let room = Room::new("lobby", 16);
243 room.join(7, name("alice"));
244 room.join(3, name("bob"));
245 let mut pairs = room.participants();
246 pairs.sort();
247 assert_eq!(pairs, vec![(3, "bob".to_string()), (7, "alice".to_string())]);
248 let mut names = room.participant_names();
249 names.sort();
250 assert_eq!(names, vec!["alice".to_string(), "bob".to_string()]);
251 assert_eq!(room.participant_count(), 2);
252 }
253
254 #[tokio::test]
255 async fn room_broadcast() {
256 let room = Room::new("chat", 16);
257 let mut rx = room.subscribe();
258 let n = room.broadcast("hello".to_string()).unwrap();
259 assert_eq!(n, 1);
260 assert_eq!(rx.recv().await.unwrap(), "hello");
261 }
262
263 #[tokio::test]
264 async fn room_broadcast_no_receiver() {
265 let room = Room::new("empty", 16);
266 let err = room.broadcast("hi".to_string()).unwrap_err();
267 assert_eq!(err, WsError::BroadcastFull);
268 }
269
270 #[test]
271 fn manager_get_or_create_idempotent() {
272 let m = RoomManager::new();
273 let r1 = m.get_or_create("room1");
274 let r2 = m.get_or_create("room1");
275 assert!(Arc::ptr_eq(&r1, &r2));
276 assert_eq!(m.room_count(), 1);
277 }
278
279 #[test]
280 fn manager_with_config_capacity() {
281 let cfg = WsConfig::builder().broadcast_capacity(2048).build();
282 let m = RoomManager::with_config(&cfg);
283 let r = m.get_or_create("room1");
284 let r2 = m.get_or_create("room1");
286 assert!(Arc::ptr_eq(&r, &r2));
287 assert_eq!(m.room_count(), 1);
288 }
289
290 #[test]
291 fn manager_cleanup_removes_empty() {
292 let m = RoomManager::new();
293 let _r = m.get_or_create("a");
294 assert_eq!(m.cleanup(), 1);
296 assert_eq!(m.room_count(), 0);
297
298 let r = m.get_or_create("b");
299 r.join(1, name("user1"));
300 assert_eq!(m.cleanup(), 0);
301 assert_eq!(m.room_count(), 1);
302 }
303
304 #[test]
305 fn manager_cleanup_empty_removes_zero_participant_rooms() {
306 let m = RoomManager::new();
307 let _a = m.get_or_create("a");
308 assert_eq!(m.cleanup_empty(), 1);
310 assert_eq!(m.room_count(), 0);
311
312 let a = m.get_or_create("a");
314 a.join(1, name("alice"));
315 assert_eq!(m.cleanup_empty(), 0);
316 assert_eq!(m.room_count(), 1);
317 let kept = m.get_or_create("a");
318 assert!(Arc::ptr_eq(&a, &kept));
319
320 a.leave(1);
321 assert_eq!(m.cleanup_empty(), 1);
322 assert_eq!(m.room_count(), 0);
323 }
324
325 #[test]
326 fn manager_total_participants() {
327 let m = RoomManager::new();
328 let r1 = m.get_or_create("r1");
329 let r2 = m.get_or_create("r2");
330 assert_eq!(m.total_participants(), 0);
331 r1.join(1, name("alice"));
332 r1.join(2, name("bob"));
333 r2.join(3, name("carol"));
334 assert_eq!(m.total_participants(), 3);
335 r2.leave(3);
336 assert_eq!(m.total_participants(), 2);
337 }
338
339 #[test]
340 fn manager_get_and_remove() {
341 let m = RoomManager::new();
342 assert!(m.get("nonexistent").is_none());
343 let r = m.get_or_create("x");
344 assert!(m.get("x").is_some());
345 let removed = m.remove("x").unwrap();
346 assert!(Arc::ptr_eq(&r, &removed));
347 assert!(m.get("x").is_none());
348 }
349
350 #[tokio::test]
351 async fn manager_broadcast_isolation() {
352 let m = RoomManager::new();
353 let r1 = m.get_or_create("r1");
354 let r2 = m.get_or_create("r2");
355 let mut rx1 = r1.subscribe();
356 let mut rx2 = r2.subscribe();
357 r1.broadcast("for r1".to_string()).unwrap();
358 assert_eq!(rx1.recv().await.unwrap(), "for r1");
359 use tokio::time::{timeout, Duration};
361 let res = timeout(Duration::from_millis(10), rx2.recv()).await;
362 assert!(res.is_err());
363 }
364}