Live location tracking and playback for the game "manhunt"

Move room code generation to signaling server

bwc9876.dev 6be4c69f 9231a51d

verified
+69 -9
+1
Cargo.lock
··· 2981 2981 "log", 2982 2982 "matchbox_protocol", 2983 2983 "matchbox_signaling", 2984 + "rand 0.9.1", 2984 2985 "tokio", 2985 2986 "tokio-util", 2986 2987 "uuid",
+12 -2
backend/src/lib.rs
··· 11 11 Game as BaseGame, GameSettings, GameUiState, Lobby as BaseLobby, LobbyState, PlayerProfile, 12 12 StartGameInfo, StateUpdateSender, 13 13 }; 14 - use manhunt_transport::{MatchboxTransport, generate_join_code, room_exists}; 14 + use manhunt_transport::{MatchboxTransport, request_room_code, room_exists}; 15 15 use serde::{Deserialize, Serialize}; 16 16 use tauri::{AppHandle, Manager, State}; 17 17 use tauri_plugin_dialog::{DialogExt, MessageDialogKind}; ··· 236 236 ) { 237 237 if let AppState::Menu(profile) = self { 238 238 let host = join_code.is_none(); 239 - let room_code = join_code.unwrap_or_else(generate_join_code); 239 + let room_code = if let Some(code) = join_code { 240 + code.to_ascii_uppercase() 241 + } else { 242 + match request_room_code().await { 243 + Ok(code) => code, 244 + Err(why) => { 245 + error_dialog(&app, &format!("Couldn't create a lobby\n\n{why:?}")); 246 + return; 247 + } 248 + } 249 + }; 240 250 let state_updates = TauriStateUpdateSender::<LobbyStateUpdate>::new(&app); 241 251 let lobby = 242 252 Lobby::new(&room_code, host, profile.clone(), settings, state_updates).await;
+1
flake.nix
··· 76 76 just 77 77 pango 78 78 webkitgtk_4_1 79 + cargo-nextest 79 80 openssl 80 81 pkg-config 81 82 gobject-introspection
+1
manhunt-signaling/Cargo.toml
··· 12 12 log = "0.4.27" 13 13 matchbox_protocol = "0.12.0" 14 14 matchbox_signaling = "0.12.0" 15 + rand = { version = "0.9.1", features = ["thread_rng"] } 15 16 tokio = { version = "1.45.1", features = ["macros"] } 16 17 tokio-util = "0.7.15" 17 18 uuid = "1.17.0"
+9
manhunt-signaling/src/main.rs
··· 63 63 let state = state.clone(); 64 64 move |router| { 65 65 let mut state2 = state.clone(); 66 + let state3 = state.clone(); 66 67 router 67 68 .route( 68 69 "/room_exists/{id}", ··· 79 80 post(move |Path(room_id): Path<String>| async move { 80 81 state2.mark_started(&room_id); 81 82 StatusCode::OK 83 + }), 84 + ) 85 + .route( 86 + "/gen_code", 87 + get(move || async move { 88 + state3 89 + .generate_room_code() 90 + .map_err(|_| StatusCode::CONFLICT) 82 91 }), 83 92 ) 84 93 }
+32
manhunt-signaling/src/state.rs
··· 9 9 SignalingError, SignalingState, 10 10 common_logic::{self, StateObj}, 11 11 }; 12 + use rand::{rngs::ThreadRng, seq::IndexedRandom}; 12 13 use tokio::sync::mpsc::UnboundedSender; 13 14 use tokio_util::sync::CancellationToken; 14 15 ··· 65 66 } 66 67 } 67 68 69 + const ROOM_CODE_CHAR_POOL: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"; 70 + const ROOM_CODE_LEN: usize = 6; 71 + const MAX_ROOM_TRIES: usize = 25; 72 + 73 + #[derive(Debug, Default, Clone, Copy)] 74 + pub struct NoRoomsError; 75 + 68 76 impl ServerState { 77 + fn random_room_code(rng: &mut ThreadRng) -> RoomId { 78 + ROOM_CODE_CHAR_POOL 79 + .choose_multiple(rng, ROOM_CODE_LEN) 80 + .copied() 81 + .map(char::from) 82 + .collect() 83 + } 84 + 85 + fn check_room_taken(&self, code: &RoomId) -> bool { 86 + self.matches.lock().unwrap().contains_key(code) 87 + } 88 + 89 + pub fn generate_room_code(&self) -> Result<RoomId, NoRoomsError> { 90 + let mut rng = rand::rng(); 91 + for _ in 0..MAX_ROOM_TRIES { 92 + let code = Self::random_room_code(&mut rng); 93 + 94 + if !self.check_room_taken(&code) { 95 + return Ok(code); 96 + } 97 + } 98 + Err(NoRoomsError) 99 + } 100 + 69 101 fn add_client(&mut self, origin: SocketAddr, code: RoomId, host: bool) { 70 102 self.waiting_clients 71 103 .lock()
+1 -1
manhunt-transport/src/lib.rs
··· 3 3 mod server; 4 4 5 5 pub use matchbox::MatchboxTransport; 6 - pub use server::{generate_join_code, room_exists}; 6 + pub use server::{request_room_code, room_exists};
+12 -6
manhunt-transport/src/server.rs
··· 66 66 .await 67 67 .context("Could not send request")? 68 68 .error_for_status() 69 - .context("Server returned error")?; 69 + .context("Server returned an error")?; 70 70 Ok(()) 71 71 } 72 72 73 - pub fn generate_join_code() -> String { 74 - // 5 character sequence of A-Z 75 - (0..5) 76 - .map(|_| (b'A' + rand::random_range(0..26)) as char) 77 - .collect::<String>() 73 + const SERVER_GEN_CODE_URL: &str = const_str::concat!(SERVER_HTTP_URL, "/gen_code"); 74 + 75 + pub async fn request_room_code() -> Result<String> { 76 + reqwest::get(SERVER_GEN_CODE_URL) 77 + .await 78 + .context("Failed to contact signaling server")? 79 + .error_for_status() 80 + .context("Server returned an error")? 81 + .text() 82 + .await 83 + .context("Failed to decode response") 78 84 }