diff --git a/Cargo.lock b/Cargo.lock index b0c2ee3..9b12cd2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -146,6 +146,11 @@ name = "ascii" version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "ascii" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "atty" version = "0.2.11" @@ -284,6 +289,18 @@ dependencies = [ "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "combine" +version = "3.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ascii 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "either 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "cookie" version = "0.11.0" @@ -932,6 +949,7 @@ dependencies = [ "env_logger 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", + "redis 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", "set 0.1.0 (git+https://github.com/GeneralSet/Set)", @@ -1243,6 +1261,22 @@ dependencies = [ "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "redis" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", + "combine 3.8.1 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", + "sha1 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-codec 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-executor 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-tcp 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "redox_syscall" version = "0.1.51" @@ -1905,6 +1939,14 @@ name = "unicode-xid" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "unreachable" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "untrusted" version = "0.6.2" @@ -1974,6 +2016,11 @@ name = "version_check" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "walrus" version = "0.4.0" @@ -2201,6 +2248,7 @@ dependencies = [ "checksum arc-swap 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "1025aeae2b664ca0ea726a89d574fe8f4e77dd712d443236ad1de00379450cf6" "checksum arrayvec 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "92c7fb76bc8826a8b33b4ee5bb07a247a81e76764ab4d55e8f73e3a4d8808c71" "checksum ascii 0.8.7 (registry+https://github.com/rust-lang/crates.io-index)" = "97be891acc47ca214468e09425d02cef3af2c94d0d82081cd02061f996802f14" +"checksum ascii 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a5fc969a8ce2c9c0c4b0429bb8431544f6658283c8326ba5ff8c762b75369335" "checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652" "checksum autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a6d640bee2da49f60a4068a7fae53acde8982514ab7bae8b8cea9e88cbcfd799" "checksum backtrace 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "cd5a90e2b463010cd0e0ce9a11d4a9d5d58d9f41d4a6ba3dcaf9e68b466e88b4" @@ -2219,6 +2267,7 @@ dependencies = [ "checksum chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "45912881121cb26fad7c38c17ba7daa18764771836b34fab7d3fbd93ed633878" "checksum chunked_transfer 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "498d20a7aaf62625b9bf26e637cf7736417cde1d0c99f1d04d1170229a85cf87" "checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" +"checksum combine 3.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "da3da6baa321ec19e1cc41d31bf599f00c783d0517095cdaf0332e3fe8d20680" "checksum cookie 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1465f8134efa296b4c19db34d909637cb2bf0f7aaf21299e23e18fa29ac557cf" "checksum crc 1.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d663548de7f5cca343f1e0a48d14dcfb0e9eb4e079ec58883b7251539fa10aeb" "checksum crc32fast 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e91d5240c6975ef33aeb5f148f35275c25eda8e8a5f95abe421978b05b8bf192" @@ -2327,6 +2376,7 @@ dependencies = [ "checksum rayon 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "373814f27745b2686b350dd261bfd24576a6fb0e2c5919b3a2b6005f820b0473" "checksum rayon-core 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b055d1e92aba6877574d8fe604a63c8b5df60f60e5982bf7ccbb1338ea527356" "checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +"checksum redis 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b543b95de413ac964ca609e91fd9fd58419312e69988fb197f3ff8770312a1af" "checksum redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)" = "423e376fffca3dfa06c9e9790a9ccd282fafb3cc6e6397d01dbf64f9bacc6b85" "checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" "checksum regex 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "53ee8cfdddb2e0291adfb9f13d31d3bbe0a03c9a402c01b1e24188d86c35b24f" @@ -2395,6 +2445,7 @@ dependencies = [ "checksum unicode-normalization 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "141339a08b982d942be2ca06ff8b076563cbe223d1befd5450716790d44e2426" "checksum unicode-segmentation 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "aa6024fc12ddfd1c6dbc14a80fa2324d4568849869b779f6bd37e5e4c03344d1" "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" +"checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" "checksum untrusted 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "55cd1f4b4e96b46aeb8d4855db4a7a9bd96eeeb5c6a1ab54593328761642ce2f" "checksum url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a" "checksum utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "796f7e48bef87609f7ade7e06495a87d5cd06c7866e6a5cbfceffc558a243737" @@ -2404,6 +2455,7 @@ dependencies = [ "checksum v_htmlescape 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "020cae817dc82693aa523f01087b291b1c7a9ac8cea5c12297963f21769fb27f" "checksum vcpkg 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "def296d3eb3b12371b2c7d0e83bfe1403e4db2d7a0bba324a12b21c4ee13143d" "checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" +"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" "checksum walrus 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac68580ebc0a40571ba97f43735f91f36eecccd799bd4cd15048ca5be4f13c63" "checksum walrus-macro 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2091d3de555b62f4c2e0f564daa2cb13cb4635cd95c5eb297405a970f72a7f87" "checksum wasm-bindgen 0.2.38 (registry+https://github.com/rust-lang/crates.io-index)" = "f7148f7446b911dd8e8d490a4fd44bf7fcd6caa21ba2fed9aef7bf34b9a974d4" diff --git a/Cargo.toml b/Cargo.toml index 14d05e7..931a160 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,3 +16,5 @@ serde_json = "1.0" actix = "0.7" actix-web = "0.7" + +redis = "0.10.0" diff --git a/README.md b/README.md index 5f8b3f9..04cd3c4 100644 --- a/README.md +++ b/README.md @@ -9,4 +9,12 @@ cargo run ## Run With Docker ``` docker-compose up +``` + +## notes +``` +kompose convert +kubectl apply -f actix-service.yaml actix-doployment.yaml +kubectl apply -f redis-service.yaml redis-doployment.yaml redis-data-persistentvolumeclaim.yaml +kubectl expose deployment/actix --type="NodePort" --port 3001 --name=mp ``` \ No newline at end of file diff --git a/actix-deployment.yaml b/actix-deployment.yaml new file mode 100644 index 0000000..23df07e --- /dev/null +++ b/actix-deployment.yaml @@ -0,0 +1,32 @@ +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + annotations: + kompose.cmd: kompose convert + kompose.version: 1.18.0 () + creationTimestamp: null + labels: + io.kompose.service: actix + name: actix +spec: + replicas: 1 + strategy: {} + template: + metadata: + creationTimestamp: null + labels: + io.kompose.service: actix + spec: + containers: + - env: + - name: RUST_ENV + value: docker + - name: RUST_LOG + value: debug + image: generalset/multiplayer_server:init + name: actix + ports: + - containerPort: 3001 + resources: {} + restartPolicy: Always +status: {} diff --git a/actix-service.yaml b/actix-service.yaml new file mode 100644 index 0000000..5bd15de --- /dev/null +++ b/actix-service.yaml @@ -0,0 +1,19 @@ +apiVersion: v1 +kind: Service +metadata: + annotations: + kompose.cmd: kompose convert + kompose.version: 1.18.0 () + creationTimestamp: null + labels: + io.kompose.service: actix + name: actix +spec: + ports: + - name: "3001" + port: 3001 + targetPort: 3001 + selector: + io.kompose.service: actix +status: + loadBalancer: {} diff --git a/docker-compose.yml b/docker-compose.yml index 179ed4f..820d3f3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,8 +1,20 @@ version: '3' services: actix: + image: generalset/multiplayer_server:init environment: - RUST_ENV=docker + - RUST_LOG=debug build: . ports: - - '3001:3001' \ No newline at end of file + - '3001:3001' + depends_on: + - redis + redis: + image: redis + ports: + - '6379:6379' + volumes: + - redis-data:/data +volumes: + redis-data: \ No newline at end of file diff --git a/redis-data-persistentvolumeclaim.yaml b/redis-data-persistentvolumeclaim.yaml new file mode 100644 index 0000000..f9ab537 --- /dev/null +++ b/redis-data-persistentvolumeclaim.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + creationTimestamp: null + labels: + io.kompose.service: redis-data + name: redis-data +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 100Mi +status: {} diff --git a/redis-deployment.yaml b/redis-deployment.yaml new file mode 100644 index 0000000..55fd5a9 --- /dev/null +++ b/redis-deployment.yaml @@ -0,0 +1,35 @@ +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + annotations: + kompose.cmd: kompose convert + kompose.version: 1.18.0 () + creationTimestamp: null + labels: + io.kompose.service: redis + name: redis +spec: + replicas: 1 + strategy: + type: Recreate + template: + metadata: + creationTimestamp: null + labels: + io.kompose.service: redis + spec: + containers: + - image: redis + name: redis + ports: + - containerPort: 6379 + resources: {} + volumeMounts: + - mountPath: /data + name: redis-data + restartPolicy: Always + volumes: + - name: redis-data + persistentVolumeClaim: + claimName: redis-data +status: {} diff --git a/redis-service.yaml b/redis-service.yaml new file mode 100644 index 0000000..3220ae9 --- /dev/null +++ b/redis-service.yaml @@ -0,0 +1,19 @@ +apiVersion: v1 +kind: Service +metadata: + annotations: + kompose.cmd: kompose convert + kompose.version: 1.18.0 () + creationTimestamp: null + labels: + io.kompose.service: redis + name: redis +spec: + ports: + - name: "6379" + port: 6379 + targetPort: 6379 + selector: + io.kompose.service: redis +status: + loadBalancer: {} diff --git a/src/main.rs b/src/main.rs index 990a50e..e223f81 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,10 +8,10 @@ extern crate actix; extern crate actix_web; extern crate serde; extern crate serde_json; +extern crate redis; extern crate set; extern crate rand; -use std::env; use rand::prelude::*; use serde_json::{Result as JSON_Result}; use serde::{Deserialize, Serialize}; @@ -177,30 +177,19 @@ fn main() { let sys = actix::System::new("multiplayer-server"); - // Start server actor in separate thread - let server = Arbiter::start(|_| server::Server::default()); - // Create Http server with websocket support - let env = match env::var("RUST_ENV") { - Result::Ok(env) => env, - _ => "development".to_string(), - }; - let address = match env.as_str() { - "docker" => "0.0.0.0:3001", - _ => "127.0.0.1:3001", - }; - error!("{}", address); HttpServer::new(move || { + // Start server actor in separate thread + let addr = Arbiter::start(|_| server::Server::default()); + // Websocket sessions state - let state = WsSessionState { - addr: server.clone(), - }; + let state = WsSessionState { addr }; App::with_state(state) // websocket .resource("/", |r| r.route().f(chat_route)) - }).bind(address) + }).bind("0.0.0.0:3001") .unwrap() .start(); diff --git a/src/server.rs b/src/server.rs index 95a3ddc..908e36d 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,8 +1,9 @@ use actix::prelude::*; use std::collections::{HashMap}; -use serde_json::{Result as JSON_Result}; use serde::{Deserialize, Serialize}; +use redis::{PubSubCommands, Commands, ControlFlow}; use set::Set; +use std::thread; /// Server sends this messages to session #[derive(Message)] @@ -32,8 +33,8 @@ pub struct GameUpdateMessage { gameState: Game, } +#[derive(Serialize, Deserialize)] pub struct User { - addr: Recipient, name: String, points: isize, } @@ -53,6 +54,7 @@ pub struct Game { previousSelection: Option } +#[derive(Serialize, Deserialize)] pub struct Lobby { users: HashMap, game_type: Option, @@ -62,13 +64,15 @@ pub struct Lobby { /// `Server` manages rooms and responsible for coordinating game /// sessions. implementation is super primitive pub struct Server { - sessions: HashMap, + sessions: HashMap>, + redis: redis::Client } impl Default for Server { fn default() -> Server { Server { sessions: HashMap::new(), + redis: redis::Client::open("redis://redis:6379").unwrap(), } } } @@ -82,69 +86,48 @@ impl Actor for Server { #[allow(unused_must_use)] impl Server { - fn emit_users(&mut self, room_name: &String, _skip_id: usize) { - if let Some(session) = self.sessions.get_mut(room_name) { - - let mut message = UserMessage { - eventType: "users".to_string(), - users: Vec::new(), - }; - for user in session.users.values() { - message.users.push( - ClientUser { - name: user.name.clone(), - points: user.points.clone(), + fn subscribe( + &mut self, + channel: String, + addr: Addr + ) -> () { + let client = self.redis.clone(); + + thread::spawn(move || { + let mut conn = client.get_connection().unwrap(); + let _: () = conn.subscribe(&[channel.clone()], |msg| { + let ch = msg.get_channel_name(); + let payload: String = msg.get_payload().unwrap(); + match payload.as_ref() { + "exit" => ControlFlow::Break(()), + "user" => { + addr.do_send(EmitUsers { room_name: ch.to_string(),}); + ControlFlow::Continue + }, + "game_type" => { + error!("emit gameType update here"); + addr.do_send(EmitGameType { + room_name: ch.to_string(), + }); + ControlFlow::Continue + }, + "game_state" => { + error!("emit gameState update here"); + addr.do_send(EmitGameState { + room_name: ch.to_string(), + }); + ControlFlow::Continue + }, + a => { + panic!("Channel '{}' received '{}'.", ch, a); } - ) - } - let message_string = match serde_json::to_string(&message) { - JSON_Result::Ok(u) => u, - _ => panic!("Not able to serialize users") - }; - - for (_id, user) in &session.users { - // TODO continue if skip_id == user key - user.addr.do_send(Message(message_string.to_owned())); - } - } - } - - fn emit_game_type(&mut self, room_name: &String) { - let session = self.sessions.get(room_name).unwrap(); - let game_type = session.game_type.as_ref().unwrap(); - let message = GameTypeMessage { - eventType: "setGameType".to_string(), - gameType: game_type.to_string(), - }; - let message_string = match serde_json::to_string(&message) { - JSON_Result::Ok(u) => u, - _ => panic!("Not able to serialize users") - }; - for (_id, user) in &session.users { - // TODO continue if skip_id == user key - user.addr.do_send(Message(message_string.to_owned())); - } - } - - fn emit_game_update(&mut self, room_name: &String) { - let session = self.sessions.get(room_name).unwrap(); - let game_state = session.game_state.as_ref().unwrap(); - - let message = GameUpdateMessage { - eventType: "updateGame".to_string(), - gameState: game_state.clone(), - }; - let message_string = match serde_json::to_string(&message) { - JSON_Result::Ok(u) => u, - _ => panic!("Not able to serialize users") - }; - for (_id, user) in &session.users { - // TODO continue if skip_id == user key - user.addr.do_send(Message(message_string.to_owned())); - } + } + }).unwrap(); + }); } } + /// Join room, if room does not exists create new one. #[derive(Message)] pub struct Join { @@ -159,29 +142,56 @@ pub struct Join { impl Handler for Server { type Result = (); - fn handle(&mut self, msg: Join, _: &mut Context) { + fn handle(&mut self, msg: Join, ctx: &mut Context) { let Join { id, addr, username, room_name } = msg; - if self.sessions.get_mut(&room_name).is_none() { - self.sessions.insert( - room_name.clone(), - Lobby { - users: HashMap::new(), - game_type: None, - game_state: None, - } - ); - } + // add user to redis channel + self.subscribe(room_name.clone(), ctx.address()); + // add refrence to user addr to server session + self.sessions.insert(id, addr.clone()); + + // get/create lobby + let con = self.redis.get_connection().unwrap(); + let existing_lobby: redis::RedisResult = con.get(&room_name); + let mut new_lobby: Lobby = match existing_lobby { + Ok(l) => { + serde_json::from_str(&l).unwrap() + }, + _ => Lobby { + users: HashMap::new(), + game_type: None, + game_state: None + } + }; - self.sessions.get_mut(&room_name).unwrap().users.insert( + // add new user to lobby + new_lobby.users.insert( id, User { - addr: addr, name: username, - points: 0, + points: 0 } ); - self.emit_users(&room_name, id); + let lobby_json = serde_json::to_string(&new_lobby).unwrap(); + let _: () = con.set(&room_name, lobby_json).unwrap(); + + // publish updated state of the lobby to redis channel + let _: () = con.publish(room_name.clone(), "user").unwrap(); + // update client that joined with lobby + let mut message = UserMessage { + eventType: "users".to_string(), + users: Vec::new(), + }; + for user in new_lobby.users.values() { + message.users.push( + ClientUser { + name: user.name.clone(), + points: user.points.clone(), + } + ) + } + let message_string = serde_json::to_string(&message).unwrap(); + addr.do_send(Message(message_string.to_owned())); } } @@ -199,12 +209,23 @@ impl Handler for Server { fn handle(&mut self, msg: SetGameType, _: &mut Context) { let SetGameType { game_type, room_name } = msg; - if self.sessions.get_mut(&room_name).is_none() { - return - } + // get lobby + let con = self.redis.get_connection().unwrap(); + let existing_lobby: redis::RedisResult = con.get(&room_name); + let mut new_lobby: Lobby = match existing_lobby { + Ok(l) => { + serde_json::from_str(&l).unwrap() + }, + _ => panic!("requested lobby does not exist") + }; + + // update lobby with new game type + new_lobby.game_type = Some(game_type.clone()); + let lobby_json = serde_json::to_string(&new_lobby).unwrap(); + let _: () = con.set(&room_name, lobby_json).unwrap(); - self.sessions.get_mut(&room_name).unwrap().game_type = Some(game_type.clone()); - self.emit_game_type(&room_name); + // publish game type update + let _: () = con.publish(room_name.clone(), "game_type").unwrap(); } } @@ -221,10 +242,17 @@ impl Handler for Server { fn handle(&mut self, msg: StartGame, _: &mut Context) { let StartGame { room_name } = msg; - if self.sessions.get_mut(&room_name).is_none() { - return - } + // get lobby + let con = self.redis.get_connection().unwrap(); + let existing_lobby: redis::RedisResult = con.get(&room_name); + let mut new_lobby: Lobby = match existing_lobby { + Ok(l) => { + serde_json::from_str(&l).unwrap() + }, + _ => panic!("requested lobby does not exist") + }; + // init game state let set = Set::new(4, 3); let deck = set.init_deck(); let update_board = set.update_board(deck, "".to_string()); @@ -234,9 +262,15 @@ impl Handler for Server { numberOfSets: update_board.sets, previousSelection: None }; - self.sessions.get_mut(&room_name).unwrap().game_state = Some(game_state); - self.emit_game_update(&room_name); + + // update lobby with new game state + new_lobby.game_state = Some(game_state.clone()); + let lobby_json = serde_json::to_string(&new_lobby).unwrap(); + let _: () = con.set(&room_name, lobby_json).unwrap(); + + // publish game update to redis channel + let _: () = con.publish(room_name.clone(), "game_state").unwrap(); } } @@ -255,31 +289,38 @@ impl Handler for Server { fn handle(&mut self, msg: VerifySet, _: &mut Context) { let VerifySet { id, room_name, selected } = msg; - if self.sessions.get_mut(&room_name).is_none() { - return - } - + // get lobby + let con = self.redis.get_connection().unwrap(); + let existing_lobby: redis::RedisResult = con.get(&room_name); + let mut new_lobby: Lobby = match existing_lobby { + Ok(l) => { + serde_json::from_str(&l).unwrap() + }, + _ => panic!("requested lobby does not exist") + }; + + // check if selected cards are a set let set = Set::new(4, 3); let is_valid_set = set.is_set(selected.clone()); if is_valid_set { + // add a point to the user who selected the set { - let user = self.sessions.get_mut(&room_name).unwrap() - .users.get_mut(&id).unwrap(); + let user = new_lobby.users.get_mut(&id).unwrap(); user.points = user.points + 1; } - self.emit_users(&room_name, id); - - let name = self.sessions.get(&room_name).unwrap().users.get(&id).unwrap().name.to_string(); - - let old_board = self.sessions.get(&room_name).unwrap().game_state.as_ref().unwrap().board.to_string(); - let deck = self.sessions.get(&room_name).unwrap().game_state.as_ref().unwrap().deck.to_string(); + // get current game state + let name = new_lobby.users.get(&id).unwrap().name.to_string(); + let old_board = new_lobby.game_state.as_ref().unwrap().board.to_string(); + let deck = new_lobby.game_state.as_ref().unwrap().deck.to_string(); + // remove selected cards from the board let mut board: Vec<&str> = old_board.split(",").collect(); for card in selected.split(",") { board.remove_item(&card); } + // create updated game state let set = Set::new(4, 3); let update_board = set.update_board(deck, board.join(",")); let game_state = Game { @@ -293,29 +334,137 @@ impl Handler for Server { }), }; - self.sessions.get_mut(&room_name).unwrap().game_state = Some(game_state); + // update lobby with new game state + new_lobby.game_state = Some(game_state.clone()); + let lobby_json = serde_json::to_string(&new_lobby).unwrap(); + let _: () = con.set(&room_name, lobby_json).unwrap(); - self.emit_game_update(&room_name); + // publish game and user update to redis channel + let _: () = con.publish(room_name.clone(), "user").unwrap(); + let _: () = con.publish(room_name.clone(), "game_state").unwrap(); } else { + // subtract a point to the user who selected the set { - let user = self.sessions.get_mut(&room_name).unwrap() - .users.get_mut(&id).unwrap(); + let user = new_lobby.users.get_mut(&id).unwrap(); user.points = user.points - 1; } - self.emit_users(&room_name, id); - - let name = self.sessions.get(&room_name).unwrap().users.get(&id).unwrap().name.to_string(); - + // create previous selection + let name = new_lobby.users.get(&id).unwrap().name.to_string(); let previousSelection = Some(Selection{ user: name, valid: false, - selection: selected, + selection: selected.to_string(), }); - self.sessions.get_mut(&room_name).unwrap().game_state.as_mut().unwrap().previousSelection = previousSelection; + // update lobby with new previous selection + new_lobby.game_state.as_mut().unwrap().previousSelection = previousSelection; + let lobby_json = serde_json::to_string(&new_lobby).unwrap(); + let _: () = con.set(&room_name, lobby_json).unwrap(); - self.emit_game_update(&room_name); + // publish game and user update to redis channel + let _: () = con.publish(room_name.clone(), "user").unwrap(); + let _: () = con.publish(room_name.clone(), "game_state").unwrap(); + } + } +} + +/// EmitUsers room, if room does not exists create new one. +#[derive(Message)] +pub struct EmitUsers { + room_name: String, +} + +impl Handler for Server { + type Result = (); + + fn handle(&mut self, msg: EmitUsers, _: &mut Context) { + let EmitUsers { room_name } = msg; + + let con = self.redis.get_connection().unwrap(); + let l: String = con.get(&room_name).unwrap(); + let lobby: Lobby = serde_json::from_str(&l).unwrap(); + + let mut message = UserMessage { + eventType: "users".to_string(), + users: Vec::new(), + }; + + for user in lobby.users.values() { + message.users.push( + ClientUser { + name: user.name.clone(), + points: user.points.clone(), + } + ) + } + + let message_string = serde_json::to_string(&message).unwrap(); + for (id, _user) in lobby.users { + match self.sessions.get(&id) { + Some(addr) => addr.do_send(Message(message_string.to_owned())), + _ => Ok(()) // user is not connected to this server + }; + } + } +} + +#[derive(Message)] +pub struct EmitGameType { + room_name: String, +} + +impl Handler for Server { + type Result = (); + + fn handle(&mut self, msg: EmitGameType, _: &mut Context) { + let EmitGameType { room_name } = msg; + + let con = self.redis.get_connection().unwrap(); + let l: String = con.get(&room_name).unwrap(); + let lobby: Lobby = serde_json::from_str(&l).unwrap(); + + let message = GameTypeMessage { + eventType: "setGameType".to_string(), + gameType: lobby.game_type.unwrap(), + }; + + let message_string = serde_json::to_string(&message).unwrap(); + for (id, _user) in lobby.users { + match self.sessions.get(&id) { + Some(addr) => addr.do_send(Message(message_string.to_owned())), + _ => Ok(()) // user is not connected to this server + }; + } + } +} + +#[derive(Message)] +pub struct EmitGameState { + room_name: String, +} + +impl Handler for Server { + type Result = (); + + fn handle(&mut self, msg: EmitGameState, _: &mut Context) { + let EmitGameState { room_name } = msg; + + let con = self.redis.get_connection().unwrap(); + let l: String = con.get(&room_name).unwrap(); + let lobby: Lobby = serde_json::from_str(&l).unwrap(); + + let message = GameUpdateMessage { + eventType: "updateGame".to_string(), + gameState: lobby.game_state.unwrap(), + }; + + let message_string = serde_json::to_string(&message).unwrap(); + for (id, _user) in lobby.users { + match self.sessions.get(&id) { + Some(addr) => addr.do_send(Message(message_string.to_owned())), + _ => Ok(()) // user is not connected to this server + }; } } } \ No newline at end of file