bones_matchmaker/
matchmaking.rs1use super::matchmaker::{start_game, MATCHMAKER_STATE};
2use anyhow::Result;
3use bones_matchmaker_proto::{MatchInfo, MatchmakerResponse};
4use iroh::{endpoint::Connection, Endpoint};
5use tokio::time::{sleep, Duration};
6
7pub async fn handle_stop_matchmaking(
9 conn: Connection,
10 match_info: MatchInfo,
11 send: &mut iroh::endpoint::SendStream,
12) -> Result<()> {
13 let stable_id = conn.stable_id();
14 info!("[{}] Handling stop matchmaking request", stable_id);
15 let state = MATCHMAKER_STATE.lock().await;
16
17 let removed = state
18 .matchmaking_rooms
19 .update(&match_info, |_, members| {
20 if let Some(pos) = members
21 .iter()
22 .position(|member| member.stable_id() == stable_id)
23 {
24 members.remove(pos);
25 true
26 } else {
27 false
28 }
29 })
30 .unwrap_or(false);
31
32 let response = if removed {
33 info!(
34 "[{}] Player successfully removed from matchmaking queue",
35 stable_id
36 );
37 MatchmakerResponse::Accepted
38 } else {
39 info!("[{}] Player not found in matchmaking queue", stable_id);
40 MatchmakerResponse::Error("Not found in matchmaking queue".to_string())
41 };
42
43 let message = postcard::to_allocvec(&response)?;
44 send.write_all(&message).await?;
45 send.finish()?;
46 send.stopped().await?;
47
48 if removed {
50 drop(state); if let Ok(active_connections) = send_matchmaking_updates(&match_info, 0).await {
52 let player_count = active_connections.len();
53 info!(
54 "[{}] Sent updated matchmaking status to other players in the matchmaking room. Current player count: {}",
55 stable_id, player_count
56 );
57 } else {
58 info!(
59 "[{}] Failed to send update to other players in the matchmaking room",
60 stable_id
61 );
62 }
63 }
64
65 Ok(())
66}
67
68pub async fn handle_request_matchaking(
70 ep: &Endpoint,
71 conn: Connection,
72 match_info: MatchInfo,
73 send: &mut iroh::endpoint::SendStream,
74) -> Result<()> {
75 let stable_id = conn.stable_id();
76 info!("[{}] Handling start matchmaking search request", stable_id);
77 let mut state = MATCHMAKER_STATE.lock().await;
78
79 for i in 0..200 {
81 let room_is_full = state
82 .matchmaking_rooms
83 .get(&match_info)
84 .map(|room| room.get().len() >= match_info.max_players as usize)
85 .unwrap_or(false);
86
87 if !room_is_full {
88 info!(
89 "[{}] Found available space in matchmaking room after waiting for {} milliseconds",
90 stable_id,
91 i as f64 * 100.0
92 );
93 break;
94 } else if i == 0 {
95 info!(
96 "[{}] Matchmaking room is full, waiting for current room to clear...)",
97 stable_id
98 );
99 }
100 drop(state);
102 sleep(Duration::from_millis(100)).await;
103 state = MATCHMAKER_STATE.lock().await;
104 }
105
106 let can_join = state
108 .matchmaking_rooms
109 .get(&match_info)
110 .map(|room| room.get().len() < match_info.max_players as usize)
111 .unwrap_or(true);
112
113 if !can_join {
117 info!(
118 "[{}] Matchmaking room is full after waiting, rejecting request",
119 stable_id
120 );
121 let error_message = postcard::to_allocvec(&MatchmakerResponse::Error(
122 "Matchmaking room is full. Please try matchmaking again shortly.".to_string(),
123 ))?;
124 send.write_all(&error_message).await?;
125 send.finish()?;
126 send.stopped().await?;
127 return Ok(());
128 }
129
130 let message = postcard::to_allocvec(&MatchmakerResponse::Accepted)?;
132 send.write_all(&message).await?;
133 send.finish()?;
134 send.stopped().await?;
135
136 let new_player_count = state
138 .matchmaking_rooms
139 .update(&match_info, |_, members| {
140 members.push(conn.clone());
141 info!(
142 "[{}] Added player to matchmaking room. New count: {}",
143 stable_id,
144 members.len()
145 );
146 members.len() as u32
147 })
148 .unwrap_or_else(|| {
149 let members = vec![conn.clone()];
150 info!("[{}] Created new matchmaking room with 1 player", stable_id);
151 if let Err(e) = state.matchmaking_rooms.insert(match_info.clone(), members) {
152 warn!(
153 "[{}] Failed to insert new matchmaking room: {:?}",
154 stable_id, e
155 );
156 }
157 1_u32
158 });
159
160 drop(state);
162
163 info!(
165 "[{}] Sending update to all players & cleaning connections in the matchmaking room ",
166 stable_id
167 );
168 let active_connections = send_matchmaking_updates(&match_info, new_player_count).await?;
169
170 let player_count = active_connections.len();
171 info!(
172 "[{}] Active connections after cleaning/sending update: {}",
173 stable_id, player_count
174 );
175
176 if player_count >= match_info.max_players as usize {
178 info!(
179 "[{}] Matchmaking room is full. Starting the game.",
180 stable_id
181 );
182 start_matchmaked_game_if_ready(ep, &match_info).await?;
183 } else {
184 info!(
185 "[{}] Matchmaking room is not full yet. Waiting for more players.",
186 stable_id
187 );
188 }
189
190 Ok(())
191}
192
193async fn send_matchmaking_updates(
197 match_info: &MatchInfo,
198 new_player_count: u32,
199) -> Result<Vec<Connection>> {
200 let connections = {
201 let state = MATCHMAKER_STATE.lock().await;
202 state
203 .matchmaking_rooms
204 .get(match_info)
205 .map(|room| room.get().clone())
206 .unwrap_or_default()
207 };
208
209 let current_count = connections.len() as u32;
210 let mut active_connections = Vec::new();
211
212 let first_update_message = postcard::to_allocvec(&MatchmakerResponse::MatchmakingUpdate {
214 player_count: current_count,
215 })?;
216
217 for conn in connections.into_iter() {
219 if let Ok(mut send) = conn.open_uni().await {
220 if send.write_all(&first_update_message).await.is_ok()
221 && send.finish().is_ok()
222 && send.stopped().await.is_ok()
223 {
224 active_connections.push(conn);
225 }
226 }
227 }
228
229 if active_connections.len() as u32 != new_player_count {
231 let second_update_message =
232 postcard::to_allocvec(&MatchmakerResponse::MatchmakingUpdate {
233 player_count: active_connections.len() as u32,
234 })?;
235
236 for (index, member) in active_connections.iter().enumerate() {
237 if let Ok(mut send) = member.open_uni().await {
238 if let Err(e) = send.write_all(&second_update_message).await {
239 warn!("Connection to client {} has closed. {:?}", index, e);
240 } else if let Err(e) = send.finish() {
241 warn!("Connection to client {} has closed. {:?}", index, e);
242 } else if let Err(e) = send.stopped().await {
243 warn!("Connection to client {} has closed. {:?}", index, e);
244 }
245 }
246 }
247 }
248
249 {
251 let state = MATCHMAKER_STATE.lock().await;
252 if state.matchmaking_rooms.remove(match_info).is_none() {
253 warn!("Failed to remove matchmaking room: {:?}", &match_info);
254 }
255 if let Err(e) = state
256 .matchmaking_rooms
257 .insert(match_info.clone(), active_connections.clone())
258 {
259 warn!(
260 "Failed to insert updated matchmaking room: {:?}. Error: {:?}",
261 &match_info, e
262 );
263 }
264 }
265
266 Ok(active_connections)
267}
268
269async fn start_matchmaked_game_if_ready(ep: &Endpoint, match_info: &MatchInfo) -> Result<()> {
271 let members = {
272 let state = MATCHMAKER_STATE.lock().await;
273 state
274 .matchmaking_rooms
275 .remove(match_info)
276 .map(|(_, connections)| connections)
277 };
278
279 if let Some(members) = members {
280 let cloned_match_info = match_info.clone();
281 let players_len = members.len();
282 let ep = ep.clone();
283 tokio::spawn(async move {
284 match start_game(ep, members, &cloned_match_info).await {
285 Ok(_) => info!("Starting matchmaked game with {} players", players_len),
286 Err(e) => error!("Error starting match: {:?}", e),
287 }
288 });
289 } else {
290 warn!("Failed to remove matchmaking room when starting game");
291 }
292
293 Ok(())
294}