From 4e131fac31dd86cff34dd4d8d6a0d7bd324fc090 Mon Sep 17 00:00:00 2001 From: Joshua Higgins Date: Fri, 27 Mar 2026 13:08:20 -0400 Subject: [PATCH] feat: remove the need for constant polling data --- src/main.rs | 2 + src/server.rs | 62 +++++++++++++++++++++++++++-- src/tournaments/knockout_bracket.rs | 47 +++++++++++++++++++--- src/tournaments/round_robin.rs | 28 ++++++++++--- 4 files changed, 124 insertions(+), 15 deletions(-) diff --git a/src/main.rs b/src/main.rs index b9174a4..caaf0da 100644 --- a/src/main.rs +++ b/src/main.rs @@ -326,6 +326,8 @@ async fn handle_connection( sd.clients.write().await.remove(&username); sd.usernames.write().await.remove(&addr); + sd.broadcast(&format!("DISCONNECT:{}", username)).await; + break; } } diff --git a/src/server.rs b/src/server.rs index 8df245a..6f1781a 100644 --- a/src/server.rs +++ b/src/server.rs @@ -44,14 +44,21 @@ impl Server { ) -> Result<(), anyhow::Error> { if requested_username.is_empty() { return Err(anyhow::anyhow!(format!( - "ERROR:INVALID:ID:{}", + "ERROR:INVALID:CONNECT:{}", + requested_username + ))); + } + + if requested_username.chars().any(|c| matches!(c, ':' | ',' | '|') || c.is_control()) { + return Err(anyhow::anyhow!(format!( + "ERROR:INVALID:CONNECT:{}", requested_username ))); } if requested_username == SERVER_PLAYER_USERNAME { return Err(anyhow::anyhow!(format!( - "ERROR:INVALID:ID:{}", + "ERROR:INVALID:CONNECT:{}", requested_username ))); } @@ -66,7 +73,7 @@ impl Server { let existing_client = clients_guard.get(&requested_username).cloned(); if existing_client.is_some() && !reconnecting { return Err(anyhow::anyhow!(format!( - "ERROR:INVALID:ID:{}", + "ERROR:INVALID:CONNECT:{}", requested_username ))); } @@ -81,12 +88,14 @@ impl Server { self.clients.write().await.insert( requested_username.clone(), Arc::new(RwLock::new(Client::new( - requested_username, + requested_username.clone(), tx.clone(), addr.to_string().parse()?, ))), ); + self.broadcast(&format!("CONNECT:{}", requested_username)).await; + return Ok(()); } @@ -217,6 +226,7 @@ impl Server { self.clients.write().await.remove(&username); self.usernames.write().await.remove(&addr); let _ = send(&tx, "DISCONNECT:ACK"); + self.broadcast(&format!("DISCONNECT:{}", username)).await; Ok(()) } @@ -264,6 +274,7 @@ impl Server { let _ = send(&tx, "READY:ACK"); drop(client); drop(clients_guard); + self.broadcast(&format!("READY:{}:{}", username, true)).await; if let Some(opponent_username) = self.find_reservation_opponent(&username).await { let clients_guard = self.clients.read().await; @@ -278,6 +289,14 @@ impl Server { false, ))); self.matches.write().await.insert(match_id, new_match.clone()); + let match_guard = new_match.read().await; + self + .broadcast(&format!( + "GAME:START:{},{},{}", + match_id, match_guard.player1, match_guard.player2 + )) + .await; + drop(match_guard); client.ready = false; client.current_match = Some(match_id); @@ -294,6 +313,7 @@ impl Server { opponent.ready = false; opponent.current_match = Some(match_id); opponent.color = !client.color; + let opponent_username = opponent.username.clone(); self .reservations @@ -301,6 +321,12 @@ impl Server { .await .retain(|(p1, p2)| !(p1 == &client.username && p2 == &opponent.username)); + drop(opponent); + drop(client); + drop(clients_guard); + self.broadcast(&format!("READY:{}:{}", username, false)).await; + self.broadcast(&format!("READY:{}:{}", opponent_username, false)).await; + return Ok(()); } @@ -316,6 +342,15 @@ impl Server { is_demo_mode, ))); self.matches.write().await.insert(match_id, new_match.clone()); + let match_guard = new_match.read().await; + self + .broadcast(&format!( + "GAME:START:{},{},{}", + match_id, match_guard.player1, match_guard.player2 + )) + .await; + drop(match_guard); + client.ready = false; client.current_match = Some(match_id); client.color = if new_match.read().await.player1 == username { @@ -355,6 +390,9 @@ impl Server { } })); } + + drop(client); + self.broadcast(&format!("READY:{}:{}", username, false)).await; } Ok(()) @@ -1202,8 +1240,17 @@ impl Server { false, ))); self.matches.write().await.insert(match_id, new_match.clone()); + let match_guard = new_match.read().await; + self + .broadcast(&format!( + "GAME:START:{},{},{}", + match_id, match_guard.player1, match_guard.player2 + )) + .await; + drop(match_guard); player1.ready = false; + let player1_name = player1.username.clone(); player1.current_match = Some(match_id); player1.color = if new_match.read().await.player1 == player1_username { let _ = send(&tx, "GAME:START:1"); @@ -1216,6 +1263,7 @@ impl Server { }; player2.ready = false; + let player2_name = player2.username.clone(); player2.current_match = Some(match_id); player2.color = !player1.color; @@ -1224,6 +1272,12 @@ impl Server { .write() .await .retain(|(p1, p2)| !(p1 == &player1_username && p2 == &player2_username)); + + drop(player2); + drop(player1); + drop(clients_guard); + self.broadcast(&format!("READY:{}:{}", player1_name, false)).await; + self.broadcast(&format!("READY:{}:{}", player2_name, false)).await; } } diff --git a/src/tournaments/knockout_bracket.rs b/src/tournaments/knockout_bracket.rs index 7a50856..c248923 100644 --- a/src/tournaments/knockout_bracket.rs +++ b/src/tournaments/knockout_bracket.rs @@ -19,6 +19,7 @@ pub struct KnockoutBracket { pub started: bool, pub clients: Clients, pub matches: Matches, + pub observers: Observers, pub usernames: Vec, } @@ -39,12 +40,16 @@ impl KnockoutBracket { player2_username.clone(), false, ))); - let match_guard = new_match.read().await; - let mut player1 = clients_guard.get(&player1_username).unwrap().write().await; + let mut player1 = clients_guard.get(&player1_username).unwrap().write().await; player1.current_match = Some(match_id); player1.ready = false; + broadcast_message( + &self.observers, + &format!("READY:{}:{}", player1.username.clone(), false), + ) + .await; if match_guard.player1 == player1_username { player1.color = Color::Red; @@ -53,13 +58,16 @@ impl KnockoutBracket { player1.color = Color::Yellow; let _ = send(&player1.connection, "GAME:START:0"); } - drop(player1); let mut player2 = clients_guard.get(&player2_username).unwrap().write().await; - player2.current_match = Some(match_id); player2.ready = false; + broadcast_message( + &self.observers, + &format!("READY:{}:{}", player2.username.clone(), false), + ) + .await; if match_guard.player1 == player2_username { player2.color = Color::Red; @@ -68,10 +76,17 @@ impl KnockoutBracket { player2.color = Color::Yellow; let _ = send(&player2.connection, "GAME:START:0"); } - drop(player2); self.matches.write().await.insert(match_id, new_match.clone()); + broadcast_message( + &self.observers, + &format!( + "GAME:START:{},{},{}", + match_id, match_guard.player1, match_guard.player2 + ), + ) + .await; i += 2 } @@ -95,6 +110,7 @@ impl Tournament for KnockoutBracket { started: false, clients: server.clients.clone(), matches: server.matches.clone(), + observers: server.observers.clone(), usernames: ready_players.to_vec(), } } @@ -219,6 +235,7 @@ impl Tournament for KnockoutBracket { player1.current_match = Some(match_id); player1.ready = false; + let player1_name = player1.username.clone(); if match_guard.player1 == player1.username { player1.color = Color::Red; @@ -234,6 +251,7 @@ impl Tournament for KnockoutBracket { player2.current_match = Some(match_id); player2.ready = false; + let player2_name = player2.username.clone(); if match_guard.player1 == player2.username { player2.color = Color::Red; @@ -245,8 +263,27 @@ impl Tournament for KnockoutBracket { drop(player2); + broadcast_message( + &self.observers, + &format!("READY:{}:{}", player1_name, false), + ) + .await; + broadcast_message( + &self.observers, + &format!("READY:{}:{}", player2_name, false), + ) + .await; + self.current_matches.push(match_id); self.matches.write().await.insert(match_id, new_match.clone()); + broadcast_message( + &self.observers, + &format!( + "GAME:START:{},{},{}", + match_id, match_guard.player1, match_guard.player2 + ), + ) + .await; } let mut loser = String::new(); diff --git a/src/tournaments/round_robin.rs b/src/tournaments/round_robin.rs index e6a3301..5c3a601 100644 --- a/src/tournaments/round_robin.rs +++ b/src/tournaments/round_robin.rs @@ -16,6 +16,7 @@ pub struct RoundRobin { pub completed: bool, pub current_matches: Vec, pub usernames: Vec, + pub observers: Observers, } impl RoundRobin { @@ -39,12 +40,16 @@ impl RoundRobin { ))); self.current_matches.push(match_id.clone()); - let match_guard = new_match.read().await; + let mut player1 = clients_guard.get(&player1_username.0).unwrap().write().await; - player1.current_match = Some(match_id); player1.ready = false; + broadcast_message( + &self.observers, + &format!("READY:{}:{}", player1.username.clone(), false), + ) + .await; if match_guard.player1 == player1_username.0 { player1.color = Color::Red; @@ -53,13 +58,16 @@ impl RoundRobin { player1.color = Color::Yellow; let _ = send(&player1.connection, "GAME:START:0"); } - drop(player1); let mut player2 = clients_guard.get(&player2_addr.0).unwrap().write().await; - player2.current_match = Some(match_id); player2.ready = false; + broadcast_message( + &self.observers, + &format!("READY:{}:{}", player2.username.clone(), false), + ) + .await; if match_guard.player1 == player2_addr.0 { player2.color = Color::Red; @@ -68,17 +76,24 @@ impl RoundRobin { player2.color = Color::Yellow; let _ = send(&player2.connection, "GAME:START:0"); } - drop(player2); matches.write().await.insert(match_id, new_match.clone()); + broadcast_message( + &self.observers, + &format!( + "GAME:START:{},{},{}", + match_id, match_guard.player1, match_guard.player2 + ), + ) + .await; } } } #[async_trait] impl Tournament for RoundRobin { - async fn new(ready_players: &[String], _: &Server) -> RoundRobin { + async fn new(ready_players: &[String], server: &Server) -> RoundRobin { let mut result = RoundRobin { players: HashMap::new(), top_half: Vec::new(), @@ -86,6 +101,7 @@ impl Tournament for RoundRobin { completed: false, current_matches: Vec::new(), usernames: ready_players.to_vec(), + observers: server.observers.clone(), }; let size = ready_players.len();