Skip to content

Commit

Permalink
chore: extract more of player info into scoreboard
Browse files Browse the repository at this point in the history
  • Loading branch information
haongo138 committed Dec 14, 2023
1 parent fe1638f commit 190ad90
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 8 deletions.
10 changes: 9 additions & 1 deletion server/src/actors/game_actor.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::{
actors::{ClientWsActor, StoreActor},
game::{Game, TICKS_PER_SECOND},
models::messages::{ClientStop, PlayerGameCommand, ServerCommand, SetScoreboardCommand},
models::messages::{ClientStop, PlayerGameCommand, ServerCommand, SetScoreboardCommand, SetPlayerInfoCommand},
};
use actix::{Actor, Addr, AsyncContext, Context, Handler, Message};
use futures::sync::oneshot;
Expand Down Expand Up @@ -224,6 +224,8 @@ impl Handler<SocketEvent> for GameActor {
SocketEvent::Join(api_key, team_name, addr) => {
let key_clone = api_key.clone();
let addr_clone = addr.clone();
let cache_api_key = api_key.clone();
let cache_team_name = team_name.clone();

info!("person joined - {:?}", api_key);

Expand Down Expand Up @@ -264,6 +266,12 @@ impl Handler<SocketEvent> for GameActor {
for addr in self.connections.values().chain(self.spectators.iter()) {
addr.do_send(ServerToClient::TeamNames(self.team_names.clone()));
}

// Store player info to DB
let mut fields = HashMap::new();
fields.insert("api_key".to_string(), cache_api_key);
fields.insert("team_name".to_string(), cache_team_name);
self.store_actor_addr.do_send(SetPlayerInfoCommand { player_id, fields });
}
},
SocketEvent::Leave(api_key, addr) => {
Expand Down
41 changes: 39 additions & 2 deletions server/src/actors/store_actor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::collections::HashMap;
use actix::prelude::*;
use redis::{Client, Commands, Connection};

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

#[derive(Debug)]
pub struct StoreActor {
Expand Down Expand Up @@ -47,7 +47,44 @@ impl Handler<GetScoreboardCommand> for StoreActor {
let total_points = total_points_str.parse::<f64>().unwrap_or_default() as u32;
result.insert(player_id, total_points);
}

Ok(result)
}
}

impl Handler<SetPlayerInfoCommand> for StoreActor {
type Result = Result<String, redis::RedisError>;

fn handle(&mut self, msg: SetPlayerInfoCommand, _: &mut Self::Context) -> Self::Result {
let mut con: Connection = self.client.get_connection()?;

// Use hset_multiple to set multiple fields at the same time
let query_key = format!("player:{}:info", msg.player_id);
let fields: Vec<(&str, &str)> =
msg.fields.iter().map(|(k, v)| (k.as_str(), v.as_str())).collect();
let result: redis::RedisResult<()> = con.hset_multiple(query_key, &fields);

result
.map_err(|e| e.into())
.map(|_| format!("Fields are set for player_id {}", msg.player_id))
}
}

impl Handler<GetMultiplePlayerInfo> for StoreActor {
type Result = Result<HashMap<u32, String>, redis::RedisError>;

fn handle(&mut self, msg: GetMultiplePlayerInfo, _: &mut Self::Context) -> Self::Result {
let mut con: Connection = self.client.get_connection()?;
let mut results = HashMap::new();
for key in msg.player_ids {
let hash_key: String = format!("player:{}:info", key);
let player_info: HashMap<String, String> = con.hgetall(&hash_key)?;

// If you need to filter out empty rooms, you can add a condition here.
// For example, you can check if room_data is not empty before inserting into results.
results.insert(key.clone(), serde_json::to_string(&player_info).unwrap());
}

Ok(results)
}
}
60 changes: 55 additions & 5 deletions server/src/controllers/api.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
use std::collections::HashMap;

use crate::{
actors::{ClientWsActor, CreateRoom, JoinRoom, ListRooms},
models::messages::{GetScoreboardCommand, ServerCommand},
actors::{ClientWsActor, CreateRoom, JoinRoom, ListRooms, StoreActor},
models::messages::{GetMultiplePlayerInfo, GetScoreboardCommand, ServerCommand},
AppState,
};
use actix::Addr;
use actix_web::{http::StatusCode, HttpRequest, Path, Query, State};
use futures::Future;

Expand All @@ -13,10 +16,18 @@ pub struct QueryString {
name: String,
}

#[derive(Debug, Deserialize)]
pub struct PlayerInfo {
api_key: String,
team_name: String,
}

#[derive(Serialize, Deserialize)]
struct ScoreboardEntry {
pub struct ScoreboardEntry {
player_id: u32,
total_points: u32,
api_key: String,
team_name: String,
}

#[derive(Serialize, Deserialize)]
Expand Down Expand Up @@ -119,19 +130,58 @@ pub fn list_rooms_handler(
}
}

fn get_scoreboard_player_info(player_ids: Vec<u32>, addr: Addr<StoreActor>) -> Result<HashMap<u32, PlayerInfo>, actix_web::Error> {
let result: Result<HashMap<u32, String>, redis::RedisError> =
addr.send(GetMultiplePlayerInfo { player_ids }).wait().unwrap();

match result {
Ok(players) => {
let mut player_infos: HashMap<u32, PlayerInfo> = HashMap::new();
for (id, player_data_json) in players {
let player_info: PlayerInfo = serde_json::from_str(&player_data_json).expect("Failed to deserialize JSON");
player_infos.insert(id, player_info);
}
Ok(player_infos)
},
Err(_) => Err(actix_web::error::ErrorBadRequest(String::from("failed to query player info data")))
}
}

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.store_actor_addr.send(GetScoreboardCommand(room_token)).wait().unwrap();
match result {
Ok(scoreboard) => {
let player_ids: Vec<u32> = scoreboard.keys().cloned().collect();
let player_info_map = get_scoreboard_player_info(player_ids, state.store_actor_addr.clone()).unwrap();
let scoreboard_response: ScoreboardResponse = ScoreboardResponse {
scoreboard: scoreboard
.into_iter()
.map(|(player_id, total_points)| ScoreboardEntry { player_id, total_points })
.map(|(player_id, total_points)| {
player_info_map
.get(&player_id)
.map_or_else(
|| {
info!("Failed to query player info by player_id");
ScoreboardEntry {
player_id,
total_points,
api_key: String::from(""),
team_name: String::from(""),
}
},
|info| ScoreboardEntry {
player_id,
total_points,
api_key: info.api_key.clone(),
team_name: info.team_name.clone(),
},
)
})
.collect(),
};
};
let body = serde_json::to_string(&scoreboard_response)?;
Ok(actix_web::HttpResponse::with_body(StatusCode::OK, body))
},
Expand Down
13 changes: 13 additions & 0 deletions server/src/models/messages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,16 @@ pub struct SetScoreboardCommand {
#[derive(Message)]
#[rtype(result = "Result<HashMap<u32, u32>, redis::RedisError>")]
pub struct GetScoreboardCommand(pub String);

#[derive(Message)]
#[rtype(result = "Result<String, redis::RedisError>")]
pub struct SetPlayerInfoCommand {
pub player_id: u32,
pub fields: HashMap<String, String>, // Use a HashMap to represent multiple fields
}

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

0 comments on commit 190ad90

Please sign in to comment.