From b2ea79a7f7c63d9b374d7a9d4b1cfc431ab40cd0 Mon Sep 17 00:00:00 2001 From: Stephen Seo Date: Sat, 30 Apr 2022 16:27:43 +0900 Subject: [PATCH] Impl conditionally update front-end board When the front-end polls the back-end for the game-state, the back-end includes a "date_updated" String in the JSON. If the String is the same as in the front-end, then no updates are needed, but if they are not the same, then the front-end will update the board. Because the front-end polls the back-end's board state approximately every second, this should make the front-end more efficient. --- back_end/src/db_handler.rs | 73 +++++++++++++------ back_end/src/json_handlers.rs | 16 ++-- front_end/src/state.rs | 1 + front_end/src/yew_components.rs | 40 +++++++--- .../backend_protocol_specification.md | 7 +- 5 files changed, 99 insertions(+), 38 deletions(-) diff --git a/back_end/src/db_handler.rs b/back_end/src/db_handler.rs index f08bcf5..5420473 100644 --- a/back_end/src/db_handler.rs +++ b/back_end/src/db_handler.rs @@ -31,8 +31,14 @@ pub type GetIDSenderType = (Option, Option); /// third bool is if cyan player pub type CheckPairingType = (bool, bool, bool); -/// second String is board string, third String is received emote type -pub type BoardStateType = (DBGameState, Option, Option); +/// second String is board string, third String is date updated, fourth value +/// is EmoteEnum +pub type BoardStateType = ( + DBGameState, + Option, + Option, + Option, +); pub type PlaceResultType = Result<(DBPlaceStatus, Option), DBPlaceError>; @@ -250,7 +256,7 @@ impl DBHandler { // don't stop server on send fail, may have timed out and // dropped the receiver response_sender - .send((DBGameState::UnknownID, None, None)) + .send((DBGameState::UnknownID, None, None, None)) .ok(); return false; } @@ -677,66 +683,91 @@ impl DBHandler { } // TODO maybe handle "opponent_disconnected" case - let row_result: Result<(String, i64, Option, Option), RusqliteError> = conn.query_row( - "SELECT games.board, games.status, games.cyan_player, games.magenta_player FROM games JOIN players WHERE players.id = ? AND games.id = players.game_id;", + let row_result: Result<(String, i64, Option, Option, String), RusqliteError> = conn.query_row( + "SELECT games.board, games.status, games.cyan_player, games.magenta_player, games.turn_time_start FROM games JOIN players WHERE players.id = ? AND games.id = players.game_id;", [player_id], |row| { let board_result = row.get(0); let status_result = row.get(1); let cyan_player = row.get(2); let magenta_player = row.get(3); - if board_result.is_ok() && status_result.is_ok() && cyan_player.is_ok() && magenta_player.is_ok() { - if let (Ok(board), Ok(status), Ok(cyan_id), Ok(magenta_id)) = (board_result, status_result, cyan_player, magenta_player) { - Ok((board, status, cyan_id, magenta_id)) + let updated_time = row.get(4); + if board_result.is_ok() && status_result.is_ok() && cyan_player.is_ok() && magenta_player.is_ok() && updated_time.is_ok() { + if let (Ok(board), Ok(status), Ok(cyan_id), Ok(magenta_id), Ok(updated_time)) = (board_result, status_result, cyan_player, magenta_player, updated_time) { + Ok((board, status, cyan_id, magenta_id, updated_time)) } else { - unreachable!("Both row items should be Ok"); + unreachable!("All row items should be Ok"); } } else if board_result.is_err() { board_result - .map(|_| (String::from("this value should never be returned"), 0, None, None)) + .map(|_| (String::from("this value should never be returned"), 0, None, None, String::new())) } else if status_result.is_err() { status_result - .map(|_| (String::from("this value should never be returned"), 0, None, None)) + .map(|_| (String::from("this value should never be returned"), 0, None, None, String::new())) } else if cyan_player.is_err() { cyan_player - .map(|_| (String::from("this value should never be returned"), 0, None, None)) - } else { + .map(|_| (String::from("this value should never be returned"), 0, None, None, String::new())) + } else if magenta_player.is_err() { magenta_player - .map(|_| (String::from("this value should never be returned"), 0, None, None)) + .map(|_| (String::from("this value should never be returned"), 0, None, None, String::new())) + } else { + updated_time + .map(|_| (String::from("this value should never be returned"), 0, None, None, String::new())) } } ); - if let Ok((board, status, cyan_opt, magenta_opt)) = row_result { + if let Ok((board, status, cyan_opt, magenta_opt, updated_time)) = row_result { if board.len() != (ROWS * COLS) as usize { // board is invalid size - Ok((DBGameState::InternalError, None, received_emote)) + Ok(( + DBGameState::InternalError, + None, + Some(updated_time), + received_emote, + )) } else if cyan_opt.is_none() || magenta_opt.is_none() { // One player disconnected self.disconnect_player(Some(conn), player_id).ok(); // Remove the game(s) with disconnected players if self.clear_empty_games(Some(conn)).is_err() { - Ok((DBGameState::InternalError, None, received_emote)) + Ok(( + DBGameState::InternalError, + None, + Some(updated_time), + received_emote, + )) } else if status == 2 || status == 3 { - Ok((DBGameState::from(status), Some(board), received_emote)) + Ok(( + DBGameState::from(status), + Some(board), + Some(updated_time), + received_emote, + )) } else { Ok(( DBGameState::OpponentDisconnected, Some(board), + Some(updated_time), received_emote, )) } } else { // Game in progress, or other state depending on "status" - Ok((DBGameState::from(status), Some(board), received_emote)) + Ok(( + DBGameState::from(status), + Some(board), + Some(updated_time), + received_emote, + )) } } else if let Err(RusqliteError::QueryReturnedNoRows) = row_result { // No rows is either player doesn't exist or not paired let (exists, is_paired, _is_cyan) = self.check_if_player_is_paired(Some(conn), player_id)?; if !exists { - Ok((DBGameState::UnknownID, None, received_emote)) + Ok((DBGameState::UnknownID, None, None, received_emote)) } else if !is_paired { - Ok((DBGameState::NotPaired, None, received_emote)) + Ok((DBGameState::NotPaired, None, None, received_emote)) } else { unreachable!("either exists or is_paired must be false"); } diff --git a/back_end/src/json_handlers.rs b/back_end/src/json_handlers.rs index 81e376b..35a7e7a 100644 --- a/back_end/src/json_handlers.rs +++ b/back_end/src/json_handlers.rs @@ -266,19 +266,25 @@ fn handle_game_state(root: Value, tx: SyncSender) -> Result, pub peer_emote: Option, + pub updated_time: Option, } #[derive(Debug, Serialize, Deserialize)] diff --git a/front_end/src/yew_components.rs b/front_end/src/yew_components.rs index 28f23ad..a8b07ea 100644 --- a/front_end/src/yew_components.rs +++ b/front_end/src/yew_components.rs @@ -392,6 +392,7 @@ pub struct Wrapper { place_request: Option, do_backend_tick: bool, cleanup_id_callback: Rc>>, + board_updated_time: Option, } impl Wrapper { @@ -555,11 +556,12 @@ impl Wrapper { _ => NetworkedGameState::InternalError, }; - WrapperMsg::BackendResponse(BREnum::GotStatus( + WrapperMsg::BackendResponse(BREnum::GotStatus { networked_game_state, - response.board, - response.peer_emote, - )) + board_string: response.board, + received_emote: response.peer_emote, + updated_time: response.updated_time, + }) }); } @@ -725,8 +727,14 @@ pub enum BREnum { Error(String), GotID(u32, Option), GotPairing(Option), - /// Second opt string is board_str, third opt string is received emote - GotStatus(NetworkedGameState, Option, Option), + /// Second opt string is board_str, third opt string is received emote, + /// fourth opt string is updated_time + GotStatus { + networked_game_state: NetworkedGameState, + board_string: Option, + received_emote: Option, + updated_time: Option, + }, GotPlaced(PlacedEnum, String), } @@ -762,6 +770,7 @@ impl Component for Wrapper { place_request: None, do_backend_tick: true, cleanup_id_callback: Rc::new(RefCell::new(None)), + board_updated_time: None, } } @@ -1526,13 +1535,18 @@ impl Component for Wrapper { .ok(); } } - BREnum::GotStatus(networked_game_state, board_opt, emote_opt) => { + BREnum::GotStatus { + networked_game_state, + board_string, + received_emote, + updated_time, + } => { let current_side = shared .game_state .borrow() .get_networked_current_side() .expect("Should be Networked mode"); - if let Some(emote_string) = emote_opt { + if let Some(emote_string) = received_emote { if let Ok(emote_enum) = EmoteEnum::try_from(emote_string.as_str()) { append_to_info_text( &document, @@ -1561,8 +1575,14 @@ impl Component for Wrapper { } } - if let Some(board_string) = board_opt { - self.update_board_from_string(&shared, &document, board_string); + // only update board string if updated_time is different + if self.board_updated_time != updated_time { + if let Some(updated_time) = updated_time { + self.board_updated_time.replace(updated_time); + if let Some(board_string) = board_string { + self.update_board_from_string(&shared, &document, board_string); + } + } } let mut current_game_state: GameState = shared.game_state.borrow().clone(); diff --git a/specifications/backend_protocol_specification.md b/specifications/backend_protocol_specification.md index 119cffe..d9ef9c8 100644 --- a/specifications/backend_protocol_specification.md +++ b/specifications/backend_protocol_specification.md @@ -149,7 +149,7 @@ then the back-end will respond with "too\_many\_players". // "opponent_disconnected", "internal_error" // "board" may not be in the response if "unknown_id" is the status - "board": "abcdefg..." // 56-char long string with mapping: + "board": "abcdefg...",// 56-char long string with mapping: // a - empty // b - cyan // c - magenta @@ -160,7 +160,10 @@ then the back-end will respond with "too\_many\_players". // h - cyan winning and placed piece // i - magenta winning and placed piece // optional "peer_emote" entry is message from opponent - "peer_emote": "smile" // or "frown", or "neutral", or "think" + "peer_emote": "smile",// or "frown", or "neutral", or "think" + + // should always be available when "board" is available + "updated_time": "2022-04-30 12:00:00" } ```