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 },
}
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 },
NewMessage { username: String, message: String },
LeaveGameSuccess,
LeaveGameFailure { reason: String },
+ LeaveLobbySuccess,
+ LeaveLobbyFailure { reason: String },
HandHistory { games: Vec<Game> },
ProtocolError { reason: String },
}
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 {
#[derive(Debug, Clone)]
pub enum LoggedInState {
- InLobby,
+ Idle,
+ InLobby { game_list: GameList },
InGame { game: Game },
}
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();
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});
+ }
}
}
}
}
(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 {
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}) => {
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);
}
}
}
+ (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
}
},
}
+#[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 {
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;
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 {
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 {
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<()> {
}
}
+#[derive(Debug, Clone)]
struct AsJson<T>(T);
impl<T> AsJson<T> {