|
@@ -0,0 +1,260 @@
|
|
|
+use std::collections::BTreeMap;
|
|
|
+use std::collections::BTreeSet;
|
|
|
+use std::time::{Duration, Instant};
|
|
|
+use std::sync::{Mutex, RwLock, Arc};
|
|
|
+use actix::dev::{MessageResponse, ResponseChannel};
|
|
|
+use actix::prelude::*;
|
|
|
+use actix_web::{web, Error, HttpRequest, HttpResponse};
|
|
|
+use actix_web_actors::ws;
|
|
|
+use futures::executor::block_on;
|
|
|
+use crate::game;
|
|
|
+use crate::websocket::{ClientMessage, UpdateMessage};
|
|
|
+
|
|
|
+const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(5);
|
|
|
+const CLIENT_TIMEOUT: Duration = Duration::from_secs(10);
|
|
|
+
|
|
|
+#[derive(Message)]
|
|
|
+#[rtype(result = "Answer")]
|
|
|
+struct JoinRequest{ lobby_id: String, p: Addr<GameConnection> }
|
|
|
+
|
|
|
+
|
|
|
+#[derive(Message)]
|
|
|
+#[rtype(result = "Answer")]
|
|
|
+struct CreateLobbyRequest{ lobby_id: String, p: Addr<GameConnection> }
|
|
|
+
|
|
|
+#[derive(Message)]
|
|
|
+#[rtype(result = "game::Game")]
|
|
|
+struct GetGame;
|
|
|
+
|
|
|
+enum Answer {
|
|
|
+ LobbyJoined(Addr<GameLobby>),
|
|
|
+ LobbyCreated(Addr<GameLobby>),
|
|
|
+ LobbyAlreadyExists,
|
|
|
+ NoSuchLobby,
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+impl<A, M> MessageResponse<A, M> for Answer
|
|
|
+where
|
|
|
+ A: Actor,
|
|
|
+ M: Message<Result = Answer>,
|
|
|
+{
|
|
|
+ fn handle<R: ResponseChannel<M>>(self, _: &mut A::Context, tx: Option<R>) {
|
|
|
+ if let Some(tx) = tx {
|
|
|
+ tx.send(self);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+impl<A, M> MessageResponse<A, M> for game::Game
|
|
|
+where
|
|
|
+ A: Actor,
|
|
|
+ M: Message<Result = game::Game>,
|
|
|
+{
|
|
|
+ fn handle<R: ResponseChannel<M>>(self, _: &mut A::Context, tx: Option<R>) {
|
|
|
+ if let Some(tx) = tx {
|
|
|
+ tx.send(self);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+pub struct Server {
|
|
|
+ pub lobbies: BTreeMap<String, Addr<GameLobby>>,
|
|
|
+}
|
|
|
+
|
|
|
+impl Actor for Server {
|
|
|
+ type Context = Context<Self>;
|
|
|
+ fn started(&mut self, ctx: &mut Self::Context) {
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl Handler<JoinRequest> for Server {
|
|
|
+ type Result = Answer;
|
|
|
+ fn handle(&mut self, jr: JoinRequest, ctx: &mut Self::Context) -> Self::Result {
|
|
|
+ let mb_lobby = self.lobbies.get(&jr.lobby_id);
|
|
|
+ match mb_lobby {
|
|
|
+ Some(lobby) => {
|
|
|
+ let _sent = lobby.send(jr);
|
|
|
+ Answer::LobbyJoined(lobby.clone())
|
|
|
+ },
|
|
|
+ None => Answer::NoSuchLobby
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl Handler<CreateLobbyRequest> for Server {
|
|
|
+ type Result = Answer;
|
|
|
+ fn handle(&mut self, clr: CreateLobbyRequest, ctx: &mut Self::Context) -> Self::Result {
|
|
|
+ let existing_lobby = self.lobbies.get(&clr.lobby_id);
|
|
|
+ match existing_lobby {
|
|
|
+ Some(_) => Answer::LobbyAlreadyExists,
|
|
|
+ None => {
|
|
|
+ let lobby = GameLobby::new(clr.lobby_id.clone());
|
|
|
+ let lobby_addr = lobby.start();
|
|
|
+ self.lobbies.insert(clr.lobby_id, lobby_addr.clone());
|
|
|
+ Answer::LobbyCreated(lobby_addr)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+pub struct GameLobby {
|
|
|
+ connected_players: Vec<Addr<GameConnection>>,
|
|
|
+ game_id: String,
|
|
|
+ game: game::Game,
|
|
|
+}
|
|
|
+
|
|
|
+impl Actor for GameLobby {
|
|
|
+ type Context = Context<Self>;
|
|
|
+ fn started(&mut self, ctx: &mut Self::Context) {
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl Handler<JoinRequest> for GameLobby {
|
|
|
+ type Result = Answer;
|
|
|
+ fn handle(&mut self, jr: JoinRequest, ctx: &mut Self::Context) -> Self::Result {
|
|
|
+ self.connected_players.push(jr.p);
|
|
|
+ Answer::NoSuchLobby
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl Handler<GetGame> for GameLobby {
|
|
|
+ type Result = game::Game;
|
|
|
+ fn handle(&mut self, gg: GetGame, ctx: &mut Self::Context) -> Self::Result {
|
|
|
+ self.game.clone()
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl GameLobby {
|
|
|
+ pub fn new(gi: String) -> Self {
|
|
|
+ GameLobby {
|
|
|
+ connected_players: vec![],
|
|
|
+ game_id: gi,
|
|
|
+ game: game::Game::new()
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+///
|
|
|
+/// connection to one single client
|
|
|
+///
|
|
|
+pub struct GameConnection {
|
|
|
+ heartbeat: Instant,
|
|
|
+ nick: Option<String>,
|
|
|
+ game_id: Option<String>,
|
|
|
+ server: Addr<Server>,
|
|
|
+ game_lobby: Option<Addr<GameLobby>>
|
|
|
+}
|
|
|
+
|
|
|
+impl Actor for GameConnection {
|
|
|
+ type Context = ws::WebsocketContext<Self>;
|
|
|
+
|
|
|
+ fn started(&mut self, ctx: &mut Self::Context) {
|
|
|
+ self.initiate_heartbeat(ctx);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for GameConnection {
|
|
|
+ fn handle(
|
|
|
+ &mut self,
|
|
|
+ msg: Result<ws::Message, ws::ProtocolError>,
|
|
|
+ ctx: &mut Self::Context,
|
|
|
+ ) {
|
|
|
+ // process websocket messages
|
|
|
+ match msg {
|
|
|
+ Ok(ws::Message::Ping(msg)) => {
|
|
|
+ self.heartbeat = Instant::now();
|
|
|
+ ctx.pong(&msg);
|
|
|
+ }
|
|
|
+ Ok(ws::Message::Pong(_)) => {
|
|
|
+ self.heartbeat = Instant::now();
|
|
|
+ }
|
|
|
+ Ok(ws::Message::Text(text)) => {
|
|
|
+ println!("hmmm: {:?}", text);
|
|
|
+ self.handle_message(&text, ctx);
|
|
|
+ },
|
|
|
+ Ok(ws::Message::Binary(bin)) => ctx.binary(bin),
|
|
|
+ Ok(ws::Message::Close(reason)) => {
|
|
|
+ ctx.close(reason);
|
|
|
+ ctx.stop();
|
|
|
+ }
|
|
|
+ _ => ctx.stop(),
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl GameConnection {
|
|
|
+ pub fn new(server_addr: Addr<Server>) -> Self {
|
|
|
+ GameConnection {
|
|
|
+ heartbeat: Instant::now(),
|
|
|
+ nick: None,
|
|
|
+ game_id: None,
|
|
|
+ server: server_addr,
|
|
|
+ game_lobby: None
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ pub fn initiate_heartbeat(&self, ctx: &mut <Self as Actor>::Context) {
|
|
|
+ ctx.run_interval(HEARTBEAT_INTERVAL, |act, ctx| {
|
|
|
+ if Instant::now().duration_since(act.heartbeat) > CLIENT_TIMEOUT {
|
|
|
+ //println!("Websocket Client heartbeat failed, disconnecting!");
|
|
|
+ ctx.stop();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ ctx.ping(b"");
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ pub fn send_message(&self, m: &UpdateMessage, ctx: &mut <Self as Actor>::Context) {
|
|
|
+ let txt = serde_json::to_string(m).unwrap();
|
|
|
+ println!("{:?}", txt);
|
|
|
+ ctx.text(txt);
|
|
|
+ }
|
|
|
+
|
|
|
+ pub fn handle_message(&mut self, text: &str, ctx: &mut <Self as Actor>::Context) {
|
|
|
+ println!("hm: {:?}", text);
|
|
|
+ let parsed: Result<ClientMessage, _> = serde_json::from_str(text);
|
|
|
+ if let Ok(msg) = parsed {
|
|
|
+ match msg {
|
|
|
+ ClientMessage::CreateGame{game_id, nick} => {
|
|
|
+ self.game_id = Some(game_id.clone());
|
|
|
+ self.nick = Some(nick);
|
|
|
+ let lobby_addr = block_on(self.server.send(CreateLobbyRequest{ lobby_id: game_id.clone(), p: ctx.address() }));
|
|
|
+ match lobby_addr {
|
|
|
+ Ok(Answer::LobbyAlreadyExists) => {
|
|
|
+ self.send_message(&UpdateMessage::GameAlreadyExists{ game_id: game_id.clone() }, ctx);
|
|
|
+ },
|
|
|
+ Ok(Answer::LobbyCreated(lobby_addr)) => {
|
|
|
+ let gm = block_on(lobby_addr.send(GetGame));
|
|
|
+ self.send_message(&UpdateMessage::GameState(gm.unwrap()), ctx);
|
|
|
+ },
|
|
|
+ _ => {
|
|
|
+ println!("internal error creating lobby");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ ClientMessage::Join{game_id, nick} => {
|
|
|
+ self.game_id = Some(game_id.clone());
|
|
|
+ self.nick = Some(nick);
|
|
|
+ let lobby_addr = block_on(self.server.send(JoinRequest{ lobby_id: game_id.clone(), p: ctx.address() }));
|
|
|
+ match lobby_addr {
|
|
|
+ Ok(Answer::NoSuchLobby) => {
|
|
|
+ self.send_message(&UpdateMessage::GameNotFound{ game_id: game_id.clone() }, ctx);
|
|
|
+ },
|
|
|
+ Ok(Answer::LobbyJoined(lobby_addr)) => {
|
|
|
+ let gm = block_on(lobby_addr.send(GetGame));
|
|
|
+ self.send_message(&UpdateMessage::GameState(gm.unwrap()), ctx);
|
|
|
+ },
|
|
|
+ _ => {
|
|
|
+ println!("internal error joining lobby");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ println!("error parsing json");
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|