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:
Gunter Labes 2008-03-19 04:11:40 +00:00
parent 84f575bd88
commit afbb4bcebb
16 changed files with 2232 additions and 602 deletions

View file

@ -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

View file

@ -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);

View file

@ -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();

View file

@ -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.

View file

@ -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";

View file

@ -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);

View file

@ -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

View file

@ -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_;

View file

@ -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() {

View file

@ -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_;

View file

@ -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);
}
}

View file

@ -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
View 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
View 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