diff --git a/src/crashlog.cpp b/src/crashlog.cpp index eae4a3bef8..dfa029ca44 100644 --- a/src/crashlog.cpp +++ b/src/crashlog.cpp @@ -102,6 +102,10 @@ void CrashLog::FillCrashLog() this->survey["stacktrace"] = "crashed while gathering information"; } + if (!this->TryExecute("session", [this]() { SurveyGameSession(this->survey["session"]); return true; })) { + this->survey["session"] = "crashed while gathering information"; + } + { auto &info = this->survey["info"]; if (!this->TryExecute("os", [&info]() { SurveyOS(info["os"]); return true; })) { diff --git a/src/gfx.cpp b/src/gfx.cpp index 4b6b7b8d0c..c5bedda078 100644 --- a/src/gfx.cpp +++ b/src/gfx.cpp @@ -46,8 +46,8 @@ bool _screen_disable_anim = false; ///< Disable palette animation (important f std::atomic _exit_game; GameMode _game_mode; SwitchMode _switch_mode; ///< The next mainloop command. -std::chrono::steady_clock::time_point _switch_mode_time; ///< The time when the switch mode was requested. PauseMode _pause_mode; +GameSessionStats _game_session_stats; ///< Statistics about the current session. static byte _stringwidth_table[FS_END][224]; ///< Cache containing width of often used characters. @see GetCharacterWidth() DrawPixelInfo *_cur_dpi; diff --git a/src/misc.cpp b/src/misc.cpp index dd95950dda..d6467a1812 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -36,8 +36,6 @@ #include "safeguards.h" -std::string _savegame_id; ///< Unique ID of the current savegame. - extern TileIndex _cur_tileloop_tile; extern void MakeNewgameSettingsLive(); @@ -89,7 +87,7 @@ std::string GenerateUid(std::string_view subject) */ void GenerateSavegameId() { - _savegame_id = GenerateUid("OpenTTD Savegame ID"); + _game_session_stats.savegame_id = GenerateUid("OpenTTD Savegame ID"); } void InitializeGame(uint size_x, uint size_y, bool reset_date, bool reset_settings) diff --git a/src/network/core/config.h b/src/network/core/config.h index df8991ad69..18bafb66e2 100644 --- a/src/network/core/config.h +++ b/src/network/core/config.h @@ -48,7 +48,7 @@ static const size_t COMPAT_MTU = 1460; ///< Number of bytes we can pack in a sin static const byte NETWORK_GAME_ADMIN_VERSION = 3; ///< What version of the admin network do we use? static const byte NETWORK_GAME_INFO_VERSION = 7; ///< What version of game-info do we use? static const byte NETWORK_COORDINATOR_VERSION = 6; ///< What version of game-coordinator-protocol do we use? -static const byte NETWORK_SURVEY_VERSION = 1; ///< What version of the survey do we use? +static const byte NETWORK_SURVEY_VERSION = 2; ///< What version of the survey do we use? static const uint NETWORK_NAME_LENGTH = 80; ///< The maximum length of the server name and map name, in bytes including '\0' static const uint NETWORK_COMPANY_NAME_LENGTH = 128; ///< The maximum length of the company name, in bytes including '\0' diff --git a/src/network/network_client.cpp b/src/network/network_client.cpp index c4f5149ce6..7d961ab005 100644 --- a/src/network/network_client.cpp +++ b/src/network/network_client.cpp @@ -882,7 +882,8 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_MAP_DONE(Packet SendMapOk(); /* As we skipped switch-mode, update the time we "switched". */ - _switch_mode_time = std::chrono::steady_clock::now(); + _game_session_stats.start_time = std::chrono::steady_clock::now(); + _game_session_stats.savegame_size = std::nullopt; ShowClientList(); diff --git a/src/network/network_survey.cpp b/src/network/network_survey.cpp index 59d9b22d60..9bdddc40c2 100644 --- a/src/network/network_survey.cpp +++ b/src/network/network_survey.cpp @@ -19,8 +19,6 @@ #include "../safeguards.h" -extern std::string _savegame_id; - NetworkSurveyHandler _survey = {}; NLOHMANN_JSON_SERIALIZE_ENUM(NetworkSurveyHandler::Reason, { @@ -43,7 +41,6 @@ std::string NetworkSurveyHandler::CreatePayload(Reason reason, bool for_preview) survey["schema"] = NETWORK_SURVEY_VERSION; survey["reason"] = reason; - survey["id"] = _savegame_id; survey["date"] = fmt::format("{:%Y-%m-%d %H:%M:%S} (UTC)", fmt::gmtime(time(nullptr))); #ifdef SURVEY_KEY @@ -53,6 +50,8 @@ std::string NetworkSurveyHandler::CreatePayload(Reason reason, bool for_preview) survey["key"] = ""; #endif + SurveyGameSession(survey["session"]); + { auto &info = survey["info"]; SurveyOS(info["os"]); diff --git a/src/openttd.cpp b/src/openttd.cpp index 207ff37b41..e1e2cf1880 100644 --- a/src/openttd.cpp +++ b/src/openttd.cpp @@ -517,7 +517,8 @@ static const OptionData _options[] = { */ int openttd_main(int argc, char *argv[]) { - _switch_mode_time = std::chrono::steady_clock::now(); + _game_session_stats.start_time = std::chrono::steady_clock::now(); + _game_session_stats.savegame_size = std::nullopt; std::string musicdriver; std::string sounddriver; @@ -1086,7 +1087,10 @@ void SwitchToMode(SwitchMode new_mode) if (_game_mode == GM_NORMAL && new_mode != SM_SAVE_GAME) _survey.Transmit(NetworkSurveyHandler::Reason::LEAVE); /* Keep track when we last switch mode. Used for survey, to know how long someone was in a game. */ - if (new_mode != SM_SAVE_GAME) _switch_mode_time = std::chrono::steady_clock::now(); + if (new_mode != SM_SAVE_GAME) { + _game_session_stats.start_time = std::chrono::steady_clock::now(); + _game_session_stats.savegame_size = std::nullopt; + } switch (new_mode) { case SM_EDITOR: // Switch to scenario editor diff --git a/src/openttd.h b/src/openttd.h index 93875dfd6d..08f2988787 100644 --- a/src/openttd.h +++ b/src/openttd.h @@ -52,9 +52,15 @@ enum DisplayOptions { DO_SHOW_COMPETITOR_SIGNS = 7, ///< Display signs, station names and waypoint names of opponent companies. Buoys and oilrig-stations are always shown, even if this option is turned off. }; +struct GameSessionStats { + std::chrono::steady_clock::time_point start_time; ///< Time when the current game was started. + std::string savegame_id; ///< Unique ID of the savegame. + std::optional savegame_size; ///< Size of the last saved savegame in bytes, or std::nullopt if not saved yet. +}; + extern GameMode _game_mode; extern SwitchMode _switch_mode; -extern std::chrono::steady_clock::time_point _switch_mode_time; +extern GameSessionStats _game_session_stats; extern std::atomic _exit_game; extern bool _save_config; diff --git a/src/saveload/misc_sl.cpp b/src/saveload/misc_sl.cpp index 7ec076310a..f72ad09dbc 100644 --- a/src/saveload/misc_sl.cpp +++ b/src/saveload/misc_sl.cpp @@ -29,7 +29,6 @@ extern TileIndex _cur_tileloop_tile; extern uint16_t _disaster_delay; extern byte _trees_tick_ctr; -extern std::string _savegame_id; /* Keep track of current game position */ int _saved_scrollpos_x; @@ -98,7 +97,7 @@ static const SaveLoad _date_desc[] = { SLEG_VAR("company_tick_counter", _cur_company_tick_index, SLE_FILE_U8 | SLE_VAR_U32), SLEG_VAR("trees_tick_counter", _trees_tick_ctr, SLE_UINT8), SLEG_CONDVAR("pause_mode", _pause_mode, SLE_UINT8, SLV_4, SL_MAX_VERSION), - SLEG_CONDSSTR("id", _savegame_id, SLE_STR, SLV_SAVEGAME_ID, SL_MAX_VERSION), + SLEG_CONDSSTR("id", _game_session_stats.savegame_id, SLE_STR, SLV_SAVEGAME_ID, SL_MAX_VERSION), /* For older savegames, we load the current value as the "period"; afterload will set the "fired" and "elapsed". */ SLEG_CONDVAR("next_competitor_start", _new_competitor_timeout.period, SLE_FILE_U16 | SLE_VAR_U32, SL_MIN_VERSION, SLV_109), SLEG_CONDVAR("next_competitor_start", _new_competitor_timeout.period, SLE_UINT32, SLV_109, SLV_AI_START_DATE), diff --git a/src/saveload/saveload.cpp b/src/saveload/saveload.cpp index 4dd474a8db..09209ddd4a 100644 --- a/src/saveload/saveload.cpp +++ b/src/saveload/saveload.cpp @@ -2182,7 +2182,10 @@ struct FileReader : LoadFilter { /** Make sure everything is cleaned up. */ ~FileReader() { - if (this->file != nullptr) fclose(this->file); + if (this->file != nullptr) { + _game_session_stats.savegame_size = ftell(this->file) - this->begin; + fclose(this->file); + } this->file = nullptr; } @@ -2231,7 +2234,10 @@ struct FileWriter : SaveFilter { void Finish() override { - if (this->file != nullptr) fclose(this->file); + if (this->file != nullptr) { + _game_session_stats.savegame_size = ftell(this->file); + fclose(this->file); + } this->file = nullptr; } }; diff --git a/src/survey.cpp b/src/survey.cpp index 89a57fc717..f9310c976d 100644 --- a/src/survey.cpp +++ b/src/survey.cpp @@ -235,6 +235,20 @@ void SurveyOpenTTD(nlohmann::json &survey) ; } +/** + * Convert game session information to JSON. + * + * @param survey The JSON object. + */ +void SurveyGameSession(nlohmann::json &survey) +{ + survey["id"] = _game_session_stats.savegame_id; + survey["seconds"] = std::chrono::duration_cast(std::chrono::steady_clock::now() - _game_session_stats.start_time).count(); + if (_game_session_stats.savegame_size.has_value()) { + survey["savegame_size"] = _game_session_stats.savegame_size.value(); + } +} + /** * Convert generic game information to JSON. * @@ -330,7 +344,6 @@ void SurveyCompanies(nlohmann::json &survey) void SurveyTimers(nlohmann::json &survey) { survey["ticks"] = TimerGameTick::counter; - survey["seconds"] = std::chrono::duration_cast(std::chrono::steady_clock::now() - _switch_mode_time).count(); TimerGameEconomy::YearMonthDay economy_ymd = TimerGameEconomy::ConvertDateToYMD(TimerGameEconomy::date); survey["economy"] = fmt::format("{:04}-{:02}-{:02} ({})", economy_ymd.year, economy_ymd.month + 1, economy_ymd.day, TimerGameEconomy::date_fract); diff --git a/src/survey.h b/src/survey.h index 0e74641a27..bb235160ad 100644 --- a/src/survey.h +++ b/src/survey.h @@ -16,6 +16,7 @@ std::string SurveyMemoryToText(uint64_t memory); void SurveyCompanies(nlohmann::json &survey); void SurveyCompiler(nlohmann::json &survey); +void SurveyGameSession(nlohmann::json &survey); void SurveyConfiguration(nlohmann::json &survey); void SurveyFont(nlohmann::json &survey); void SurveyGameScript(nlohmann::json &survey);