Merge pull request #866 from GregoryLundberg/GL_delete_old_networking
Delete last vestiges of old networking code.
This commit is contained in:
commit
e8ee1803c2
10 changed files with 0 additions and 2109 deletions
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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: */
|
|
@ -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
|
185
src/thread.cpp
185
src/thread.cpp
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
247
src/thread.hpp
247
src/thread.hpp
|
@ -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
|
Loading…
Add table
Reference in a new issue