diff --git a/src/command.cpp b/src/command.cpp index 49a72db30c..a8efcebb23 100644 --- a/src/command.cpp +++ b/src/command.cpp @@ -401,6 +401,9 @@ CommandCost CommandHelperBase::InternalExecuteProcessResult(Commands cmd, Comman SubtractMoneyFromCompany(res_exec); + /* Record if there was a command issues during pause; ignore pause/other setting related changes. */ + if (_pause_mode != PM_UNPAUSED && _command_proc_table[cmd].type != CMDT_SERVER_SETTING) _pause_mode |= PM_COMMAND_DURING_PAUSE; + /* update signals if needed */ UpdateSignalsInBuffer(); diff --git a/src/lang/english.txt b/src/lang/english.txt index ec9d72dc5d..52575bd4a3 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -983,10 +983,10 @@ STR_GAME_OPTIONS_AUTOSAVE_DROPDOWN_TOOLTIP :{BLACK}Select i # Autosave dropdown ###length 5 STR_GAME_OPTIONS_AUTOSAVE_DROPDOWN_OFF :Off -STR_GAME_OPTIONS_AUTOSAVE_DROPDOWN_EVERY_1_MONTH :Every month -STR_GAME_OPTIONS_AUTOSAVE_DROPDOWN_EVERY_3_MONTHS :Every 3 months -STR_GAME_OPTIONS_AUTOSAVE_DROPDOWN_EVERY_6_MONTHS :Every 6 months -STR_GAME_OPTIONS_AUTOSAVE_DROPDOWN_EVERY_12_MONTHS :Every 12 months +STR_GAME_OPTIONS_AUTOSAVE_DROPDOWN_EVERY_10_MINUTES :Every 10 minutes +STR_GAME_OPTIONS_AUTOSAVE_DROPDOWN_EVERY_30_MINUTES :Every 30 minutes +STR_GAME_OPTIONS_AUTOSAVE_DROPDOWN_EVERY_60_MINUTES :Every 60 minutes +STR_GAME_OPTIONS_AUTOSAVE_DROPDOWN_EVERY_120_MINUTES :Every 120 minutes STR_GAME_OPTIONS_LANGUAGE :{BLACK}Language STR_GAME_OPTIONS_LANGUAGE_TOOLTIP :{BLACK}Select the interface language to use diff --git a/src/misc_cmd.cpp b/src/misc_cmd.cpp index 25fc827f67..7b3553810a 100644 --- a/src/misc_cmd.cpp +++ b/src/misc_cmd.cpp @@ -173,6 +173,11 @@ CommandCost CmdPause(DoCommandFlag flags, PauseMode mode, bool pause) _pause_mode |= mode; } else { _pause_mode &= ~mode; + + /* If the only remaining reason to be paused is that we saw a command during pause, unpause. */ + if (_pause_mode == PM_COMMAND_DURING_PAUSE) { + _pause_mode = PM_UNPAUSED; + } } NetworkHandlePauseChange(prev_mode, mode); diff --git a/src/openttd.cpp b/src/openttd.cpp index 283ea903d9..f9182b255d 100644 --- a/src/openttd.cpp +++ b/src/openttd.cpp @@ -70,6 +70,7 @@ #include "misc_cmd.h" #include "timer/timer.h" #include "timer/timer_game_calendar.h" +#include "timer/timer_game_realtime.h" #include "timer/timer_game_tick.h" #include "linkgraph/linkgraphschedule.h" @@ -103,12 +104,12 @@ bool _request_newgrf_scan = false; NewGRFScanCallback *_request_newgrf_scan_callback = nullptr; /** Available settings for autosave intervals. */ -static const TimerGameCalendar::Month _autosave_months[] = { - 0, ///< never - 1, ///< every month - 3, ///< every 3 months - 6, ///< every 6 months - 12, ///< every 12 months +static const std::chrono::milliseconds _autosave_ticks[] = { + std::chrono::minutes::zero(), ///< never + std::chrono::minutes(10), + std::chrono::minutes(30), + std::chrono::minutes(60), + std::chrono::minutes(120), }; /** @@ -1033,6 +1034,9 @@ void SwitchToMode(SwitchMode new_mode) /* Make sure all AI controllers are gone at quitting game */ if (new_mode != SM_SAVE_GAME) AI::KillAll(); + /* When we change mode, reset the autosave. */ + if (new_mode != SM_SAVE_GAME) ChangeAutosaveFrequency(true); + switch (new_mode) { case SM_EDITOR: // Switch to scenario editor MakeNewEditorWorld(); @@ -1415,23 +1419,35 @@ void StateGameLoop() assert(IsLocalCompany()); } -static IntervalTimer _autosave_interval({TimerGameCalendar::MONTH, TimerGameCalendar::Priority::AUTOSAVE}, [](auto) +/** Interval for regular autosaves. Initialized at zero to disable till settings are loaded. */ +static IntervalTimer _autosave_interval({std::chrono::milliseconds::zero(), TimerGameRealtime::AUTOSAVE}, [](auto) { - if (_settings_client.gui.autosave == 0) return; - if ((TimerGameCalendar::month % _autosave_months[_settings_client.gui.autosave]) != 0) return; + /* We reset the command-during-pause mode here, so we don't continue + * to make auto-saves when nothing more is changing. */ + _pause_mode &= ~PM_COMMAND_DURING_PAUSE; _do_autosave = true; SetWindowDirty(WC_STATUS_BAR, 0); + + static FiosNumberedSaveName _autosave_ctr("autosave"); + DoAutoOrNetsave(_autosave_ctr); + + _do_autosave = false; + SetWindowDirty(WC_STATUS_BAR, 0); }); /** - * Create an autosave. The default name is "autosave#.sav". However with - * the setting 'keep_all_autosave' the name defaults to company-name + date + * Reset the interval of the autosave. + * + * If reset is not set, this does not set the elapsed time on the timer, + * so if the interval is smaller, it might result in an autosave being done + * immediately. + * + * @param reset Whether to reset the timer back to zero, or to continue. */ -static void DoAutosave() +void ChangeAutosaveFrequency(bool reset) { - static FiosNumberedSaveName _autosave_ctr("autosave"); - DoAutoOrNetsave(_autosave_ctr); + _autosave_interval.SetInterval({_autosave_ticks[_settings_client.gui.autosave], TimerGameRealtime::AUTOSAVE}, reset); } /** @@ -1469,11 +1485,14 @@ void GameLoop() ProcessAsyncSaveFinish(); - /* autosave game? */ - if (_do_autosave) { - DoAutosave(); - _do_autosave = false; - SetWindowDirty(WC_STATUS_BAR, 0); + if (_game_mode == GM_NORMAL) { + static auto last_time = std::chrono::steady_clock::now(); + auto now = std::chrono::steady_clock::now(); + auto delta_ms = std::chrono::duration_cast(now - last_time); + if (delta_ms.count() != 0) { + TimerManager::Elapsed(delta_ms); + last_time = now; + } } /* switch game mode? */ diff --git a/src/openttd.h b/src/openttd.h index 1534016fd6..0c1547178c 100644 --- a/src/openttd.h +++ b/src/openttd.h @@ -66,6 +66,7 @@ enum PauseMode : byte { PM_PAUSED_ACTIVE_CLIENTS = 1 << 4, ///< A game paused for 'min_active_clients' PM_PAUSED_GAME_SCRIPT = 1 << 5, ///< A game paused by a game script PM_PAUSED_LINK_GRAPH = 1 << 6, ///< A game paused due to the link graph schedule lagging + PM_COMMAND_DURING_PAUSE = 1 << 7, ///< A game paused, and a command executed during the pause; resets on autosave /** Pause mode bits when paused for network reasons. */ PMB_PAUSED_NETWORK = PM_PAUSED_ACTIVE_CLIENTS | PM_PAUSED_JOIN, @@ -87,5 +88,6 @@ void SwitchToMode(SwitchMode new_mode); bool RequestNewGRFScan(struct NewGRFScanCallback *callback = nullptr); void OpenBrowser(const char *url); +void ChangeAutosaveFrequency(bool reset); #endif /* OPENTTD_H */ diff --git a/src/settings_gui.cpp b/src/settings_gui.cpp index 31a79b8331..87add4bfba 100644 --- a/src/settings_gui.cpp +++ b/src/settings_gui.cpp @@ -51,10 +51,10 @@ static const StringID _autosave_dropdown[] = { STR_GAME_OPTIONS_AUTOSAVE_DROPDOWN_OFF, - STR_GAME_OPTIONS_AUTOSAVE_DROPDOWN_EVERY_1_MONTH, - STR_GAME_OPTIONS_AUTOSAVE_DROPDOWN_EVERY_3_MONTHS, - STR_GAME_OPTIONS_AUTOSAVE_DROPDOWN_EVERY_6_MONTHS, - STR_GAME_OPTIONS_AUTOSAVE_DROPDOWN_EVERY_12_MONTHS, + STR_GAME_OPTIONS_AUTOSAVE_DROPDOWN_EVERY_10_MINUTES, + STR_GAME_OPTIONS_AUTOSAVE_DROPDOWN_EVERY_30_MINUTES, + STR_GAME_OPTIONS_AUTOSAVE_DROPDOWN_EVERY_60_MINUTES, + STR_GAME_OPTIONS_AUTOSAVE_DROPDOWN_EVERY_120_MINUTES, INVALID_STRING_ID, }; @@ -595,6 +595,7 @@ struct GameOptionsWindow : Window { case WID_GO_AUTOSAVE_DROPDOWN: // Autosave options _settings_client.gui.autosave = index; + ChangeAutosaveFrequency(false); this->SetDirty(); break; diff --git a/src/timer/CMakeLists.txt b/src/timer/CMakeLists.txt index 37612481b8..f4f82e1a5a 100644 --- a/src/timer/CMakeLists.txt +++ b/src/timer/CMakeLists.txt @@ -1,6 +1,8 @@ add_files( timer_game_calendar.cpp timer_game_calendar.h + timer_game_realtime.cpp + timer_game_realtime.h timer_game_tick.cpp timer_game_tick.h timer_window.cpp diff --git a/src/timer/timer_game_calendar.h b/src/timer/timer_game_calendar.h index 4932647ecb..133051ae11 100644 --- a/src/timer/timer_game_calendar.h +++ b/src/timer/timer_game_calendar.h @@ -42,7 +42,6 @@ public: /* All other may have a Random() call in them, so order is important. * For safety, you can only setup a single timer on a single priority. */ - AUTOSAVE, COMPANY, DISASTER, ENGINE, diff --git a/src/timer/timer_game_realtime.cpp b/src/timer/timer_game_realtime.cpp new file mode 100644 index 0000000000..379924d139 --- /dev/null +++ b/src/timer/timer_game_realtime.cpp @@ -0,0 +1,69 @@ +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** + * @file timer_game_realtime.cpp + * This file implements the timer logic for the real time game-timer. + */ + +#include "stdafx.h" +#include "openttd.h" +#include "timer.h" +#include "timer_game_realtime.h" + +#include "safeguards.h" + +template<> +void IntervalTimer::Elapsed(TimerGameRealtime::TElapsed delta) +{ + if (this->period.period == std::chrono::milliseconds::zero()) return; + if (this->period.flag == TimerGameRealtime::PeriodFlags::AUTOSAVE && _pause_mode != PM_UNPAUSED && (_pause_mode & PM_COMMAND_DURING_PAUSE) == 0) return; + if (this->period.flag == TimerGameRealtime::PeriodFlags::UNPAUSED && _pause_mode != PM_UNPAUSED) return; + + this->storage.elapsed += delta; + + uint count = 0; + while (this->storage.elapsed >= this->period.period) { + this->storage.elapsed -= this->period.period; + count++; + } + + if (count > 0) { + this->callback(count); + } +} + +template<> +void TimeoutTimer::Elapsed(TimerGameRealtime::TElapsed delta) +{ + if (this->fired) return; + if (this->period.period == std::chrono::milliseconds::zero()) return; + if (this->period.flag == TimerGameRealtime::PeriodFlags::AUTOSAVE && _pause_mode != PM_UNPAUSED && (_pause_mode & PM_COMMAND_DURING_PAUSE) == 0) return; + if (this->period.flag == TimerGameRealtime::PeriodFlags::UNPAUSED && _pause_mode != PM_UNPAUSED) return; + + this->storage.elapsed += delta; + + if (this->storage.elapsed >= this->period.period) { + this->callback(); + this->fired = true; + } +} + +template<> +void TimerManager::Elapsed(TimerGameRealtime::TElapsed delta) +{ + for (auto timer : TimerManager::GetTimers()) { + timer->Elapsed(delta); + } +} + +#ifdef WITH_ASSERT +template<> +void TimerManager::Validate(TimerGameRealtime::TPeriod period) +{ +} +#endif /* WITH_ASSERT */ diff --git a/src/timer/timer_game_realtime.h b/src/timer/timer_game_realtime.h new file mode 100644 index 0000000000..22432501b3 --- /dev/null +++ b/src/timer/timer_game_realtime.h @@ -0,0 +1,59 @@ +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file timer_game_realtime.h Definition of the real time game-timer */ + +#ifndef TIMER_GAME_REALTIME_H +#define TIMER_GAME_REALTIME_H + +#include + +/** + * Timer that represents real time for game-related purposes. + * + * For pausing, there are several modes: + * - Continue to tick during pause (PeriodFlags::ALWAYS). + * - Stop ticking when paused (PeriodFlags::UNPAUSED). + * - Only tick when unpaused or when there was a Command executed recently (recently: since last autosave) (PeriodFlags::AUTOSAVE). + * + * @note The lowest possible interval is 1ms, although realistic the lowest + * interval is 27ms. This timer is only updated when the game-thread makes + * a tick, which happens every 27ms. + * @note Callbacks are executed in the game-thread. + */ +class TimerGameRealtime { +public: + enum PeriodFlags { + ALWAYS, ///< Always run, even when paused. + UNPAUSED, ///< Only run when not paused. + AUTOSAVE, ///< Only run when not paused or there was a Command executed recently. + }; + + struct TPeriod { + std::chrono::milliseconds period; + PeriodFlags flag; + + TPeriod(std::chrono::milliseconds period, PeriodFlags flag) : period(period), flag(flag) {} + + bool operator < (const TPeriod &other) const + { + if (this->flag != other.flag) return this->flag < other.flag; + return this->period < other.period; + } + + bool operator == (const TPeriod &other) const + { + return this->flag == other.flag && this->period == other.period; + } + }; + using TElapsed = std::chrono::milliseconds; + struct TStorage { + std::chrono::milliseconds elapsed; + }; +}; + +#endif /* TIMER_GAME_REALTIME_H */