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.
This commit is contained in:
Stephen Seo 2022-04-30 16:27:43 +09:00
parent b4eaba09c5
commit b2ea79a7f7
5 changed files with 99 additions and 38 deletions

View file

@ -31,8 +31,14 @@ pub type GetIDSenderType = (Option<u32>, Option<bool>);
/// third bool is if cyan player /// third bool is if cyan player
pub type CheckPairingType = (bool, bool, bool); pub type CheckPairingType = (bool, bool, bool);
/// second String is board string, third String is received emote type /// second String is board string, third String is date updated, fourth value
pub type BoardStateType = (DBGameState, Option<String>, Option<EmoteEnum>); /// is EmoteEnum
pub type BoardStateType = (
DBGameState,
Option<String>,
Option<String>,
Option<EmoteEnum>,
);
pub type PlaceResultType = Result<(DBPlaceStatus, Option<String>), DBPlaceError>; 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 // don't stop server on send fail, may have timed out and
// dropped the receiver // dropped the receiver
response_sender response_sender
.send((DBGameState::UnknownID, None, None)) .send((DBGameState::UnknownID, None, None, None))
.ok(); .ok();
return false; return false;
} }
@ -677,66 +683,91 @@ impl DBHandler {
} }
// TODO maybe handle "opponent_disconnected" case // TODO maybe handle "opponent_disconnected" case
let row_result: Result<(String, i64, Option<u32>, Option<u32>), RusqliteError> = conn.query_row( 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 FROM games JOIN players WHERE players.id = ? AND games.id = players.game_id;", "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], [player_id],
|row| { |row| {
let board_result = row.get(0); let board_result = row.get(0);
let status_result = row.get(1); let status_result = row.get(1);
let cyan_player = row.get(2); let cyan_player = row.get(2);
let magenta_player = row.get(3); let magenta_player = row.get(3);
if board_result.is_ok() && status_result.is_ok() && cyan_player.is_ok() && magenta_player.is_ok() { let updated_time = row.get(4);
if let (Ok(board), Ok(status), Ok(cyan_id), Ok(magenta_id)) = (board_result, status_result, cyan_player, magenta_player) { if board_result.is_ok() && status_result.is_ok() && cyan_player.is_ok() && magenta_player.is_ok() && updated_time.is_ok() {
Ok((board, status, cyan_id, magenta_id)) 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 { } else {
unreachable!("Both row items should be Ok"); unreachable!("All row items should be Ok");
} }
} else if board_result.is_err() { } else if board_result.is_err() {
board_result 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() { } else if status_result.is_err() {
status_result 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() { } else if cyan_player.is_err() {
cyan_player cyan_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 { } else if magenta_player.is_err() {
magenta_player 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 { if board.len() != (ROWS * COLS) as usize {
// board is invalid size // 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() { } else if cyan_opt.is_none() || magenta_opt.is_none() {
// One player disconnected // One player disconnected
self.disconnect_player(Some(conn), player_id).ok(); self.disconnect_player(Some(conn), player_id).ok();
// Remove the game(s) with disconnected players // Remove the game(s) with disconnected players
if self.clear_empty_games(Some(conn)).is_err() { 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 { } 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 { } else {
Ok(( Ok((
DBGameState::OpponentDisconnected, DBGameState::OpponentDisconnected,
Some(board), Some(board),
Some(updated_time),
received_emote, received_emote,
)) ))
} }
} else { } else {
// Game in progress, or other state depending on "status" // 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 { } else if let Err(RusqliteError::QueryReturnedNoRows) = row_result {
// No rows is either player doesn't exist or not paired // No rows is either player doesn't exist or not paired
let (exists, is_paired, _is_cyan) = let (exists, is_paired, _is_cyan) =
self.check_if_player_is_paired(Some(conn), player_id)?; self.check_if_player_is_paired(Some(conn), player_id)?;
if !exists { if !exists {
Ok((DBGameState::UnknownID, None, received_emote)) Ok((DBGameState::UnknownID, None, None, received_emote))
} else if !is_paired { } else if !is_paired {
Ok((DBGameState::NotPaired, None, received_emote)) Ok((DBGameState::NotPaired, None, None, received_emote))
} else { } else {
unreachable!("either exists or is_paired must be false"); unreachable!("either exists or is_paired must be false");
} }

View 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()); 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) resp_rx.recv_timeout(DB_REQUEST_TIMEOUT)
{ {
if let Some(board_string) = board_string_opt { 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 { if let Some(emote) = received_emote_opt {
Ok(format!( Ok(format!(
"{{\"type\":\"game_state\", \"status\":\"{}\", \"board\":\"{}\", \"peer_emote\": \"{}\"}}", "{{\"type\":\"game_state\", \"status\":\"{}\", \"board\":\"{}\", \"peer_emote\": \"{}\", \"updated_time\": \"{}\"}}",
db_game_state, board_string, emote db_game_state, board_string, emote, updated_time
)) ))
} else { } else {
Ok(format!( Ok(format!(
"{{\"type\":\"game_state\", \"status\":\"{}\", \"board\":\"{}\"}}", "{{\"type\":\"game_state\", \"status\":\"{}\", \"board\":\"{}\", \"updated_time\": \"{}\"}}",
db_game_state, board_string db_game_state, board_string, updated_time
)) ))
} }
} else { } else {

View file

@ -558,6 +558,7 @@ pub struct GameStateResponse {
pub status: String, pub status: String,
pub board: Option<String>, pub board: Option<String>,
pub peer_emote: Option<String>, pub peer_emote: Option<String>,
pub updated_time: Option<String>,
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]

View file

@ -392,6 +392,7 @@ pub struct Wrapper {
place_request: Option<u8>, place_request: Option<u8>,
do_backend_tick: bool, do_backend_tick: bool,
cleanup_id_callback: Rc<RefCell<Option<Function>>>, cleanup_id_callback: Rc<RefCell<Option<Function>>>,
board_updated_time: Option<String>,
} }
impl Wrapper { impl Wrapper {
@ -555,11 +556,12 @@ impl Wrapper {
_ => NetworkedGameState::InternalError, _ => NetworkedGameState::InternalError,
}; };
WrapperMsg::BackendResponse(BREnum::GotStatus( WrapperMsg::BackendResponse(BREnum::GotStatus {
networked_game_state, networked_game_state,
response.board, board_string: response.board,
response.peer_emote, received_emote: response.peer_emote,
)) updated_time: response.updated_time,
})
}); });
} }
@ -725,8 +727,14 @@ pub enum BREnum {
Error(String), Error(String),
GotID(u32, Option<Turn>), GotID(u32, Option<Turn>),
GotPairing(Option<Turn>), GotPairing(Option<Turn>),
/// Second opt string is board_str, third opt string is received emote /// Second opt string is board_str, third opt string is received emote,
GotStatus(NetworkedGameState, Option<String>, Option<String>), /// 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), GotPlaced(PlacedEnum, String),
} }
@ -762,6 +770,7 @@ impl Component for Wrapper {
place_request: None, place_request: None,
do_backend_tick: true, do_backend_tick: true,
cleanup_id_callback: Rc::new(RefCell::new(None)), cleanup_id_callback: Rc::new(RefCell::new(None)),
board_updated_time: None,
} }
} }
@ -1526,13 +1535,18 @@ impl Component for Wrapper {
.ok(); .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 let current_side = shared
.game_state .game_state
.borrow() .borrow()
.get_networked_current_side() .get_networked_current_side()
.expect("Should be Networked mode"); .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()) { if let Ok(emote_enum) = EmoteEnum::try_from(emote_string.as_str()) {
append_to_info_text( append_to_info_text(
&document, &document,
@ -1561,9 +1575,15 @@ impl Component for Wrapper {
} }
} }
if let Some(board_string) = board_opt { // 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); self.update_board_from_string(&shared, &document, board_string);
} }
}
}
let mut current_game_state: GameState = shared.game_state.borrow().clone(); let mut current_game_state: GameState = shared.game_state.borrow().clone();
match networked_game_state { match networked_game_state {

View file

@ -149,7 +149,7 @@ then the back-end will respond with "too\_many\_players".
// "opponent_disconnected", "internal_error" // "opponent_disconnected", "internal_error"
// "board" may not be in the response if "unknown_id" is the status // "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 // a - empty
// b - cyan // b - cyan
// c - magenta // c - magenta
@ -160,7 +160,10 @@ then the back-end will respond with "too\_many\_players".
// h - cyan winning and placed piece // h - cyan winning and placed piece
// i - magenta winning and placed piece // i - magenta winning and placed piece
// optional "peer_emote" entry is message from opponent // 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"
} }
``` ```