diff --git a/server/src/api.rs b/server/src/api.rs index 7c53fb0..8539882 100644 --- a/server/src/api.rs +++ b/server/src/api.rs @@ -16,132 +16,23 @@ use std::{net::SocketAddr, sync::Arc}; pub mod message_handler; use crate::message_handler::*; -/// Generate message-of-the-day server greeting -fn motd() -> String { - to_string::(&ChatMessage { - text: "Greetings from the game server!".to_string(), - }) - .unwrap() -} - -/// Generate server summary update - mostly debug stuff -fn server_summary_update(state: &Arc) -> String { - to_string::(&ServerStateSummary { - online_users: state.online_users.lock().unwrap().len(), - active_games: state.games.lock().unwrap().len(), - }) - .unwrap() -} - -/// Generate chatroom metadata update -fn chat_meta_update(state: &Arc) -> String { - // this may get expensive if there are many users - let mut names = vec![]; - - for user in state.online_users.lock().unwrap().iter() { - names.push(user.1.name.clone()); - } - - to_string::(&ChatUpdate { - room: "Lobby".to_string(), - users: names, - }) - .unwrap() -} - -/// Generate games list update -fn games_update(state: &Arc) -> String { - // this may get expensive if there are many games - let mut names = vec![]; - - for game in state.games.lock().unwrap().iter() { - names.push(game.name.clone()); - } - - to_string::(&GamesUpdate { games: names }).unwrap() -} - -/// Generate chatroom join announcement -fn announce_join(state: &Arc, addr: &SocketAddr) -> String { - let msg = format!( - "{} joined.", - state.online_users.lock().unwrap().get(addr).unwrap().name - ); - - tracing::debug!("{}", &msg); - to_string::(&ChatMessage { text: msg }).unwrap() -} - -/// Create a new user object from incoming data -fn generate_new_user(state: &Arc) -> User { - User { - name: format!( - "{} {}", - state.first_names.choose(&mut rand::thread_rng()).unwrap(), - state.last_names.choose(&mut rand::thread_rng()).unwrap(), - ), - } -} - -/// Generate message to notify client of user changes -fn client_self_user_update(new_user: &User) -> String { - to_string::(&UserUpdate { - username: new_user.name.clone(), - }) - .unwrap() -} - -/// Create, Register, and Hydrate new user -async fn handle_new_user( - sender: &mut SplitSink, - state: &Arc, - addr: &SocketAddr, -) -> Result<()> { - // Create - let new_user = Box::pin(generate_new_user(state)); - tracing::debug!("User created at ptr: {:p}", new_user); - tracing::debug!("User borrowed ptr: {:p}", *&new_user); - - // Notify client of new username - sender - .send(Message::Text(client_self_user_update(&new_user))) - .await?; - - // Register using `addr` as key until something longer lived exists - state.online_users.lock().unwrap().insert(*addr, new_user); - tracing::debug!( - "New user inserted at ptr: {:p}", - state.online_users.lock().unwrap().get(addr).unwrap() - ); - tracing::debug!( - "New user hashmap deref ptr: {:p}", - *state.online_users.lock().unwrap().get(addr).unwrap() - ); - - // Hydrate client - // this should probably be combined and sent as one - sender.send(Message::Text(chat_meta_update(state))).await?; - sender.send(Message::Text(motd())).await?; - sender - .send(Message::Text(server_summary_update(state))) - .await?; - sender.send(Message::Text(games_update(state))).await?; - state.tx.send(announce_join(state, addr))?; - state.tx.send(server_summary_update(state))?; - state.tx.send(chat_meta_update(state))?; - - Ok(()) +/// Establish the WebSocket connection +pub async fn websocket_connection_handler( + ws: WebSocketUpgrade, + // user_agent: Option>, + ConnectInfo(addr): ConnectInfo, + State(state): State>, +) -> impl IntoResponse { + tracing::debug!("New connection from {}", &addr); + ws.on_upgrade(move |socket| on_websocket_connection(socket, state, addr)) } /// This runs right after a WebSocket connection is established -pub async fn on_websocket_connection( - stream: WebSocket, - state: Arc, - addr: SocketAddr, -) { +pub async fn on_websocket_connection(stream: WebSocket, state: Arc, addr: SocketAddr) { // Split channels to send and receive asynchronously. let (mut sender, mut receiver) = stream.split(); + // Set up new user handle_new_user(&mut sender, &state, &addr) .await .expect("Error creating new user!"); @@ -174,13 +65,122 @@ pub async fn on_websocket_connection( }; } -/// Establish the WebSocket connection -pub async fn websocket_connection_handler( - ws: WebSocketUpgrade, - // user_agent: Option>, - ConnectInfo(addr): ConnectInfo, - State(state): State>, -) -> impl IntoResponse { - tracing::debug!("New connection from {}", &addr); - ws.on_upgrade(move |socket| on_websocket_connection(socket, state, addr)) +/// Create, Register, and Hydrate new user +async fn handle_new_user( + sender: &mut SplitSink, + state: &Arc, + addr: &SocketAddr, +) -> Result<()> { + // Create + let new_user = Box::pin(generate_new_user(&state)); + tracing::debug!("User created at ptr: {:p}", new_user); + tracing::debug!("User borrowed ptr: {:p}", *&new_user); + + // Notify client of new username + sender + .send(Message::Text(client_self_user_update(&new_user))) + .await?; + + // Register using `addr` as key until something longer lived exists + state.online_users.lock().unwrap().insert(*addr, new_user); + tracing::debug!( + "New user inserted at ptr: {:p}", + state.online_users.lock().unwrap().get(addr).unwrap() + ); + tracing::debug!( + "New user hashmap deref ptr: {:p}", + *state.online_users.lock().unwrap().get(addr).unwrap() + ); + + // Hydrate client + // this should probably be combined and sent as one + sender.send(Message::Text(chat_meta_update(&state))).await?; + sender.send(Message::Text(motd())).await?; + sender + .send(Message::Text(server_summary_update(&state))) + .await?; + sender.send(Message::Text(games_update(&state))).await?; + + // Broadcast new user's existence + // this should probably be combined and sent as one + state.tx.send(announce_join(&state, addr))?; + state.tx.send(server_summary_update(&state))?; + state.tx.send(chat_meta_update(&state))?; + + Ok(()) +} + +/// Create a new user object from incoming data +fn generate_new_user(state: &Arc) -> User { + User { + name: format!( + "{} {}", + state.first_names.choose(&mut rand::thread_rng()).unwrap(), + state.last_names.choose(&mut rand::thread_rng()).unwrap(), + ), + } +} + +/// Generate message to notify client of user changes +fn client_self_user_update(new_user: &User) -> String { + to_string::(&UserUpdate { + username: new_user.name.clone(), + }) + .unwrap() +} + +/// Generate chatroom metadata update +fn chat_meta_update(state: &Arc) -> String { + // this may get expensive if there are many users + let mut names = vec![]; + + for user in state.online_users.lock().unwrap().iter() { + names.push(user.1.name.clone()); + } + + to_string::(&ChatUpdate { + room: "Lobby".to_string(), + users: names, + }) + .unwrap() +} + +/// Generate message-of-the-day server greeting +fn motd() -> String { + to_string::(&ChatMessage { + text: "Greetings from the game server!".to_string(), + }) + .unwrap() +} + +/// Generate server summary update - mostly debug stuff +fn server_summary_update(state: &Arc) -> String { + to_string::(&ServerStateSummary { + online_users: state.online_users.lock().unwrap().len(), + active_games: state.games.lock().unwrap().len(), + }) + .unwrap() +} + +/// Generate games list update +fn games_update(state: &Arc) -> String { + // this may get expensive if there are many games + let mut names = vec![]; + + for game in state.games.lock().unwrap().iter() { + names.push(game.name.clone()); + } + + to_string::(&GamesUpdate { games: names }).unwrap() +} + +/// Generate chatroom join announcement +fn announce_join(state: &Arc, addr: &SocketAddr) -> String { + let msg = format!( + "{} joined.", + state.online_users.lock().unwrap().get(addr).unwrap().name + ); + + tracing::debug!("{}", &msg); + to_string::(&ChatMessage { text: msg }).unwrap() } diff --git a/server/src/api/message_handler.rs b/server/src/api/message_handler.rs index 72c7922..05b1dab 100644 --- a/server/src/api/message_handler.rs +++ b/server/src/api/message_handler.rs @@ -6,6 +6,47 @@ use axum::extract::ws::CloseFrame; use serde_json::{from_str, to_string}; use tokio::sync::broadcast::Sender; +/// Handle incoming messages over the WebSocket +pub async fn message_handler( + state: Arc, + addr: SocketAddr, + message: Message, +) -> Result<()> { + let tx = &state.tx; + + match message { + Message::Text(text) => match text { + _new_game if let Ok(_new_game) = from_str::(&text) => { + tracing::debug!("New game request received."); + handle_new_game(_new_game, &state, tx, addr)?; + } + _chat_message if let Ok(_chat_message) = from_str::(&text) => { + handle_chat_message(_chat_message, &state, tx, addr)?; + } + _user_log_in if let Ok(_user_log_in) = from_str::(&text) => { + handle_user_log_in(_user_log_in, &state, tx, addr)?; + } + _ => { + tracing::debug!("Unhandled text message: {}", &text); + } + }, + Message::Binary(data) => { + tracing::debug!("Binary: {:?}", data) + } + Message::Close(close_frame) => { + handle_close(close_frame, &state, tx, addr)?; + } + Message::Pong(ping) => { + tracing::debug!("Pong received with: {:?}", ping); + } + Message::Ping(pong) => { + tracing::debug!("Pong received with: {:?}", pong); + } + } + + Ok(()) +} + /// This runs when a NewGameRequest is received fn handle_new_game( new_game: NewGameRequest, @@ -181,49 +222,8 @@ fn handle_close( "User offline deref ptr: {:p}", *state.offline_users.lock().unwrap().get(&name).unwrap() ); - tx.send(server_summary_update(state))?; - tx.send(chat_meta_update(state))?; - - Ok(()) -} - -/// Handle incoming messages over the WebSocket -pub async fn message_handler( - state: Arc, - addr: SocketAddr, - message: Message, -) -> Result<()> { - let tx = &state.tx; - - match message { - Message::Text(text) => match text { - _new_game if let Ok(_new_game) = from_str::(&text) => { - tracing::debug!("New game request received."); - handle_new_game(_new_game, &state, tx, addr)?; - } - _chat_message if let Ok(_chat_message) = from_str::(&text) => { - handle_chat_message(_chat_message, &state, tx, addr)?; - } - _user_log_in if let Ok(_user_log_in) = from_str::(&text) => { - handle_user_log_in(_user_log_in, &state, tx, addr)?; - } - _ => { - tracing::debug!("Unhandled text message: {}", &text); - } - }, - Message::Binary(data) => { - tracing::debug!("Binary: {:?}", data) - } - Message::Close(close_frame) => { - handle_close(close_frame, &state, tx, addr)?; - } - Message::Pong(ping) => { - tracing::debug!("Pong received with: {:?}", ping); - } - Message::Ping(pong) => { - tracing::debug!("Pong received with: {:?}", pong); - } - } + tx.send(server_summary_update(&state))?; + tx.send(chat_meta_update(&state))?; Ok(()) }