]> git.seodisparate.com - EN605.607.81.SP22_ASDM_Project/commitdiff
Impl conditionally update front-end board
authorStephen Seo <seo.disparate@gmail.com>
Sat, 30 Apr 2022 07:27:43 +0000 (16:27 +0900)
committerStephen Seo <seo.disparate@gmail.com>
Sat, 30 Apr 2022 07:44:48 +0000 (16:44 +0900)
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
back_end/src/json_handlers.rs
front_end/src/state.rs
front_end/src/yew_components.rs
specifications/backend_protocol_specification.md

index f08bcf546038a462c46c8c87b9e2fbe0018f92d1..54204737f41eb73859d2860c67c0b51a469ac3e5 100644 (file)
@@ -31,8 +31,14 @@ pub type GetIDSenderType = (Option<u32>, Option<bool>);
 /// 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<String>, Option<EmoteEnum>);
+/// second String is board string, third String is date updated, fourth value
+/// is EmoteEnum
+pub type BoardStateType = (
+    DBGameState,
+    Option<String>,
+    Option<String>,
+    Option<EmoteEnum>,
+);
 
 pub type PlaceResultType = Result<(DBPlaceStatus, Option<String>), 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<u32>, Option<u32>), 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<u32>, Option<u32>, 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");
             }
index 81e376b691ca7c34903ec2bcbe201f9affbc43e6..35a7e7af4f44e09395eba0a1a16bd0460bd2107d 100644 (file)
@@ -266,19 +266,25 @@ fn handle_game_state(root: Value, tx: SyncSender<DBHandlerRequest>) -> Result<St
         return Err("{\"type\":\"game_state\", \"status\":\"internal_error\"}".into());
     }
 
-    if let Ok((db_game_state, board_string_opt, received_emote_opt)) =
+    if let Ok((db_game_state, board_string_opt, updated_time_opt, received_emote_opt)) =
         resp_rx.recv_timeout(DB_REQUEST_TIMEOUT)
     {
         if let Some(board_string) = board_string_opt {
+            let updated_time = if let Some(time_string) = updated_time_opt {
+                time_string
+            } else {
+                return Err("{\"type\":\"game_state\", \"status\":\"internal_error\"}".into());
+            };
+
             if let Some(emote) = received_emote_opt {
                 Ok(format!(
-                    "{{\"type\":\"game_state\", \"status\":\"{}\", \"board\":\"{}\", \"peer_emote\": \"{}\"}}",
-                    db_game_state, board_string, emote
+                    "{{\"type\":\"game_state\", \"status\":\"{}\", \"board\":\"{}\", \"peer_emote\": \"{}\", \"updated_time\": \"{}\"}}",
+                    db_game_state, board_string, emote, updated_time
                 ))
             } else {
                 Ok(format!(
-                    "{{\"type\":\"game_state\", \"status\":\"{}\", \"board\":\"{}\"}}",
-                    db_game_state, board_string
+                    "{{\"type\":\"game_state\", \"status\":\"{}\", \"board\":\"{}\", \"updated_time\": \"{}\"}}",
+                    db_game_state, board_string, updated_time
                 ))
             }
         } else {
index 39d553861962f52025de6ba7d2317c123ef6638e..d2808f450694125ef45cb3bf4c8bdb6d13ebb990 100644 (file)
@@ -558,6 +558,7 @@ pub struct GameStateResponse {
     pub status: String,
     pub board: Option<String>,
     pub peer_emote: Option<String>,
+    pub updated_time: Option<String>,
 }
 
 #[derive(Debug, Serialize, Deserialize)]
index 28f23adb840586f0cfe31f1686390c8192453284..a8b07ea9076efccf5705d8508160c7fd403d1947 100644 (file)
@@ -392,6 +392,7 @@ pub struct Wrapper {
     place_request: Option<u8>,
     do_backend_tick: bool,
     cleanup_id_callback: Rc<RefCell<Option<Function>>>,
+    board_updated_time: Option<String>,
 }
 
 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<Turn>),
     GotPairing(Option<Turn>),
-    /// Second opt string is board_str, third opt string is received emote
-    GotStatus(NetworkedGameState, Option<String>, Option<String>),
+    /// 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<String>,
+        received_emote: Option<String>,
+        updated_time: Option<String>,
+    },
     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();
index 119cffee5f9cb274368c05d9369a1c93ead9bc8b..d9ef9c84f138d7ed45ff6f25688772ec81064f50 100644 (file)
@@ -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"
     }
 ```