add new JoinLobby command to subscribe to game list
authorGeoffrey Allott <geoffrey@allott.email>
Fri, 5 Feb 2021 19:33:21 +0000 (19:33 +0000)
committerGeoffrey Allott <geoffrey@allott.email>
Fri, 5 Feb 2021 19:33:21 +0000 (19:33 +0000)
src/api.rs
src/client.rs
src/game.rs
src/main.rs
src/server.rs

index 8daac46b4902b61dffbd3b2ec01811047f757ac5..c4f464c994fd0ad45085342f2798a66c3a5e9a8c 100644 (file)
@@ -19,11 +19,12 @@ pub enum ClientMessage {
     ChangeNickname { nickname: String },
     Logout,
     CreateGame { settings: GameSettings },
-    GetGameList { tags: Vec<String> },
+    JoinLobby { filter: String },
     JoinGame { id: u32 },
     TakeAction { action: Action },
     SendMessage { scope: Scope, message: String },
     LeaveGame,
+    LeaveLobby,
     GetHandHistory { scope: Scope },
 }
 
@@ -42,8 +43,9 @@ pub enum ServerMessage {
     LogoutSuccess,
     CreateGameSuccess { id: u32 },
     CreateGameFailure { reason: String },
-    GameList { games: Vec<GameSummary> },
-    GameListFailure { reason: String },
+    JoinLobbySuccess { games: Vec<GameSummary> },
+    JoinLobbyFailure { reason: String },
+    NewGame { game: GameSummary },
     JoinGameSuccess { game: Game },
     JoinGameFailure { reason: String },
     NewAction { action: UserAction },
@@ -52,6 +54,8 @@ pub enum ServerMessage {
     NewMessage { username: String, message: String },
     LeaveGameSuccess,
     LeaveGameFailure { reason: String },
+    LeaveLobbySuccess,
+    LeaveLobbyFailure { reason: String },
     HandHistory { games: Vec<Game> },
     ProtocolError { reason: String },
 }
index 2bb2e399d69c8f5aa771be78e53505716c369be3..d50873ab0fbf8c88ecb7415f84d8853ca3db512b 100644 (file)
@@ -3,7 +3,7 @@ use std::collections::HashSet;
 use futures::stream::{Stream, StreamExt, empty, iter, once};
 
 use crate::api::{ClientMessage, ServerMessage};
-use crate::game::{Game, UserAction};
+use crate::game::{Game, GameList, UserAction};
 use crate::server::{ActionStatus, ServerState};
 
 pub struct ConnectionState {
@@ -30,7 +30,8 @@ pub enum ClientInterest {
 
 #[derive(Debug, Clone)]
 pub enum LoggedInState {
-    InLobby,
+    Idle,
+    InLobby { game_list: GameList },
     InGame { game: Game },
 }
 
@@ -44,7 +45,16 @@ impl ConnectionState {
 
     pub async fn retrieve_updates(&mut self, update: ClientInterest) -> impl Stream<Item=ServerMessage> {
         match update {
-            ClientInterest::GameList => empty().boxed(), // TODO
+            ClientInterest::GameList => match &mut self.client {
+                ClientState::LoggedIn{state: LoggedInState::InLobby{ref mut game_list}, ..} => {
+                    let from = game_list.games_len();
+                    match self.server.update_game_list(game_list).await {
+                        Ok(()) => iter(game_list.update(from)).map(|game| ServerMessage::NewGame{game}).boxed(),
+                        Err(err) => once(async move { ServerMessage::ProtocolError{reason: err.to_string()} }).boxed(),
+                    }
+                }
+                _ => empty().boxed(),
+            }
             ClientInterest::Game{id} => match &mut self.client {
                 ClientState::LoggedIn{ref username, state: LoggedInState::InGame{ref mut game}} if game.id() == id => {
                     let from = game.actions_len();
@@ -64,12 +74,17 @@ impl ConnectionState {
         if let ClientState::LoggedIn{ref username, ref state} = &self.client {
             let username = username.to_string();
             ret.insert(ClientInterest::User{username});
-            ret.insert(ClientInterest::GameList);
-            if let LoggedInState::InGame{ref game} = state {
-                ret.insert(ClientInterest::Game{id: game.id()});
-                for username in game.players() {
-                    let username = username.to_string();
-                    ret.insert(ClientInterest::User{username});
+            match state {
+                LoggedInState::Idle => {},
+                LoggedInState::InLobby{..} => {
+                    ret.insert(ClientInterest::GameList);
+                }
+                LoggedInState::InGame{ref game} => {
+                    ret.insert(ClientInterest::Game{id: game.id()});
+                    for username in game.players() {
+                        let username = username.to_string();
+                        ret.insert(ClientInterest::User{username});
+                    }
                 }
             }
         }
@@ -91,7 +106,7 @@ impl ConnectionState {
             }
             (ClientState::LoginAuthIssued{username, challenge}, ClientMessage::LoginAuthResponse{signature}) => {
                 if self.server.verify(&username, &challenge, &signature).await {
-                    self.client = ClientState::LoggedIn{username: username.clone(), state: LoggedInState::InLobby};
+                    self.client = ClientState::LoggedIn{username: username.clone(), state: LoggedInState::Idle};
                     self.server.register_interests(self.interests()).await;
                     ServerMessage::LoginSuccess
                 } else {
@@ -99,10 +114,15 @@ impl ConnectionState {
                     ServerMessage::LoginFailure{reason: "Invalid username or password".to_string()}
                 }
             }
-            (ClientState::LoggedIn{..}, ClientMessage::GetGameList{..}) => {
-                match self.server.get_game_list().await {
-                    Ok(games) => ServerMessage::GameList{games},
-                    Err(err) => ServerMessage::GameListFailure{reason: err.to_string()},
+            (ClientState::LoggedIn{username, state: LoggedInState::Idle}, ClientMessage::JoinLobby{filter}) => {
+                let mut game_list = GameList::new(filter);
+                match self.server.update_game_list(&mut game_list).await {
+                    Ok(()) => {
+                        let games = game_list.update(0);
+                        self.client = ClientState::LoggedIn{username: username.clone(), state: LoggedInState::InLobby{game_list}};
+                        ServerMessage::JoinLobbySuccess{games}
+                    }
+                    Err(err) => ServerMessage::JoinLobbyFailure{reason: err.to_string()},
                 }
             }
             (ClientState::LoggedIn{..}, ClientMessage::CreateGame{settings}) => {
@@ -111,7 +131,7 @@ impl ConnectionState {
                     Err(err) => ServerMessage::CreateGameFailure{reason: err.to_string()},
                 }
             }
-            (ClientState::LoggedIn{username, state: LoggedInState::InLobby}, ClientMessage::JoinGame{id}) => {
+            (ClientState::LoggedIn{username, ..}, ClientMessage::JoinGame{id}) => {
                 match self.server.get_game(id).await {
                     Ok(game) => {
                         let game_view = game.view_for(&username);
@@ -144,8 +164,13 @@ impl ConnectionState {
                     }
                 }
             }
+            (ClientState::LoggedIn{username, state: LoggedInState::InLobby{..}}, ClientMessage::LeaveLobby) => {
+                self.client = ClientState::LoggedIn{username: username.clone(), state: LoggedInState::Idle};
+                self.server.register_interests(self.interests()).await;
+                ServerMessage::LeaveGameSuccess
+            }
             (ClientState::LoggedIn{username, state: LoggedInState::InGame{..}}, ClientMessage::LeaveGame) => {
-                self.client = ClientState::LoggedIn{username: username.clone(), state: LoggedInState::InLobby};
+                self.client = ClientState::LoggedIn{username: username.clone(), state: LoggedInState::Idle};
                 self.server.register_interests(self.interests()).await;
                 ServerMessage::LeaveGameSuccess
             }
index b4d8f073512a5af395cc4945d4761255917a228c..39e87e507d4dd776f85aded3441bfdf2f7a93e0e 100644 (file)
@@ -13,6 +13,34 @@ pub enum GameSettings {
     },
 }
 
+#[derive(Debug, Clone, Serialize, Deserialize)]
+#[serde(tag = "format")]
+pub struct GameList {
+    filter: String,
+    games: Vec<GameSummary>,
+}
+
+impl GameList {
+    pub fn new(filter: String) -> Self {
+        Self {
+            filter,
+            games: Vec::new(),
+        }
+    }
+
+    pub fn update(&self, from: usize) -> Vec<GameSummary> {
+        self.games.iter().skip(from).cloned().collect()
+    }
+
+    pub fn games_len(&self) -> usize {
+        self.games.len()
+    }
+
+    pub fn push(&mut self, game: GameSummary) {
+        self.games.push(game);
+    }
+}
+
 #[derive(Debug, Clone, Serialize, Deserialize)]
 #[serde(tag = "action")]
 pub enum Action {
index 793244c5c6edeab067171b0c9b2661c6ace37708..77da1d7d653728e617267f0b990dd077925f9f47 100644 (file)
@@ -5,7 +5,7 @@ use std::collections::HashSet;
 use std::convert::TryFrom;
 use std::mem::swap;
 
-use futures::{channel::mpsc::{channel, Receiver}, future::{Either, select}, select, FutureExt, SinkExt, StreamExt, pin_mut, stream::{self, FuturesUnordered, pending}};
+use futures::{channel::mpsc::{channel, Receiver}, future::{Either, select}, select, FutureExt, SinkExt, StreamExt, pin_mut, stream::{self, FuturesUnordered}};
 use redis::{aio::PubSub, Client, Msg};
 use signal_hook::consts::signal::*;
 use signal_hook_async_std::Signals;
@@ -52,7 +52,7 @@ pub async fn handle_client_interest(mut connection: PubSub, mut new_clients: Rec
                 next_client_interest.push(sender.register_interest.next().map(move |interest| (index, interest)));
             }
             let mut next_client_interest = next_client_interest.select_next_some();
-            let mut action = Action::ConnectionClosed;
+            let action;
             select! {
                 interest = next_interest => {
                     if let Some(interest) = interest {
index 0e32637d8cd21e20482de8089c178b1afbb17c6f..588644872f1ad4fee50c7b16de64f058e76074b0 100644 (file)
@@ -7,7 +7,7 @@ use serde::{Serialize, Deserialize};
 
 use crate::auth::Auth;
 use crate::client::ClientInterest;
-use crate::game::{Game, GameSettings, GameSummary, UserAction};
+use crate::game::{Game, GameList, GameSettings, GameSummary, UserAction};
 
 #[derive(Clone)]
 pub struct Server {
@@ -149,17 +149,19 @@ impl ServerState {
         self.redis.rpush("games", AsJson(settings)).await.map(|i: u32| i - 1)
     }
 
-    pub async fn get_game_list(&mut self) -> RedisResult<Vec<GameSummary>> {
+    pub async fn update_game_list(&mut self, game_list: &mut GameList) -> RedisResult<()> {
         const GAME_LIST_BLOCK_SIZE: isize = 1024;
-        let mut ret = Vec::new();
-        for i in (0..).step_by(GAME_LIST_BLOCK_SIZE as usize) {
+        debug!("update_game_list: from: {}", game_list.games_len());
+        for i in (game_list.games_len() as isize..).step_by(GAME_LIST_BLOCK_SIZE as usize) {
+            debug!("update_game_list: LRANGE games {} {}", i, i + GAME_LIST_BLOCK_SIZE);
             let games: Vec<AsJson<GameSettings>> = self.redis.lrange("games", i, i + GAME_LIST_BLOCK_SIZE).await?;
+            debug!("update_game_list: received games: {:#?}", games);
             if games.is_empty() { break; }
             for (j, AsJson(settings)) in games.into_iter().enumerate() {
-                ret.push(GameSummary::new(i as u32 + j as u32, settings));
+                game_list.push(GameSummary::new(i as u32 + j as u32, settings));
             }
         }
-        Ok(ret)
+        Ok(())
     }
 
     pub async fn update_game_state(&mut self, game: &mut Game) -> RedisResult<()> {
@@ -210,6 +212,7 @@ impl FromRedisValue for ActionStatus {
     }
 }
 
+#[derive(Debug, Clone)]
 struct AsJson<T>(T);
 
 impl<T> AsJson<T> {