Merge pull request #141 from gfgtdf/sync_fix

fixes bugs intrduced in pr 121 and more
This commit is contained in:
gfgtdf 2014-04-11 23:53:27 +02:00
commit f6fbfb6879
18 changed files with 431 additions and 193 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

@ -1377,8 +1377,9 @@ namespace
{
}
virtual config query_user() const
virtual config query_user(int /*side*/) const
{
//the 'side' parameter might differ from side_num_-
int res = 0;
team t = (*resources::teams)[side_num_ - 1];
//i wonder how this got included here ?
@ -1423,7 +1424,7 @@ namespace
return retv;
}
virtual config random_choice() const
virtual config random_choice(int /*side*/) const
{
config retv;
retv["value"] = 0;

View file

@ -145,7 +145,7 @@ namespace { // Types
, has_text_input(ht), options(o)
{}
virtual config query_user() const
virtual config query_user(int /*side*/) const
{
std::string image = get_image(cfg, speaker);
std::string caption = get_caption(cfg, speaker);
@ -190,7 +190,7 @@ namespace { // Types
return cfg;
}
virtual config random_choice() const
virtual config random_choice(int /*side*/) const
{
return config();
}
@ -206,7 +206,7 @@ namespace { // Types
: nb_options(o), loc(l), use_dialog(d)
{}
virtual config query_user() const
virtual config query_user(int /*side*/) const
{
int selected;
if (use_dialog) {
@ -221,7 +221,7 @@ namespace { // Types
return cfg;
}
virtual config random_choice() const
virtual config random_choice(int /*side*/) const
{
config cfg;
cfg["value"] = random_new::generator->next_random() % nb_options;
@ -1111,7 +1111,7 @@ WML_HANDLER_FUNCTION(message, event_info, cfg)
{
/* Always show the dialog if it has no input, whether we are
replaying or not. */
msg.query_user();
msg.query_user(resources::controller->current_side());
}
else
{

View file

@ -27,7 +27,7 @@
#include "util.hpp"
#include "variable.hpp"
#include <cassert>
//TODO: remove LOG_PERSIST, ERR_PERSIST from persist_context.hpp to .cpp files.
#define DBG_PERSIST LOG_STREAM(debug, log_persist)
@ -41,15 +41,17 @@ struct persist_choice: mp_sync::user_choice {
, var_name(name)
, side(side_num) {
}
virtual config query_user() const {
virtual config query_user(int side_for) const {
assert(side == side_for);
config ret;
ret["side"] = side;
ret.add_child("variables",ctx.get_var(var_name));
return ret;
}
virtual config random_choice() const {
virtual config random_choice(int /*side_for*/) const {
return config();
}
virtual bool is_visible() const { return false; }
};
static void get_global_variable(persist_context &ctx, const vconfig &pcfg)

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, 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)
{
// 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, res, 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,33 +464,25 @@ 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;
network::connection from = network::null_connection;
if(data_backlog_.empty() == false) {
have_data = true;
cfg = data_backlog_.front();
data_backlog_.pop_front();
} else {
from = network::receive_data(cfg);
have_data = from != 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, from, 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
return;
}
if (result == turn_info::PROCESS_RESTART_TURN) {
player_type_changed_ = true;
return;
@ -510,7 +497,7 @@ void playmp_controller::play_network_turn(){
{
bool was_skipping = recorder.is_skipping();
recorder.set_skip(skip_replay_);
if(do_replay(current_side()))
if(do_replay(current_side()) == REPLAY_FOUND_END_TURN)
{
break;
}
@ -534,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);
}
@ -563,14 +550,19 @@ 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);
turn_data.send_data();
}
else if ((name == "ai_gamestate_changed") || (name == "ai_sync_network")){
turn_data.sync_network();
turn_info::PROCESS_DATA_RESULT res = turn_data.sync_network();
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;
}
}
else if (name == "host_transfer"){
is_host_ = 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();
}
@ -618,7 +618,7 @@ void playsingle_controller::play_turn(bool save)
if (replaying_) {
LOG_NG << "doing replay " << player_number_ << "\n";
replaying_ = ::do_replay(player_number_);
replaying_ = ::do_replay(player_number_) == REPLAY_FOUND_END_TURN;
LOG_NG << "result of replay: " << (replaying_?"true":"false") << "\n";
} else {
// If a side is dead end the turn, but play at least side=1's
@ -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,38 +38,43 @@
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();
}
turn_info::~turn_info(){
turn_info::~turn_info()
{
}
void turn_info::sync_network()
turn_info::PROCESS_DATA_RESULT turn_info::sync_network()
{
//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(network::connection res = network::receive_data(cfg)) {
std::deque<config> backlog;
process_network_data(cfg,res,backlog,false);
while( (retv == turn_info::PROCESS_CONTINUE) && network_reader_.read(cfg)) {
retv = process_network_data(cfg,false);
cfg.clear();
}
send_data();
}
return retv;
}
void turn_info::send_data()
@ -81,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_);
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()
@ -118,56 +111,69 @@ void turn_info::do_save()
}
}
turn_info::PROCESS_DATA_RESULT turn_info::process_network_data(const config& cfg,
network::connection /*from*/, 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);
@ -221,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;
@ -364,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);
@ -373,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)
@ -402,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();
void sync_network();
void send_data();
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
};
//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.
//data will be forwarded to all peers other than 'from', unless 'from' is null, in
//which case data will not be forwarded
PROCESS_DATA_RESULT process_network_data(const config& cfg,network::connection from,std::deque<config>& backlog, bool skip_replay);
PROCESS_DATA_RESULT sync_network();
void send_data();
//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

View file

@ -692,7 +692,7 @@ static void show_oos_error_error_function(const std::string& message, bool /*hea
replay::process_error(message);
}
bool do_replay(int side_num)
REPLAY_RETURN do_replay(int side_num)
{
log_scope("do replay");
@ -701,10 +701,10 @@ bool do_replay(int side_num)
}
update_locker lock_update(resources::screen->video(),get_replay_source().is_skipping());
return do_replay_handle(side_num, "");
return do_replay_handle(side_num);
}
bool do_replay_handle(int side_num, const std::string &do_untill)
REPLAY_RETURN do_replay_handle(int side_num)
{
//team &current_team = (*resources::teams)[side_num - 1];
@ -728,14 +728,7 @@ bool do_replay_handle(int side_num, const std::string &do_untill)
//if there is nothing more in the records
if(cfg == NULL) {
//replayer.set_skip(false);
return false;
}
// We return if caller wants it for this tag
if (!do_untill.empty() && cfg->child(do_untill))
{
get_replay_source().revert_action();
return false;
return REPLAY_RETURN_AT_END;
}
config::all_children_itors ch_itors = cfg->all_children_range();
@ -822,7 +815,7 @@ bool do_replay_handle(int side_num, const std::string &do_untill)
verify(*resources::units, child);
}
return true;
return REPLAY_FOUND_END_TURN;
}
else if (const config &change = cfg->child("record_change_controller"))
{
@ -888,7 +881,7 @@ bool do_replay_handle(int side_num, const std::string &do_untill)
std::string child_name = cfg->all_children_range().first->key;
DBG_REPLAY << "got an dependent action name = " << child_name <<"\n";
get_replay_source().revert_action();
return false;
return REPLAY_FOUND_DEPENDENT;
}
else
{
@ -973,7 +966,7 @@ static std::map<int, config> get_user_choice_internal(const std::string &name, c
/*
there might be speak or similar commands in the replay before the user input.
*/
do_replay_handle(current_side, name);
do_replay_handle(current_side);
/*
these value might change due to player left/reassign during pull_remote_user_input
@ -1005,7 +998,7 @@ static std::map<int, config> get_user_choice_internal(const std::string &name, c
/* At least one of the decisions is ours, and it will be inserted
into the replay. */
DBG_REPLAY << "MP synchronization: local choice\n";
config cfg = uch.query_user();
config cfg = uch.query_user(local_side);
recorder.user_input(name, cfg, local_side);
retv[local_side]= cfg;
@ -1071,10 +1064,11 @@ static std::map<int, config> get_user_choice_internal(const std::string &name, c
std::map<int,config> mp_sync::get_user_choice_multiple_sides(const std::string &name, const mp_sync::user_choice &uch,
std::set<int> sides)
{
//pass sides by copy becasue we need a copy.
bool is_synced = synced_context::get_syced_state() == synced_context::SYNCED;
//pass sides by copy because we need a copy.
const bool is_synced = synced_context::get_syced_state() == synced_context::SYNCED;
const int max_side = static_cast<int>(resources::teams->size());
//we currently don't check for too early because luas sync choice doesn't necessarily show screen dialogs.
//It (currently) in the responsibility of the user of sync choice to not use dialogs during prestart events..
if(!is_synced)
{
//we got called from inside luas wesnoth.synchronize_choice or from a select event.
@ -1083,7 +1077,7 @@ std::map<int,config> mp_sync::get_user_choice_multiple_sides(const std::string &
}
/*
for empty sides we want to use reandom choice instead.
for empty sides we want to use random choice instead.
*/
std::set<int> empty_sides;
BOOST_FOREACH(int side, sides)
@ -1104,7 +1098,7 @@ std::map<int,config> mp_sync::get_user_choice_multiple_sides(const std::string &
BOOST_FOREACH(int side, empty_sides)
{
retv[side] = uch.random_choice();
retv[side] = uch.random_choice(side);
}
return retv;
@ -1116,25 +1110,34 @@ std::map<int,config> mp_sync::get_user_choice_multiple_sides(const std::string &
config mp_sync::get_user_choice(const std::string &name, const mp_sync::user_choice &uch,
int side)
{
bool is_synced = synced_context::get_syced_state() == synced_context::SYNCED;
bool is_mp_game = network::nconnections() != 0;
bool is_side_null_controlled;
const bool is_too_early = resources::gamedata->phase() != game_data::START && resources::gamedata->phase() != game_data::PLAY;
const bool is_synced = synced_context::get_syced_state() == synced_context::SYNCED;
const bool is_mp_game = network::nconnections() != 0;//Only used in debugging output below
const int max_side = static_cast<int>(resources::teams->size());
int current_side = resources::controller->current_side();
const int current_side = resources::controller->current_side();
bool is_side_null_controlled;
if(!is_synced)
{
//we got called from inside luas wesnoth.synchronize_choice or from a select event.
//This doesn't cause problems but someone could use it for example to use a [message][option] inside a wesnoth.synchronize_choice wich could be useful,
//we got called from inside luas wesnoth.synchronize_choice or from a select event (or maybe a preload event?).
//This doesn't cause problems and someone could use it for example to use a [message][option] inside a wesnoth.synchronize_choice which could be useful,
//so just give a warning.
WRN_REPLAY << "MP synchronization called during an unsynced context.";;
return uch.query_user();
return uch.query_user(side);
}
//technicly we can use mp_sync in start/prestarte events, but the question is wether that makes sense
//because it's unclear to decide on which side the function should be executed.
//However, for advancements we can just decide on the side that owns the unit and that's in the responsibility of advance_unit_at.
if(is_too_early && uch.is_visible())
{
//We are in a prestart event or even earlier.
//Although we are able to sync them, we cannot use query_user,
//because we cannot (or shouldn't) put things on the screen inside a prestart event, this is true for SP and MP games.
//Quotation form event wiki: "For things displayed on-screen such as character dialog, use start instead"
return uch.random_choice(side);
}
//in start events it's unclear to decide on which side the function should be executed (default= side1 still).
//But for advancements we can just decide on the side that owns the unit and that's in the responsibility of advance_unit_at.
//For [message][option] and luas sync_choice the scenario designer is responsible for that.
//For [get_global_variable] side is never null.
/*
side = 0 should default to the currently active side per definition.
*/
@ -1158,7 +1161,7 @@ config mp_sync::get_user_choice(const std::string &name, const mp_sync::user_cho
if (is_side_null_controlled)
{
DBG_REPLAY << "MP synchronization: side 1 being null-controlled in get_user_choice.\n";
//most likeley we are in a start event with an empty side 1
//most likely we are in a start event with an empty side 1
//but calling [set_global_variable] to an empty side might also cause this.
//i think in that case we should better use uch.random_choice(),
//which could return something like config_of("invalid", true);

View file

@ -154,11 +154,17 @@ replay& get_replay_source();
extern replay recorder;
enum REPLAY_RETURN
{
REPLAY_RETURN_AT_END,
REPLAY_FOUND_DEPENDENT,
REPLAY_FOUND_END_TURN
};
//replays up to one turn from the recorder object
//returns true if it got to the end of the turn without data running out
bool do_replay(int side_num);
REPLAY_RETURN do_replay(int side_num);
bool do_replay_handle(int side_num, const std::string &do_untill);
REPLAY_RETURN do_replay_handle(int side_num);
class replay_network_sender
{
@ -182,8 +188,11 @@ namespace mp_sync {
struct user_choice
{
virtual ~user_choice() {}
virtual config query_user() const = 0;
virtual config random_choice() const = 0;
virtual config query_user(int side) const = 0;
virtual config random_choice(int side) const = 0;
///whether the choice is visible for the user like an advacement choice
///a non-visible choice is for example get_global_variable
virtual bool is_visible() const { return true; }
};
/**

View file

@ -21,6 +21,7 @@
#include "gettext.hpp"
#include "log.hpp"
#include "map_label.hpp"
#include "mouse_handler_base.hpp"
#include "replay.hpp"
#include "random_new_deterministic.hpp"
#include "replay_controller.hpp"
@ -444,7 +445,7 @@ void replay_controller::play_side(const unsigned int /*team_index*/, bool){
// if have reached the end we don't want to execute finish_side_turn and finish_turn
// becasue we might not have enough data to execute them (like advancements during turn_end for example)
// !has_end_turn == we reached the end of teh replay without finding and end turn tag.
has_end_turn = do_replay(player_number_);
has_end_turn = do_replay(player_number_) == REPLAY_FOUND_END_TURN;
if(!has_end_turn)
{
return;
@ -548,16 +549,13 @@ bool replay_controller::can_execute_command(const hotkey::hotkey_command& cmd, i
return true;
//commands we only can do before the end of the replay
case hotkey::HOTKEY_REPLAY_PLAY:
case hotkey::HOTKEY_REPLAY_STOP:
return !recorder.at_end();
case hotkey::HOTKEY_REPLAY_PLAY:
case hotkey::HOTKEY_REPLAY_NEXT_TURN:
case hotkey::HOTKEY_REPLAY_NEXT_SIDE:
if(recorder.at_end()) {
return false;
} else {
return true;
}
//we have one events_disabler when starting the replay_controller and a second when entering the synced context.
return (events::commands_disabled <= 1 ) && !recorder.at_end();
default:
return result;
}

View file

@ -2655,17 +2655,17 @@ namespace {
lua_State *L;
lua_synchronize(lua_State *l): L(l) {}
virtual config query_user() const
virtual config query_user(int side) const
{
config cfg;
int index = 1;
if (!lua_isnoneornil(L, 2)) {
int side = resources::controller->current_side();
if ((*resources::teams)[side - 1].is_ai())
index = 2;
}
lua_pushvalue(L, index);
if (luaW_pcall(L, 0, 1, false)) {
lua_pushnumber(L, side);
if (luaW_pcall(L, 1, 1, false)) {
if(!luaW_toconfig(L, -1, cfg) && game_config::debug) {
chat_message("Lua warning", "function returned to wesnoth.synchronize_choice a table which was partially invalid");
}
@ -2673,10 +2673,14 @@ namespace {
return cfg;
}
virtual config random_choice() const
virtual config random_choice(int /*side*/) const
{
return config();
}
//Although luas sync_choice can show a dialog, (and will in most cases)
//we return false to enable other possible things that do not contain UI things.
//it's in the responsbility of the umc dev to not show dialogs durign prestart events.
virtual bool is_visible() const { return false; }
};
}//unnamed namespace for lua_synchronize

View file

@ -221,7 +221,7 @@ config synced_context::ask_server(const std::string &name, const mp_sync::user_c
*/
while(true){
do_replay_handle(current_side, name);
do_replay_handle(current_side);
// the current_side on the server is a lie because it can happen on one client we are already executing side 2
bool is_local_side = (*resources::teams)[side-1].is_local();
bool is_replay_end = get_replay_source().at_end();
@ -231,7 +231,7 @@ config synced_context::ask_server(const std::string &name, const mp_sync::user_c
/* The decision is ours, and it will be inserted
into the replay. */
DBG_REPLAY << "MP synchronization: local server choice\n";
config cfg = uch.query_user();
config cfg = uch.query_user(-1);
//-1 for "server" todo: change that.
recorder.user_input(name, cfg, -1);
return cfg;
@ -264,7 +264,7 @@ config synced_context::ask_server(const std::string &name, const mp_sync::user_c
/* The decision has already been made, and must
be extracted from the replay. */
DBG_REPLAY << "MP synchronization: replay server choice\n";
do_replay_handle(resources::controller->current_side(), name);
do_replay_handle(resources::controller->current_side());
const config *action = get_replay_source().get_next_action();
if (!action)
{
@ -287,19 +287,19 @@ config synced_context::ask_server(const std::string &name, const mp_sync::user_c
}
set_scontext_synced::set_scontext_synced(const std::string& commandname)
: new_rng_(synced_context::get_rng_for(commandname)), new_checkup_(recorder.get_last_real_command().child_or_add("checkup"))
: new_rng_(synced_context::get_rng_for(commandname)), new_checkup_(recorder.get_last_real_command().child_or_add("checkup")), disabler_()
{
init();
}
set_scontext_synced::set_scontext_synced()
: new_rng_(synced_context::get_rng_for("")), new_checkup_(recorder.get_last_real_command().child_or_add("checkup"))
: new_rng_(synced_context::get_rng_for("")), new_checkup_(recorder.get_last_real_command().child_or_add("checkup")), disabler_()
{
init();
}
set_scontext_synced::set_scontext_synced(int number)
: new_rng_(synced_context::get_rng_for("")), new_checkup_(recorder.get_last_real_command().child_or_add("checkup" + boost::lexical_cast<std::string>(number)))
: new_rng_(synced_context::get_rng_for("")), new_checkup_(recorder.get_last_real_command().child_or_add("checkup" + boost::lexical_cast<std::string>(number))), disabler_()
{
init();
}
@ -378,7 +378,7 @@ random_seed_choice::~random_seed_choice()
}
config random_seed_choice::query_user() const
config random_seed_choice::query_user(int /*side*/) const
{
//getting here means we are in a sp game
@ -387,7 +387,7 @@ config random_seed_choice::query_user() const
retv["new_seed"] = rand();
return retv;
}
config random_seed_choice::random_choice() const
config random_seed_choice::random_choice(int /*side*/) const
{
//it obviously doesn't make sense to call the uninitialized random generator to generatoe a seed ofr the same random generator;
//this shoud never happen

View file

@ -21,6 +21,7 @@
#include "random_new.hpp"
#include "random_new_synced.hpp"
#include "generic_event.hpp"
#include "mouse_handler_base.hpp"
#include <boost/shared_ptr.hpp>
class config;
@ -150,6 +151,7 @@ private:
boost::shared_ptr<random_new::rng> new_rng_;
checkup* old_checkup_;
synced_checkup new_checkup_;
events::command_disabler disabler_;
};
/*
@ -173,8 +175,8 @@ class random_seed_choice : public mp_sync::user_choice
public:
random_seed_choice();
virtual ~random_seed_choice();
virtual config query_user() const;
virtual config random_choice() const;
virtual config query_user(int /*side*/) const;
virtual config random_choice(int /*side*/) const;
};