diff --git a/src/command.cpp b/src/command.cpp index 0bb267c381..2d906a94bd 100644 --- a/src/command.cpp +++ b/src/command.cpp @@ -1269,6 +1269,9 @@ CommandCost DoCommandPInternal(TileIndex tile, uint32 p1, uint32 p2, uint64 p3, UpdateSignalsInBuffer(); if (_extra_aspects > 0) FlushDeferredAspectUpdates(); + /* Record if there was a command issues during pause; ignore pause/other setting related changes. */ + if (_pause_mode != PM_UNPAUSED && command.type != CMDT_SERVER_SETTING) _pause_mode |= PM_COMMAND_DURING_PAUSE; + return_dcpi(res2); } #undef return_dcpi diff --git a/src/openttd.cpp b/src/openttd.cpp index 0ecad36495..2af5b8044b 100644 --- a/src/openttd.cpp +++ b/src/openttd.cpp @@ -90,6 +90,7 @@ #include "scope_info.h" #include "network/network_survey.h" #include "timer/timer.h" +#include "timer/timer_game_realtime.h" #include "timer/timer_game_tick.h" #include "network/network_sync.h" @@ -1330,6 +1331,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); + /* Transmit the survey if we were in normal-mode and not saving. It always means we leaving the current game. */ if (_game_mode == GM_NORMAL && new_mode != SM_SAVE_GAME) _survey.Transmit(NetworkSurveyHandler::Reason::LEAVE); @@ -2200,6 +2204,33 @@ static void DoAutosave() DoAutoOrNetsave(GetAutoSaveFiosNumberedSaveName(), true, lt_counter); } +/** Interval for regular autosaves. Initialized at zero to disable till settings are loaded. */ +static IntervalTimer _autosave_interval({std::chrono::milliseconds::zero(), TimerGameRealtime::AUTOSAVE}, [](auto) +{ + /* 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; + DoAutosave(); + _do_autosave = false; + SetWindowDirty(WC_STATUS_BAR, 0); +}); + +/** + * 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. + */ +void ChangeAutosaveFrequency(bool reset) +{ + _autosave_interval.SetInterval({_settings_client.gui.autosave_realtime, TimerGameRealtime::AUTOSAVE}, reset); +} + /** * Request a new NewGRF scan. This will be executed on the next game-tick. * This is mostly needed to ensure NewGRF scans (which are blocking) are @@ -2259,6 +2290,16 @@ void GameLoop() if (unlikely(_check_special_modes)) GameLoopSpecial(); + 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? */ if (_switch_mode != SM_NONE && !HasModalProgress()) { SwitchToMode(_switch_mode); diff --git a/src/openttd.h b/src/openttd.h index 8f3cd04634..032ae4a36a 100644 --- a/src/openttd.h +++ b/src/openttd.h @@ -100,5 +100,6 @@ bool RequestNewGRFScan(struct NewGRFScanCallback *callback = nullptr); void GenerateSavegameId(); void OpenBrowser(const char *url); +void ChangeAutosaveFrequency(bool reset); #endif /* OPENTTD_H */ diff --git a/src/settings.cpp b/src/settings.cpp index c4f3376f6e..1958413b42 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -1495,6 +1495,8 @@ static void TrainSpeedAdaptationChanged(int32 new_value) { } static void AutosaveModeChanged(int32 new_value) { + extern void ChangeAutosaveFrequency(bool reset); + ChangeAutosaveFrequency(false); InvalidateWindowClassesData(WC_GAME_OPTIONS); } diff --git a/src/settings_gui.cpp b/src/settings_gui.cpp index 2c67cae500..5143ccddd0 100644 --- a/src/settings_gui.cpp +++ b/src/settings_gui.cpp @@ -719,6 +719,7 @@ struct GameOptionsWindow : Window { ShowQueryString(STR_JUST_INT, STR_GAME_OPTIONS_AUTOSAVE_MINUTES_QUERY_CAPT, 4, this, CS_NUMERAL, QSF_ACCEPT_UNCHANGED); } else { _settings_client.gui.autosave_interval = _autosave_dropdown_to_minutes[index]; + ChangeAutosaveFrequency(false); this->SetDirty(); } break; @@ -781,6 +782,7 @@ struct GameOptionsWindow : Window { case QueryTextItem::AutosaveCustomRealTimeMinutes: _settings_client.gui.autosave_interval = Clamp(value, 1, 8000); + ChangeAutosaveFrequency(false); this->SetDirty(); break; } diff --git a/src/timer/CMakeLists.txt b/src/timer/CMakeLists.txt index 50c294c3ae..b3944543a4 100644 --- a/src/timer/CMakeLists.txt +++ b/src/timer/CMakeLists.txt @@ -1,4 +1,6 @@ add_files( + timer_game_realtime.cpp + timer_game_realtime.h timer_game_tick.cpp timer_game_tick.h timer_manager.h diff --git a/src/timer/timer_game_realtime.cpp b/src/timer/timer_game_realtime.cpp new file mode 100644 index 0000000000..62b10f9b05 --- /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) +{ +} +#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 */