use std::collections::HashSet;
use std::convert::TryFrom;
-use futures::{channel::mpsc::{Receiver, Sender, channel}, SinkExt};
+use futures::{channel::mpsc::{Receiver, Sender, channel}, SinkExt, future::try_join_all};
use redis::{AsyncCommands, ErrorKind, FromRedisValue, Msg, RedisError, RedisResult, RedisWrite, Script, ToRedisArgs, Value, aio::MultiplexedConnection};
use serde::{Serialize, Deserialize};
type Error = ClientInterestFromMsgError;
fn try_from(msg: Msg) -> Result<Self, Self::Error> {
let channel_name = msg.get_channel_name();
- if channel_name == "__keyspace@0__:games" {
+ if channel_name == "__keyspace@0__:game:list" {
Ok(ClientInterest::GameList)
} else if let Some(username) = channel_name.strip_prefix("__keyspace@0__:user:") {
username.parse().map_err(ClientInterestFromMsgError::UsernameParseError)
.map(|username| ClientInterest::User{username})
- } else if let Some(Ok(id)) = channel_name.strip_prefix("__keyspace@0__:game:").map(str::parse) {
+ } else if let Some(Ok(id)) = channel_name.strip_prefix("__keyspace@0__:game:").and_then(|str| str.strip_suffix(":actions")).map(str::parse) {
Ok(ClientInterest::Game{id})
} else {
Err(ClientInterestFromMsgError::InvalidChannelName{channel_name: channel_name.to_string()})
where W: ?Sized + RedisWrite
{
match self {
- ClientInterest::GameList => out.write_arg(b"__keyspace@0__:games"),
- ClientInterest::Game{id} => out.write_arg_fmt(format!("__keyspace@0__:game:{}", id)),
+ ClientInterest::GameList => out.write_arg(b"__keyspace@0__:game:list"),
+ ClientInterest::Game{id} => out.write_arg_fmt(format!("__keyspace@0__:game:{}:actions", id)),
ClientInterest::User{username} => out.write_arg_fmt(format!("__keyspace@0__:user:{}", username)),
}
}
format!("user:{}", username)
}
-fn game_key(id: u32) -> String {
- format!("game:{}", id)
+fn game_settings_key(id: u32) -> String {
+ format!("game:{}:settings", id)
+}
+
+fn game_actions_key(id: u32) -> String {
+ format!("game:{}:actions", id)
}
impl ServerState {
}
pub async fn create_game(&mut self, settings: GameSettings) -> RedisResult<u32> {
- self.redis.rpush("games", AsJson(settings)).await.map(|i: u32| i - 1)
+ let id = self.redis.incr("game:next_id", 1u32).await?;
+ let key = game_settings_key(id);
+ let () = self.redis.set(key, AsJson(settings)).await?;
+ let () = self.redis.rpush("game:list", id).await?;
+ Ok(id)
}
pub async fn game_list(&mut self, from: usize) -> RedisResult<Vec<GameSummary>> {
debug!("game_list(from: {})", from);
- let games: Vec<AsJson<GameSettings>> = self.redis.lrange("games", from as isize, -1).await?;
- Ok(games.into_iter().map(AsJson::get).enumerate().map(|(id, settings)| GameSummary::new((from + id) as u32, settings)).collect())
+ let games: Vec<u32> = self.redis.lrange("game:list", from as isize, -1).await?;
+ let mut summaries = Vec::new();
+ for id in games {
+ summaries.push(self.game_summary(id).await?);
+ }
+ Ok(summaries)
}
pub async fn game_state(&mut self, id: u32, from: usize) -> RedisResult<Vec<ValidatedUserAction>> {
- let key = game_key(id);
+ let key = game_actions_key(id);
let actions: Vec<AsJson<ValidatedUserAction>> = self.redis.lrange(&key, from as isize, -1).await?;
Ok(actions.into_iter().map(AsJson::get).collect())
}
pub async fn game_summary(&mut self, id: u32) -> RedisResult<GameSummary> {
- let settings = self.redis.lindex("games", id as isize).await.map(AsJson::get)?;
- Ok(GameSummary::new(id, settings))
+ let key = game_settings_key(id);
+ self.redis.get(key).await.map(AsJson::get).map(|settings| GameSummary::new(id, settings))
}
pub async fn take_action(&mut self, id: u32, len: usize, action: &ValidatedUserAction) -> RedisResult<ActionStatus> {
- let key = game_key(id);
+ let key = game_actions_key(id);
debug!("take_action: EVAL {{TAKE_ACTION_LUA_SCRIPT}} 1 {} {} {:?}", key, len, action);
self.take_action_script.key(key).arg(len).arg(AsJson(action)).invoke_async(&mut self.redis).await
}