fix bug chat during action

when a player chats while multiple actions from another player are
executed on the local machine we before got an assertion error becasue
replay s add_command expects that there are no other commands yet to be
processed on the replay.
(this was broken at da4cdef146)
we now fix this problem by ensuring that there is never more data on the
replay than needed.

for this purpose i wrote that playturn_network_adapter.cpp whose main
purpose it to split incoming [turn]s into smaller [turn]s
we also don't need the backlog anymore because now with playturn_network_adapter we only feed process_network_data with small data pieces that can always be handled at once.

I also removed the replay_ member of turn_info.
Before, we read actions from replay_srouce and wrote actions to recorder.
And after the actions in replay_source has been executed they had been pushed into recorder.
With get_user_choice this doesn't work, because we might end up pushing the 'answer' (the user choice) of a get_user_choice in the recorder while the 'question' (the invoking user action) was in replay_source and then would be pushed onto the recorder after the 'answer', leading to a wrong order in the recorder. This could maybe also have been fixed by always pushing user choices into replay_source, and syncing the replay_source somehow after we enter a user choice, but the way it was done in this commit seemed easier.

note that replay_ and backlog were already made unused in da4cdef146 (that was a rather uncomplete commit)
We maybe (i didnt test, just a guess) could also fix this bug by using add_nonundoable_command instead of add_command for nonundoable commands in replay.cpp with add_nonundoable_command  defined as:
config& add_nonundoable_command()
{
	auto c = cfg_.add_child_at("command",config(), pos_);
	pos_++;
	return c;
}
One reason why i decided against it is, that i feared add_child_at to be slow (vector::insert) if there are a lot of entries in cfg_ (for example when we join game that has already run may turns).
With the playturn_network_adapter this shouldn't be an issue anymore, but it also isnt needed anymore.
This commit is contained in:
gfgtdf 2014-04-08 00:48:43 +02:00
parent d1cd2e0b17
commit 847067dac7
9 changed files with 324 additions and 117 deletions

View file

@ -845,6 +845,7 @@ set(wesnoth-main_SRC
playmp_controller.cpp
playsingle_controller.cpp
playturn.cpp
playturn_network_adapter.cpp
portrait.cpp
random_new.cpp
random_new_deterministic.cpp

View file

@ -478,6 +478,7 @@ wesnoth_sources = Split("""
playmp_controller.cpp
playsingle_controller.cpp
playturn.cpp
playturn_network_adapter.cpp
portrait.cpp
random_new.cpp
random_new_deterministic.cpp

View file

@ -190,11 +190,9 @@ void playmp_controller::play_human_turn(){
try {
config cfg;
const network::connection res = network::receive_data(cfg);
std::deque<config> backlog;
if(res != network::null_connection) {
if (turn_data_->process_network_data(cfg, backlog, skip_replay_) == turn_info::PROCESS_RESTART_TURN)
if(network_reader_.read(cfg)) {
if (turn_data_->process_network_data(cfg, skip_replay_) == turn_info::PROCESS_RESTART_TURN)
{
// Clean undo stack if turn has to be restarted (losing control)
if ( undo_stack_->can_undo() )
@ -274,11 +272,8 @@ void playmp_controller::play_idle_loop()
{
try {
config cfg;
const network::connection res = network::receive_data(cfg);
std::deque<config> backlog;
if(res != network::null_connection) {
if (turn_data_->process_network_data(cfg, res, backlog, skip_replay_) == turn_info::PROCESS_RESTART_TURN)
if(network_reader_.read(cfg)) {
if (turn_data_->process_network_data(cfg, skip_replay_) == turn_info::PROCESS_RESTART_TURN)
{
throw end_turn_exception(gui_->playing_side());
}
@ -400,26 +395,26 @@ void playmp_controller::wait_for_upload()
init_turn_data();
}
config cfg;
network_reader_.set_source(playturn_network_adapter::get_source_from_config(cfg));
while(true) {
try {
config cfg;
const network::connection res = dialogs::network_receive_dialog(
*gui_, _("Waiting for next scenario..."), cfg);
std::deque<config> backlog;
if(res != network::null_connection) {
if (turn_data_->process_network_data(cfg, backlog, skip_replay_)
== turn_info::PROCESS_END_LINGER) {
if (turn_data_->process_network_data_from_reader(skip_replay_) == turn_info::PROCESS_END_LINGER) {
break;
}
}
} catch(const end_level_exception&) {
network_reader_.set_source(playturn_network_adapter::read_network);
turn_data_->send_data();
throw;
}
}
network_reader_.set_source(playturn_network_adapter::read_network);
if(set_turn_data) {
delete turn_data_;
turn_data_ = NULL;
@ -469,30 +464,20 @@ void playmp_controller::play_network_turn(){
LOG_NG << "is networked...\n";
end_turn_enable(false);
turn_info turn_data(player_number_, replay_sender_);
turn_info turn_data(player_number_, replay_sender_, network_reader_);
turn_data.host_transfer().attach_handler(this);
for(;;) {
if (!network_processing_stopped_){
bool have_data = false;
config cfg;
if(data_backlog_.empty() == false) {
have_data = true;
cfg = data_backlog_.front();
data_backlog_.pop_front();
} else {
have_data = network::receive_data(cfg) != network::null_connection;
}
if(have_data) {
if(network_reader_.read(cfg)) {
if (replay_last_turn_ <= turn()){
if (skip_replay_) {
skip_replay_ = false;
}
}
const turn_info::PROCESS_DATA_RESULT result = turn_data.process_network_data(cfg, data_backlog_, skip_replay_);
const turn_info::PROCESS_DATA_RESULT result = turn_data.process_network_data(cfg, skip_replay_);
if(player_type_changed_ == true)
{
//we received a player change/quit during waiting in get_user_choice/synced_context::pull_remote_user_input
@ -536,7 +521,7 @@ void playmp_controller::play_network_turn(){
}
void playmp_controller::init_turn_data() {
turn_data_ = new turn_info(player_number_, replay_sender_);
turn_data_ = new turn_info(player_number_, replay_sender_,network_reader_);
turn_data_->host_transfer().attach_handler(this);
}
@ -565,7 +550,7 @@ void playmp_controller::process_oos(const std::string& err_msg) const {
}
void playmp_controller::handle_generic_event(const std::string& name){
turn_info turn_data(player_number_, replay_sender_);
turn_info turn_data(player_number_, replay_sender_, network_reader_);
if (name == "ai_user_interact"){
playsingle_controller::handle_generic_event(name);
@ -573,9 +558,7 @@ void playmp_controller::handle_generic_event(const std::string& name){
}
else if ((name == "ai_gamestate_changed") || (name == "ai_sync_network")){
turn_info::PROCESS_DATA_RESULT res = turn_data.sync_network();
//we should have stopped before getting end turn.
//for res == PROCESS_END_TURN a OOS error would be better i think.
assert(res == turn_info::PROCESS_CONTINUE || res == turn_info::PROCESS_RESTART_TURN);
assert(res == turn_info::PROCESS_CONTINUE || res == turn_info::PROCESS_RESTART_TURN || res == turn_info::PROCESS_FOUND_DEPENDENT);
if(res == turn_info::PROCESS_RESTART_TURN)
{
player_type_changed_ = true;

View file

@ -63,9 +63,9 @@ playsingle_controller::playsingle_controller(const config& level,
const config& game_config, CVideo& video, bool skip_replay) :
play_controller(level, state_of_game, ticks, num_turns, game_config, video, skip_replay),
cursor_setter(cursor::NORMAL),
data_backlog_(),
textbox_info_(),
replay_sender_(recorder),
network_reader_(),
end_turn_(false),
player_type_changed_(false),
replaying_(false),
@ -609,7 +609,7 @@ void playsingle_controller::play_turn(bool save)
init_side(player_number_ - 1);
} catch (end_turn_exception) {
if (current_team().is_network() == false) {
turn_info turn_data(player_number_, replay_sender_);
turn_info turn_data(player_number_, replay_sender_,network_reader_);
recorder.end_turn();
turn_data.sync_network();
}
@ -626,7 +626,7 @@ void playsingle_controller::play_turn(bool save)
if (current_team().is_human() && side_units(player_number_) == 0
&& (resources::units->size() != 0 || player_number_ != 1))
{
turn_info turn_data(player_number_, replay_sender_);
turn_info turn_data(player_number_, replay_sender_, network_reader_);
recorder.end_turn();
turn_data.sync_network();
continue;
@ -926,7 +926,7 @@ void playsingle_controller::play_ai_turn(){
synced_context::run_in_synced_context("auto_shroud", replay_helper::get_auto_shroud(true));
}
turn_info turn_data(player_number_, replay_sender_);
turn_info turn_data(player_number_, replay_sender_, network_reader_);
try {
ai::manager::play_turn(player_number_);

View file

@ -17,6 +17,7 @@
#define PLAYSINGLE_CONTROLLER_H_INCLUDED
#include "play_controller.hpp"
#include "playturn_network_adapter.hpp"
#include "replay.hpp"
class playsingle_controller : public play_controller
@ -92,10 +93,10 @@ protected:
void store_gold(bool obs = false);
const cursor::setter cursor_setter;
std::deque<config> data_backlog_;
gui::floating_textbox textbox_info_;
replay_network_sender replay_sender_;
playturn_network_adapter network_reader_;
bool end_turn_;
bool player_type_changed_;

View file

@ -38,14 +38,17 @@
static lg::log_domain log_network("network");
#define ERR_NW LOG_STREAM(err, log_network)
turn_info::turn_info(unsigned team_num, replay_network_sender &replay_sender) :
turn_info::turn_info(unsigned team_num, replay_network_sender &replay_sender,playturn_network_adapter &network_reader) :
team_num_(team_num),
replay_sender_(replay_sender),
host_transfer_("host_transfer"),
replay_()
network_reader_(network_reader)
{
/**
* We do network sync so [init_side] is transferred to network hosts
* TODO: i think it is unintiutive that creating this object automatictly sends data over the network.
* For example it means that playmp_controller::handle_generic_event("ai_user_interact") casues send_data,
* Idk whether that is intended, but an explicit call would be better.
*/
if(network::nconnections() > 0)
send_data();
@ -57,19 +60,18 @@ turn_info::~turn_info()
turn_info::PROCESS_DATA_RESULT turn_info::sync_network()
{
turn_info::PROCESS_DATA_RESULT retv = turn_info::PROCESS_CONTINUE;
//there should be nothing left on the replay and we should get turn_info::PROCESS_CONTINUE back.
turn_info::PROCESS_DATA_RESULT retv = replay_to_process_data_result(do_replay(team_num_));
if(network::nconnections() > 0) {
//receive data first, and then send data. When we sent the end of
//the AI's turn, we don't want there to be any chance where we
//could get data back pertaining to the next turn.
config cfg;
while( (retv == turn_info::PROCESS_CONTINUE) && (network::receive_data(cfg) != network::null_connection)) {
std::deque<config> backlog;
retv = process_network_data(cfg,backlog,false);
while( (retv == turn_info::PROCESS_CONTINUE) && network_reader_.read(cfg)) {
retv = process_network_data(cfg,false);
cfg.clear();
}
send_data();
}
return retv;
@ -84,33 +86,21 @@ void turn_info::send_data()
}
}
void turn_info::handle_turn(
bool& turn_end,
turn_info::PROCESS_DATA_RESULT turn_info::handle_turn(
const config& t,
const bool skip_replay,
std::deque<config>& backlog)
const bool skip_replay)
{
if(turn_end == false) {
/** @todo FIXME: Check what commands we execute when it's our turn! */
//TODO: can we remove replay_ ? i think yes.
replay_.append(t);
replay_.set_skip(skip_replay);
//t can contain a [command] or a [upload_log]
assert(t.all_children_count() == 1);
/** @todo FIXME: Check what commands we execute when it's our turn! */
bool was_skipping = recorder.is_skipping();
recorder.set_skip(skip_replay);
//turn_end = do_replay(team_num_, &replay_);
//note, that this cunfion might call itself recursively: do_replay -> ... -> persist_var -> ... -> handle_generic_event -> sync_network -> handle_turn
recorder.add_config(t, replay::MARK_AS_SENT);
turn_end = do_replay(team_num_) == REPLAY_FOUND_END_TURN;
recorder.set_skip(was_skipping);
} else {
//this turn has finished, so push the remaining moves
//into the backlog
backlog.push_back(config());
backlog.back().add_child("turn", t);
}
bool was_skipping = recorder.is_skipping();
recorder.set_skip(skip_replay);
//note, that this function might call itself recursively: do_replay -> ... -> persist_var -> ... -> handle_generic_event -> sync_network -> handle_turn
recorder.add_config(t, replay::MARK_AS_SENT);
PROCESS_DATA_RESULT retv = replay_to_process_data_result(do_replay(team_num_));
recorder.set_skip(was_skipping);
return retv;
}
void turn_info::do_save()
@ -121,56 +111,69 @@ void turn_info::do_save()
}
}
turn_info::PROCESS_DATA_RESULT turn_info::process_network_data(const config& cfg,
std::deque<config>& backlog, bool skip_replay)
turn_info::PROCESS_DATA_RESULT turn_info::process_network_data_from_reader(bool skip_replay)
{
config cfg;
while(this->network_reader_.read(cfg))
{
PROCESS_DATA_RESULT res = process_network_data(cfg, skip_replay);
if(res != PROCESS_CONTINUE)
{
return res;
}
}
return PROCESS_CONTINUE;
}
turn_info::PROCESS_DATA_RESULT turn_info::process_network_data(const config& cfg, bool skip_replay)
{
// we cannot be connected to multiple peers anymore because
// the simple wesnothserver implementation in wesnoth was removed years ago.
assert(network::nconnections() <= 1);
assert(cfg.all_children_count() == 1);
assert(cfg.attribute_range().first == cfg.attribute_range().second);
if(!recorder.at_end())
{
ERR_NW << "processing network data while still having data on the replay.\n";
}
if (const config &msg = cfg.child("message"))
{
resources::screen->add_chat_message(time(NULL), msg["sender"], msg["side"],
msg["message"], events::chat_handler::MESSAGE_PUBLIC,
preferences::message_bell());
}
if (const config &msg = cfg.child("whisper") /*&& is_observer()*/)
else if (const config &msg = cfg.child("whisper") /*&& is_observer()*/)
{
resources::screen->add_chat_message(time(NULL), "whisper: " + msg["sender"].str(), 0,
msg["message"], events::chat_handler::MESSAGE_PRIVATE,
preferences::message_bell());
}
BOOST_FOREACH(const config &ob, cfg.child_range("observer")) {
else if (const config &ob = cfg.child("observer") )
{
resources::screen->add_observer(ob["name"]);
}
BOOST_FOREACH(const config &ob, cfg.child_range("observer_quit")) {
else if (const config &ob = cfg.child("observer_quit"))
{
resources::screen->remove_observer(ob["name"]);
}
if (cfg.child("leave_game")) {
else if (cfg.child("leave_game")) {
throw network::error("");
}
bool turn_end = false;
config::const_child_itors turns = cfg.child_range("turn");
const config& change = cfg.child_or_empty("change_controller");
const std::string& side_drop = cfg["side_drop"].str();
BOOST_FOREACH(const config &t, turns)
else if (const config &turn = cfg.child("turn"))
{
recorder.add_config(t, replay::MARK_AS_SENT);
return handle_turn(turn, skip_replay);
}
handle_turn(turn_end, config(), skip_replay, backlog);
resources::whiteboard->process_network_data(cfg);
if (!change.empty())
else if (cfg.has_child("whiteboard"))
{
resources::whiteboard->process_network_data(cfg);
}
else if (const config &change = cfg.child("change_controller"))
{
if(change.empty())
{
return PROCESS_CONTINUE;
}
//don't use lexical_cast_default it's "safer" to end on error
const int side = lexical_cast<int>(change["side"]);
const size_t index = static_cast<size_t>(side-1);
@ -224,10 +227,12 @@ turn_info::PROCESS_DATA_RESULT turn_info::process_network_data(const config& cfg
return restart ? PROCESS_RESTART_TURN : PROCESS_CONTINUE;
}
}
//if a side has dropped out of the game.
if(!side_drop.empty()) {
const std::string controller = cfg["controller"];
else if (const config &side_drop_c = cfg.child("side_drop"))
{
const std::string& side_drop = side_drop_c["side_num"].str();
const std::string controller = side_drop_c["controller"];
//if a side has dropped out of the game.
int side = atoi(side_drop.c_str());
const size_t side_index = side-1;
@ -367,7 +372,7 @@ turn_info::PROCESS_DATA_RESULT turn_info::process_network_data(const config& cfg
// The host has ended linger mode in a campaign -> enable the "End scenario" button
// and tell we did get the notification.
if (cfg.child("notify_next_scenario")) {
else if (cfg.child("notify_next_scenario")) {
gui::button* btn_end = resources::screen->find_action_button("button-endturn");
if(btn_end) {
btn_end->enable(true);
@ -376,13 +381,17 @@ turn_info::PROCESS_DATA_RESULT turn_info::process_network_data(const config& cfg
}
//If this client becomes the new host, notify the play_controller object about it
if (const config &cfg_host_transfer = cfg.child("host_transfer")){
else if (const config &cfg_host_transfer = cfg.child("host_transfer")){
if (cfg_host_transfer["value"] == "1") {
host_transfer_.notify_observers();
}
}
else
{
ERR_NW << "found unknown command:\n" << cfg.debug() << "\n";
}
return turn_end ? PROCESS_END_TURN : PROCESS_CONTINUE;
return PROCESS_CONTINUE;
}
void turn_info::change_controller(const std::string& side, const std::string& controller)
@ -405,6 +414,22 @@ void turn_info::change_side_controller(const std::string& side, const std::strin
network::send_data(cfg, 0);
}
turn_info::PROCESS_DATA_RESULT turn_info::replay_to_process_data_result(REPLAY_RETURN replayreturn)
{
switch(replayreturn)
{
case REPLAY_RETURN_AT_END:
return PROCESS_CONTINUE;
case REPLAY_FOUND_DEPENDENT:
return PROCESS_FOUND_DEPENDENT;
case REPLAY_FOUND_END_TURN:
return PROCESS_END_TURN;
default:
assert(false);
throw "found invalid REPLAY_RETURN";
}
}
#if 0
void turn_info::take_side(const std::string& side, const std::string& controller)
{

View file

@ -20,46 +20,47 @@ class replay_network_sender;
#include "generic_event.hpp"
#include "network.hpp"
#include "playturn_network_adapter.hpp"
#include "replay.hpp"
class turn_info
{
public:
turn_info(unsigned team_num, replay_network_sender &network_sender);
turn_info(unsigned team_num, replay_network_sender &network_sender, playturn_network_adapter &network_reader);
~turn_info();
enum PROCESS_DATA_RESULT {
enum PROCESS_DATA_RESULT
{
PROCESS_CONTINUE,
PROCESS_RESTART_TURN,
PROCESS_END_TURN,
/** When the host uploaded the next scenario this is returned. */
PROCESS_END_LINGER
};
PROCESS_END_LINGER,
/** When we couldn't process the network data because we found a dependent command, this should only happen if we were called playmp_controller::from handle_generic_event -> sync_network*/
PROCESS_FOUND_DEPENDENT
};
PROCESS_DATA_RESULT sync_network();
void send_data();
//function which will process incoming network data, and act on it. If there is
//more data than a single turn's worth, excess data will be placed into 'backlog'.
//No more than one turn's worth of data will be placed into a single backlog item,
//so it is safe to assume that backlog won't be touched if cfg is a member of a previous
//backlog.
PROCESS_DATA_RESULT process_network_data(const config& cfg,std::deque<config>& backlog, bool skip_replay);
//function which will process incoming network data received with playturn_network_adapter, and act on it.
PROCESS_DATA_RESULT process_network_data(const config& cfg, bool skip_replay);
//reads as much data from network_reader_ as possible and processed it.
PROCESS_DATA_RESULT process_network_data_from_reader(bool skip_replay);
events::generic_event& host_transfer() { return host_transfer_; }
private:
static void change_controller(const std::string& side, const std::string& controller);
static void change_side_controller(const std::string& side, const std::string& player);
void handle_turn(
bool& turn_end,
static PROCESS_DATA_RESULT replay_to_process_data_result(REPLAY_RETURN replayreturn);
PROCESS_DATA_RESULT handle_turn(
const config& t,
const bool skip_replay,
std::deque<config>& backlog);
const bool skip_replay);
void do_save();
@ -69,7 +70,7 @@ private:
events::generic_event host_transfer_;
replay replay_;
playturn_network_adapter& network_reader_;
};
#endif

View file

@ -0,0 +1,149 @@
#include "playturn_network_adapter.hpp"
#include "network.hpp"
#include "config_assign.hpp"
#include "log.hpp"
#include <boost/assign.hpp>
#include <boost/bind.hpp>
#include <iostream>
#include <cassert>
static lg::log_domain log_network("network");
#define ERR_NW LOG_STREAM(err, log_network)
void playturn_network_adapter::read_from_network()
{
assert(data_.size() > 0);
this->data_.push_back(config());
config& back = data_.back();
bool has_data = this->network_reader_(back);
//ping is handeled by network.cpp and we can ignore it.
back.remove_attribute("ping");
if((!has_data) || back.empty())
{
this->data_.pop_back();
return;
}
assert(!data_.back().empty());
if(back.has_attribute("side_drop"))
{
config child;
child["side_num"] = back["side_drop"];
child["controller"] = back["controller"];
this->data_.push_back(config_of("side_drop", child));
back.remove_attribute("side_drop");
back.remove_attribute("controller");
}
assert(!data_.back().empty());
//there should be no attributes left.
if(back.attribute_range().first != back.attribute_range().second )
{
ERR_NW << "found unexpected attribute:" <<back.debug() << "\n";
}
}
bool playturn_network_adapter::is_at_end()
{
assert(data_.size() > 0);
return this->next_ == data_.back().ordered_end();
}
bool playturn_network_adapter::read(config& dst)
{
assert(dst.empty());
if(is_at_end())
{
read_from_network();
}
if(is_at_end())
{
//that means we couldn't read anything from the network.
return false;
}
//skip empty data.
while(next_ == data_.begin()->ordered_end())
{
data_.pop_front();
next_ = data_.front().ordered_begin();
assert(!is_at_end());
}
config& child = dst.add_child(next_->key);
//TODO: implement a non const version of ordered children
config& child_old = const_cast<config&>(next_->cfg);
if(next_->key == "turn")
{
//split [turn] indo different [turn] for each child.
assert(next_->cfg.all_children_count() > next_command_num_);
config::all_children_iterator itor = child_old.ordered_begin();
//TODO: implement operator + (all_children_iterator, int ) properly
for(unsigned int i = 0; i < next_command_num_; i++) {itor++;}
//TODO: implement a non const version of ordered children
config& childchild_old = const_cast<config&>(itor->cfg);
config& childchild = child.add_child(itor->key);
childchild.swap(childchild_old);
next_command_num_++;
if(next_->cfg.all_children_count() == next_command_num_)
{
next_command_num_ = 0;
next_++;
}
return true;
}
else
{
child.swap(child_old);
next_++;
return true;
}
}
playturn_network_adapter::playturn_network_adapter(source_type source)
: data_(boost::assign::list_of(config()).convert_to_container<std::list<config> >()),
next_(data_.front().ordered_end()),
next_command_num_(0),
network_reader_(source)
{
}
playturn_network_adapter::~playturn_network_adapter()
{
if(!is_at_end())
{
ERR_NW << "Destroing playturn_network_adapter with an non empty buffer, this means loss of network data\n";
}
}
void playturn_network_adapter::set_source(source_type source)
{
network_reader_ = source;
}
static bool read_config(config& src, config& dst)
{
assert(dst.empty());
if(!src.empty())
{
src.swap(dst);
return true;
}
else
{
return false;
}
}
playturn_network_adapter::source_type playturn_network_adapter::get_source_from_config(config& cfg)
{
return boost::bind(read_config, cfg, _1);
}
bool playturn_network_adapter::read_network(config& cfg)
{
return network::receive_data(cfg) != network::null_connection;
}

View file

@ -0,0 +1,46 @@
#ifndef PLAYTURN_NETWORK_ADAPTER_HPP_INCLUDED
#define PLAYTURN_NETWORK_ADAPTER_HPP_INCLUDED
#include "config.hpp"
#include <list>
#include <boost/function.hpp>
/*
The purpose if this class is to preprocess incoming network data, and provide a steam that always returns just one command/action at a time.
Especialy we want each replay command in his own [turn].
*/
class playturn_network_adapter
{
public:
typedef boost::function1<bool, config&> source_type;
playturn_network_adapter(source_type source = read_network);
~playturn_network_adapter();
//returns true on success.
//dst has to be empty befor the call.
//after the call dst contains one child chen returned true otherise it's empty.
bool read(config& dst);
//returns false if there is still data in the internal buffer.
bool is_at_end();
void set_source(source_type source);
//returns a function to be passed to set_source.
static source_type get_source_from_config(config& src);
//a function to be passed to set_source.
static bool read_network(config& dst);
private:
//reads data from the network stream.
void read_from_network();
//this always contains one empty config becasue we want a vaid value for next_.
std::list<config> data_;
//the position of the next to be received element in data_->front().
config::all_children_iterator next_;
//if we are processing a [turn] with mutiple [command] we want to split them.
//In this case next_command_num_ is the next to be processed turn into a command otherwise it's 0;
unsigned int next_command_num_;
//a function to receive data from the network.
source_type network_reader_;
};
#endif