refactor get_user_choice

this prepares a patch to implement wait dialogs for user choices during
prestart events.
This commit is contained in:
gfgtdf 2015-09-20 19:47:44 +00:00
parent 3c11aa63f8
commit de3db53447
3 changed files with 221 additions and 149 deletions

View file

@ -260,15 +260,7 @@ namespace
void synced_context::pull_remote_user_input()
{
//code copied form persist_var, feels strange to call ai::.. functions for something where the ai isn't involved....
//note that ai::manager::raise_sync_network isn't called by the ai at all anymore (one more reason to put it somehwere else)
try{
if(resources::gamedata->phase() == game_data::PLAY || resources::gamedata->phase() == game_data::START)
{
//during the prestart/preload event the screen is locked and we shouldn't call user_interact.
//because that might result in crashs if someone clicks anywhere during screenlock.
ai::manager::raise_user_interact();
}
syncmp_registry::pull_remote_choice();
}
catch(network::error& err)

View file

@ -151,144 +151,6 @@ namespace
};
}
static std::map<int, config> get_user_choice_internal(const std::string &name, const mp_sync::user_choice &uch, const std::set<int>& sides)
{
const int max_side = static_cast<int>(resources::teams->size());
BOOST_FOREACH(int side, sides)
{
//the caller has to ensure this.
assert(1 <= side && side <= max_side);
assert(!(*resources::teams)[side-1].is_empty());
}
//this should never change during the execution of this function.
const int current_side = resources::controller->current_side();
const bool is_mp_game = network::nconnections() != 0;
// whether sides contains a side that is not the currently active side.
const bool contains_other_side = !sides.empty() && (sides.size() != 1 || sides.find(current_side) == sides.end());
if(contains_other_side)
{
synced_context::set_is_simultaneously();
}
std::map<int,config> retv;
notifer_ptr notifer;
/*
when we got all our answers we stop.
*/
while(retv.size() != sides.size())
{
/*
there might be speak or similar commands in the replay before the user input.
*/
do_replay_handle();
/*
these value might change due to player left/reassign during pull_remote_user_input
*/
//equals to any side in sides that is local, 0 if no such side exists.
int local_side = 0;
//if for any side from which we need an answer
std::string message;
BOOST_FOREACH(int side, sides)
{
//and we havent already received our answer from that side
if(retv.find(side) == retv.end())
{
message += " ";
message += lexical_cast<std::string>(side);
//and it is local
if((*resources::teams)[side-1].is_local() && !(*resources::teams)[side-1].is_idle())
{
//then we have to make a local choice.
local_side = side;
break;
}
}
}
message = "waiting for " + uch.description() + " from side(s)" + message;
bool has_local_side = local_side != 0;
bool is_replay_end = resources::recorder->at_end();
if (is_replay_end && has_local_side)
{
notifer.deactivate();
leave_synced_context sync;
/* 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(local_side);
resources::recorder->user_input(name, cfg, local_side);
retv[local_side]= cfg;
//send data to others.
//but if there wasn't any data sended during this turn, we don't want to bein wth that now.
//TODO: we should send user choices during nonundoable actions immideatley.
if(synced_context::is_simultaneously() || current_side != local_side)
{
synced_context::send_user_choice();
}
continue;
}
else if(is_replay_end && !has_local_side)
{
//we are in a mp game, and the data has not been recieved yet.
DBG_REPLAY << "MP synchronization: waiting for remote choice\n";
assert(is_mp_game);
synced_context::pull_remote_user_input();
notifer.activate();
notifer.update(message);
SDL_Delay(10);
continue;
}
else if(!is_replay_end)
{
DBG_REPLAY << "MP synchronization: extracting choice from replay with has_local_side=" << has_local_side << "\n";
const config *action = resources::recorder->get_next_action();
assert(action); //action cannot be null because resources::recorder->at_end() returned false.
if( !action->has_child(name) || !(*action)["dependent"].to_bool())
{
replay::process_error("[" + name + "] expected but none found\n. found instead:\n" + action->debug());
//We save this action for later
resources::recorder->revert_action();
//and let the user try to get the intended result.
BOOST_FOREACH(int side, sides)
{
if(retv.find(side) == retv.end())
{
retv[side] = uch.query_user(side);
}
}
return retv;
}
int from_side = (*action)["from_side"].to_int(0);
if ((*action)["side_invalid"].to_bool(false) == true)
{
//since this 'cheat' can have a quite heavy effect especialy in umc content we give an oos error .
replay::process_error("MP synchronization: side_invalid in replay data, this could mean someone wants to cheat.\n");
}
if (sides.find(from_side) == sides.end())
{
replay::process_error("MP synchronization: we got an answer from side " + boost::lexical_cast<std::string>(from_side) + "for [" + name + "] which is not was we expected\n");
continue;
}
if(retv.find(from_side) != retv.end())
{
replay::process_error("MP synchronization: we got already our answer from side " + boost::lexical_cast<std::string>(from_side) + "for [" + name + "] now we have it twice.\n");
}
retv[from_side] = action->child(name);
continue;
}
}//while
return retv;
}
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)
{
@ -322,7 +184,7 @@ std::map<int,config> mp_sync::get_user_choice_multiple_sides(const std::string &
sides.erase(side);
}
std::map<int,config> retv = get_user_choice_internal(name, uch, sides);
std::map<int,config> retv = user_choice_manager::get_user_choice_internal(name, uch, sides);
BOOST_FOREACH(int side, empty_sides)
{
@ -403,7 +265,7 @@ config mp_sync::get_user_choice(const std::string &name, const mp_sync::user_cho
std::set<int> sides;
sides.insert(side);
std::map<int, config> retv = get_user_choice_internal(name, uch, sides);
std::map<int, config> retv = user_choice_manager::get_user_choice_internal(name, uch, sides);
if(retv.find(side) == retv.end())
{
//An error occured, get_user_choice_internal should have given an oos error message
@ -411,3 +273,181 @@ config mp_sync::get_user_choice(const std::string &name, const mp_sync::user_cho
}
return retv[side];
}
user_choice_manager::user_choice_manager(const std::string &name, const mp_sync::user_choice &uch, std::set<int> sides)
: required_(sides)
, res_()
, local_choice_(0)
, wait_message_()
, oos_(false)
, uch_(uch)
, tagname_(name)
, current_side_(resources::controller->current_side())
, changed_event_("user_choice_update")
{
update_local_choice();
}
void user_choice_manager::pull()
{
assert(waiting());
// there might be speak or similar commands in the replay before the user input.
do_replay_handle();
synced_context::pull_remote_user_input();
do_replay_handle();
update_local_choice();
bool is_replay_end = resources::recorder->at_end();
if(!resources::recorder->at_end())
{
changed_event_.notify_observers();
DBG_REPLAY << "MP synchronization: extracting choice from replay with has_local_side=" << has_local_choice() << "\n";
const config *action = resources::recorder->get_next_action();
assert(action); //action cannot be null because resources::recorder->at_end() returned false.
if( !action->has_child(tagname_) || !(*action)["dependent"].to_bool())
{
replay::process_error("[" + tagname_ + "] expected but none found\n. found instead:\n" + action->debug());
//We save this action for later
resources::recorder->revert_action();
// execute this local choice locally
oos_ = true;
}
int from_side = (*action)["from_side"].to_int(0);
if((*action)["side_invalid"].to_bool(false) == true)
{
//since this 'cheat' can have a quite heavy effect especialy in umc content we give an oos error .
replay::process_error("MP synchronization: side_invalid in replay data, this could mean someone wants to cheat.\n");
}
if(required_.find(from_side) == required_.end())
{
replay::process_error("MP synchronization: we got an answer from side " + boost::lexical_cast<std::string>(from_side) + "for [" + tagname_ + "] which is not was we expected\n");
}
if(res_.find(from_side) != res_.end())
{
replay::process_error("MP synchronization: we got already our answer from side " + boost::lexical_cast<std::string>(from_side) + "for [" + tagname_ + "] now we have it twice.\n");
}
res_[from_side] = action->child(tagname_);
}
}
void user_choice_manager::update_local_choice()
{
int local_choice_prev = local_choice_;
//equals to any side in sides that is local, 0 if no such side exists.
local_choice_ = 0;
//if for any side from which we need an answer
wait_message_ = "";
BOOST_FOREACH(int side, required_)
{
//and we havent already received our answer from that side
if(res_.find(side) == res_.end())
{
wait_message_ += " ";
wait_message_ += lexical_cast<std::string>(side);
//and it is local
if((*resources::teams)[side-1].is_local() && !(*resources::teams)[side-1].is_idle())
{
//then we have to make a local choice.
local_choice_ = side;
break;
}
}
}
wait_message_ = "waiting for " + uch_.description() + " from side(s)" + wait_message_;
if(local_choice_prev != local_choice_) {
changed_event_.notify_observers();
}
}
void user_choice_manager::ask_local_choice()
{
assert(local_choice_ != 0);
leave_synced_context sync;
/* 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(local_choice_);
if(res_.find(local_choice_) != res_.end()) {
// It might be possible that we this choice was already made by another client while we were in uch_.query_user
// becase our side might be ressigned while we made our choice.
WRN_REPLAY << "Discarding a local choice becasue we found it already on the replay";
return;
}
resources::recorder->user_input(tagname_, cfg, local_choice_);
res_[local_choice_] = cfg;
//send data to others.
//but if there wasn't any data sended during this turn, we don't want to bein wth that now.
//TODO: we should send user choices during nonundoable actions immideatley.
if(synced_context::is_simultaneously() || current_side_ != local_choice_)
{
synced_context::send_user_choice();
}
update_local_choice();
}
void user_choice_manager::fix_oos()
{
assert(oos_);
ERR_REPLAY << "A sync error appeared while waiting for a synced user choice of type '" << uch_.description() << "' ([" + tagname_ + "]), doing the choice locally\n";
BOOST_FOREACH(int side, required_)
{
if(res_.find(side) == res_.end())
{
ERR_REPLAY << "Doing a local choice for side " << side << "\n";
res_[side] = uch_.query_user(side);
}
}
oos_ = false;
}
static void wait_ingame(user_choice_manager& man)
{
notifer_ptr notifer;
notifer.activate();
while(!man.finished() && man.waiting())
{
if(resources::gamedata->phase() == game_data::PLAY || resources::gamedata->phase() == game_data::START)
{
//during the prestart/preload event the screen is locked and we shouldn't call user_interact.
//because that might result in crashs if someone clicks anywhere during screenlock.
// calls man.pull via events.cpp -> pump_monitor::process
resources::controller->play_slice();
}
notifer.update(man.wait_message());
}
}
std::map<int, config> user_choice_manager::get_user_choice_internal(const std::string &name, const mp_sync::user_choice &uch, const std::set<int>& sides)
{
user_choice_manager man(name, uch, sides);
while(!man.finished())
{
if(man.waiting())
{
wait_ingame(man);
}
else if(man.has_local_choice())
{
man.ask_local_choice();
}
else
{
man.fix_oos();
}
}
return man.res_;
}
void user_choice_manager::process(events::pump_info&)
{
if(waiting())
{
pull();
}
}

View file

@ -15,6 +15,8 @@
#include "global.hpp"
#include "config.hpp"
#include "events.hpp"
#include "generic_event.hpp"
#include <map>
#include <set>
@ -68,4 +70,42 @@ config get_user_choice(const std::string &name, const user_choice &uch,
std::map<int, config> get_user_choice_multiple_sides(const std::string &name, const user_choice &uch,
std::set<int> sides);
}
}
class user_choice_manager : events::pump_monitor
{
// The sides which should execute this local choice
std::set<int> required_;
// The results
std::map<int, config> res_;
// The side for which we shoudl do a choice locally (0 if no suhc side exists)
// Note that even if there is currently no localy choice to do it is still possible that we need to do a local choice later becasue we took control over a side
int local_choice_;
// the message displayed for sides which currently don't have to do a choice.
std::string wait_message_;
// If we failed to read the remote choices this flag is when which indicated that we shoudl do all choices locally
bool oos_;
const mp_sync::user_choice& uch_;
const std::string& tagname_;
const int current_side_;
// private constructor
user_choice_manager(const std::string &name, const mp_sync::user_choice &uch, std::set<int> sides);
~user_choice_manager() {}
public:
void pull();
bool finished()
{ return required_.size() == res_.size(); }
bool has_local_choice()
{ return local_choice_ != 0; }
bool waiting()
{ return local_choice_ == 0 && !oos_; }
void update_local_choice();
void ask_local_choice();
void fix_oos();
const std::string& wait_message() { return wait_message_; }
static std::map<int, config> get_user_choice_internal(const std::string &name, const mp_sync::user_choice &uch, const std::set<int>& sides);
void process(events::pump_info&);
events::generic_event changed_event_;
};