Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

448 lines (361 sloc) 11.311 kb
#include <algorithm>
#include <iostream>
#include <string>
#include <ctime>
#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>
#include "asserts.hpp"
#include "filesystem.hpp"
#include "foreach.hpp"
#include "formatter.hpp"
#include "tbs_server.hpp"
#include "string_utils.hpp"
#include "utils.hpp"
#include "variant_utils.hpp"
namespace tbs {
server::game_info::game_info(const variant& value)
{
game_state = game::create(value);
}
server::client_info::client_info() : nplayer(0), last_contact(0)
{}
server::server(boost::asio::io_service& io_service)
: timer_(io_service), nheartbeat_(0), scheduled_write_(0), status_id_(0)
{
heartbeat();
}
void server::adopt_ajax_socket(socket_ptr socket, int session_id, const variant& msg)
{
const std::string& type = msg["type"].as_string();
if(session_id == -1) {
if(type == "create_game") {
game_info_ptr g(new game_info(msg));
if(!g->game_state) {
std::cerr << "COULD NOT CREATE GAME TYPE: " << msg["game_type"].as_string() << "\n";
send_msg(socket, "{ \"type\": \"create_game_failed\" }");
return;
}
std::vector<variant> users = msg["users"].as_list();
for(int i = 0; i != users.size(); ++i) {
const std::string user = users[i]["user"].as_string();
const int session_id = users[i]["session_id"].as_int();
if(clients_.count(session_id) && session_id != -1) {
std::cerr << "ERROR: REUSED SESSION ID WHEN CREATING GAME: " << session_id << "\n";
send_msg(socket, "{ \"type\": \"create_game_failed\" }");
return;
}
if(session_id != -1) {
client_info& cli_info = clients_[session_id];
cli_info.user = user;
cli_info.game = g;
cli_info.nplayer = i;
cli_info.last_contact = nheartbeat_;
cli_info.session_id = session_id;
g->game_state->add_player(user);
} else {
g->game_state->add_ai_player(user);
}
g->clients.push_back(session_id);
}
const game_context context(g->game_state.get());
g->game_state->setup_game();
games_.push_back(g);
send_msg(socket, formatter() << "{ \"type\": \"game_created\", \"game_id\": " << g->game_state->game_id() << " }");
status_change();
return;
} else if(type == "observe_game") {
const int id = msg["game_id"].as_int();
const std::string user = msg["user"].as_string();
const int session_id = msg["session_id"].as_int();
game_info_ptr g;
foreach(const game_info_ptr& gm, games_) {
if(gm->game_state->game_id() == id) {
g = gm;
break;
}
}
if(!g) {
send_msg(socket, "{ \"type\": \"unknown_game\" }");
return;
}
if(clients_.count(session_id)) {
send_msg(socket, "{ \"type\": \"reuse_session_id\" }");
return;
}
client_info& cli_info = clients_[session_id];
cli_info.user = user;
cli_info.game = g;
cli_info.nplayer = -1;
cli_info.last_contact = nheartbeat_;
cli_info.session_id = session_id;
g->clients.push_back(session_id);
send_msg(socket, formatter() << "{ \"type\": \"observing_game\" }");
return;
} else if(type == "get_status") {
const int last_status = msg["last_seen"].as_int();
if(last_status == status_id_) {
status_sockets_.push_back(socket);
} else {
send_msg(socket, create_lobby_msg().write_json());
}
return;
} else {
send_msg(socket, "{ \"type\": \"unknown_message\" }");
return;
}
}
std::map<int, client_info>::iterator client_itor = clients_.find(session_id);
if(client_itor == clients_.end()) {
std::cerr << "BAD SESSION ID: " << session_id << "\n";
send_msg(socket, "{ \"type\": \"invalid_session\" }");
return;
}
client_info& cli_info = client_itor->second;
socket_info& info = connections_[socket];
info.nick = cli_info.user;
handle_message_internal(socket, cli_info, msg);
close_ajax(socket, cli_info);
}
void server::clear_games()
{
games_.clear();
}
void server::handle_message_internal(socket_ptr socket, client_info& cli_info, const variant& msg)
{
const std::string& user = cli_info.user;
const std::string& type = msg["type"].as_string();
cli_info.last_contact = nheartbeat_;
if(cli_info.game) {
if(type == "quit") {
quit_games(cli_info.session_id);
queue_msg(cli_info.session_id, "{ \"type\": \"bye\" }");
return;
}
const bool game_started = cli_info.game->game_state->started();
const game_context context(cli_info.game->game_state.get());
cli_info.game->game_state->handle_message(cli_info.nplayer, msg);
flush_game_messages(*cli_info.game);
}
}
void server::close_ajax(socket_ptr socket, client_info& cli_info)
{
socket_info& info = connections_[socket];
if(cli_info.msg_queue.empty() == false) {
for(std::map<socket_ptr,std::string>::iterator s = waiting_connections_.begin();
s != waiting_connections_.end(); ++s) {
if(s->second == info.nick && s->first != socket) {
send_msg(s->first, "{ \"type\": \"keepalive\" }");
break;
}
}
send_msg(socket, cli_info.msg_queue.front());
cli_info.msg_queue.pop_front();
} else {
waiting_connections_[socket] = info.nick;
}
}
void server::queue_msg(int session_id, const std::string& msg)
{
if(session_id == -1) {
return;
}
clients_[session_id].msg_queue.push_back(msg);
}
void server::send_msg(socket_ptr socket, const variant& msg)
{
send_msg(socket, msg.write_json());
}
void server::send_msg(socket_ptr socket, const char* msg)
{
send_msg(socket, std::string(msg));
}
void server::send_msg(socket_ptr socket, const std::string& msg)
{
const socket_info& info = connections_[socket];
std::stringstream buf;
buf <<
"HTTP/1.1 200 OK\r\n"
"Date: " << get_http_datetime() << "\r\n"
"Connection: close\r\n"
"Server: Wizard/1.0\r\n"
"Accept-Ranges: bytes\r\n"
"Access-Control-Allow-Origin: *\r\n"
"Content-Type: application/json\r\n"
"Content-Length: " << std::dec << (int)msg.size() << "\r\n"
"Last-Modified: " << get_http_datetime() << "\r\n\r\n";
std::string header = buf.str();
boost::shared_ptr<std::string> str_buf(new std::string(header.empty() ? msg : (header + msg)));
boost::asio::async_write(*socket, boost::asio::buffer(*str_buf),
boost::bind(&server::handle_send, this, socket, _1, _2, str_buf));
}
void server::handle_send(socket_ptr socket, const boost::system::error_code& e, size_t nbytes, boost::shared_ptr<std::string> buf)
{
if(e) {
std::cerr << "ERROR SENDING DATA\n";
}
disconnect(socket);
}
void server::disconnect(socket_ptr socket)
{
waiting_connections_.erase(socket);
connections_.erase(socket);
socket->close();
}
void server::heartbeat()
{
timer_.expires_from_now(boost::posix_time::milliseconds(100));
timer_.async_wait(boost::bind(&server::heartbeat, this));
foreach(game_info_ptr g, games_) {
g->game_state->process();
}
nheartbeat_++;
if(nheartbeat_%10 != 0) {
return;
}
sys::pump_file_modifications();
const bool send_heartbeat = nheartbeat_%100 == 0;
for(std::map<socket_ptr, std::string>::iterator i = waiting_connections_.begin(); i != waiting_connections_.end(); ++i) {
socket_ptr socket = i->first;
socket_info& info = connections_[socket];
client_info& cli_info = clients_[info.session_id];
if(cli_info.msg_queue.empty() == false) {
send_msg(socket, cli_info.msg_queue.front());
cli_info.msg_queue.pop_front();
} else if(send_heartbeat) {
if(!cli_info.game) {
send_msg(socket, "{ \"type\": \"heartbeat\" }");
} else {
variant_builder doc;
doc.add("type", "heartbeat");
std::vector<variant> items;
foreach(int client_session, cli_info.game->clients) {
const client_info& info = clients_[client_session];
variant_builder value;
value.add("nick", info.user);
value.add("ingame", info.game == cli_info.game);
value.add("lag", nheartbeat_ - info.last_contact);
items.push_back(value.build());
}
foreach(const std::string& ai, cli_info.game->game_state->get_ai_players()) {
variant_builder value;
value.add("nick", ai);
value.add("ingame", true);
value.add("lag", 0);
items.push_back(value.build());
}
doc.set("players", variant(&items));
send_msg(socket, doc.build());
}
}
}
if(send_heartbeat) {
status_change();
}
if(nheartbeat_ >= scheduled_write_) {
//write out the game recorded data.
}
}
void server::schedule_write()
{
if(scheduled_write_) {
return;
}
scheduled_write_ = nheartbeat_ + 10;
}
void server::flush_game_messages(game_info& info)
{
std::vector<game::message> game_response;
info.game_state->swap_outgoing_messages(game_response);
foreach(game::message& msg, game_response) {
if(msg.recipients.empty()) {
foreach(int session_id, info.clients) {
if(session_id != -1) {
queue_msg(session_id, msg.contents);
}
}
} else {
foreach(int player, msg.recipients) {
if(player >= static_cast<int>(info.clients.size())) {
//Might be an observer who since disconnected.
continue;
}
if(player >= 0) {
queue_msg(info.clients[player], msg.contents);
} else {
//A message for observers
for(size_t n = info.game_state->players().size(); n < info.clients.size(); ++n) {
queue_msg(info.clients[n], msg.contents);
}
}
}
}
}
}
void server::quit_games(int session_id)
{
client_info& cli_info = clients_[session_id];
foreach(game_info_ptr& g, games_) {
if(std::count(g->clients.begin(), g->clients.end(), session_id)) {
const bool is_first_client = g->clients.front() == session_id;
g->clients.erase(std::remove(g->clients.begin(), g->clients.end(), session_id), g->clients.end());
if(!g->game_state->started()) {
g->game_state->remove_player(cli_info.user);
if(is_first_client) {
g->clients.clear();
//TODO: remove joining clients from the game nicely.
} else {
const std::string msg = create_game_info_msg(g).write_json();
foreach(int client, g->clients) {
queue_msg(client, msg);
}
}
} else if(g->game_state->get_player_index(cli_info.user) != -1) {
std::cerr << "sending quit message...\n";
g->game_state->queue_message(formatter() << "<message text=\"" << cli_info.user << " has quit\"/>");
flush_game_messages(*g);
}
if(g->clients.empty()) {
//game has no more clients left, so kill it.
g.reset();
}
}
}
int games_size = games_.size();
games_.erase(std::remove(games_.begin(), games_.end(), game_info_ptr()), games_.end());
cli_info.game.reset();
if(games_size != games_.size()) {
status_change();
}
}
variant server::create_lobby_msg() const
{
variant_builder value;
value.add("type", "lobby");
value.add("status_id", status_id_);
std::vector<variant> games;
foreach(game_info_ptr g, games_) {
games.push_back(create_game_info_msg(g));
}
value.set("games", variant(&games));
return value.build();
}
variant server::create_game_info_msg(game_info_ptr g) const
{
variant_builder value;
value.add("type", "game_info");
value.add("id", g->game_state->game_id());
value.add("started", g->game_state->started());
return value.build();
}
void server::status_change()
{
++status_id_;
if(!status_sockets_.empty()) {
std::string msg = create_lobby_msg().write_json();
foreach(socket_ptr socket, status_sockets_) {
send_msg(socket, msg);
}
status_sockets_.clear();
}
}
}
Jump to Line
Something went wrong with that request. Please try again.