Use an independent ID for the database's GAME_ID column.

Currently, wesnothd uses the game instance's `id_` value when inserting into the game_info, game_player_info, and game_modification_info tables.  However, this value is unique only per game instance, not per scenario.  This then results in problems when handling MP campaigns, which re-use the same game instance for multiple scenarios.  Incrementing the game instance's `id_` was determined to not be suitable since it's used in a boost multi-index, and instead creating a brand new game instance for each scenario in an MP campaign is complex without being especially useful beyond this case.

This commit instead adds a separate `db_id_` value to each game instance, which can be incremented without causing any issues since it's used nowhere else, and is what will be inserted into the database.  This means the `db_id_` is unique per scenario, and therefore resolves the below two issues.

Fixes #4281
Fixes #4341

Backport of 1a46bea027
This commit is contained in:
Pentarctagon 2020-02-26 21:18:14 -06:00
parent 0cc2bf4ca3
commit 54dd488d57
No known key found for this signature in database
GPG key ID: 29E48D667D52CCF3
3 changed files with 29 additions and 6 deletions

View file

@ -84,6 +84,7 @@ void send_to_players(simple_wml::document& data, const Container& players, socke
}
int game::id_num = 1;
int game::db_id_num = 1;
void game::missing_user(socket_ptr /*socket*/, const std::string& func) const
{
@ -98,6 +99,7 @@ game::game(player_connections& player_connections,
const std::string& replay_save_path)
: player_connections_(player_connections)
, id_(id_num++)
, db_id_(db_id_num++)
, name_(name)
, password_()
, owner_(host)
@ -1835,7 +1837,7 @@ static bool is_invalid_filename_char(char c)
std::string game::get_replay_filename()
{
std::stringstream name;
name << (*starting_pos(level_.root()))["name"] << " Turn " << current_turn() << " (" << id_ << ").bz2";
name << (*starting_pos(level_.root()))["name"] << " Turn " << current_turn() << " (" << db_id_ << ").bz2";
std::string filename(name.str());
std::replace(filename.begin(), filename.end(), ' ', '_');
filename.erase(std::remove_if(filename.begin(), filename.end(), is_invalid_filename_char), filename.end());

View file

@ -56,6 +56,16 @@ public:
return id_;
}
int db_id() const
{
return db_id_;
}
void next_db_id()
{
db_id_ = db_id_num++;
}
const std::string& name() const
{
return name_;
@ -458,9 +468,16 @@ private:
player_connections& player_connections_;
// used for unique identification of game instances within wesnothd
static int id_num;
int id_;
// used for unique identification of games played in the database
// necessary since for MP campaigns multiple scenarios can be played within the same game instance
// and we need a unique ID per scenario played, not per game instance
static int db_id_num;
int db_id_;
/** The name of the game. */
std::string name_;
std::string password_;

View file

@ -1428,7 +1428,7 @@ void server::cleanup_game(game* game_ptr)
metrics_.game_terminated(game_ptr->termination_reason());
if(user_handler_){
user_handler_->db_update_game_end(uuid_, game_ptr->id(), game_ptr->get_replay_filename());
user_handler_->db_update_game_end(uuid_, game_ptr->db_id(), game_ptr->get_replay_filename());
}
simple_wml::node* const gamelist = games_and_users_list_.child("gamelist");
@ -1671,6 +1671,9 @@ void server::handle_player_in_game(socket_ptr socket, std::shared_ptr<simple_wml
}
g.save_replay();
if(user_handler_){
user_handler_->db_update_game_end(uuid_, g.db_id(), g.get_replay_filename());
}
g.new_scenario(socket);
g.reset_last_synced_context_id();
@ -1678,6 +1681,7 @@ void server::handle_player_in_game(socket_ptr socket, std::shared_ptr<simple_wml
// Record the full scenario in g.level()
g.level().clear();
scenario->copy_into(g.level().root());
g.next_db_id();
if(g.description() == nullptr) {
ERR_SERVER << client_address(socket) << "\tERROR: \"" << g.name() << "\" (" << g.id()
@ -1748,7 +1752,7 @@ void server::handle_player_in_game(socket_ptr socket, std::shared_ptr<simple_wml
// 1.14.9 and earlier also use whether observers are allowed to determine if the replay should be public
// 1.14.10+ have a separate attribute for that
bool is_public = m["private_replay"].to_string() == "" ? m["observer"].to_bool() : !m["private_replay"].to_bool();
user_handler_->db_insert_game_info(uuid_, g.id(), game_config::wesnoth_version.str(), g.name(), m["mp_scenario"].to_string(), m["mp_era"].to_string(), g.is_reload(), m["observer"].to_bool(), is_public, g.has_password());
user_handler_->db_insert_game_info(uuid_, g.db_id(), game_config::wesnoth_version.str(), g.name(), m["mp_scenario"].to_string(), m["mp_era"].to_string(), g.is_reload(), m["observer"].to_bool(), is_public, g.has_password());
const simple_wml::node::child_list& sides = g.get_sides_list();
for(unsigned side_index = 0; side_index < sides.size(); ++side_index) {
@ -1769,13 +1773,13 @@ void server::handle_player_in_game(socket_ptr socket, std::shared_ptr<simple_wml
source = "Default";
}
}
user_handler_->db_insert_game_player_info(uuid_, g.id(), side["player_id"].to_string(), side["side"].to_int(), side["is_host"].to_bool(), side["faction"].to_string(), version, source, side["current_player"].to_string());
user_handler_->db_insert_game_player_info(uuid_, g.db_id(), side["player_id"].to_string(), side["side"].to_int(), side["is_host"].to_bool(), side["faction"].to_string(), version, source, side["current_player"].to_string());
}
const std::string mods = m["active_mods"].to_string();
if(mods != "") {
for(const std::string mod : utils::split(mods, ',')){
user_handler_->db_insert_modification_info(uuid_, g.id(), mod);
user_handler_->db_insert_modification_info(uuid_, g.db_id(), mod);
}
}
}
@ -1901,7 +1905,7 @@ void server::handle_player_in_game(socket_ptr socket, std::shared_ptr<simple_wml
if((*info)["condition"].to_string() == "out of sync") {
g.send_server_message_to_all(player.name() + " reports out of sync errors.");
if(user_handler_){
user_handler_->db_set_oos_flag(uuid_, g.id());
user_handler_->db_set_oos_flag(uuid_, g.db_id());
}
}
}