//! # Dispatcher actors //! //! Allows to establish connections between human players use std::collections::HashMap; use std::time::Duration; use actix::{Actor, Addr, AsyncContext, Context, Handler, Message}; use crate::consts::INVITE_CODE_LENGTH; use crate::data::GameRules; use crate::game::Game; use crate::human_player_ws::{CloseConnection, HumanPlayerWS, ServerMessage, SetGame}; use crate::utils::string_utils::rand_str; /// How often garbage collector is run const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(60); #[derive(Message)] #[rtype(result = "()")] pub struct CreateInvite(pub GameRules, pub Addr); #[derive(Message)] #[rtype(result = "()")] pub struct AcceptInvite(pub String, pub Addr); #[derive(Message)] #[rtype(result = "()")] pub struct PlayRandom(pub Addr); #[derive(Debug, Clone)] struct PendingPlayer { player: Addr, rules: GameRules, } #[derive(Default)] pub struct DispatcherActor { with_invite: HashMap, random_player: Option, } impl DispatcherActor { /// Run garbage collector fn run_gc(&mut self) { // Garbage collect invites let ids = self .with_invite .iter() .filter(|p| !p.1.player.connected()) .map(|p| p.0.clone()) .collect::>(); for id in ids { log::debug!("Remove dead invite: {}", id); self.with_invite.remove(&id); } // Garbage collect random player if let Some(player) = self.random_player.clone() { if !player.player.connected() { self.random_player = None; } } } } impl Actor for DispatcherActor { type Context = Context; fn started(&mut self, ctx: &mut Self::Context) { ctx.run_interval(HEARTBEAT_INTERVAL, |act, _ctx| { act.run_gc(); }); } } impl Handler for DispatcherActor { type Result = (); fn handle(&mut self, msg: CreateInvite, _ctx: &mut Self::Context) -> Self::Result { let mut invite_code = rand_str(INVITE_CODE_LENGTH).to_uppercase(); while self.with_invite.contains_key(&invite_code) { invite_code = rand_str(INVITE_CODE_LENGTH).to_uppercase(); } log::debug!("Insert new invitation: {}", invite_code); msg.1.do_send(ServerMessage::SetInviteCode { code: invite_code.clone(), }); self.with_invite.insert( invite_code, PendingPlayer { player: msg.1, rules: msg.0, }, ); } } impl Handler for DispatcherActor { type Result = (); fn handle(&mut self, msg: AcceptInvite, _ctx: &mut Self::Context) -> Self::Result { self.run_gc(); let entry = match self.with_invite.remove(&msg.0) { None => { msg.1.do_send(ServerMessage::InvalidInviteCode); msg.1.do_send(CloseConnection); return; } Some(e) => e, }; let game = Game::new(entry.rules).start(); entry.player.do_send(SetGame(game.clone())); msg.1.do_send(SetGame(game)); } } impl Handler for DispatcherActor { type Result = (); fn handle(&mut self, msg: PlayRandom, _ctx: &mut Self::Context) -> Self::Result { self.run_gc(); match self.random_player.take() { None => { self.random_player = Some(PendingPlayer { player: msg.0, rules: GameRules::random_players_rules(), }); } Some(p) => { let game = Game::new(p.rules).start(); p.player.do_send(SetGame(game.clone())); msg.0.do_send(SetGame(game)); self.random_player = None; } } } }