refactor random seed server choices
we add a more generic server choice mechnism. The plan is to use it also for controller changes by wml.
This commit is contained in:
parent
249aa23295
commit
3b01d92547
5 changed files with 95 additions and 42 deletions
|
@ -102,7 +102,7 @@ game::game(player_map& players, const network::connection host,
|
|||
save_replays_(save_replays),
|
||||
replay_save_path_(replay_save_path),
|
||||
rng_(),
|
||||
last_synced_context_id_(-1) /* or maybe 0 ? it shouldn't matter*/
|
||||
last_choice_request_id_(-1) /* or maybe 0 ? it shouldn't matter*/
|
||||
{
|
||||
assert(owner_);
|
||||
players_.push_back(owner_);
|
||||
|
@ -1009,37 +1009,26 @@ bool game::process_turn(simple_wml::document& data, const player_map::const_iter
|
|||
|
||||
void game::require_random(const simple_wml::document &data, const player_map::iterator user)
|
||||
{
|
||||
// note, that during end turn events, it's side=1 for the server but side= side_count() on the clients.
|
||||
|
||||
// OUTDATED INFO:
|
||||
// Currently the clients make sure that one "require random seed" is sended to the server per synced context.
|
||||
// This is ensured becasue only the currently active player sends the "require random seed" the drawback is,
|
||||
// that in the situation that another client was faster, he has to wait for the current player to get
|
||||
// to the same point where the current player then sends the "require random action".
|
||||
// Then the server sends that "random_seed" to all the clients and the other client who was faster can continue.
|
||||
// This can especialy happen during 'start' events where the 'current_player' is player 1.
|
||||
// TODO: it would be better if all clients could send "require random seed" and then the server would just ignore
|
||||
// all non-first "require random seed" per synced context. The plan could be that we add a numerical
|
||||
// "synced context id" in "require random seed"
|
||||
|
||||
// NOTE:
|
||||
// With the new strategy we allow observers to cause OOS for the playing clients by sending
|
||||
// [require_random] packages based on incompatible local changes. We might want to block
|
||||
// [require_random] from observers if this is a problem.
|
||||
//Compability to older clients.
|
||||
const simple_wml::node* require_random = data.root().child("require_random");
|
||||
if(!require_random) return;
|
||||
if(require_random->has_attr("request_id"))
|
||||
{
|
||||
int context_id = (*require_random)["request_id"].to_int();
|
||||
if(context_id <= last_synced_context_id_)
|
||||
if(context_id <= last_choice_request_id_)
|
||||
{
|
||||
// We gave already a random seed for this synced context.
|
||||
return;
|
||||
}
|
||||
DBG_GAME << "answering seed request " << context_id << " by player " << user->second.name() << "(" << user->first << ")" << std::endl;
|
||||
last_synced_context_id_ = context_id;
|
||||
last_choice_request_id_ = context_id;
|
||||
}
|
||||
handle_random_choice(*require_random);
|
||||
|
||||
}
|
||||
|
||||
void game::handle_random_choice(const simple_wml::node&)
|
||||
{
|
||||
uint32_t seed = rng_.get_next_random();
|
||||
|
||||
std::stringstream stream;
|
||||
|
@ -1055,8 +1044,33 @@ void game::require_random(const simple_wml::document &data, const player_map::it
|
|||
|
||||
send_data(*mdata, 0, "game replay");
|
||||
record_data(mdata);
|
||||
}
|
||||
|
||||
void game::handle_choice(const simple_wml::node& data, const player_map::iterator user)
|
||||
{
|
||||
// note, that during end turn events, it's side=1 for the server but side= side_count() on the clients.
|
||||
|
||||
// Otherwise we allow observers to cause OOS for the playing clients by sending
|
||||
// server choice requests based on incompatible local changes. To solve this we block
|
||||
// server choice requests from observers.
|
||||
if (user->first != owner_ && !is_player(user->first)) {
|
||||
return;
|
||||
}
|
||||
int request_id = lexical_cast_default<int>(data["request_id"], -10);
|
||||
|
||||
if(request_id <= last_choice_request_id_) {
|
||||
// We gave already an anwer to this request.
|
||||
return;
|
||||
}
|
||||
DBG_GAME << "answering seed request " << request_id << " by player " << user->second.name() << "(" << user->first << ")" << std::endl;
|
||||
last_choice_request_id_ = request_id;
|
||||
|
||||
if(const simple_wml::node* rand = data.child("random_seed")) {
|
||||
handle_random_choice(*rand);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void game::process_whiteboard(simple_wml::document& data, const player_map::const_iterator user)
|
||||
{
|
||||
if(!started_ || !is_player(user->first))
|
||||
|
|
|
@ -232,7 +232,11 @@ public:
|
|||
|
||||
void require_random(const simple_wml::document &data, const player_map::iterator user);
|
||||
|
||||
void reset_last_synced_context_id() { last_synced_context_id_ = -1; }
|
||||
void handle_choice(const simple_wml::node& data, const player_map::iterator user);
|
||||
|
||||
void handle_random_choice(const simple_wml::node& data);
|
||||
|
||||
void reset_last_synced_context_id() { last_choice_request_id_ = -1; }
|
||||
/**
|
||||
* Function which returns true iff 'player' controls any of the sides spcified in 'sides'.
|
||||
*/
|
||||
|
@ -409,7 +413,7 @@ private:
|
|||
|
||||
/** A wrapper for mersenne twister rng which generates randomness for this game */
|
||||
rand_rng::mt_rng rng_;
|
||||
int last_synced_context_id_;
|
||||
int last_choice_request_id_;
|
||||
};
|
||||
|
||||
struct game_is_member {
|
||||
|
|
|
@ -2707,6 +2707,9 @@ void server::process_data_game(const network::connection sock,
|
|||
} else if (data.child("require_random")) {
|
||||
g.require_random(data,pl);
|
||||
return;
|
||||
} else if (simple_wml::node* sch = data.child("request_choice")) {
|
||||
g.handle_choice(*sch, pl);
|
||||
return;
|
||||
} else if (data.child("message")) {
|
||||
g.process_message(data, pl);
|
||||
return;
|
||||
|
|
|
@ -172,9 +172,30 @@ void synced_context::set_synced_state(synced_state newstate)
|
|||
state_ = newstate;
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
class random_server_choice : public synced_context::server_choice
|
||||
{
|
||||
public:
|
||||
/// We are in a game with no mp server and need to do this choice locally
|
||||
virtual config local_choice() const
|
||||
{
|
||||
return config_of("new_seed", seed_rng::next_seed_str());
|
||||
}
|
||||
/// the request which is sended to the mp server.
|
||||
virtual config request() const
|
||||
{
|
||||
return config();
|
||||
}
|
||||
virtual const char* name() const
|
||||
{
|
||||
return "random_seed";
|
||||
}
|
||||
};
|
||||
}
|
||||
std::string synced_context::generate_random_seed()
|
||||
{
|
||||
config retv_c = synced_context::ask_server_for_seed();
|
||||
config retv_c = synced_context::ask_server_choice(random_server_choice());
|
||||
config::attribute_value seed_val = retv_c["new_seed"];
|
||||
|
||||
return seed_val.str();
|
||||
|
@ -268,20 +289,20 @@ boost::shared_ptr<random_new::rng> synced_context::get_rng_for_action()
|
|||
}
|
||||
}
|
||||
|
||||
static void send_require_random()
|
||||
void synced_context::server_choice::send_request() const
|
||||
{
|
||||
config data;
|
||||
config& rr = data.add_child("require_random");
|
||||
rr["request_id"] = resources::controller->get_server_request_number();
|
||||
network::send_data(data,0);
|
||||
network::send_data(config_of("request_choice", config_of
|
||||
("request_id", resources::controller->get_server_request_number())
|
||||
(name(), request())
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
config synced_context::ask_server_for_seed()
|
||||
|
||||
config synced_context::ask_server_choice(const server_choice& sch)
|
||||
{
|
||||
set_is_simultaneously();
|
||||
resources::controller->increase_server_request_number();
|
||||
std::string name = "random_seed";
|
||||
assert(get_synced_state() == synced_context::SYNCED);
|
||||
const bool is_mp_game = network::nconnections() != 0;
|
||||
bool did_require = false;
|
||||
|
@ -304,9 +325,9 @@ config synced_context::ask_server_for_seed()
|
|||
/* The decision is ours, and it will be inserted
|
||||
into the replay. */
|
||||
DBG_REPLAY << "MP synchronization: local server choice\n";
|
||||
config cfg = config_of("new_seed", seed_rng::next_seed_str());
|
||||
config cfg = sch.local_choice();
|
||||
//-1 for "server" todo: change that.
|
||||
resources::recorder->user_input(name, cfg, -1);
|
||||
resources::recorder->user_input(sch.name(), cfg, -1);
|
||||
return cfg;
|
||||
|
||||
}
|
||||
|
@ -322,7 +343,7 @@ config synced_context::ask_server_for_seed()
|
|||
*/
|
||||
if(!did_require)
|
||||
{
|
||||
send_require_random();
|
||||
sch.send_request();
|
||||
did_require = true;
|
||||
}
|
||||
|
||||
|
@ -339,23 +360,23 @@ config synced_context::ask_server_for_seed()
|
|||
const config *action = resources::recorder->get_next_action();
|
||||
if (!action)
|
||||
{
|
||||
replay::process_error("[" + name + "] expected but none found\n");
|
||||
replay::process_error("[" + std::string(sch.name()) + "] expected but none found\n");
|
||||
resources::recorder->revert_action();
|
||||
return config_of("new_seed", seed_rng::next_seed_str());
|
||||
return sch.local_choice();
|
||||
}
|
||||
if (!action->has_child(name))
|
||||
if (!action->has_child(sch.name()))
|
||||
{
|
||||
replay::process_error("[" + name + "] expected but none found, found instead:\n " + action->debug() + "\n");
|
||||
replay::process_error("[" + std::string(sch.name()) + "] expected but none found, found instead:\n " + action->debug() + "\n");
|
||||
|
||||
resources::recorder->revert_action();
|
||||
return config_of("new_seed", seed_rng::next_seed_str());
|
||||
return sch.local_choice();
|
||||
}
|
||||
if((*action)["from_side"].str() != "server" || (*action)["side_invalid"].to_bool(false) )
|
||||
{
|
||||
//we can proceed without getting OOS in this case, but allowing this would allow a "player chan choose their attack results in mp" cheat
|
||||
replay::process_error("wrong from_side or side_invalid this could mean someone wants to cheat\n");
|
||||
}
|
||||
return action->child(name);
|
||||
return action->child(sch.name());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -119,11 +119,22 @@ public:
|
|||
static bool can_undo();
|
||||
static void set_last_unit_id(int id);
|
||||
static int get_unit_id_diff();
|
||||
private:
|
||||
|
||||
class server_choice
|
||||
{
|
||||
public:
|
||||
/// We are in a game with no mp server and need to do this choice locally
|
||||
virtual config local_choice() const = 0;
|
||||
/// the request which is sended to the mp server.
|
||||
virtual config request() const = 0;
|
||||
virtual const char* name() const = 0;
|
||||
void send_request() const;
|
||||
};
|
||||
/*
|
||||
generates a random seed, if we are in a mp game, ask the server, otherwise generate the seed ourselves.
|
||||
if we are in a mp game, ask the server, otherwise generate the answer ourselves.
|
||||
*/
|
||||
static config ask_server_for_seed();
|
||||
static config ask_server_choice(const server_choice&);
|
||||
private:
|
||||
/*
|
||||
weather we are in a synced move, in a user_choice, or none of them
|
||||
*/
|
||||
|
|
Loading…
Add table
Reference in a new issue