Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement redis caching actor #4

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,4 @@ listenfd = "0.3"
failure = "0.1"
futures = "0.1"
url = "1.7"
redis = "0.24.0"
24 changes: 19 additions & 5 deletions server/src/actors/game_actor.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::{
actors::ClientWsActor,
actors::{ClientWsActor, RedisActor},
game::{Game, TICKS_PER_SECOND},
models::messages::{ClientStop, PlayerGameCommand, ServerCommand},
models::messages::{ClientStop, PlayerGameCommand, ServerCommand, SetScoreboardCommand},
};
use actix::{Actor, Addr, AsyncContext, Context, Handler, Message};
use futures::sync::oneshot;
Expand All @@ -15,6 +15,7 @@ use tokyo::models::*;

#[derive(Debug)]
pub struct GameActor {
redis_actor_addr: Addr<RedisActor>,
connections: HashMap<String, Addr<ClientWsActor>>,
spectators: HashSet<Addr<ClientWsActor>>,
team_names: HashMap<u32, String>,
Expand All @@ -26,6 +27,7 @@ pub struct GameActor {
game_config: GameConfig,
max_players: u32,
time_limit_seconds: u32,
room_token: String,
}

#[derive(Debug)]
Expand All @@ -37,10 +39,11 @@ pub enum GameLoopCommand {
}

impl GameActor {
pub fn new(config: GameConfig, max_players: u32, time_limit_seconds: u32) -> GameActor {
pub fn new(config: GameConfig, redis_actor_addr: Addr<RedisActor>, max_players: u32, time_limit_seconds: u32, room_token: String) -> GameActor {
let (msg_tx, msg_rx) = channel();

GameActor {
redis_actor_addr,
connections: HashMap::new(),
spectators: HashSet::new(),
team_names: HashMap::new(),
Expand All @@ -52,17 +55,20 @@ impl GameActor {
game_config: config,
max_players,
time_limit_seconds,
room_token,
}
}
}

fn game_loop(
game_actor: Addr<GameActor>,
redis_actor: Addr<RedisActor>,
msg_chan: Receiver<GameLoopCommand>,
mut cancel_chan: oneshot::Receiver<()>,
config: GameConfig,
max_players: u32,
time_limit_seconds: u32,
room_token: String,
) {
let mut loop_helper = LoopHelper::builder().build_with_target_rate(TICKS_PER_SECOND);

Expand Down Expand Up @@ -119,6 +125,12 @@ fn game_loop(
println!("Ending game!");
status = GameStatus::Finished;
game_over_at = None;

// store scoreboard on game end
redis_actor.do_send(SetScoreboardCommand {
room_token: room_token.clone(),
scoreboard: game.state.scoreboard.clone(),
});
}

if status.is_running() {
Expand Down Expand Up @@ -180,6 +192,8 @@ impl Actor for GameActor {
info!("Game Actor started!");
let (cancel_tx, cancel_rx) = oneshot::channel();
let addr = ctx.address();
let redis_actor_addr = self.redis_actor_addr.clone();
let room_token = self.room_token.clone();

// "Take" the receiving end of the channel and give it
// to the game loop thread
Expand All @@ -189,7 +203,7 @@ impl Actor for GameActor {
let max_players = self.max_players;
let time_limit_seconds = self.time_limit_seconds;
std::thread::spawn(move || {
game_loop(addr, msg_rx, cancel_rx, config, max_players, time_limit_seconds);
game_loop(addr, redis_actor_addr, msg_rx, cancel_rx, config, max_players, time_limit_seconds, room_token);
});

self.cancel_chan = Some(cancel_tx);
Expand Down
2 changes: 2 additions & 0 deletions server/src/actors/mod.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
pub mod client_ws_actor;
pub mod game_actor;
pub mod redis_actor;

pub use client_ws_actor::ClientWsActor;
pub use game_actor::GameActor;

pub mod room_manager_actor;
pub use room_manager_actor::{CreateRoom, JoinRoom, ListRooms, RoomManagerActor};
pub use redis_actor::RedisActor;
53 changes: 53 additions & 0 deletions server/src/actors/redis_actor.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
use std::collections::HashMap;

use actix::prelude::*;
use redis::{Client, Commands, Connection};

use crate::models::messages::{SetScoreboardCommand, GetScoreboardCommand};

#[derive(Debug)]
pub struct RedisActor {
client: Client,
}

impl RedisActor {
haongo138 marked this conversation as resolved.
Show resolved Hide resolved
pub fn new(redis_url: String) -> RedisActor {
let client = Client::open(redis_url).expect("Failed to create Redis client");
RedisActor { client }
}
}

impl Actor for RedisActor {
type Context = Context<Self>;
}

impl Handler<SetScoreboardCommand> for RedisActor {
type Result = Result<(), redis::RedisError>;

fn handle(&mut self, msg: SetScoreboardCommand, _: &mut Self::Context) -> Self::Result {
let mut con: Connection = self.client.get_connection()?;
let query_key = format!("room:{}:scoreboard", msg.room_token);
for (player_id, points) in &msg.scoreboard {
con.zadd(query_key.clone(), *points as f64, *player_id)?;
}
Ok(())
}
}

impl Handler<GetScoreboardCommand> for RedisActor {
type Result = Result<HashMap<u32, u32>, redis::RedisError>;

fn handle(&mut self, msg: GetScoreboardCommand, _: &mut Self::Context) -> Self::Result {
let mut con: Connection = self.client.get_connection()?;
let query_key = format!("room:{}:scoreboard", msg.0);
let scoreboard: Vec<(String, String)> = con.zrevrange_withscores(query_key, 0, -1)?;
let mut result = HashMap::new();
for (total_points_str, player_id_str) in scoreboard {
let player_id = player_id_str.parse::<u32>().unwrap_or_default();
let total_points = total_points_str.parse::<f64>().unwrap_or_default() as u32;
result.insert(player_id, total_points);
}

Ok(result)
}
}
11 changes: 7 additions & 4 deletions server/src/actors/room_manager_actor.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::actors::GameActor;
use crate::actors::{GameActor, RedisActor};
use actix::prelude::*;
use rand::{distributions::Alphanumeric, Rng};
use std::{
Expand All @@ -12,6 +12,7 @@ const TOKEN_LENGTH: usize = 8;
// RoomManagerActor is responsible for creating and managing rooms
pub struct RoomManagerActor {
config: GameConfig,
redis_actor_addr: Addr<RedisActor>,
id_counter: u32,
rooms: HashMap<String, Room>,
}
Expand All @@ -29,6 +30,7 @@ struct Room {
impl Room {
pub fn new(
config: &GameConfig,
redis_actor_addr: Addr<RedisActor>,
id: u32,
name: String,
max_players: u32,
Expand All @@ -37,15 +39,15 @@ impl Room {
) -> Room {
let game_cfg = GameConfig { bound_x: config.bound_x, bound_y: config.bound_y };

let game_actor = GameActor::new(game_cfg, max_players, time_limit_seconds);
let game_actor = GameActor::new(game_cfg, redis_actor_addr, max_players, time_limit_seconds, token.clone());
let game_actor_addr = game_actor.start();
Room { id, name, max_players, time_limit_seconds, token, game: game_actor_addr }
}
}

impl RoomManagerActor {
pub fn new(cfg: GameConfig) -> RoomManagerActor {
RoomManagerActor { config: cfg, id_counter: 0, rooms: HashMap::new() }
pub fn new(cfg: GameConfig, redis_actor_addr: Addr<RedisActor>) -> RoomManagerActor {
RoomManagerActor { config: cfg, id_counter: 0, rooms: HashMap::new(), redis_actor_addr: redis_actor_addr.clone() }
haongo138 marked this conversation as resolved.
Show resolved Hide resolved
}

pub fn create_room(
Expand All @@ -66,6 +68,7 @@ impl RoomManagerActor {
token.clone(),
Room::new(
&self.config,
self.redis_actor_addr.clone(),
self.id_counter,
name.to_string(),
max_players,
Expand Down
38 changes: 36 additions & 2 deletions server/src/controllers/api.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use crate::{
actors::{ClientWsActor, CreateRoom, JoinRoom, ListRooms},
models::messages::ServerCommand,
models::messages::{GetScoreboardCommand, ServerCommand},
AppState,
};
use actix_web::{http::StatusCode, HttpRequest, Query, State};
use actix_web::{http::StatusCode, HttpRequest, Path, Query, State};
use futures::Future;

#[derive(Debug, Deserialize)]
Expand All @@ -13,6 +13,17 @@ pub struct QueryString {
name: String,
}

#[derive(Serialize, Deserialize)]
struct ScoreboardEntry {
player_id: u32,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we expose more data? how about name, sth like that. We can't show the data just the id

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I just pushed my refactor to include more info (api_key, team_name) in scoreboard response.

total_points: u32,
}

#[derive(Serialize, Deserialize)]
struct ScoreboardResponse {
scoreboard: Vec<ScoreboardEntry>,
}

pub fn socket_handler(
(req, state, query): (HttpRequest<AppState>, State<AppState>, Query<QueryString>),
) -> Result<actix_web::HttpResponse, actix_web::Error> {
Expand Down Expand Up @@ -107,3 +118,26 @@ pub fn list_rooms_handler(
Err(_) => Err(actix_web::error::ErrorBadRequest("Failed to list rooms")),
}
}

pub fn get_room_scoreboard(
(_req, state, path): (HttpRequest<AppState>, State<AppState>, Path<String>),
) -> Result<actix_web::HttpResponse, actix_web::Error> {
let room_token = path.into_inner();
let result = state.redis_actor_addr.send(GetScoreboardCommand(room_token)).wait().unwrap();
match result {
Ok(scoreboard) => {
let scoreboard_response: ScoreboardResponse = ScoreboardResponse {
scoreboard: scoreboard
.into_iter()
.map(|(player_id, total_points)| ScoreboardEntry { player_id, total_points })
.collect(),
};
let body = serde_json::to_string(&scoreboard_response)?;
Ok(actix_web::HttpResponse::with_body(StatusCode::OK, body))
},
Err(e) => Err(actix_web::error::ErrorBadRequest(format!(
"Failed to get room's scoreboard: {}",
e
))),
}
}
15 changes: 13 additions & 2 deletions server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ mod models;
use crate::actors::{GameActor, RoomManagerActor};
use actix::{Actor, Addr, System};
use actix_web::{http::Method, middleware::Logger, server, App};
use actors::RedisActor;
use lazy_static::lazy_static;
use listenfd::ListenFd;
use std::collections::HashSet;
Expand All @@ -25,10 +26,12 @@ pub struct AppConfig {
api_keys: HashSet<String>,
dev_mode: bool,
game_config: GameConfig,
redis_uri: Option<String>,
}

pub struct AppState {
game_addr: Addr<GameActor>,
redis_actor_addr: Addr<RedisActor>,
room_manager_addr: Addr<RoomManagerActor>,
}

Expand All @@ -52,15 +55,20 @@ fn main() -> Result<(), String> {

let actor_system = System::new("meetup-server");

let game_actor = GameActor::new(APP_CONFIG.game_config, 0, 0);
let redis_uri = APP_CONFIG.redis_uri.clone().unwrap_or("redis://127.0.0.1/".into());
let redis_actor = RedisActor::new(redis_uri);
let redis_actor_addr = redis_actor.start();

let game_actor = GameActor::new(APP_CONFIG.game_config, redis_actor_addr.clone(), 0, 0, String::from(""));
let game_actor_addr = game_actor.start();

let room_manager_actor = actors::RoomManagerActor::new(APP_CONFIG.game_config);
let room_manager_actor = actors::RoomManagerActor::new(APP_CONFIG.game_config, redis_actor_addr.clone());
let room_manager_addr = room_manager_actor.start();

let mut server = server::new(move || {
let app_state = AppState {
game_addr: game_actor_addr.clone(),
redis_actor_addr: redis_actor_addr.clone(),
room_manager_addr: room_manager_addr.clone(),
};

Expand All @@ -70,6 +78,9 @@ fn main() -> Result<(), String> {
r.method(Method::POST).with(controllers::api::create_room_handler);
r.method(Method::GET).with(controllers::api::list_rooms_handler);
})
.resource("/rooms/{room_token}/scoreboard", |r| {
r.method(Method::GET).with(controllers::api::get_room_scoreboard);
})
.resource("/socket", |r| {
r.method(Method::GET).with(controllers::api::socket_handler);
})
Expand Down
13 changes: 13 additions & 0 deletions server/src/models/messages.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::collections::HashMap;

use actix::Message;
use tokyo::models::GameCommand;

Expand All @@ -14,3 +16,14 @@ pub struct ClientStop {}
pub enum ServerCommand {
Reset
}

#[derive(Message)]
#[rtype(result = "Result<(), redis::RedisError>")]
pub struct SetScoreboardCommand {
pub room_token: String,
pub scoreboard: HashMap<u32, u32>,
}

#[derive(Message)]
#[rtype(result = "Result<HashMap<u32, u32>, redis::RedisError>")]
pub struct GetScoreboardCommand(pub String);
1 change: 1 addition & 0 deletions tokyo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
server_port = 8080
api_keys = ["webuild"]
dev_mode = true
redis_uri = "redis://127.0.0.1/"

[game_config]
bound_x = 3500
Expand Down