#![doc(
html_favicon_url = "https://github.com/bestia-dev/mem6_game/raw/master/webfolder/mem6/images/icons-16.png"
)]
#![doc(
html_logo_url = "https://github.com/bestia-dev/mem6_game/raw/master/webfolder/mem6/images/icons-192.png"
)]
#![warn(
clippy::all,
clippy::restriction,
clippy::pedantic,
clippy::nursery,
clippy::cargo,
clippy::shadow_reuse,
clippy::shadow_same,
clippy::shadow_unrelated,
)]
#![allow(
clippy::cargo_common_metadata,
clippy::multiple_crate_versions,
clippy::wildcard_dependencies,
clippy::implicit_return,
clippy::doc_markdown,
)]
use mem6_common::{WsMessage};
use unwrap::unwrap;
use clap::{App, Arg};
use env_logger::Env;
use futures::{sync::mpsc, Future, Stream};
use std::{
collections::HashMap,
net::{SocketAddr, IpAddr, Ipv4Addr},
sync::{Arc, Mutex},
};
use warp::{
ws::{Message, WebSocket},
Filter,
};
use log::info;
type Users = Arc<Mutex<HashMap<usize, mpsc::UnboundedSender<Message>>>>;
fn main() {
let mut builder = env_logger::from_env(Env::default().default_filter_or("info"));
builder.format_timestamp_nanos();
builder.init();
let matches = App::new(env!("CARGO_PKG_NAME"))
.version(env!("CARGO_PKG_VERSION"))
.author(env!("CARGO_PKG_AUTHORS"))
.about(env!("CARGO_PKG_DESCRIPTION"))
.arg(
Arg::with_name("prm_ip")
.value_name("ip")
.default_value("127.0.0.1")
.help("ip address for listening"),
)
.arg(
Arg::with_name("prm_port")
.value_name("port")
.default_value("8086")
.help("port for listening"),
)
.get_matches();
let fnl_prm_ip = matches
.value_of("prm_ip")
.expect("error on prm_ip")
.to_lowercase();
let fnl_prm_port = matches
.value_of("prm_port")
.expect("error on prm_port")
.to_lowercase();
let local_ip = IpAddr::V4(fnl_prm_ip.parse::<Ipv4Addr>().expect("not an ip address"));
let local_port = u16::from_str_radix(&fnl_prm_port, 10).expect("not a number");
let local_addr = SocketAddr::new(local_ip, local_port);
info!(
"mem6 http server listening on {} and WebSocket on /mem6ws/",
ansi_term::Colour::Red.paint(local_addr.to_string())
);
let users = Arc::new(Mutex::new(HashMap::new()));
let users = warp::any().map(move || {
Arc::<
std::sync::Mutex<
std::collections::HashMap<
usize,
futures::sync::mpsc::UnboundedSender<warp::ws::Message>,
>,
>,
>::clone(&users)
});
let websocket = warp::path("mem6ws")
.and(warp::ws2())
.and(users)
.and(warp::path::param::<String>())
.map(|ws: warp::ws::Ws2, users, url_param| {
ws.on_upgrade(move |socket| user_connected(socket, users, url_param))
});
let fileserver = warp::fs::dir("./mem6/");
let routes = fileserver.or(websocket);
warp::serve(routes).run(local_addr);
}
#[allow(clippy::needless_pass_by_value)]
fn user_connected(
ws: WebSocket,
users: Users,
url_param: String,
) -> impl Future<Item = (), Error = ()> {
info!("user_connect() url_param: {}", url_param);
let my_id = unwrap!(url_param.parse::<usize>());
let mut user_exist = false;
for (&uid, ..) in users.lock().expect("error users.lock()").iter() {
if uid == my_id {
user_exist = true;
break;
}
}
if user_exist {
info!("user_disconnected for reconnect: {}", my_id);
user_disconnected(my_id, &users);
}
let (user_ws_tx, user_ws_rx) = ws.split();
let (tx, rx) = mpsc::unbounded();
warp::spawn(
rx.map_err(|()| -> warp::Error { unreachable!("unbounded rx never errors") })
.forward(user_ws_tx)
.map(|_tx_rx| ())
.map_err(|ws_err| info!("WebSocket send error: {}", ws_err)),
);
info!("users.insert: {}", my_id);
users.lock().expect("error uses.lock()").insert(my_id, tx);
let users2 = Arc::<
std::sync::Mutex<
std::collections::HashMap<
usize,
futures::sync::mpsc::UnboundedSender<warp::ws::Message>,
>,
>,
>::clone(&users);
user_ws_rx
.for_each(move |msg| {
receive_message(my_id, &msg, &users);
Ok(())
})
.then(move |result| {
user_disconnected(my_id, &users2);
result
})
.map_err(move |e| {
info!("WebSocket error(uid={}): {}", my_id, e);
})
}
fn receive_message(ws_uid_of_message: usize, messg: &Message, users: &Users) {
let msg = if let Ok(s) = messg.to_str() {
s
} else {
return;
};
let new_msg = msg.to_string();
let msg: WsMessage = serde_json::from_str(&new_msg).unwrap_or_else(|_x| WsMessage::MsgDummy {
dummy: String::from("error"),
});
match msg {
WsMessage::MsgDummy { dummy } => info!("MsgDummy: {}", dummy),
WsMessage::MsgRequestWsUid {
my_ws_uid,
players_ws_uid,
} => {
info!("MsgRequestWsUid: {} {}", my_ws_uid, players_ws_uid);
let j = serde_json::to_string(
&WsMessage::MsgResponseWsUid {
your_ws_uid: ws_uid_of_message,
server_version: env!("CARGO_PKG_VERSION").to_string(),
})
.expect("serde_json::to_string(&WsMessage::MsgResponseWsUid { your_ws_uid: ws_uid_of_message })");
info!("send MsgResponseWsUid: {}", j);
match users
.lock()
.expect("error users.lock()")
.get(&ws_uid_of_message)
.unwrap()
.unbounded_send(Message::text(j))
{
Ok(()) => (),
Err(_disconnected) => {}
}
send_to_other_players(users, ws_uid_of_message, &new_msg, &players_ws_uid)
}
WsMessage::MsgPing { msg_id } => {
let j = unwrap!(serde_json::to_string(&WsMessage::MsgPong { msg_id }));
match users
.lock()
.expect("error users.lock()")
.get(&ws_uid_of_message)
.unwrap()
.unbounded_send(Message::text(j))
{
Ok(()) => (),
Err(_disconnected) => {}
}
}
WsMessage::MsgPong { msg_id: _ } => {
unreachable!("mem6_server must not receive MsgPong");
}
WsMessage::MsgResponseWsUid { .. } => {
info!("MsgResponseWsUid: {}", "");
}
WsMessage::MsgStartGame { players_ws_uid, .. }
| WsMessage::MsgClick1stCard { players_ws_uid, .. }
| WsMessage::MsgClick2ndCard { players_ws_uid, .. }
| WsMessage::MsgTakeTurn { players_ws_uid, .. }
| WsMessage::MsgGameOver { players_ws_uid, .. }
| WsMessage::MsgAllGameData { players_ws_uid, .. }
| WsMessage::MsgAck { players_ws_uid, .. }
| WsMessage::MsgJoin { players_ws_uid, .. }
| WsMessage::MsgDrinkEnd { players_ws_uid, .. }
| WsMessage::MsgPlayAgain { players_ws_uid, .. }
| WsMessage::MsgAskPlayer1ForResync { players_ws_uid, .. } => {
send_to_other_players(users, ws_uid_of_message, &new_msg, &players_ws_uid)
}
}
}
fn send_to_other_players(
users: &Users,
ws_uid_of_message: usize,
new_msg: &str,
players_ws_uid: &str,
) {
let vec_players_ws_uid: Vec<usize> = unwrap!(serde_json::from_str(players_ws_uid));
for (&uid, tx) in users.lock().expect("error users.lock()").iter() {
let mut is_player;
is_player = false;
for &pl_ws_uid in &vec_players_ws_uid {
if pl_ws_uid == uid {
is_player = true;
}
}
if ws_uid_of_message != uid && is_player {
match tx.unbounded_send(Message::text(String::from(new_msg))) {
Ok(()) => (),
Err(_disconnected) => {
}
}
}
}
}
fn user_disconnected(my_id: usize, users: &Users) {
info!("good bye user: {}", my_id);
users.lock().expect("users.lock").remove(&my_id);
}