new wesnothd implementation using simple_wml...
...(plus subsequent fixes/tweaks; merge of 2008-03-17T05:11:14Z!davewx7@gmail.com, 2008-03-17T16:31:32Z!davewx7@gmail.com, part of 2008-03-17T22:14:49Z!davewx7@gmail.com, 2008-03-17T22:33:36Z!soliton@wesnoth.org, 2008-03-18T00:40:44Z!soliton@wesnoth.org, 2008-03-18T03:07:45Z!davewx7@gmail.com, 2008-03-18T03:57:31Z!soliton@wesnoth.org, 2008-03-19T02:03:34Z!davewx7@gmail.com from trunk)
This commit is contained in:
parent
84f575bd88
commit
afbb4bcebb
16 changed files with 2232 additions and 602 deletions
|
@ -153,6 +153,7 @@ wesnothd_SOURCES = \
|
|||
server/player.cpp \
|
||||
server/proxy.cpp \
|
||||
server/server.cpp \
|
||||
server/simple_wml.cpp \
|
||||
network.cpp \
|
||||
network_worker.cpp \
|
||||
loadscreen_empty.cpp
|
||||
|
|
|
@ -1004,7 +1004,7 @@ void connect::process_event()
|
|||
if(network::nconnections() > 0) {
|
||||
config cfg;
|
||||
cfg.add_child("leave_game");
|
||||
network::send_data(cfg, 0, false);
|
||||
network::send_data(cfg, 0, true);
|
||||
}
|
||||
|
||||
set_result(QUIT);
|
||||
|
|
106
src/network.cpp
106
src/network.cpp
|
@ -260,6 +260,11 @@ manager::~manager()
|
|||
}
|
||||
}
|
||||
|
||||
void set_raw_data_only()
|
||||
{
|
||||
network_worker_pool::set_raw_data_only();
|
||||
}
|
||||
|
||||
server_manager::server_manager(int port, CREATE_SERVER create_server) : free_(false)
|
||||
{
|
||||
if(create_server != NO_SERVER && !server_socket) {
|
||||
|
@ -715,6 +720,79 @@ connection receive_data(config& cfg, connection connection_num)
|
|||
return result;
|
||||
}
|
||||
|
||||
connection receive_data(std::vector<char>& buf)
|
||||
{
|
||||
if(!socket_set) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
check_error();
|
||||
|
||||
if(disconnection_queue.empty() == false) {
|
||||
const network::connection sock = disconnection_queue.front();
|
||||
disconnection_queue.pop_front();
|
||||
throw error("",sock);
|
||||
}
|
||||
|
||||
if(bad_sockets.count(0)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(sockets.empty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const int res = SDLNet_CheckSockets(socket_set,0);
|
||||
|
||||
for(std::set<network::connection>::iterator i = waiting_sockets.begin(); res != 0 && i != waiting_sockets.end(); ) {
|
||||
connection_details& details = get_connection_details(*i);
|
||||
const TCPsocket sock = details.sock;
|
||||
if(SDLNet_SocketReady(sock)) {
|
||||
|
||||
// See if this socket is still waiting for it to be assigned its remote handle.
|
||||
// If it is, then the first 4 bytes must be the remote handle.
|
||||
if(is_pending_remote_handle(*i)) {
|
||||
char buf[4];
|
||||
int len = SDLNet_TCP_Recv(sock,buf,4);
|
||||
if(len != 4) {
|
||||
throw error("Remote host disconnected",*i);
|
||||
}
|
||||
|
||||
const int remote_handle = SDLNet_Read32(buf);
|
||||
set_remote_handle(*i,remote_handle);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
waiting_sockets.erase(i++);
|
||||
SDLNet_TCP_DelSocket(socket_set,sock);
|
||||
network_worker_pool::receive_data(sock);
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TCPsocket sock = network_worker_pool::get_received_data(buf);
|
||||
if (sock == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
SDLNet_TCP_AddSocket(socket_set,sock);
|
||||
|
||||
connection result = 0;
|
||||
for(connection_map::const_iterator j = connections.begin(); j != connections.end(); ++j) {
|
||||
if(j->second.sock == sock) {
|
||||
result = j->first;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
assert(result != 0);
|
||||
waiting_sockets.insert(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
//! @todo Note the gzipped parameter should be removed later, we want to send
|
||||
//! all data gzipped. This can be done once the campaign server is also updated
|
||||
//! to work with gzipped data.
|
||||
|
@ -752,6 +830,34 @@ void send_data(const config& cfg, connection connection_num, const bool gzipped)
|
|||
network_worker_pool::queue_data(info->second.sock, cfg, gzipped);
|
||||
}
|
||||
|
||||
void send_raw_data(const char* buf, int len, connection connection_num)
|
||||
{
|
||||
if(len == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(bad_sockets.count(connection_num) || bad_sockets.count(0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(!connection_num) {
|
||||
for(sockets_list::const_iterator i = sockets.begin();
|
||||
i != sockets.end(); ++i) {
|
||||
send_raw_data(buf, len, connection_num);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const connection_map::iterator info = connections.find(connection_num);
|
||||
if (info == connections.end()) {
|
||||
ERR_NW << "Error: socket: " << connection_num
|
||||
<< "\tnot found in connection_map. Not sending...\n";
|
||||
return;
|
||||
}
|
||||
|
||||
network_worker_pool::queue_raw_data(info->second.sock, buf, len);
|
||||
}
|
||||
|
||||
void process_send_queue(connection, size_t)
|
||||
{
|
||||
check_error();
|
||||
|
|
|
@ -23,6 +23,7 @@ class config;
|
|||
#include "SDL_net.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace threading
|
||||
{
|
||||
|
@ -55,6 +56,8 @@ private:
|
|||
void operator=(const manager&);
|
||||
};
|
||||
|
||||
void set_raw_data_only();
|
||||
|
||||
//! A server manager causes listening on a given port
|
||||
//! to occur for the duration of its lifetime.
|
||||
struct server_manager {
|
||||
|
@ -120,12 +123,15 @@ void queue_disconnect(connection connection_num);
|
|||
//! Throws error if an error occurred.
|
||||
connection receive_data(config& cfg, connection connection_num=0);
|
||||
connection receive_data(config& cfg, connection connection_num, unsigned int timeout);
|
||||
connection receive_data(std::vector<char>& buf);
|
||||
|
||||
//! Function to send data down a given connection,
|
||||
//! or broadcast to all peers if connection_num is 0.
|
||||
//! Throws error.
|
||||
void send_data(const config& cfg, connection connection_num /*= 0*/, const bool gzipped);
|
||||
|
||||
void send_raw_data(const char* buf, int len, connection connection_num);
|
||||
|
||||
//! Function to send any data that is in a connection's send_queue,
|
||||
//! up to a maximum of 'max_size' bytes --
|
||||
//! or the entire send queue if 'max_size' bytes is 0.
|
||||
|
|
|
@ -121,10 +121,14 @@ struct buffer {
|
|||
//! been removed.
|
||||
bool gzipped;
|
||||
|
||||
//this field is used if we're sending a raw buffer instead of
|
||||
//through a config object. It will contain the entire contents of
|
||||
//the buffer being sent.
|
||||
std::vector<char> raw_buffer;
|
||||
};
|
||||
|
||||
|
||||
bool managed = false;
|
||||
bool managed = false, raw_data_only = false;
|
||||
typedef std::vector< buffer* > buffer_set;
|
||||
buffer_set bufs[NUM_SHARDS];
|
||||
|
||||
|
@ -266,19 +270,22 @@ static void output_to_buffer(TCPsocket sock, const config& cfg, std::ostringstre
|
|||
}
|
||||
}
|
||||
|
||||
static SOCKET_STATE send_buffer(TCPsocket sock, std::ostringstream& compressor, const bool gzipped) {
|
||||
static void make_network_buffer(const char* input, int len, std::vector<char>& buf)
|
||||
{
|
||||
buf.resize(4 + len + 1);
|
||||
SDLNet_Write32(len + 1, &buf[0]);
|
||||
memcpy(&buf[4], input, len);
|
||||
buf.back() = 0;
|
||||
}
|
||||
|
||||
static SOCKET_STATE send_buffer(TCPsocket sock, std::vector<char>& buf)
|
||||
{
|
||||
#ifdef __BEOS__
|
||||
int timeout = 15000;
|
||||
#endif
|
||||
size_t upto = 0;
|
||||
|
||||
std::string const &value = compressor.str();
|
||||
std::vector<char> buf(4 + value.size() + 1);
|
||||
SDLNet_Write32(value.size()+1,&buf[0]);
|
||||
std::copy(value.begin(),value.end(),buf.begin()+4);
|
||||
buf.back() = 0;
|
||||
|
||||
size_t size = buf.size();
|
||||
|
||||
{
|
||||
const threading::lock lock(*stats_mutex);
|
||||
transfer_stats[sock].first.fresh_current(size);
|
||||
|
@ -491,7 +498,12 @@ static int process_queue(void* shard_num)
|
|||
std::vector<char> buf;
|
||||
|
||||
if(sent_buf) {
|
||||
result = send_buffer(sent_buf->sock, sent_buf->stream, sent_buf->gzipped);
|
||||
if(sent_buf->raw_buffer.empty()) {
|
||||
const std::string &value = sent_buf->stream.str();
|
||||
make_network_buffer(value.c_str(), value.size(), sent_buf->raw_buffer);
|
||||
}
|
||||
|
||||
result = send_buffer(sent_buf->sock, sent_buf->raw_buffer);
|
||||
delete sent_buf;
|
||||
} else {
|
||||
result = receive_buf(sock,buf);
|
||||
|
@ -505,24 +517,29 @@ static int process_queue(void* shard_num)
|
|||
}
|
||||
//if we received data, add it to the queue
|
||||
buffer* received_data = new buffer(sock);
|
||||
std::string buffer(buf.begin(), buf.end());
|
||||
std::istringstream stream(buffer);
|
||||
// Binary wml starts with a char < 4, the first char of a gzip header is 31
|
||||
// so test that here and use the proper reader.
|
||||
try {
|
||||
if(stream.peek() == 31) {
|
||||
read_gz(received_data->config_buf, stream);
|
||||
} else {
|
||||
compression_schema *compress;
|
||||
{
|
||||
const threading::lock lock_schemas(*schemas_mutex);
|
||||
compress = &schemas.insert(std::pair<TCPsocket,schema_pair>(sock,schema_pair())).first->second.incoming;
|
||||
|
||||
if(raw_data_only) {
|
||||
received_data->raw_buffer.swap(buf);
|
||||
} else {
|
||||
std::string buffer(buf.begin(), buf.end());
|
||||
std::istringstream stream(buffer);
|
||||
// Binary wml starts with a char < 4, the first char of a gzip header is 31
|
||||
// so test that here and use the proper reader.
|
||||
try {
|
||||
if(stream.peek() == 31) {
|
||||
read_gz(received_data->config_buf, stream);
|
||||
} else {
|
||||
compression_schema *compress;
|
||||
{
|
||||
const threading::lock lock_schemas(*schemas_mutex);
|
||||
compress = &schemas.insert(std::pair<TCPsocket,schema_pair>(sock,schema_pair())).first->second.incoming;
|
||||
}
|
||||
read_compressed(received_data->config_buf, stream, *compress);
|
||||
}
|
||||
read_compressed(received_data->config_buf, stream, *compress);
|
||||
} catch(config::error &e)
|
||||
{
|
||||
received_data->config_error = e.message;
|
||||
}
|
||||
} catch(config::error &e)
|
||||
{
|
||||
received_data->config_error = e.message;
|
||||
}
|
||||
|
||||
{
|
||||
|
@ -608,6 +625,11 @@ manager::~manager()
|
|||
}
|
||||
}
|
||||
|
||||
void set_raw_data_only()
|
||||
{
|
||||
raw_data_only = true;
|
||||
}
|
||||
|
||||
void receive_data(TCPsocket sock)
|
||||
{
|
||||
{
|
||||
|
@ -624,6 +646,7 @@ void receive_data(TCPsocket sock)
|
|||
|
||||
TCPsocket get_received_data(TCPsocket sock, config& cfg)
|
||||
{
|
||||
assert(!raw_data_only);
|
||||
const threading::lock lock_received(*received_mutex);
|
||||
received_queue::iterator itor = received_data_queue.begin();
|
||||
if(sock != NULL) {
|
||||
|
@ -653,6 +676,36 @@ TCPsocket get_received_data(TCPsocket sock, config& cfg)
|
|||
}
|
||||
}
|
||||
|
||||
TCPsocket get_received_data(std::vector<char>& out)
|
||||
{
|
||||
assert(raw_data_only);
|
||||
const threading::lock lock_received(*received_mutex);
|
||||
if(received_data_queue.empty()) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
buffer* buf = received_data_queue.front();
|
||||
received_data_queue.pop_front();
|
||||
out.swap(buf->raw_buffer);
|
||||
const TCPsocket res = buf->sock;
|
||||
return res;
|
||||
}
|
||||
|
||||
void queue_raw_data(TCPsocket sock, const char* buf, int len)
|
||||
{
|
||||
buffer* queued_buf = new buffer(sock);
|
||||
assert(*buf == 31);
|
||||
make_network_buffer(buf, len, queued_buf->raw_buffer);
|
||||
const int shard = get_shard(sock);
|
||||
const threading::lock lock(*shard_mutexes[shard]);
|
||||
bufs[shard].push_back(queued_buf);
|
||||
socket_state_map::const_iterator i = sockets_locked[shard].insert(std::pair<TCPsocket,SOCKET_STATE>(sock,SOCKET_READY)).first;
|
||||
if(i->second == SOCKET_READY || i->second == SOCKET_ERRORED) {
|
||||
cond[shard]->notify_one();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void queue_data(TCPsocket sock,const config& buf, const bool gzipped)
|
||||
{
|
||||
DBG_NW << "queuing data...\n";
|
||||
|
|
|
@ -36,11 +36,15 @@ private:
|
|||
bool active_;
|
||||
};
|
||||
|
||||
void set_raw_data_only();
|
||||
|
||||
//! Function to asynchronously received data to the given socket.
|
||||
void receive_data(TCPsocket sock);
|
||||
|
||||
TCPsocket get_received_data(TCPsocket sock, config& cfg);
|
||||
TCPsocket get_received_data(std::vector<char>& buf);
|
||||
|
||||
void queue_raw_data(TCPsocket sock, const char* buf, int len);
|
||||
void queue_data(TCPsocket sock, const config& buf, const bool gzipped);
|
||||
bool is_locked(const TCPsocket sock);
|
||||
bool close_socket(TCPsocket sock, bool force=false);
|
||||
|
|
|
@ -355,7 +355,6 @@ void read(config &cfg, std::string &in, std::string* error_log)
|
|||
|
||||
void read_gz(config &cfg, std::istream &file, std::string* error_log)
|
||||
{
|
||||
|
||||
boost::iostreams::filtering_stream<boost::iostreams::input> filter;
|
||||
filter.push(boost::iostreams::gzip_decompressor());
|
||||
filter.push(file);
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -15,9 +15,10 @@
|
|||
#ifndef GAME_HPP_INCLUDED
|
||||
#define GAME_HPP_INCLUDED
|
||||
|
||||
#include "../config.hpp"
|
||||
#include "../network.hpp"
|
||||
|
||||
#include "simple_wml.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
|
@ -31,6 +32,7 @@ class game
|
|||
{
|
||||
public:
|
||||
game(player_map& players, const network::connection host=0, const std::string name="");
|
||||
~game();
|
||||
|
||||
int id() const { return id_; }
|
||||
const std::string& name() const { return name_; }
|
||||
|
@ -51,11 +53,11 @@ public:
|
|||
|
||||
void mute_all_observers();
|
||||
//! Mute an observer by name.
|
||||
void mute_observer(const config& mute, const player_map::const_iterator muter);
|
||||
void mute_observer(const simple_wml::node& mute, const player_map::const_iterator muter);
|
||||
//! Kick a member by name.
|
||||
network::connection kick_member(const config& kick, const player_map::const_iterator kicker);
|
||||
network::connection kick_member(const simple_wml::node& kick, const player_map::const_iterator kicker);
|
||||
//! Ban and kick a user by name. He doesn't need to be in this game.
|
||||
network::connection ban_user(const config& ban, const player_map::const_iterator banner);
|
||||
network::connection ban_user(const simple_wml::node& ban, const player_map::const_iterator banner);
|
||||
|
||||
void add_player(const network::connection player, const bool observer = false);
|
||||
bool remove_player(const network::connection player, const bool disconnect=false);
|
||||
|
@ -75,31 +77,32 @@ public:
|
|||
//! Resets the side configuration according to the scenario data.
|
||||
void update_side_data();
|
||||
//! Let's a player owning a side give it to another player or observer.
|
||||
void transfer_side_control(const network::connection sock, const config& cfg);
|
||||
void transfer_side_control(const network::connection sock, const simple_wml::node& cfg);
|
||||
|
||||
void process_message(config data, const player_map::iterator user);
|
||||
void process_message(simple_wml::document& data, const player_map::iterator user);
|
||||
//! Filters and processes (some) commands.
|
||||
//! Returns true iff the turn ended.
|
||||
bool process_turn(config data, const player_map::const_iterator user);
|
||||
bool process_turn(simple_wml::document& data, const player_map::const_iterator user);
|
||||
//! Set the description to the number of available slots.
|
||||
//! Returns true iff the number of slots has changed.
|
||||
bool describe_slots();
|
||||
|
||||
config construct_server_message(const std::string& message) const;
|
||||
void send_server_message(const char* message, network::connection sock=0, simple_wml::document* doc=NULL) const;
|
||||
//! Send data to all players in this game except 'exclude'.
|
||||
void send_and_record_server_message(const std::string& message,
|
||||
void send_and_record_server_message(const char* message,
|
||||
const network::connection exclude=0);
|
||||
void send_data(const config& data, const network::connection exclude=0) const;
|
||||
void send_data(simple_wml::document& data, const network::connection exclude=0) const;
|
||||
void send_to_one(simple_wml::document& data, const network::connection sock) const;
|
||||
|
||||
void record_data(const config& data);
|
||||
void record_data(simple_wml::document* data);
|
||||
|
||||
//! The full scenario data.
|
||||
config& level() { return level_; }
|
||||
simple_wml::document& level() { return level_; }
|
||||
|
||||
//! Functions to set/get the address of the game's summary description as
|
||||
//! sent to players in the lobby.
|
||||
void set_description(config* desc);
|
||||
config* description() const { return description_; }
|
||||
void set_description(simple_wml::node* desc);
|
||||
simple_wml::node* description() const { return description_; }
|
||||
|
||||
void set_password(const std::string& passwd) { password_ = passwd; }
|
||||
bool password_matches(const std::string& passwd) const {
|
||||
|
@ -117,6 +120,10 @@ public:
|
|||
}
|
||||
|
||||
private:
|
||||
//forbidden operations
|
||||
game(const game&);
|
||||
void operator=(const game&);
|
||||
|
||||
size_t current_side() const { return (nsides_ ? end_turn_ % nsides_ : 0); }
|
||||
network::connection current_player() const
|
||||
{ return (nsides_ ? sides_[current_side()] : 0); }
|
||||
|
@ -132,22 +139,23 @@ private:
|
|||
const player_map::const_iterator newplayer,
|
||||
const bool player_left=true);
|
||||
void transfer_ai_sides();
|
||||
void send_data_team(const config& data, const std::string& team,
|
||||
void send_data_team(simple_wml::document& data, const simple_wml::string_span& team,
|
||||
const network::connection exclude=0) const;
|
||||
void send_data_observers(const config& data, const network::connection exclude=0) const;
|
||||
void send_data_observers(simple_wml::document& data, const network::connection exclude=0) const;
|
||||
//! Send [observer] tags of all the observers in the game to the user or
|
||||
//! everyone if none given.
|
||||
void send_observerjoins(const network::connection sock=0) const;
|
||||
void send_history(const network::connection sock) const;
|
||||
//! In case of a host transfer, notify the new host about its status.
|
||||
void notify_new_host();
|
||||
//! Convenience function for finding a user by name.
|
||||
player_map::const_iterator find_user(const std::string& name) const;
|
||||
player_map::const_iterator find_user(const simple_wml::string_span& name) const;
|
||||
|
||||
bool observers_can_label() const { return false; }
|
||||
bool observers_can_chat() const { return true; }
|
||||
bool is_legal_command(const config& command, bool is_player);
|
||||
bool is_legal_command(const simple_wml::node& command, bool is_player);
|
||||
//! Function which returns true iff 'player' is on 'team'.
|
||||
bool is_on_team(const std::string& team, const network::connection player) const;
|
||||
bool is_on_team(const simple_wml::string_span& team, const network::connection player) const;
|
||||
|
||||
//! Function which should be called every time a player ends their turn
|
||||
//! (i.e. [end_turn] received). This will update the 'turn' attribute for
|
||||
|
@ -192,11 +200,11 @@ private:
|
|||
bool started_;
|
||||
|
||||
//! The current scenario data.
|
||||
config level_;
|
||||
simple_wml::document level_;
|
||||
//! Replay data.
|
||||
config history_;
|
||||
std::vector<simple_wml::document*> history_;
|
||||
//! Pointer to the game's description in the games_and_users_list_.
|
||||
config* description_;
|
||||
simple_wml::node* description_;
|
||||
|
||||
int end_turn_;
|
||||
|
||||
|
@ -209,7 +217,7 @@ private:
|
|||
|
||||
struct game_id_matches {
|
||||
game_id_matches(int id) : id_(id) {}
|
||||
bool operator()(const game& g) const { return g.id() == id_; }
|
||||
bool operator()(const game* g) const { return g->id() == id_; }
|
||||
|
||||
private:
|
||||
int id_;
|
||||
|
|
|
@ -16,20 +16,20 @@
|
|||
|
||||
#include "player.hpp"
|
||||
|
||||
player::player(const std::string& n, config& cfg, const size_t max_messages, const size_t time_period)
|
||||
player::player(const std::string& n, simple_wml::node& cfg, const size_t max_messages, const size_t time_period)
|
||||
: name_(n), cfg_(cfg), flood_start_(0), messages_since_flood_start_(0),
|
||||
MaxMessages(max_messages), TimePeriod(time_period)
|
||||
{
|
||||
cfg_["name"] = n;
|
||||
cfg_.set_attr_dup("name", n.c_str());
|
||||
mark_available();
|
||||
}
|
||||
|
||||
// keep 'available' and game name ('location') for backward compatibility
|
||||
void player::mark_available(const int game_id, const std::string location)
|
||||
{
|
||||
cfg_["available"] = (game_id == 0) ? "yes" : "no";
|
||||
cfg_["game_id"] = lexical_cast<std::string>(game_id);
|
||||
cfg_["location"] = location;
|
||||
cfg_.set_attr("available", (game_id == 0) ? "yes" : "no");
|
||||
cfg_.set_attr_dup("game_id", lexical_cast<std::string>(game_id).c_str());
|
||||
cfg_.set_attr_dup("location", location.c_str());
|
||||
}
|
||||
|
||||
bool player::is_message_flooding() {
|
||||
|
|
|
@ -18,13 +18,14 @@
|
|||
#include <ctime>
|
||||
|
||||
#include "../config.hpp"
|
||||
#include "simple_wml.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
class player
|
||||
{
|
||||
public:
|
||||
player(const std::string& n, config& cfg, const size_t max_messages=4, const size_t time_period=10);
|
||||
player(const std::string& n, simple_wml::node& cfg, const size_t max_messages=4, const size_t time_period=10);
|
||||
|
||||
// mark a player as member of the game 'game_id' or as located in the lobby
|
||||
void mark_available(const int game_id=0, const std::string location="");
|
||||
|
@ -32,14 +33,14 @@ public:
|
|||
|
||||
const std::string& name() const { return name_; }
|
||||
|
||||
config* config_address() const { return &cfg_; }
|
||||
simple_wml::node* config_address() const { return &cfg_; }
|
||||
|
||||
bool silenced() const { return messages_since_flood_start_ > MaxMessages; }
|
||||
bool is_message_flooding();
|
||||
|
||||
private:
|
||||
const std::string name_;
|
||||
config& cfg_;
|
||||
simple_wml::node& cfg_;
|
||||
|
||||
time_t flood_start_;
|
||||
unsigned int messages_since_flood_start_;
|
||||
|
|
|
@ -75,14 +75,15 @@ void disconnect(network::connection sock)
|
|||
network::disconnect(peer);
|
||||
}
|
||||
|
||||
void received_data(network::connection sock, const config& data)
|
||||
void received_data(network::connection sock, simple_wml::document& data)
|
||||
{
|
||||
const network::connection peer = find_peer(sock);
|
||||
if(!peer) {
|
||||
return;
|
||||
}
|
||||
|
||||
network::send_data(data, peer, true);
|
||||
const simple_wml::string_span& output = data.output_compressed();
|
||||
network::send_raw_data(output.begin(), output.size(), peer);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -15,8 +15,8 @@
|
|||
#ifndef PROXY_HPP_INCLUDED
|
||||
#define PROXY_HPP_INCLUDED
|
||||
|
||||
#include "config.hpp"
|
||||
#include "network.hpp"
|
||||
#include "simple_wml.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
|
@ -27,7 +27,7 @@ void create_proxy(network::connection sock, const std::string& host, int port);
|
|||
bool is_proxy(network::connection sock);
|
||||
void disconnect(network::connection sock);
|
||||
|
||||
void received_data(network::connection sock, const config& data);
|
||||
void received_data(network::connection sock, simple_wml::document& data);
|
||||
|
||||
}
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
927
src/server/simple_wml.cpp
Normal file
927
src/server/simple_wml.cpp
Normal file
|
@ -0,0 +1,927 @@
|
|||
#include <assert.h>
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <zlib.h>
|
||||
|
||||
#include <boost/iostreams/filtering_stream.hpp>
|
||||
#include <boost/iostreams/filter/gzip.hpp>
|
||||
|
||||
#include "../serialization/binary_wml.hpp"
|
||||
#include "simple_wml.hpp"
|
||||
|
||||
namespace simple_wml {
|
||||
|
||||
namespace {
|
||||
|
||||
char* uncompress_buffer(const string_span& input, string_span* span)
|
||||
{
|
||||
std::istringstream stream(std::string(input.begin(), input.end()));
|
||||
boost::iostreams::filtering_stream<boost::iostreams::input> filter;
|
||||
filter.push(boost::iostreams::gzip_decompressor());
|
||||
filter.push(stream);
|
||||
|
||||
const int chunk_size = input.size() * 10;
|
||||
std::vector<char> buf(chunk_size);
|
||||
int len = 0;
|
||||
int pos = 0;
|
||||
while(filter.good() && (len = filter.read(&buf[pos], chunk_size).gcount()) == chunk_size) {
|
||||
pos += len;
|
||||
buf.resize(pos + chunk_size);
|
||||
len = 0;
|
||||
}
|
||||
|
||||
if(!filter.eof() && !filter.good()) {
|
||||
throw error("failed to uncompress");
|
||||
}
|
||||
|
||||
pos += len;
|
||||
buf.resize(pos);
|
||||
|
||||
char* small_out = new char[pos+1];
|
||||
memcpy(small_out, &buf[0], pos);
|
||||
small_out[pos] = 0;
|
||||
|
||||
*span = string_span(small_out, pos);
|
||||
return small_out;
|
||||
}
|
||||
|
||||
char* compress_buffer(const char* input, string_span* span)
|
||||
{
|
||||
std::string in(input);
|
||||
std::istringstream stream(in);
|
||||
boost::iostreams::filtering_stream<boost::iostreams::input> filter;
|
||||
filter.push(boost::iostreams::gzip_compressor());
|
||||
filter.push(stream);
|
||||
|
||||
std::vector<char> buf(in.size()*2 + 80);
|
||||
const int len = filter.read(&buf[0], buf.size()).gcount();
|
||||
if(!filter.eof() && !filter.good() || len == buf.size()) {
|
||||
throw error("failed to compress");
|
||||
}
|
||||
|
||||
buf.resize(len);
|
||||
|
||||
char* small_out = new char[len];
|
||||
memcpy(small_out, &buf[0], len);
|
||||
|
||||
*span = string_span(small_out, len);
|
||||
assert(*small_out == 31);
|
||||
return small_out;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool string_span::to_bool(bool default_value) const
|
||||
{
|
||||
if(empty()) {
|
||||
return default_value;
|
||||
}
|
||||
|
||||
return operator==("yes") || operator==("on") || operator==("true") || operator==("1");
|
||||
}
|
||||
|
||||
int string_span::to_int() const
|
||||
{
|
||||
const int buf_size = 64;
|
||||
if(size() >= buf_size) {
|
||||
return 0;
|
||||
}
|
||||
char buf[64];
|
||||
memcpy(buf, begin(), size());
|
||||
buf[size()] = 0;
|
||||
return atoi(buf);
|
||||
}
|
||||
|
||||
std::string string_span::to_string() const
|
||||
{
|
||||
return std::string(begin(), end());
|
||||
}
|
||||
|
||||
char* string_span::duplicate() const
|
||||
{
|
||||
char* buf = new char[size() + 1];
|
||||
memcpy(buf, begin(), size());
|
||||
buf[size()] = 0;
|
||||
return buf;
|
||||
}
|
||||
|
||||
error::error(const char* msg)
|
||||
{
|
||||
std::cerr << "ERROR: '" << msg << "'\n";
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& o, const string_span& s)
|
||||
{
|
||||
o << std::string(s.begin(), s.end());
|
||||
return o;
|
||||
}
|
||||
|
||||
node::node(document& doc, node* parent)
|
||||
: doc_(&doc), parent_(parent)
|
||||
{}
|
||||
|
||||
node::node(document& doc, node* parent, const char** str, int depth)
|
||||
: doc_(&doc), parent_(parent)
|
||||
{
|
||||
if(depth >= 30) {
|
||||
throw error("elements nested too deep");
|
||||
}
|
||||
|
||||
const char*& s = *str;
|
||||
|
||||
const char* const begin = s;
|
||||
while(*s) {
|
||||
switch(*s) {
|
||||
case '[': {
|
||||
if(s[1] == '/') {
|
||||
output_cache_ = string_span(begin, s - begin);
|
||||
s = strchr(s, ']');
|
||||
if(s == NULL) {
|
||||
throw error("end element unterminated");
|
||||
}
|
||||
|
||||
++s;
|
||||
return;
|
||||
}
|
||||
|
||||
++s;
|
||||
const char* end = strchr(s, ']');
|
||||
if(end == NULL) {
|
||||
throw error("unterminated element");
|
||||
}
|
||||
|
||||
child_list& list = children_[string_span(s, end - s)];
|
||||
|
||||
s = end + 1;
|
||||
|
||||
list.push_back(new node(doc, this, str, depth+1));
|
||||
|
||||
break;
|
||||
}
|
||||
case ' ':
|
||||
case '\t':
|
||||
case '\n':
|
||||
++s;
|
||||
break;
|
||||
case '#':
|
||||
s = strchr(s, '\n');
|
||||
if(s == NULL) {
|
||||
throw error("could not find newline after #");
|
||||
}
|
||||
break;
|
||||
default: {
|
||||
const char* end = strchr(s, '=');
|
||||
if(end == NULL) {
|
||||
std::cerr << "attribute: " << s << "\n";
|
||||
throw error("could not find '=' after attribute");
|
||||
}
|
||||
|
||||
string_span name(s, end - s);
|
||||
s = end + 1;
|
||||
if(*s == '_') {
|
||||
s = strchr(s, '"');
|
||||
if(s == NULL) {
|
||||
throw error("could not find '\"' after _");
|
||||
}
|
||||
}
|
||||
|
||||
if(*s != '"') {
|
||||
std::cerr << "no quotes for attribute '" << name << "'\n";
|
||||
throw error("did not find quotes around attribute");
|
||||
}
|
||||
|
||||
end = s;
|
||||
|
||||
for(;;) {
|
||||
while((end = strchr(end+1, '"')) && end[1] == '"') {
|
||||
++end;
|
||||
}
|
||||
|
||||
if(end == NULL) {
|
||||
std::cerr << "ATTR: '" << name << "' (((" << s << ")))\n";
|
||||
throw error("did not find end of attribute");
|
||||
}
|
||||
|
||||
const char* endline = end;
|
||||
while(*endline && *endline != '\n' && *endline != '+') {
|
||||
++endline;
|
||||
}
|
||||
|
||||
if(*endline != '+') {
|
||||
break;
|
||||
}
|
||||
|
||||
end = strchr(endline, '"');
|
||||
if(end == NULL) {
|
||||
throw error("did not find quotes after +");
|
||||
}
|
||||
}
|
||||
|
||||
++s;
|
||||
|
||||
string_span value(s, end - s);
|
||||
if(attr_.empty() == false && !(attr_.back().first < name)) {
|
||||
std::cerr << "attributes: '" << attr_.back().first << "' < '" << name << "'\n";
|
||||
throw error("attributes not in order");
|
||||
}
|
||||
|
||||
s = end + 1;
|
||||
|
||||
attr_.push_back(attribute(name, value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
output_cache_ = string_span(begin, s - begin);
|
||||
}
|
||||
|
||||
node::~node()
|
||||
{
|
||||
for(child_map::iterator i = children_.begin(); i != children_.end(); ++i) {
|
||||
for(child_list::iterator j = i->second.begin(); j != i->second.end(); ++j) {
|
||||
delete *j;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
struct string_span_pair_comparer
|
||||
{
|
||||
bool operator()(const string_span& a, const node::attribute& b) const {
|
||||
return a < b.first;
|
||||
}
|
||||
|
||||
bool operator()(const node::attribute& a, const string_span& b) const {
|
||||
return a.first < b;
|
||||
}
|
||||
|
||||
bool operator()(const node::attribute& a,
|
||||
const node::attribute& b) const {
|
||||
return a.first < b.first;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const string_span& node::operator[](const char* key) const
|
||||
{
|
||||
static string_span empty("");
|
||||
string_span span(key);
|
||||
std::pair<attribute_list::const_iterator,
|
||||
attribute_list::const_iterator> range = std::equal_range(attr_.begin(), attr_.end(), span, string_span_pair_comparer());
|
||||
if(range.first != range.second) {
|
||||
return range.first->second;
|
||||
}
|
||||
|
||||
return empty;
|
||||
}
|
||||
|
||||
bool node::has_attr(const char* key) const
|
||||
{
|
||||
string_span span(key);
|
||||
std::pair<attribute_list::const_iterator,
|
||||
attribute_list::const_iterator> range = std::equal_range(attr_.begin(), attr_.end(), span, string_span_pair_comparer());
|
||||
return range.first != range.second;
|
||||
}
|
||||
|
||||
node& node::set_attr(const char* key, const char* value)
|
||||
{
|
||||
set_dirty();
|
||||
|
||||
string_span span(key);
|
||||
std::pair<attribute_list::iterator,
|
||||
attribute_list::iterator> range = std::equal_range(attr_.begin(), attr_.end(), span, string_span_pair_comparer());
|
||||
if(range.first != range.second) {
|
||||
range.first->second = string_span(value);
|
||||
} else {
|
||||
attr_.insert(range.first, attribute(span, string_span(value)));
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
node& node::set_attr_dup(const char* key, const char* value)
|
||||
{
|
||||
return set_attr(key, doc_->dup_string(value));
|
||||
}
|
||||
|
||||
node& node::set_attr_dup(const char* key, const string_span& value)
|
||||
{
|
||||
char* buf = value.duplicate();
|
||||
doc_->take_ownership_of_buffer(buf);
|
||||
return set_attr(key, buf);
|
||||
}
|
||||
|
||||
node& node::set_attr_dup_key_and_value(const char* key, const char* value)
|
||||
{
|
||||
return set_attr(doc_->dup_string(key), doc_->dup_string(value));
|
||||
}
|
||||
|
||||
node& node::set_attr_int(const char* key, int value)
|
||||
{
|
||||
char buf[64];
|
||||
sprintf(buf, "%d", value);
|
||||
return set_attr_dup(key, buf);
|
||||
}
|
||||
|
||||
node& node::add_child_at(const char* name, size_t index)
|
||||
{
|
||||
set_dirty();
|
||||
|
||||
child_list& list = children_[string_span(name)];
|
||||
if(index > list.size()) {
|
||||
index = list.size();
|
||||
}
|
||||
|
||||
list.insert(list.begin() + index, new node(*doc_, this));
|
||||
return *list[index];
|
||||
}
|
||||
|
||||
|
||||
node& node::add_child(const char* name)
|
||||
{
|
||||
set_dirty();
|
||||
|
||||
child_list& list = children_[string_span(name)];
|
||||
list.push_back(new node(*doc_, this));
|
||||
return *list.back();
|
||||
}
|
||||
|
||||
void node::remove_child(const string_span& name, size_t index)
|
||||
{
|
||||
set_dirty();
|
||||
|
||||
child_list& list = children_[name];
|
||||
if(index >= list.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
delete list[index];
|
||||
list.erase(list.begin() + index);
|
||||
}
|
||||
|
||||
void node::remove_child(const char* name, size_t index)
|
||||
{
|
||||
remove_child(string_span(name), index);
|
||||
}
|
||||
|
||||
node* node::child(const char* name)
|
||||
{
|
||||
child_map::iterator itor = children_.find(string_span(name));
|
||||
if(itor == children_.end() || itor->second.empty()) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return itor->second.front();
|
||||
}
|
||||
|
||||
const node* node::child(const char* name) const
|
||||
{
|
||||
child_map::const_iterator itor = children_.find(string_span(name));
|
||||
if(itor == children_.end() || itor->second.empty()) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return itor->second.front();
|
||||
}
|
||||
|
||||
const node::child_list& node::children(const char* name) const
|
||||
{
|
||||
static const node::child_list empty;
|
||||
child_map::const_iterator itor = children_.find(string_span(name));
|
||||
if(itor == children_.end()) {
|
||||
return empty;
|
||||
}
|
||||
|
||||
return itor->second;
|
||||
}
|
||||
|
||||
int node::output_size() const
|
||||
{
|
||||
/*
|
||||
if(output_cache_.empty() == false) {
|
||||
return output_cache_.size();
|
||||
}
|
||||
*/
|
||||
|
||||
int res = 0;
|
||||
for(attribute_list::const_iterator i = attr_.begin(); i != attr_.end(); ++i) {
|
||||
res += i->first.size() + i->second.size() + 4;
|
||||
}
|
||||
|
||||
for(child_map::const_iterator i = children_.begin(); i != children_.end(); ++i) {
|
||||
for(child_list::const_iterator j = i->second.begin(); j != i->second.end(); ++j) {
|
||||
res += i->first.size()*2 + 7;
|
||||
res += (*j)->output_size();
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
void node::shift_buffers(int offset)
|
||||
{
|
||||
if(!output_cache_.empty()) {
|
||||
output_cache_ = string_span(output_cache_.begin() + offset, output_cache_.size());
|
||||
}
|
||||
|
||||
for(std::vector<attribute>::iterator i = attr_.begin(); i != attr_.end(); ++i) {
|
||||
i->first = string_span(i->first.begin() + offset, i->first.size());
|
||||
i->second = string_span(i->second.begin() + offset, i->second.size());
|
||||
}
|
||||
|
||||
for(child_map::iterator i = children_.begin(); i != children_.end(); ++i) {
|
||||
string_span& key = const_cast<string_span&>(i->first);
|
||||
key = string_span(key.begin() + offset, key.size());
|
||||
for(child_list::iterator j = i->second.begin(); j != i->second.end(); ++j) {
|
||||
(*j)->shift_buffers(offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void node::output(char*& buf)
|
||||
{
|
||||
/*
|
||||
if(output_cache_.empty() == false) {
|
||||
memcpy(buf, output_cache_.begin(), output_cache_.size());
|
||||
shift_buffers(buf - output_cache_.begin());
|
||||
buf += output_cache_.size();
|
||||
return;
|
||||
}
|
||||
*/
|
||||
char* begin = buf;
|
||||
|
||||
for(std::vector<attribute>::iterator i = attr_.begin(); i != attr_.end(); ++i) {
|
||||
memcpy(buf, i->first.begin(), i->first.size());
|
||||
i->first = string_span(buf, i->first.size());
|
||||
buf += i->first.size();
|
||||
*buf++ = '=';
|
||||
*buf++ = '"';
|
||||
memcpy(buf, i->second.begin(), i->second.size());
|
||||
i->second = string_span(buf, i->second.size());
|
||||
buf += i->second.size();
|
||||
*buf++ = '"';
|
||||
*buf++ = '\n';
|
||||
}
|
||||
|
||||
for(child_map::iterator i = children_.begin(); i != children_.end(); ++i) {
|
||||
for(child_list::iterator j = i->second.begin(); j != i->second.end(); ++j) {
|
||||
*buf++ = '[';
|
||||
memcpy(buf, i->first.begin(), i->first.size());
|
||||
const_cast<string_span&>(i->first) = string_span(buf, i->first.size());
|
||||
buf += i->first.size();
|
||||
*buf++ = ']';
|
||||
*buf++ = '\n';
|
||||
(*j)->output(buf);
|
||||
*buf++ = '[';
|
||||
*buf++ = '/';
|
||||
memcpy(buf, i->first.begin(), i->first.size());
|
||||
buf += i->first.size();
|
||||
*buf++ = ']';
|
||||
*buf++ = '\n';
|
||||
}
|
||||
}
|
||||
|
||||
output_cache_ = string_span(begin, buf - begin);
|
||||
}
|
||||
|
||||
void node::copy_into(node& n) const
|
||||
{
|
||||
n.set_dirty();
|
||||
for(attribute_list::const_iterator i = attr_.begin(); i != attr_.end(); ++i) {
|
||||
char* key = i->first.duplicate();
|
||||
char* value = i->second.duplicate();
|
||||
n.doc_->take_ownership_of_buffer(key);
|
||||
n.doc_->take_ownership_of_buffer(value);
|
||||
n.set_attr(key, value);
|
||||
}
|
||||
|
||||
for(child_map::const_iterator i = children_.begin(); i != children_.end(); ++i) {
|
||||
if(i->second.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
char* buf = i->first.duplicate();
|
||||
n.doc_->take_ownership_of_buffer(buf);
|
||||
|
||||
for(child_list::const_iterator j = i->second.begin(); j != i->second.end(); ++j) {
|
||||
(*j)->copy_into(n.add_child(buf));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void node::apply_diff(const node& diff)
|
||||
{
|
||||
set_dirty();
|
||||
const node* inserts = diff.child("insert");
|
||||
if(inserts != NULL) {
|
||||
for(attribute_list::const_iterator i = inserts->attr_.begin(); i != inserts->attr_.end(); ++i) {
|
||||
char* name = i->first.duplicate();
|
||||
char* value = i->second.duplicate();
|
||||
set_attr(name, value);
|
||||
doc_->take_ownership_of_buffer(name);
|
||||
doc_->take_ownership_of_buffer(value);
|
||||
}
|
||||
}
|
||||
|
||||
const node* deletes = diff.child("delete");
|
||||
if(deletes != NULL) {
|
||||
for(attribute_list::const_iterator i = deletes->attr_.begin(); i != deletes->attr_.end(); ++i) {
|
||||
std::pair<attribute_list::iterator,
|
||||
attribute_list::iterator> range = std::equal_range(attr_.begin(), attr_.end(), i->first, string_span_pair_comparer());
|
||||
if(range.first != range.second) {
|
||||
attr_.erase(range.first);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const child_list& child_changes = diff.children("change_child");
|
||||
for(child_list::const_iterator i = child_changes.begin(); i != child_changes.end(); ++i) {
|
||||
const size_t index = (**i)["index"].to_int();
|
||||
for(child_map::const_iterator j = (*i)->children_.begin(); j != (*i)->children_.end(); ++j) {
|
||||
const string_span& name = j->first;
|
||||
for(child_list::const_iterator k = j->second.begin(); k != j->second.end(); ++k) {
|
||||
child_map::iterator itor = children_.find(name);
|
||||
if(itor != children_.end()) {
|
||||
if(index < itor->second.size()) {
|
||||
itor->second[index]->apply_diff(**k);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const child_list& child_inserts = diff.children("insert_child");
|
||||
for(child_list::const_iterator i = child_inserts.begin(); i != child_inserts.end(); ++i) {
|
||||
const size_t index = (**i)["index"].to_int();
|
||||
for(child_map::const_iterator j = (*i)->children_.begin(); j != (*i)->children_.end(); ++j) {
|
||||
const string_span& name = j->first;
|
||||
for(child_list::const_iterator k = j->second.begin(); k != j->second.end(); ++k) {
|
||||
char* buf = name.duplicate();
|
||||
doc_->take_ownership_of_buffer(buf);
|
||||
(*k)->copy_into(add_child_at(buf, index));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const child_list& child_deletes = diff.children("delete_child");
|
||||
for(child_list::const_iterator i = child_deletes.begin(); i != child_deletes.end(); ++i) {
|
||||
const size_t index = (**i)["index"].to_int();
|
||||
for(child_map::const_iterator j = (*i)->children_.begin(); j != (*i)->children_.end(); ++j) {
|
||||
if(j->second.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const string_span& name = j->first;
|
||||
remove_child(name, index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void node::set_doc(document* doc)
|
||||
{
|
||||
doc_ = doc;
|
||||
|
||||
for(child_map::iterator i = children_.begin(); i != children_.end(); ++i) {
|
||||
for(child_list::iterator j = i->second.begin(); j != i->second.end(); ++j) {
|
||||
(*j)->set_doc(doc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int node::nchildren() const
|
||||
{
|
||||
int res = 0;
|
||||
for(child_map::const_iterator i = children_.begin(); i != children_.end(); ++i) {
|
||||
for(child_list::const_iterator j = i->second.begin(); j != i->second.end(); ++j) {
|
||||
++res;
|
||||
res += (*j)->nchildren();
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
int node::nattributes_recursive() const
|
||||
{
|
||||
int res = attr_.capacity();
|
||||
for(child_map::const_iterator i = children_.begin(); i != children_.end(); ++i) {
|
||||
for(child_list::const_iterator j = i->second.begin(); j != i->second.end(); ++j) {
|
||||
res += (*j)->nattributes_recursive();
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
void node::set_dirty()
|
||||
{
|
||||
for(node* n = this; n != NULL && n->output_cache_.is_null() == false; n = n->parent_) {
|
||||
n->output_cache_ = string_span();
|
||||
}
|
||||
}
|
||||
|
||||
document::document() : output_(NULL),
|
||||
root_(new node(*this, NULL))
|
||||
{
|
||||
attach_list();
|
||||
}
|
||||
|
||||
document::document(char* buf, INIT_BUFFER_CONTROL control) : output_(buf), root_(NULL)
|
||||
{
|
||||
if(control == INIT_TAKE_OWNERSHIP) {
|
||||
buffers_.push_back(buf);
|
||||
}
|
||||
const char* cbuf = buf;
|
||||
root_ = new node(*this, NULL, &cbuf);
|
||||
|
||||
attach_list();
|
||||
}
|
||||
|
||||
document::document(const char* buf, INIT_STATE state) : output_(NULL),
|
||||
root_(NULL)
|
||||
{
|
||||
output_ = buf;
|
||||
if(state == INIT_COMPRESSED) {
|
||||
output_compressed();
|
||||
output_ = NULL;
|
||||
} else {
|
||||
root_ = new node(*this, NULL, &buf);
|
||||
}
|
||||
|
||||
attach_list();
|
||||
}
|
||||
|
||||
document::document(string_span compressed_buf)
|
||||
: compressed_buf_(compressed_buf),
|
||||
output_(NULL),
|
||||
root_(NULL)
|
||||
{
|
||||
string_span uncompressed_buf;
|
||||
buffers_.push_back(uncompress_buffer(compressed_buf, &uncompressed_buf));
|
||||
output_ = uncompressed_buf.begin();
|
||||
const char* cbuf = output_;
|
||||
try {
|
||||
root_ = new node(*this, NULL, &cbuf);
|
||||
} catch(...) {
|
||||
delete [] buffers_.front();
|
||||
buffers_.clear();
|
||||
throw;
|
||||
}
|
||||
|
||||
attach_list();
|
||||
}
|
||||
|
||||
document::~document()
|
||||
{
|
||||
for(std::vector<char*>::iterator i = buffers_.begin(); i != buffers_.end(); ++i) {
|
||||
delete [] *i;
|
||||
}
|
||||
|
||||
buffers_.clear();
|
||||
delete root_;
|
||||
|
||||
detach_list();
|
||||
}
|
||||
|
||||
const char* document::dup_string(const char* str)
|
||||
{
|
||||
const int len = strlen(str);
|
||||
char* res = new char[len+1];
|
||||
memcpy(res, str, len + 1);
|
||||
buffers_.push_back(res);
|
||||
return res;
|
||||
}
|
||||
|
||||
const char* document::output()
|
||||
{
|
||||
if(output_ && (!root_ || root_->is_dirty() == false)) {
|
||||
return output_;
|
||||
}
|
||||
if(!root_) {
|
||||
assert(compressed_buf_.empty() == false);
|
||||
string_span uncompressed_buf;
|
||||
buffers_.push_back(uncompress_buffer(compressed_buf_, &uncompressed_buf));
|
||||
output_ = uncompressed_buf.begin();
|
||||
return output_;
|
||||
}
|
||||
|
||||
//we're dirty, so the compressed buf must also be dirty; clear it.
|
||||
compressed_buf_ = string_span();
|
||||
|
||||
std::vector<char*> bufs;
|
||||
bufs.swap(buffers_);
|
||||
|
||||
const int buf_size = root_->output_size() + 1;
|
||||
char* buf = new char[buf_size];
|
||||
buffers_.push_back(buf);
|
||||
output_ = buf;
|
||||
|
||||
root_->output(buf);
|
||||
*buf++ = 0;
|
||||
assert(buf == output_ + buf_size);
|
||||
|
||||
for(std::vector<char*>::iterator i = bufs.begin(); i != bufs.end(); ++i) {
|
||||
delete [] *i;
|
||||
}
|
||||
|
||||
bufs.clear();
|
||||
|
||||
return output_;
|
||||
}
|
||||
|
||||
string_span document::output_compressed()
|
||||
{
|
||||
if(compressed_buf_.empty() == false &&
|
||||
(root_ == NULL || root_->is_dirty() == false)) {
|
||||
assert(*compressed_buf_.begin() == 31);
|
||||
return compressed_buf_;
|
||||
}
|
||||
|
||||
buffers_.push_back(compress_buffer(output(), &compressed_buf_));
|
||||
assert(*compressed_buf_.begin() == 31);
|
||||
|
||||
return compressed_buf_;
|
||||
}
|
||||
|
||||
void document::compress()
|
||||
{
|
||||
output_compressed();
|
||||
delete root_;
|
||||
root_ = NULL;
|
||||
output_ = NULL;
|
||||
std::vector<char*> new_buffers;
|
||||
for(std::vector<char*>::iterator i = buffers_.begin(); i != buffers_.end(); ++i) {
|
||||
if(*i != compressed_buf_.begin()) {
|
||||
delete [] *i;
|
||||
} else {
|
||||
new_buffers.push_back(*i);
|
||||
}
|
||||
}
|
||||
|
||||
buffers_.swap(new_buffers);
|
||||
assert(buffers_.size() == 1);
|
||||
}
|
||||
|
||||
void document::generate_root()
|
||||
{
|
||||
if(output_ == NULL) {
|
||||
assert(compressed_buf_.empty() == false);
|
||||
string_span uncompressed_buf;
|
||||
buffers_.push_back(uncompress_buffer(compressed_buf_, &uncompressed_buf));
|
||||
output_ = uncompressed_buf.begin();
|
||||
}
|
||||
|
||||
const char* cbuf = output_;
|
||||
root_ = new node(*this, NULL, &cbuf);
|
||||
}
|
||||
|
||||
document* document::clone()
|
||||
{
|
||||
char* buf = new char[strlen(output())+1];
|
||||
strcpy(buf, output());
|
||||
return new document(buf);
|
||||
}
|
||||
|
||||
void document::swap(document& o)
|
||||
{
|
||||
std::swap(compressed_buf_, o.compressed_buf_);
|
||||
std::swap(output_, o.output_);
|
||||
buffers_.swap(o.buffers_);
|
||||
std::swap(root_, o.root_);
|
||||
|
||||
root_->set_doc(this);
|
||||
o.root_->set_doc(&o);
|
||||
}
|
||||
|
||||
void document::clear()
|
||||
{
|
||||
compressed_buf_ = string_span();
|
||||
output_ = NULL;
|
||||
delete root_;
|
||||
root_ = new node(*this, NULL);
|
||||
for(std::vector<char*>::iterator i = buffers_.begin(); i != buffers_.end(); ++i) {
|
||||
delete [] *i;
|
||||
}
|
||||
|
||||
buffers_.clear();
|
||||
}
|
||||
|
||||
namespace {
|
||||
document* head_doc = NULL;
|
||||
}
|
||||
|
||||
void document::attach_list()
|
||||
{
|
||||
prev_ = NULL;
|
||||
next_ = head_doc;
|
||||
|
||||
if(next_) {
|
||||
next_->prev_ = this;
|
||||
}
|
||||
head_doc = this;
|
||||
}
|
||||
|
||||
void document::detach_list()
|
||||
{
|
||||
if(head_doc == this) {
|
||||
head_doc = next_;
|
||||
}
|
||||
|
||||
if(next_) {
|
||||
next_->prev_ = prev_;
|
||||
}
|
||||
|
||||
if(prev_) {
|
||||
prev_->next_ = next_;
|
||||
}
|
||||
next_ = prev_ = NULL;
|
||||
}
|
||||
|
||||
std::string document::stats()
|
||||
{
|
||||
std::ostringstream s;
|
||||
int ndocs = 0;
|
||||
int ncompressed = 0;
|
||||
int compressed_size = 0;
|
||||
int ntext = 0;
|
||||
int text_size = 0;
|
||||
int nbuffers = 0;
|
||||
int nnodes = 0;
|
||||
int nhas_nodes = 0;
|
||||
int ndirty = 0;
|
||||
int nattributes = 0;
|
||||
for(document* d = head_doc; d != NULL; d = d->next_) {
|
||||
ndocs++;
|
||||
nbuffers += d->buffers_.size();
|
||||
|
||||
if(d->compressed_buf_.is_null() == false) {
|
||||
++ncompressed;
|
||||
compressed_size += d->compressed_buf_.size();
|
||||
}
|
||||
|
||||
if(d->output_) {
|
||||
++ntext;
|
||||
text_size += strlen(d->output_);
|
||||
}
|
||||
|
||||
if(d->root_) {
|
||||
++nhas_nodes;
|
||||
nnodes += 1 + d->root_->nchildren();
|
||||
nattributes += d->root_->nattributes_recursive();
|
||||
}
|
||||
|
||||
if(d->root_ && d->root_->is_dirty()) {
|
||||
++ndirty;
|
||||
}
|
||||
}
|
||||
|
||||
const int nodes_alloc = nnodes*(sizeof(node) + 12);
|
||||
const int attr_alloc = nattributes*(sizeof(string_span)*2);
|
||||
const int total_alloc = compressed_size + text_size + nodes_alloc + attr_alloc;
|
||||
|
||||
s << "WML documents: " << ndocs << "\n"
|
||||
<< "Dirty: " << ndirty << "\n"
|
||||
<< "With compression: " << ncompressed << " (" << compressed_size
|
||||
<< " bytes)\n"
|
||||
<< "With text: " << ntext << " (" << text_size
|
||||
<< " bytes)\n"
|
||||
<< "Nodes: " << nnodes << " (" << nodes_alloc << " bytes)\n"
|
||||
<< "Attr: " << nattributes << " (" << attr_alloc << " bytes)\n"
|
||||
<< "Buffers: " << nbuffers << "\n"
|
||||
<< "Total allocation: " << total_alloc << " bytes\n";
|
||||
|
||||
return s.str();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#ifdef UNIT_TEST_SIMPLE_WML
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
char* doctext = strdup(
|
||||
"[test]\n"
|
||||
"a=\"blah\"\n"
|
||||
"b=\"blah\"\n"
|
||||
"c=\"\\\\\"\n"
|
||||
"d=\"\\\"\"\n"
|
||||
"[/test]");
|
||||
std::cerr << doctext << "\n";
|
||||
simple_wml::document doc(doctext);
|
||||
|
||||
simple_wml::node& node = doc.root();
|
||||
simple_wml::node* test_node = node.child("test");
|
||||
assert(test_node);
|
||||
assert((*test_node)["a"] == "blah");
|
||||
assert((*test_node)["b"] == "blah");
|
||||
assert((*test_node)["c"] == "\\\\");
|
||||
assert((*test_node)["d"] == "\\\"");
|
||||
|
||||
node.set_attr("blah", "blah");
|
||||
test_node->set_attr("e", "f");
|
||||
std::cerr << doc.output();
|
||||
}
|
||||
|
||||
#endif
|
237
src/server/simple_wml.hpp
Normal file
237
src/server/simple_wml.hpp
Normal file
|
@ -0,0 +1,237 @@
|
|||
#ifndef SIMPLE_WML_HPP_INCLUDED
|
||||
#define SIMPLE_WML_HPP_INCLUDED
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <iosfwd>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace simple_wml {
|
||||
|
||||
struct error {
|
||||
error(const char* msg);
|
||||
};
|
||||
|
||||
class string_span
|
||||
{
|
||||
public:
|
||||
string_span() : str_(NULL), size_(0)
|
||||
{}
|
||||
string_span(const char* str, int size) : str_(str), size_(size)
|
||||
{}
|
||||
string_span(const char* str) : str_(str), size_(strlen(str))
|
||||
{}
|
||||
|
||||
bool operator==(const char* o) const {
|
||||
const char* i1 = str_;
|
||||
const char* i2 = str_ + size_;
|
||||
while(i1 != i2 && *o && *i1 == *o) {
|
||||
++i1;
|
||||
++o;
|
||||
}
|
||||
|
||||
return i1 == i2 && *o == 0;
|
||||
}
|
||||
bool operator!=(const char* o) const {
|
||||
return !operator==(o);
|
||||
}
|
||||
bool operator==(const std::string& o) const {
|
||||
return size_ == o.size() && memcmp(str_, o.data(), size_) == 0;
|
||||
}
|
||||
bool operator!=(const std::string& o) const {
|
||||
return !operator==(o);
|
||||
}
|
||||
bool operator==(const string_span& o) const {
|
||||
return size_ == o.size_ && memcmp(str_, o.str_, size_) == 0;
|
||||
}
|
||||
bool operator!=(const string_span& o) const {
|
||||
return !operator==(o);
|
||||
}
|
||||
bool operator<(const string_span& o) const {
|
||||
const int len = size_ < o.size_ ? size_ : o.size_;
|
||||
for(int n = 0; n < len; ++n) {
|
||||
if(str_[n] != o.str_[n]) {
|
||||
if(str_[n] < o.str_[n]) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return size_ < o.size_;
|
||||
}
|
||||
|
||||
const char* begin() const { return str_; }
|
||||
const char* end() const { return str_ + size_; }
|
||||
|
||||
int size() const { return size_; }
|
||||
bool empty() const { return size_ == 0; }
|
||||
bool is_null() const { return str_ == NULL; }
|
||||
|
||||
bool to_bool(bool default_value=false) const;
|
||||
int to_int() const;
|
||||
std::string to_string() const;
|
||||
|
||||
//returns a duplicate of the string span in a new[] allocated buffer
|
||||
char* duplicate() const;
|
||||
|
||||
private:
|
||||
const char* str_;
|
||||
int size_;
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& o, const string_span& s);
|
||||
|
||||
class document;
|
||||
|
||||
class node
|
||||
{
|
||||
public:
|
||||
node(document& doc, node* parent);
|
||||
node(document& doc, node* parent, const char** str, int depth=0);
|
||||
~node();
|
||||
|
||||
typedef std::pair<string_span, string_span> attribute;
|
||||
typedef std::vector<node*> child_list;
|
||||
|
||||
const string_span& operator[](const char* key) const;
|
||||
const string_span& attr(const char* key) const {
|
||||
return (*this)[key];
|
||||
}
|
||||
|
||||
bool has_attr(const char* key) const;
|
||||
|
||||
node& set_attr(const char* key, const char* value);
|
||||
node& set_attr_dup(const char* key, const char* value);
|
||||
node& set_attr_dup(const char* key, const string_span& value);
|
||||
node& set_attr_dup_key_and_value(const char* key, const char* value);
|
||||
|
||||
node& set_attr_int(const char* key, int value);
|
||||
|
||||
node& add_child(const char* name);
|
||||
node& add_child_at(const char* name, size_t index);
|
||||
void remove_child(const char* name, size_t index);
|
||||
void remove_child(const string_span& name, size_t index);
|
||||
|
||||
node* child(const char* name);
|
||||
const node* child(const char* name) const;
|
||||
|
||||
const child_list& children(const char* name) const;
|
||||
|
||||
bool is_dirty() const { return output_cache_.is_null(); }
|
||||
|
||||
int output_size() const;
|
||||
void output(char*& buf);
|
||||
|
||||
void copy_into(node& n) const;
|
||||
|
||||
bool no_children() const { return children_.empty(); }
|
||||
bool one_child() const { return children_.size() == 1 && children_.begin()->second.size() == 1; }
|
||||
|
||||
void apply_diff(const node& diff);
|
||||
|
||||
void set_doc(document* doc);
|
||||
|
||||
int nchildren() const;
|
||||
int nattributes_recursive() const;
|
||||
|
||||
private:
|
||||
node(const node&);
|
||||
void operator=(const node&);
|
||||
|
||||
void set_dirty();
|
||||
|
||||
void shift_buffers(int offset);
|
||||
|
||||
document* doc_;
|
||||
|
||||
typedef std::vector<attribute> attribute_list;
|
||||
attribute_list attr_;
|
||||
|
||||
node* parent_;
|
||||
|
||||
typedef std::map<string_span, child_list> child_map;
|
||||
child_map children_;
|
||||
|
||||
string_span output_cache_;
|
||||
};
|
||||
|
||||
enum INIT_BUFFER_CONTROL { INIT_TAKE_OWNERSHIP };
|
||||
|
||||
enum INIT_STATE { INIT_COMPRESSED, INIT_STATIC };
|
||||
|
||||
class document
|
||||
{
|
||||
public:
|
||||
document();
|
||||
explicit document(char* buf, INIT_BUFFER_CONTROL control=INIT_TAKE_OWNERSHIP);
|
||||
document(const char* buf, INIT_STATE state);
|
||||
explicit document(string_span compressed_buf);
|
||||
~document();
|
||||
const char* dup_string(const char* str);
|
||||
node& root() { if(!root_) { generate_root(); } return *root_; }
|
||||
const node& root() const { if(!root_) { const_cast<document*>(this)->generate_root(); } return *root_; }
|
||||
|
||||
const char* output();
|
||||
string_span output_compressed();
|
||||
|
||||
void compress();
|
||||
|
||||
document* clone();
|
||||
|
||||
const string_span& operator[](const char* key) const {
|
||||
return root()[key];
|
||||
}
|
||||
|
||||
const string_span& attr(const char* key) const {
|
||||
return root()[key];
|
||||
}
|
||||
|
||||
node* child(const char* name) {
|
||||
return root().child(name);
|
||||
}
|
||||
|
||||
const node* child(const char* name) const {
|
||||
return root().child(name);
|
||||
}
|
||||
|
||||
node& set_attr(const char* key, const char* value) {
|
||||
return root().set_attr(key, value);
|
||||
}
|
||||
|
||||
node& set_attr_dup(const char* key, const char* value) {
|
||||
return root().set_attr_dup(key, value);
|
||||
}
|
||||
|
||||
void take_ownership_of_buffer(char* buffer) {
|
||||
buffers_.push_back(buffer);
|
||||
}
|
||||
|
||||
void swap(document& o);
|
||||
void clear();
|
||||
|
||||
static std::string stats();
|
||||
|
||||
private:
|
||||
void generate_root();
|
||||
document(const document&);
|
||||
void operator=(const document&);
|
||||
|
||||
string_span compressed_buf_;
|
||||
const char* output_;
|
||||
std::vector<char*> buffers_;
|
||||
node* root_;
|
||||
|
||||
//linked list of documents for accounting purposes
|
||||
void attach_list();
|
||||
void detach_list();
|
||||
document* prev_;
|
||||
document* next_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
Loading…
Add table
Reference in a new issue