Merge pull request #866 from GregoryLundberg/GL_delete_old_networking

Delete last vestiges of old networking code.
This commit is contained in:
Charles Dang 2016-11-08 10:16:08 +11:00 committed by GitHub
commit e8ee1803c2
10 changed files with 0 additions and 2109 deletions

View file

@ -1,105 +0,0 @@
/*
Copyright (C) 2003 - 2016 by David White <dave@whitevine.net>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
#include "global.hpp"
#include "input_stream.hpp"
#ifndef _WIN32
#include <algorithm>
#include <iostream>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <cerrno>
#endif
input_stream::input_stream(const std::string& path) :
fd_(-1),
path_(path),
data_()
{
#ifndef _WIN32
if(path == "") {
return;
}
const int res = mkfifo(path.c_str(),0660);
if(res != 0) {
std::cerr << "could not make fifo at '" << path << "' (" << errno << ")\n";
}
fd_ = open(path.c_str(),O_RDONLY|O_NONBLOCK);
if(fd_ == -1) {
std::cerr << "failed to open fifo at '" << path << "' (" << errno << ")\n";
} else {
std::cerr << "opened fifo at '" << path << "'. Server commands may be written to this file.\n";
}
#endif
}
input_stream::~input_stream()
{
#ifndef _WIN32
stop();
#endif
}
void input_stream::stop()
{
#ifndef _WIN32
if(fd_ != -1) {
close(fd_);
unlink(path_.c_str());
fd_ = -1;
}
#endif
}
#ifndef _WIN32
bool input_stream::read_line(std::string& str)
{
if(fd_ == -1) {
return false;
}
const size_t block_size = 4096;
char block[block_size];
const size_t nbytes = read(fd_,block,block_size);
if (nbytes == static_cast<size_t>(-1)) {
return false;
}
std::copy(block,block+nbytes,std::back_inserter(data_));
const std::deque<char>::iterator itor = std::find(data_.begin(),data_.end(),'\n');
if(itor != data_.end()) {
str.resize(itor - data_.begin());
std::copy(data_.begin(),itor,str.begin());
data_.erase(data_.begin(),itor+1);
return true;
} else {
return false;
}
}
#else
bool input_stream::read_line(std::string&)
{
return false;
}
#endif

View file

@ -1,44 +0,0 @@
/*
Copyright (C) 2003 - 2016 by David White <dave@whitevine.net>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
#ifndef INPUT_STREAM_HPP_INCLUDED
#define INPUT_STREAM_HPP_INCLUDED
#include <deque>
#include <string>
class input_stream
{
public:
input_stream(const std::string& path);
~input_stream();
bool read_line(std::string& str);
void stop();
const std::string& path() const
{
return path_;
}
private:
input_stream(const input_stream&);
void operator=(const input_stream&);
int fd_;
std::string path_;
std::deque<char> data_;
};
#endif

View file

@ -1,154 +0,0 @@
/*
Copyright (C) 2009 - 2016 by Tomasz Sniatowski <kailoran@gmail.com>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
#include "config.hpp"
#include "game.hpp"
#include "player_network.hpp"
#include "room.hpp"
#include "log.hpp"
#include "serialization/string_utils.hpp"
#include "util.hpp"
static lg::log_domain log_server("server");
#define ERR_ROOM LOG_STREAM(err, log_server)
#define LOG_ROOM LOG_STREAM(info, log_server)
#define DBG_ROOM LOG_STREAM(debug, log_server)
namespace wesnothd {
room::room(const std::string& name)
: name_(name)
, members_()
, persistent_(false)
, topic_()
, logged_(false)
{
}
room::room(const config& wml)
: name_(wml["name"])
, members_()
, persistent_(wml["persistent"].to_bool())
, topic_(wml["topic"])
, logged_(wml["logged"].to_bool())
{
}
void room::write(config& cfg) const
{
cfg["name"] = name_;
cfg["persistent"] = persistent_;
cfg["topic"] = topic_;
cfg["logged"] = logged_;
}
const std::string& room::name() const
{
return name_;
}
bool room::persistent() const
{
return persistent_;
}
void room::set_persistent(bool v)
{
persistent_ = v;
}
const std::string& room::topic() const
{
return topic_;
}
void room::set_topic(const std::string& v)
{
topic_ = v;
}
bool room::logged() const
{
return logged_;
}
void room::set_logged(bool v)
{
logged_ = v;
}
bool room::add_player(network::connection player)
{
if (is_member(player)) {
ERR_ROOM << "ERROR: Player is already in this room. (socket: "
<< player << ")\n";
return false;
}
members_.push_back(player);
return true;
}
void room::remove_player(network::connection player)
{
const user_vector::iterator itor =
std::find(members_.begin(), members_.end(), player);
if (itor != members_.end()) {
members_.erase(itor);
} else {
ERR_ROOM << "ERROR: Player is not in this room. (socket: "
<< player << ")\n";
}
}
void room::send_data(simple_wml::document& data,
const network::connection exclude,
std::string packet_type) const
{
wesnothd::send_to_many(data, members(), exclude, packet_type);
}
void room::send_server_message_to_all(const char* message, network::connection exclude) const
{
simple_wml::document doc;
send_server_message(message, 0, &doc);
send_data(doc, exclude, "message");
}
void room::send_server_message(const char* message, network::connection sock,
simple_wml::document* docptr) const
{
simple_wml::document docbuf;
if(docptr == nullptr) {
docptr = &docbuf;
}
simple_wml::document& doc = *docptr;
simple_wml::node& msg = doc.root().add_child("message");
msg.set_attr("sender", "server");
msg.set_attr_dup("message", message);
if(sock) {
send_to_one(doc, sock, "message");
}
}
void room::process_message(simple_wml::document& /*data*/,
const player_map::iterator /*user*/)
{
}
} //end namespace wesnothd

View file

@ -1,179 +0,0 @@
/*
Copyright (C) 2009 - 2016 by Tomasz Sniatowski <kailoran@gmail.com>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
#ifndef SERVER_ROOM_HPP_INCLUDED
#define SERVER_ROOM_HPP_INCLUDED
#include "network.hpp"
#include "player.hpp"
#include "simple_wml.hpp"
namespace wesnothd {
typedef std::vector<network::connection> connection_vector;
typedef std::map<network::connection,player> player_map;
class game;
/**
* A room is a group of players that can communicate via messages.
*/
class room {
public:
/**
* Construct a room with just a name and default settings
*/
room(const std::string& name);
/**
* Construct a room from WML
*/
room(const config& cfg);
/**
* Write room info to a config
*/
void write(config& cfg) const;
/**
* The name of this room
*/
const std::string& name() const;
/**
* Whether this room should be 'persistent', i.e. not deleted when there
* are no players within and stored on disk if needed.
*/
bool persistent() const;
/**
* Set the persistent flag for this room
*/
void set_persistent(bool v);
/**
* Whether the room is logged (and might end up in e.g. the lobby bot
*/
bool logged() const;
/**
* Set the room's logged flag
*/
void set_logged(bool v);
/**
* This room's topic/motd, sent to all joining players
*/
const std::string& topic() const;
/**
* Set the topic for this room
*/
void set_topic(const std::string& v);
/**
* Return the number of players in this room
*/
size_t size() const {
return members_.size();
}
/**
* Return true iif the room is empty
*/
bool empty() const {
return members_.empty();
}
/**
* Return the members of this room
*/
const std::vector<network::connection>& members() const {
return members_;
}
/**
* Membership checker.
* @return true iif player is a member of this room
*/
bool is_member(network::connection player) const {
return std::find(members_.begin(), members_.end(), player) != members_.end();
}
/**
* Joining the room
* @return true if the player was successfully added
*/
bool add_player(network::connection player);
/**
* Leaving the room
*/
void remove_player(network::connection player);
/**
* Chat message processing
*/
void process_message(simple_wml::document& data, const player_map::iterator user);
/**
* Convenience function for sending a wml document to all (or all except
* one) members.
* @see send_to_many
* @param data the document to send
* @param exclude if nonzero, do not send to this player
* @param packet_type the packet type, if empty the root node name is used
*/
void send_data(simple_wml::document& data, const network::connection exclude=0, std::string packet_type = "") const;
/**
* Send a text message to all members
* @param message the message text
* @param exclude if nonzero, do not send to this player
*/
void send_server_message_to_all(const char* message, network::connection exclude=0) const;
void send_server_message_to_all(const std::string& message, network::connection exclude=0) const
{
send_server_message_to_all(message.c_str(), exclude);
}
/**
* Prepare a text message and/or send it to a player. If a nonzero sock
* is passed, the message is sent to this player. If a non-null pointer
* to a simple_wml::document is passed, the message is stored in that
* document.
* @param message the message text
* @param sock the socket to send the message to, if nonzero
* @param docptr the wml document to store the message in, if nonnull
*/
void send_server_message(const char* message, network::connection sock,
simple_wml::document* docptr = nullptr) const;
void send_server_message(const std::string& message, network::connection sock,
simple_wml::document* docptr = nullptr) const
{
send_server_message(message.c_str(), sock, docptr);
}
private:
std::string name_;
connection_vector members_;
bool persistent_;
std::string topic_;
bool logged_;
};
} //end namespace wesnothd
#endif

View file

@ -1,548 +0,0 @@
/*
Copyright (C) 2009 - 2016 by Tomasz Sniatowski <kailoran@gmail.com>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
#include "game.hpp"
#include "player_network.hpp"
#include "room_manager.hpp"
#include "serialization/parser.hpp"
#include "serialization/binary_or_text.hpp"
#include "serialization/string_utils.hpp"
#include "util.hpp"
#include "filesystem.hpp"
#include "log.hpp"
static lg::log_domain log_server_lobby("server/lobby");
#define ERR_LOBBY LOG_STREAM(err, log_server_lobby)
#define WRN_LOBBY LOG_STREAM(warn, log_server_lobby)
#define LOG_LOBBY LOG_STREAM(info, log_server_lobby)
#define DBG_LOBBY LOG_STREAM(debug, log_server_lobby)
static lg::log_domain log_server("server");
#define ERR_SERVER LOG_STREAM(err, log_server)
#define WRN_SERVER LOG_STREAM(warn, log_server)
#define LOG_SERVER LOG_STREAM(info, log_server)
#define DBG_SERVER LOG_STREAM(debug, log_server)
namespace wesnothd {
const char* const room_manager::lobby_name_ = "lobby";
room_manager::room_manager(player_map &all_players)
: all_players_(all_players)
, lobby_(nullptr)
, rooms_by_name_()
, rooms_by_player_()
, player_stored_rooms_()
, filename_()
, compress_stored_rooms_(true)
, new_room_policy_(PP_EVERYONE)
, dirty_(false)
{
}
room_manager::~room_manager()
{
// this assumes the server is shutting down, so there's no need to
// send the actual room-quit messages to clients
write_rooms();
for (t_rooms_by_name_::value_type i : rooms_by_name_) {
delete i.second;
}
}
room_manager::PRIVILEGE_POLICY room_manager::pp_from_string(const std::string& str)
{
if (str == "everyone") {
return PP_EVERYONE;
} else if (str == "registered") {
return PP_REGISTERED;
} else if (str == "admins") {
return PP_ADMINS;
} else if (str == "nobody") {
return PP_NOBODY;
}
return PP_COUNT;
}
void room_manager::load_config(const config& cfg)
{
filename_ = cfg["room_save_file"].str();
compress_stored_rooms_ = cfg["compress_stored_rooms"].to_bool(true);
PRIVILEGE_POLICY pp = pp_from_string(cfg["new_room_policy"]);
if (pp != PP_COUNT) new_room_policy_ = pp;
}
void room_manager::read_rooms()
{
if (!filename_.empty() && filesystem::file_exists(filename_)) {
LOG_LOBBY << "Reading rooms from " << filename_ << "\n";
config cfg;
filesystem::scoped_istream file = filesystem::istream_file(filename_);
if (compress_stored_rooms_) {
read_gz(cfg, *file);
} else {
read(cfg, *file);
}
for (const config &c : cfg.child_range("room")) {
room* r(new room(c));
if (room_exists(r->name())) {
ERR_LOBBY << "Duplicate room ignored in stored rooms: "
<< r->name() << "\n";
delete r;
} else {
rooms_by_name_.insert(std::make_pair(r->name(), r));
}
}
}
lobby_ = get_room(lobby_name_);
if (lobby_ == nullptr) {
lobby_ = create_room(lobby_name_);
lobby_->set_persistent(true);
lobby_->set_logged(true);
dirty_ = true;
}
}
void room_manager::write_rooms()
{
if (filename_.empty()) return;
LOG_LOBBY << "Writing rooms to " << filename_ << "\n";
config cfg;
for (const t_rooms_by_name_::value_type& v : rooms_by_name_) {
const room& r = *v.second;
if (r.persistent()) {
config& c = cfg.add_child("room");
r.write(c);
}
}
filesystem::scoped_ostream file = filesystem::ostream_file(filename_);
config_writer writer(*file, compress_stored_rooms_);
writer.write(cfg);
dirty_ = false;
}
room* room_manager::get_room(const std::string &name)
{
t_rooms_by_name_::iterator i = rooms_by_name_.find(name);
if (i != rooms_by_name_.end()) {
return i->second;
} else {
return nullptr;
}
}
bool room_manager::room_exists(const std::string &name) const
{
return rooms_by_name_.find(name) != rooms_by_name_.end();
}
room* room_manager::create_room(const std::string &name)
{
if (room_exists(name)) {
DBG_LOBBY << "Requested creation of already existing room '" << name << "'\n";
return nullptr;
}
room* r = new room(name);
rooms_by_name_.insert(std::make_pair(name, r));
return r;
}
room* room_manager::get_create_room(const std::string &name, network::connection player)
{
room* r = get_room(name);
if (r == nullptr) {
bool can_create = false;
switch (new_room_policy_) {
case PP_EVERYONE:
can_create = true;
break;
case PP_REGISTERED:
{
player_map::iterator i = all_players_.find(player);
if (i != all_players_.end()) {
can_create = i->second.registered();
}
}
break;
case PP_ADMINS:
{
player_map::iterator i = all_players_.find(player);
if (i != all_players_.end()) {
can_create = i->second.is_moderator();
}
}
break;
default:
break;
}
if (can_create) { //TODO: check if player can create room
//TODO: filter room names for abuse?
r = create_room(name);
} else {
lobby_->send_server_message("The room does not exist", player);
return nullptr;
}
}
return r;
}
void room_manager::enter_lobby(network::connection player)
{
lobby_->add_player(player);
unstore_player_rooms(player);
}
void room_manager::enter_lobby(const wesnothd::game &game)
{
for (network::connection player : game.all_game_users()) {
enter_lobby(player);
}
}
void room_manager::exit_lobby(network::connection player)
{
// No messages are sent to the rooms the player is in because other members
// will receive the "player-entered-game" message (or similar) anyway, and
// will be able to deduce that he or she is no longer in any rooms
lobby_->remove_player(player);
store_player_rooms(player);
t_rooms_by_player_::iterator i = rooms_by_player_.find(player);
if (i != rooms_by_player_.end()) {
for (room* r : i->second) {
r->remove_player(player);
}
}
rooms_by_player_.erase(player);
}
bool room_manager::in_lobby(network::connection player) const
{
return lobby_->is_member(player);
}
void room_manager::remove_player(network::connection player)
{
// No messages are sent since a player-quit message is sent to everyone
// anyway.
lobby_->remove_player(player);
t_rooms_by_player_::iterator i = rooms_by_player_.find(player);
if (i != rooms_by_player_.end()) {
for (room* r : i->second) {
r->remove_player(player);
}
}
rooms_by_player_.erase(player);
player_stored_rooms_.erase(player);
}
room* room_manager::require_room(const std::string& room_name,
const player_map::iterator user,
const char *log_string)
{
room* r = get_room(room_name);
if (r == nullptr) {
lobby_->send_server_message("The room does not exist", user->first);
WRN_LOBBY << "Player " << user->second.name()
<< " (conn " << user->first << ")"
<< " attempted to " << log_string
<< "a nonexistent room '" << room_name << "'\n";
return nullptr;
}
return r;
}
room* room_manager::require_member(const std::string& room_name,
const player_map::iterator user,
const char *log_string)
{
room* r = require_room(room_name, user, log_string);
if (r == nullptr) return nullptr;
if (!r->is_member(user->first)) {
lobby_->send_server_message("You are not a member of this room", user->first);
WRN_LOBBY << "Player " << user->second.name()
<< " (conn " << user->first << ")"
<< " attempted to " << log_string
<< "room '" << room_name << "', but is not a member of that room\n";
return nullptr;
}
return r;
}
bool room_manager::player_enters_room(network::connection player, wesnothd::room *room)
{
if (room->is_member(player)) {
room->send_server_message("You are already in this room", player);
return false;
}
//TODO: implement per-room bans, check ban status here
room->add_player(player);
rooms_by_player_[player].insert(room);
return true;
}
void room_manager::player_exits_room(network::connection player, wesnothd::room *room)
{
room->remove_player(player);
rooms_by_player_[player].erase(room);
}
void room_manager::store_player_rooms(network::connection player)
{
t_rooms_by_player_::iterator i = rooms_by_player_.find(player);
if (i == rooms_by_player_.end()) {
return;
}
if (i->second.size() < 1) {
return;
}
t_player_stored_rooms_::iterator it =
player_stored_rooms_.insert(std::make_pair(player, std::set<std::string>())).first;
std::set<std::string>& store = it->second;
for (room* r : i->second) {
store.insert(r->name());
}
}
void room_manager::unstore_player_rooms(network::connection player)
{
player_map::iterator i = all_players_.find(player);
if (i != all_players_.end()) {
unstore_player_rooms(i);
}
}
void room_manager::unstore_player_rooms(const player_map::iterator user)
{
t_player_stored_rooms_::iterator it = player_stored_rooms_.find(user->first);
if (it == player_stored_rooms_.end()) {
return;
}
simple_wml::document doc;
simple_wml::node& join_msg = doc.root().add_child("room_join");
join_msg.set_attr_dup("player", user->second.name().c_str());
for (const std::string& room_name : it->second) {
room* r = get_create_room(room_name, user->first);
if (r == nullptr) {
LOG_LOBBY << "Player " << user->second.name() << " unable to rejoin room " << room_name << "\n";
continue;
}
player_enters_room(user->first, r);
join_msg.set_attr_dup("room", room_name.c_str());
r->send_data(doc, user->first);
join_msg.remove_child("members", 0);
fill_member_list(r, join_msg);
join_msg.set_attr_dup("topic", r->topic().c_str());
send_to_one(doc, user->first);
}
}
void room_manager::process_message(simple_wml::document &data, const player_map::iterator user)
{
simple_wml::node* const message = data.root().child("message");
assert (message);
message->set_attr_dup("sender", user->second.name().c_str());
std::string room_name = message->attr("room").to_string();
if (room_name.empty()) room_name = lobby_name_;
room* r = require_member(room_name, user, "message");
if (r == nullptr) {
std::stringstream ss;
ss << "You are not a member of the room '" << room_name << "'. "
<< "Your message has not been relayed.";
lobby_->send_server_message(ss.str(), user->first);
return;
}
if (user->second.is_message_flooding()) {
r->send_server_message(
"Warning: you are sending too many messages too fast. "
"Your message has not been relayed.", user->first);
return;
}
const simple_wml::string_span& msg = (*message)["message"];
chat_message::truncate_message(msg, *message);
if (r->logged()) {
if (msg.size() >= 3 && simple_wml::string_span(msg.begin(), 4) == "/me ") {
LOG_SERVER << network::ip_address(user->first)
<< "\t<" << user->second.name()
<< simple_wml::string_span(msg.begin() + 3, msg.size() - 3)
<< ">\n";
} else {
LOG_SERVER << network::ip_address(user->first) << "\t<"
<< user->second.name() << "> " << msg << "\n";
}
}
r->send_data(data, user->first, "message");
}
void room_manager::process_room_join(simple_wml::document &data, const player_map::iterator user)
{
simple_wml::node* const msg = data.root().child("room_join");
assert(msg);
std::string room_name = msg->attr("room").to_string();
room* r = get_create_room(room_name, user->first);
if (r == nullptr) {
return;
}
if (!player_enters_room(user->first, r)) {
return; //player was unable to join room
}
// notify other members
msg->set_attr_dup("player", user->second.name().c_str());
r->send_data(data, user->first);
// send member list to the new member
fill_member_list(r, *msg);
msg->set_attr_dup("topic", r->topic().c_str());
send_to_one(data, user->first);
}
void room_manager::process_room_part(simple_wml::document &data, const player_map::iterator user)
{
simple_wml::node* const msg = data.root().child("room_part");
assert(msg);
std::string room_name = msg->attr("room").to_string();
if (room_name == lobby_name_) {
lobby_->send_server_message("You cannot quit the lobby", user->first);
return;
}
room* r = require_member(room_name, user, "quit");
if (r == nullptr) return;
player_exits_room(user->first, r);
msg->set_attr_dup("player", user->second.name().c_str());
r->send_data(data);
if (r->empty() && !r->persistent()) {
LOG_LOBBY << "Last player left room " << room_name << ". Deleting room.\n";
rooms_by_name_.erase(room_name);
delete r;
}
send_to_one(data, user->first);
}
void room_manager::process_room_query(simple_wml::document& data, const player_map::iterator user)
{
simple_wml::node* const msg = data.root().child("room_query");
assert(msg);
simple_wml::document doc;
simple_wml::node& resp = doc.root().add_child("room_query_response");
simple_wml::node* q;
q = msg->child("rooms");
if (q != nullptr) {
fill_room_list(resp);
send_to_one(doc, user->first);
return;
}
std::string room_name = msg->attr("room").to_string();
if (room_name.empty()) room_name = lobby_name_;
/* room-specific queries */
room* r = require_room(room_name, user, "query");
if (r == nullptr) return;
resp.set_attr_dup("room", room_name.c_str());
q = msg->child("names");
if (q != nullptr) {
fill_member_list(r, resp);
send_to_one(doc, user->first);
return;
}
q = msg->child("persist");
if (q != nullptr) {
if (user->second.is_moderator()) {
WRN_LOBBY << "Attempted room set persistent by non-moderator";
} else {
if (q->attr("value").empty()) {
if (r->persistent()) {
resp.set_attr("message", "Room is persistent.");
} else {
resp.set_attr("message", "Room is not persistent.");
}
} else if (q->attr("value").to_bool()) {
r->set_persistent(true);
resp.set_attr("message", "Room set as persistent.");
dirty_ = true;
} else {
r->set_persistent(false);
resp.set_attr("message", "Room set as not persistent.");
dirty_ = true;
}
send_to_one(doc, user->first);
}
return;
}
q = msg->child("logged");
if (q != nullptr) {
if (user->second.is_moderator()) {
WRN_LOBBY << "Attempted room set logged by non-moderator.";
} else {
if (q->attr("value").empty()) {
if (r->persistent()) {
resp.set_attr("message", "Room is logged.");
} else {
resp.set_attr("message", "Room is not logged.");
}
} else if (q->attr("value").to_bool()) {
r->set_logged(true);
resp.set_attr("message", "Room set as logged.");
dirty_ = true;
} else {
r->set_logged(false);
resp.set_attr("message", "Room set as not logged.");
dirty_ = true;
}
send_to_one(doc, user->first);
}
return;
}
q = msg->child("topic");
if (q != nullptr) {
if (q->attr("value").empty()) {
resp.set_attr_dup("topic", r->topic().c_str());
send_to_one(doc, user->first);
} else {
if (user->second.is_moderator()) {
WRN_LOBBY << "Attempted room set topic by non-moderator.";
} else {
r->set_topic(q->attr("value").to_string());
resp.set_attr("message", "Room topic changed.");
send_to_one(doc, user->first);
}
}
}
r->send_server_message("Unknown room query type", user->first);
}
void room_manager::fill_room_list(simple_wml::node& root)
{
simple_wml::node& rooms = root.add_child("rooms");
for (const t_rooms_by_name_::value_type& tr : rooms_by_name_) {
const room& r = *tr.second;
simple_wml::node& room = rooms.add_child("room");
room.set_attr_dup("name", r.name().c_str());
room.set_attr_dup("size", lexical_cast_default<std::string>(r.members().size()).c_str());
}
}
void room_manager::fill_member_list(const room* room, simple_wml::node& root)
{
simple_wml::node& members = root.add_child("members");
for (network::connection m : room->members()) {
simple_wml::node& member = members.add_child("member");
player_map::const_iterator mi = all_players_.find(m);
if (mi != all_players_.end()) {
member.set_attr_dup("name", mi->second.name().c_str());
}
}
}
} //namespace wesnothd

View file

@ -1,257 +0,0 @@
/*
Copyright (C) 2009 - 2016 by Tomasz Sniatowski <kailoran@gmail.com>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
#include "room.hpp"
#include <boost/noncopyable.hpp>
class config;
#ifndef SERVER_ROOM_MANAGER_HPP_INCLUDED
#define SERVER_ROOM_MANAGER_HPP_INCLUDED
namespace wesnothd {
/**
* The room manager manages the lobby and other rooms in the server, and related
* client message processing.
* The lobby represents players that are on the server, but not in any game.
*/
class room_manager : private boost::noncopyable
{
public:
/**
* Room manager constructor
*/
room_manager(player_map& all_players);
/**
* Room manager destructor
*/
~room_manager();
enum PRIVILEGE_POLICY {
PP_EVERYONE,
PP_REGISTERED,
PP_ADMINS,
PP_NOBODY,
PP_COUNT
};
static PRIVILEGE_POLICY pp_from_string(const std::string& str);
/**
* Load settings from the main config file
*/
void load_config(const config& cfg);
/**
* Reads stored rooms from a file on disk, or returns immediately
* if load_config was not called before or the storage filename is empty
*/
void read_rooms();
/**
* Writes rooms to the storage file or returns immediately if load_config
* was not called beforethe storage filename is empty
*/
void write_rooms();
/**
* Dirty flag for rooms -- true if there were changes that should be written
* to disk
*/
bool dirty() const { return dirty_; }
/**
* Get a room by name, or nullptr if it does not exist
*/
room* get_room(const std::string& name);
/**
* @param name the room name to check
* @return true iif the room existst
*/
bool room_exists(const std::string& name) const;
/**
* Create room named "name" if it does not exist already.
*/
room* create_room(const std::string& name);
/**
* Get a room by name or create that room if it does not exist and
* creating rooms is allowed.
* @return a valid pointer to a room or nullptr if the room did not exist and
* could not be created.
*/
room* get_create_room(const std::string& name, network::connection player);
/**
* @return true iif the player is in the lobby
*/
bool in_lobby(network::connection player) const;
/**
* Player-enters-lobby action. Will autorejoin "stored" rooms (the ones
* the player was before enetering a game, for instance)
*/
void enter_lobby(network::connection player);
/**
* All players from a game re-enter the lobby
*/
void enter_lobby(const game& game);
/**
* Player exits lobby.
*/
void exit_lobby(network::connection player);
/**
* Remove info abut given player from all rooms
*/
void remove_player(network::connection player);
/**
* Check if the room exists, log failures.
* @return non-nullptr iff the room exists and the player is a member
*/
room* require_room(const std::string& room_name,
const player_map::iterator user, const char* log_string = "use");
/**
* Check if the room exists and if the player is a member, log failures.
* @return non-nullptr iff the room exists and the player is a member
*/
room* require_member(const std::string& room_name,
const player_map::iterator user, const char* log_string = "use");
/**
* Process a message (chat message) sent to a room. Check conditions
* and resend to other players in the room.
*/
void process_message(simple_wml::document& data, const player_map::iterator user);
/**
* Process a player's request to join a room
*/
void process_room_join(simple_wml::document& data, const player_map::iterator user);
/**
* Process a player's request to leave a room
*/
void process_room_part(simple_wml::document& data, const player_map::iterator user);
/**
* Process a general room query
*/
void process_room_query(simple_wml::document& data, const player_map::iterator user);
/**
* Lobby convenience accesor
*/
const room& lobby() const { return *lobby_; }
private:
void do_room_join(network::connection player, const std::string& room_name);
/**
* Adds a player to a room, maintaining internal consistency
* Will send appropriate error messages to the player.
* @return true iif the operation was successful, false otherwise
*/
bool player_enters_room(network::connection player, room* room);
/**
* Removes a player from a room, maintaining internal consistency
*/
void player_exits_room(network::connection player, room* room);
/**
* Stores the room names (other than lobby) of the given player for future
* use (rejoin)
*/
void store_player_rooms(network::connection player);
/**
* Unstores (rejoins) player's rooms that were previously stored.
* No action if not stored earlier or no rooms.
*/
void unstore_player_rooms(const player_map::iterator user);
/**
* Helper function that calls the player_map::iterator version
* of unstore_player_rooms
*/
void unstore_player_rooms(network::connection player);
/**
* Fill a wml node (message) with members of a room
*/
void fill_member_list(const room* room, simple_wml::node& root);
/**
* Fill a wml node (message) with a room list
*/
void fill_room_list(simple_wml::node& root);
/** Reference to the all players map */
player_map& all_players_;
/** The lobby-room, treated separetely */
room* lobby_;
/** Rooms by name */
typedef std::map<std::string, room*> t_rooms_by_name_;
t_rooms_by_name_ rooms_by_name_;
/** Rooms by player */
typedef std::map<network::connection, std::set<room*> > t_rooms_by_player_;
t_rooms_by_player_ rooms_by_player_;
/** Room names stored for players that have entered a game */
typedef std::map<network::connection, std::set<std::string> > t_player_stored_rooms_;
t_player_stored_rooms_ player_stored_rooms_;
/**
* Persistent room storage filename. If empty, rooms are not stored on disk.
*/
std::string filename_;
/**
* Flag controlling whether to compress the stored rooms or not
*/
bool compress_stored_rooms_;
/**
* Policy regarding who can create new rooms
*/
PRIVILEGE_POLICY new_room_policy_;
/**
* 'Dirty' flag with regards to room info that will be stored on disk
*/
bool dirty_;
/**
* The main (lobby) room name
*/
static const char* const lobby_name_;
};
} //namespace wesnothd
#endif

View file

@ -1,340 +0,0 @@
/*
Copyright (C) 2008 - 2016 by Pauli Nieminen <paniemin@cc.hut.fi>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
#define GETTEXT_DOMAIN "wesnoth-test"
#include <boost/test/unit_test.hpp>
#include "utils/auto_parameterized.hpp"
#include "utils/predicate.hpp"
#include "network_worker.hpp"
#include "thread.hpp"
#include "filesystem.hpp"
#include "config.hpp"
#include "game_config.hpp"
/**
* Test networking to prevent bugs there.
* Try to create all kind of unlikely error conditions
* Some test should lock management_mutex from worker to settup stress test
* It is like that this will need some threading also :(
*/
BOOST_AUTO_TEST_SUITE( test_network )
const int TEST_PORT = 15010;
const int MIN_THREADS = 1;
const int MAX_THREADS = 5;
const std::string LOCALHOST = "localhost";
network::manager* wes_manager;
network::server_manager* wes_server;
network::connection client_client1;
network::connection client_client2;
network::connection server_client1;
network::connection server_client2;
BOOST_AUTO_TEST_CASE( test_connect )
{
int connections = network::nconnections();
BOOST_WARN_MESSAGE(connections == 0, "There is open "<< connections <<" connections before test!");
BOOST_CHECK_MESSAGE(wes_manager = new network::manager(MIN_THREADS,MAX_THREADS), "network::manager failed to initialize");
BOOST_CHECK_MESSAGE(wes_server = new network::server_manager(TEST_PORT,network::server_manager::MUST_CREATE_SERVER),
"network::server_manager failed to initialize");
BOOST_REQUIRE_MESSAGE(wes_server->is_running(), "Can't start server!");
client_client1 = network::connect(LOCALHOST, TEST_PORT);
BOOST_CHECK_MESSAGE(client_client1 > 0, "Can't connect to server");
server_client1 = network::accept_connection();
BOOST_CHECK_MESSAGE(server_client1 > 0, "Can't accept connection");
}
template<class T>
static network::connection receive(T& cfg, int max_tries = 100)
{
network::connection receive_con;
while ((receive_con = network::receive_data(cfg)) == network::null_connection)
{
// loop untill data is received
SDL_Delay(50);
if (--max_tries <= 0)
{
BOOST_WARN_MESSAGE(max_tries > 0,"receiving data took too long. Preventing for ever loop");
break;
}
}
return receive_con;
}
BOOST_AUTO_TEST_CASE( test_send_client )
{
config cfg_send;
config& child = cfg_send.add_child("test_client_send");
child["test"] = "yes!";
cfg_send["test_running"] = true;
network::send_data(cfg_send, client_client1);
network::connection receive_from;
config received;
receive_from = receive(received);
BOOST_CHECK_MESSAGE( receive_from == server_client1, "Received data is not from test client 1" );
BOOST_CHECK_EQUAL(cfg_send, received);
}
static void try_send_random_seed ( const std::string seed_str, const unsigned int random_calls)
{
config cfg_send;
config& child = cfg_send.add_child("command");
child["random_seed"] = seed_str;
child["random_calls"] = random_calls;
network::send_data(cfg_send, client_client1);
network::connection receive_from;
config received;
receive_from = receive(received);
BOOST_CHECK_MESSAGE( receive_from == server_client1, "Received data is not from test client 1" );
BOOST_CHECK_EQUAL(cfg_send, received);
config rec_command = received.child("command");
std::string rec_seed_str = rec_command["random_seed"].str();
unsigned int rec_calls = rec_command["random_calls"];
BOOST_CHECK_EQUAL(seed_str, rec_seed_str);
BOOST_CHECK_EQUAL(random_calls, rec_calls);
}
BOOST_AUTO_TEST_CASE( test_send_random_seed )
{
try_send_random_seed("0000badd",0);
try_send_random_seed("00001234",1);
try_send_random_seed("deadbeef",2);
try_send_random_seed("12345678",3);
try_send_random_seed("00009999",4);
try_send_random_seed("ffffaaaa",5);
try_send_random_seed("11110000",6);
try_send_random_seed("10101010",7);
try_send_random_seed("aaaa0000",8);
}
class connect_aborter : public threading::waiter
{
public:
connect_aborter() : start_(SDL_GetTicks())
{}
ACTION process();
private:
size_t start_;
};
connect_aborter::ACTION connect_aborter::process()
{
// Abort connection after 5 ms
if(SDL_GetTicks() - start_ >= 5) {
return ABORT;
} else {
return WAIT;
}
}
#if 0
BOOST_AUTO_TEST_CASE( test_sdl_thread_wait_crash )
{
delete wes_server;
wes_server = 0;
delete wes_manager;
wes_manager = 0;
BOOST_CHECK_MESSAGE(wes_manager == 0, "network::manager nono zero after delete");
BOOST_CHECK_MESSAGE(wes_manager = new network::manager(MIN_THREADS,MAX_THREADS), "network::manager failed to initialize");
BOOST_CHECK_THROW(client_client1 = network::connect(LOCALHOST, TEST_PORT), network::error);
BOOST_CHECK_MESSAGE(client_client1 > 0, "Can't connect to server");
delete wes_manager;
wes_manager = 0;
BOOST_CHECK_MESSAGE(wes_manager = new network::manager(MIN_THREADS,MAX_THREADS), "network::manager failed to initialize");
connect_aborter aborter;
BOOST_CHECK_MESSAGE((client_client1 = network::connect("server.wesnoth.org", 15000, aborter)) == 0, "Connection create but not shoul");
delete wes_manager;
BOOST_CHECK_MESSAGE(wes_manager = new network::manager(MIN_THREADS,MAX_THREADS), "network::manager failed to initialize");
BOOST_CHECK_MESSAGE(wes_server = new network::server_manager(TEST_PORT,network::server_manager::MUST_CREATE_SERVER), "");
BOOST_CHECK_MESSAGE((client_client1 = network::connect(LOCALHOST, TEST_PORT, aborter)) == 0, "Connection create but not shoul");
delete wes_manager;
wes_manager = new network::manager(MIN_THREADS,MAX_THREADS);
client_client1 = network::connect(LOCALHOST, TEST_PORT);
BOOST_CHECK_MESSAGE(client_client1 > 0, "Can't connect to server");
delete wes_server;
delete wes_manager;
wes_manager = new network::manager(MIN_THREADS,MAX_THREADS);
wes_server = new network::server_manager(TEST_PORT,network::server_manager::MUST_CREATE_SERVER);
}
#endif
// Use 1kb, 500kb and 10Mb files for testing
struct sendfile_param {
sendfile_param(size_t size, bool system) : size_(size), system_(system) {}
size_t size_;
bool system_;
};
sendfile_param sendfile_sizes[] = {sendfile_param(1*1024,true),
sendfile_param(5*1024*1024,true),
sendfile_param(1*1024,false),
sendfile_param(5*1024*1024,false)};
static std::string create_random_sendfile(size_t size)
{
char buffer[1024];
const int buffer_size = sizeof(buffer)/sizeof(buffer[0]);
int *begin = reinterpret_cast<int*>(&buffer[0]);
int *end = begin + sizeof(buffer)/sizeof(int);
std::string filename = "sendfile.tmp";
filesystem::scoped_ostream file = filesystem::ostream_file(filename);
std::generate(begin,end,std::rand);
while( size > 0
&& !file->bad())
{
file->write(buffer, buffer_size);
size -= buffer_size;
}
return filename;
}
static void delete_random_sendfile(const std::string& file)
{
filesystem::delete_file(file);
}
template<class T>
class auto_resetter {
T& value_to_change_;
T old_val_;
public:
auto_resetter(const T& new_value, T& value_to_change) : value_to_change_(value_to_change), old_val_(value_to_change)
{
value_to_change_ = new_value;
}
~auto_resetter()
{
value_to_change_ = old_val_;
}
};
WESNOTH_PARAMETERIZED_TEST_CASE( test_multi_sendfile, sendfile_param, sendfile_sizes, size )
{
auto_resetter<std::string> path("", game_config::path);
network::set_raw_data_only();
std::string file = create_random_sendfile(size.size_);
network_worker_pool::set_use_system_sendfile(size.system_);
network::connection cl_client1, se_client1;
network::connection cl_client2, se_client2;
network::connection cl_client3, se_client3;
BOOST_CHECK_MESSAGE((cl_client1 = network::connect(LOCALHOST, TEST_PORT)) > 0, "Can't connect to server!");
BOOST_CHECK_MESSAGE((se_client1 = network::accept_connection()) > 0, "Coulnd't accept new connection");
BOOST_CHECK_MESSAGE((cl_client2 = network::connect(LOCALHOST, TEST_PORT)) > 0, "Can't connect to server!");
BOOST_CHECK_MESSAGE((se_client2 = network::accept_connection()) > 0, "Coulnd't accept new connection");
BOOST_CHECK_MESSAGE((cl_client3 = network::connect(LOCALHOST, TEST_PORT)) > 0, "Can't connect to server!");
BOOST_CHECK_MESSAGE((se_client3 = network::accept_connection()) > 0, "Coulnd't accept new connection");
network::send_file(file, cl_client1);
network::send_file(file, cl_client2);
network::send_file(file, cl_client3);
std::vector<char> data;
BOOST_CHECK_PREDICATE(test_utils::one_of<network::connection> , (receive(data,500))(3)(se_client1)(se_client2)(se_client3));
BOOST_CHECK_EQUAL(data.size(), static_cast<size_t>(filesystem::file_size(file)));
BOOST_CHECK_PREDICATE(test_utils::one_of<network::connection> , (receive(data,500))(3)(se_client1)(se_client2)(se_client3));
BOOST_CHECK_EQUAL(data.size(), static_cast<size_t>(filesystem::file_size(file)));
BOOST_CHECK_PREDICATE(test_utils::one_of<network::connection> , (receive(data,500))(3)(se_client1)(se_client2)(se_client3));
BOOST_CHECK_EQUAL(data.size(), static_cast<size_t>(filesystem::file_size(file)));
network::disconnect(cl_client1);
network::disconnect(cl_client2);
network::disconnect(cl_client3);
BOOST_CHECK_THROW(receive(data),network::error);
BOOST_CHECK_THROW(receive(data),network::error);
BOOST_CHECK_THROW(receive(data),network::error);
delete_random_sendfile(file);
}
#if 0
BOOST_AUTO_TEST_CASE( test_multiple_connections )
{
}
BOOST_AUTO_TEST_CASE( test_cancel_transfer )
{
}
BOOST_AUTO_TEST_CASE( test_detect_errors )
{
}
BOOST_AUTO_TEST_CASE( test_broken_data )
{
}
BOOST_AUTO_TEST_CASE( test_config_with_macros )
{
}
BOOST_AUTO_TEST_CASE( test_disconnect )
{
network::disconnect(client_client1);
network::disconnect(server_client1);
}
#endif
BOOST_AUTO_TEST_CASE( test_shutdown )
{
delete wes_server;
BOOST_CHECK_MESSAGE(true,"Not true");
delete wes_manager;
}
BOOST_AUTO_TEST_SUITE_END()
/* vim: set ts=4 sw=4: */

View file

@ -1,50 +0,0 @@
/*
Copyright (C) 2008 - 2016 by Pauli Nieminen <paniemin@cc.hut.fi>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
#ifndef TESTS_UTILS_FAKE_DISPLAY_HPP_INCLUDED
#define TESTS_UTILS_FAKE_DISPLAY_HPP_INCLUDED
#include <cstdarg>
namespace test_utils {
/**
* Used to check if first parameter matches one of given values
* used with BOOST_CHECK_PREDICATE
**/
template<class T>
bool one_of(const T& val, int va_number, ...)
{
T param;
va_list vl;
va_start(vl, va_number);
bool ret = false;
for (int i = 0; i < va_number; ++i)
{
param = va_arg(vl, T);
if (param == val)
{
ret = true;
break;
}
}
va_end(vl);
return ret;
}
}
#endif

View file

@ -1,185 +0,0 @@
/*
Copyright (C) 2003 - 2016 by David White <dave@whitevine.net>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
#include "global.hpp"
#include <vector>
#include "log.hpp"
#include "thread.hpp"
#include <SDL_mutex.h>
#include <SDL_thread.h>
#define ERR_G LOG_STREAM(err, lg::general())
uint32_t threading::thread::get_id() { return SDL_GetThreadID(thread_); }
uint32_t threading::get_current_thread_id() { return SDL_ThreadID(); }
static int run_async_operation(void* data)
{
threading::async_operation_ptr op(*reinterpret_cast<threading::async_operation_ptr*>(data));
op->run();
const threading::lock l(op->get_mutex());
op->notify_finished(); //in case the operation didn't notify of finishing
return 0;
}
namespace {
std::vector<SDL_Thread*> detached_threads;
}
namespace threading {
manager::~manager()
{
for(std::vector<SDL_Thread*>::iterator i = detached_threads.begin(); i != detached_threads.end(); ++i) {
SDL_WaitThread(*i,nullptr);
}
}
thread::thread(int (*f)(void*), void* data)
: thread_(SDL_CreateThread(f, "", data))
{
}
thread::~thread()
{
join();
}
void thread::join()
{
if(thread_ != nullptr) {
SDL_WaitThread(thread_,nullptr);
thread_ = nullptr;
}
}
void thread::detach()
{
detached_threads.push_back(thread_);
thread_ = nullptr;
}
mutex::mutex() : m_(SDL_CreateMutex())
{}
mutex::~mutex()
{
SDL_DestroyMutex(m_);
}
lock::lock(mutex& m) : m_(m)
{
SDL_mutexP(m_.m_);
}
lock::~lock()
{
SDL_mutexV(m_.m_);
}
condition::condition() : cond_(SDL_CreateCond())
{}
condition::~condition()
{
SDL_DestroyCond(cond_);
}
bool condition::wait(const mutex& m)
{
return SDL_CondWait(cond_,m.m_) == 0;
}
condition::WAIT_TIMEOUT_RESULT condition::wait_timeout(const mutex& m, unsigned int timeout)
{
const int res = SDL_CondWaitTimeout(cond_,m.m_,timeout);
switch(res) {
case 0: return WAIT_OK;
case SDL_MUTEX_TIMEDOUT: return WAIT_TIMED_OUT;
default:
ERR_G << "SDL_CondWaitTimeout: " << SDL_GetError() << std::endl;
return WAIT_ERROR;
}
}
bool condition::notify_one()
{
if(SDL_CondSignal(cond_) < 0) {
ERR_G << "SDL_CondSignal: " << SDL_GetError() << std::endl;
return false;
}
return true;
}
bool condition::notify_all()
{
if(SDL_CondBroadcast(cond_) < 0) {
ERR_G << "SDL_CondBroadcast: " << SDL_GetError() << std::endl;
return false;
}
return true;
}
bool async_operation::notify_finished()
{
finishedVar_ = true;
return finished_.notify_one();
}
active_operation_list async_operation::active_;
async_operation::RESULT async_operation::execute(async_operation_ptr this_ptr, waiter& wait)
{
//the thread must be created after the lock, and also destroyed after it.
//this is because during the thread's execution, we must always hold the mutex
//unless we are waiting on notification that the thread is finished, or we have
//already received that notification.
//
//we cannot hold the mutex while waiting for the thread to join though, because
//the thread needs access to the mutex before it terminates
{
const lock l(get_mutex());
active_.push_back(this_ptr);
thread_.reset(new thread(run_async_operation,&this_ptr));
bool completed = false;
while(wait.process() == waiter::WAIT) {
const condition::WAIT_TIMEOUT_RESULT res = finished_.wait_timeout(get_mutex(),20);
if(res == condition::WAIT_OK || finishedVar_) {
completed = true;
break;
} else if(res == condition::WAIT_ERROR) {
break;
}
}
if(!completed) {
aborted_ = true;
return ABORTED;
}
}
return COMPLETED;
}
}

View file

@ -1,247 +0,0 @@
/*
Copyright (C) 2003 - 2016 by David White <dave@whitevine.net>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
#ifndef THREAD_HPP_INCLUDED
#define THREAD_HPP_INCLUDED
#include <list>
#include <cstdint>
#include <boost/noncopyable.hpp>
struct SDL_Thread;
struct SDL_mutex;
struct SDL_cond;
// Threading primitives wrapper for SDL_Thread.
//
// This module defines primitives for wrapping C++ around SDL's threading
// interface
namespace threading
{
struct manager
{
~manager();
};
// Threading object.
//
// This class defines threading objects. One such object represents a
// thread and admits killing and joining on threads. Intended to be
// used for manipulating threads instead of poking around with SDL_Thread
// calls.
class thread
: private boost::noncopyable
{
public:
// Construct a new thread to start executing the function
// pointed to by f. The void* data will be passed to f, to
// facilitate passing of parameters to f.
//
// \param f the function at which the thread should start executing
// \param data passed to f
//
// \pre f != nullptr
explicit thread(int (*f)(void*), void* data=nullptr);
// Destroy the thread object. This is done by waiting on the
// thread with the join() operation, thus blocking until the
// thread object has finished its operation.
~thread();
// Join (wait) on the thread to finish. When the thread finishes,
// the function will return. calling wait() on an already killed
// thread is a no-op.
void join();
void detach();
uint32_t get_id();
private:
SDL_Thread* thread_;
};
uint32_t get_current_thread_id();
// Binary mutexes.
//
// Implements an interface to binary mutexes. This class only defines the
// mutex itself. Locking is handled through the friend class lock,
// and monitor interfacing through condition variables is handled through
// the friend class condition.
class mutex
: private boost::noncopyable
{
public:
mutex();
~mutex();
friend class lock;
friend class condition;
private:
SDL_mutex* const m_;
};
// Binary mutex locking.
//
// Implements a locking object for mutexes. The creation of a lock
// object on a mutex will lock the mutex as long as this object is
// not deleted.
class lock
: private boost::noncopyable
{
public:
// Create a lock object on the mutex given as a parameter to
// the constructor. The lock will be held for the duration
// of the object existence.
// If the mutex is already locked, the constructor will
// block until the mutex lock can be acquired.
//
// \param m the mutex on which we should try to lock.
explicit lock(mutex& m);
// Delete the lock object, thus releasing the lock acquired
// on the mutex which the lock object was created with.
~lock();
private:
mutex& m_;
};
// Condition variable locking.
//
// Implements condition variables for mutexes. A condition variable
// allows you to free up a lock inside a critical section
// of the code and regain it later. Condition classes only make
// sense to do operations on, if one already acquired a mutex.
class condition
: private boost::noncopyable
{
public:
condition();
~condition();
// Wait on the condition. When the condition is met, you
// have a lock on the mutex and can do work in the critical
// section. When the condition is not met, wait blocks until
// the condition is met and atomically frees up the lock on
// the mutex. One will automatically regain the lock when the
// thread unblocks.
//
// If wait returns false we have an error. In this case one cannot
// assume that he has a lock on the mutex anymore.
//
// \param m the mutex you wish to free the lock for
// \returns true: the wait was successful, false: an error occurred
//
// \pre You have already acquired a lock on mutex m
//
bool wait(const mutex& m);
enum WAIT_TIMEOUT_RESULT { WAIT_OK, WAIT_TIMED_OUT, WAIT_ERROR };
// wait on the condition with a timeout. Basically the same as the
// wait() function, but if the lock is not acquired before the
// timeout, the function returns with an error.
//
// \param m the mutex you wish free the lock for.
// \param timeout the allowed timeout in milliseconds (ms)
// \returns result based on whether condition was met, it timed out,
// or there was an error
WAIT_TIMEOUT_RESULT wait_timeout(const mutex& m, unsigned int timeout);
// signal the condition and wake up one thread waiting on the
// condition. If no thread is waiting, notify_one() is a no-op.
// Does not unlock the mutex.
bool notify_one();
// signal all threads waiting on the condition and let them contend
// for the lock. This is often used when varying resource amounts are
// involved and you do not know how many processes might continue.
// The function should be used with care, especially if many threads are
// waiting on the condition variable.
bool notify_all();
private:
SDL_cond* const cond_;
};
//class which defines an interface for waiting on an asynchronous operation
class waiter {
public:
enum ACTION { WAIT, ABORT };
virtual ~waiter() {}
virtual ACTION process() = 0;
};
class async_operation;
typedef std::shared_ptr<async_operation> async_operation_ptr;
typedef std::list<async_operation_ptr> active_operation_list;
//class which defines an asynchronous operation. Objects of this class are accessed from
//both the worker thread and the calling thread, and so it has 'strange' allocation semantics.
//It is allocated by the caller, and generally deleted by the caller. However, in some cases
//the asynchronous operation is aborted, and the caller abandons it. The caller cannot still
//delete the operation, since the worker thread might still access it, so in the case when the
//operation is aborted, the worker thread will delete it.
//
//The caller should hold these objects using the async_operation_holder class below, which will
//handle the delete semantics
class async_operation
{
public:
enum RESULT { COMPLETED, ABORTED };
async_operation() :
thread_(), aborted_(false), finished_(), finishedVar_(false), mutex_()
{
while (!active_.empty() && active_.front().unique())
active_.pop_front();
}
virtual ~async_operation() {}
RESULT execute(async_operation_ptr this_ptr, waiter& wait);
mutex& get_mutex() { return mutex_; }
virtual void run() = 0;
//notify that the operation is finished. Can be called from within the thread
//while holding the mutex and after checking is_aborted()
//if we want to be sure that if the operation is completed, the caller is notified.
//will be called in any case after the operation returns
bool notify_finished();
//must hold the mutex before calling this function from the worker thread
bool is_aborted() const { return aborted_; }
private:
std::unique_ptr<thread> thread_;
bool aborted_;
condition finished_;
bool finishedVar_;
mutex mutex_;
static active_operation_list active_;
};
}
#endif