Changeset - r27088:7e2816121458
[Not reviewed]
master
0 5 7
Patric Stout - 14 months ago 2023-04-13 15:16:48
truebrain@openttd.org
Codechange: introduce a framework for all our timers

IntervalTimer and TimeoutTimer use RAII, and can be used to replace
all the time-based timeouts, lag-detection, "execute every N" we
have.
As it uses RAII, you can safely use it as static variable, class
member, temporary variable, etc. As soon as it goes out-of-scope,
it will be safely removed.
This allows for much easier to read code when it comes to intervals.
12 files changed with 591 insertions and 85 deletions:
0 comments (0 inline, 0 general)
src/CMakeLists.txt
Show inline comments
 
@@ -15,24 +15,25 @@ add_subdirectory(game)
 
add_subdirectory(lang)
 
add_subdirectory(linkgraph)
 
add_subdirectory(misc)
 
add_subdirectory(music)
 
add_subdirectory(network)
 
add_subdirectory(os)
 
add_subdirectory(pathfinder)
 
add_subdirectory(saveload)
 
add_subdirectory(sound)
 
add_subdirectory(spriteloader)
 
add_subdirectory(table)
 
add_subdirectory(tests)
 
add_subdirectory(timer)
 
add_subdirectory(video)
 
add_subdirectory(widgets)
 

	
 
add_files(
 
    viewport_sprite_sorter_sse4.cpp
 
    CONDITION SSE_FOUND
 
)
 

	
 
add_files(
 
    aircraft.h
 
    aircraft_cmd.cpp
 
    aircraft_cmd.h
src/date.cpp
Show inline comments
 
@@ -11,24 +11,26 @@
 
#include "network/network.h"
 
#include "network/network_func.h"
 
#include "currency.h"
 
#include "window_func.h"
 
#include "settings_type.h"
 
#include "date_func.h"
 
#include "vehicle_base.h"
 
#include "rail_gui.h"
 
#include "linkgraph/linkgraph.h"
 
#include "saveload/saveload.h"
 
#include "newgrf_profiling.h"
 
#include "widgets/statusbar_widget.h"
 
#include "timer/timer.h"
 
#include "timer/timer_game_calendar.h"
 

	
 
#include "safeguards.h"
 

	
 
Year      _cur_year;   ///< Current year, starting at 0
 
Month     _cur_month;  ///< Current month (0..11)
 
Date      _date;       ///< Current date in days (day counter)
 
DateFract _date_fract; ///< Fractional part of the day.
 
uint64 _tick_counter;  ///< Ever incrementing tick counter for setting off various events
 

	
 
/**
 
 * Set the date.
 
 * @param date  New date
 
@@ -180,25 +182,25 @@ extern void ShowEndGameChart();
 
/** Available settings for autosave intervals. */
 
static const Month _autosave_months[] = {
 
	 0, ///< never
 
	 1, ///< every month
 
	 3, ///< every 3 months
 
	 6, ///< every 6 months
 
	12, ///< every 12 months
 
};
 

	
 
/**
 
 * Runs various procedures that have to be done yearly
 
 */
 
static void OnNewYear()
 
static IntervalTimer<TimerGameCalendar> _on_new_year({TimerGameCalendar::YEAR, TimerGameCalendar::Priority::NONE}, [](auto)
 
{
 
	CompaniesYearlyLoop();
 
	VehiclesYearlyLoop();
 
	TownsYearlyLoop();
 
	InvalidateWindowClassesData(WC_BUILD_STATION);
 
	InvalidateWindowClassesData(WC_BUS_STATION);
 
	InvalidateWindowClassesData(WC_TRUCK_STATION);
 
	if (_network_server) NetworkServerYearlyLoop();
 

	
 
	if (_cur_year == _settings_client.gui.semaphore_build_before) ResetSignalVariant();
 

	
 
	/* check if we reached end of the game (end of ending year); 0 = never */
 
@@ -213,95 +215,54 @@ static void OnNewYear()
 
		_cur_year--;
 
		days_this_year = IsLeapYear(_cur_year) ? DAYS_IN_LEAP_YEAR : DAYS_IN_YEAR;
 
		_date -= days_this_year;
 
		for (Vehicle *v : Vehicle::Iterate()) v->ShiftDates(-days_this_year);
 
		for (LinkGraph *lg : LinkGraph::Iterate()) lg->ShiftDates(-days_this_year);
 

	
 
		/* Because the _date wraps here, and text-messages expire by game-days, we have to clean out
 
		 *  all of them if the date is set back, else those messages will hang for ever */
 
		NetworkInitChatMessage();
 
	}
 

	
 
	if (_settings_client.gui.auto_euro) CheckSwitchToEuro();
 
}
 
});
 

	
 
/**
 
 * Runs various procedures that have to be done monthly
 
 */
 
static void OnNewMonth()
 
static IntervalTimer<TimerGameCalendar> _on_new_month({TimerGameCalendar::MONTH, TimerGameCalendar::Priority::NONE}, [](auto)
 
{
 
	if (_settings_client.gui.autosave != 0 && (_cur_month % _autosave_months[_settings_client.gui.autosave]) == 0) {
 
		_do_autosave = true;
 
		SetWindowDirty(WC_STATUS_BAR, 0);
 
	}
 

	
 
	SetWindowClassesDirty(WC_CHEATS);
 
	CompaniesMonthlyLoop();
 
	EnginesMonthlyLoop();
 
	TownsMonthlyLoop();
 
	IndustryMonthlyLoop();
 
	SubsidyMonthlyLoop();
 
	StationMonthlyLoop();
 
	if (_network_server) NetworkServerMonthlyLoop();
 
}
 
});
 

	
 
/**
 
 * Runs various procedures that have to be done daily
 
 */
 
static void OnNewDay()
 
static IntervalTimer<TimerGameCalendar> _on_new_day({TimerGameCalendar::DAY, TimerGameCalendar::Priority::NONE}, [](auto)
 
{
 
	if (!_newgrf_profilers.empty() && _newgrf_profile_end_date <= _date) {
 
		NewGRFProfiler::FinishAll();
 
	}
 

	
 
	if (_network_server) NetworkServerDailyLoop();
 

	
 
	DisasterDailyLoop();
 
	IndustryDailyLoop();
 

	
 
	SetWindowWidgetDirty(WC_STATUS_BAR, 0, WID_S_LEFT);
 
	EnginesDailyLoop();
 

	
 
	/* Refresh after possible snowline change */
 
	SetWindowClassesDirty(WC_TOWN_VIEW);
 
}
 

	
 
/**
 
 * Increases the tick counter, increases date  and possibly calls
 
 * procedures that have to be called daily, monthly or yearly.
 
 */
 
void IncreaseDate()
 
{
 
	/* increase day, and check if a new day is there? */
 
	_tick_counter++;
 

	
 
	if (_game_mode == GM_MENU) return;
 

	
 
	_date_fract++;
 
	if (_date_fract < DAY_TICKS) return;
 
	_date_fract = 0;
 

	
 
	/* increase day counter */
 
	_date++;
 

	
 
	YearMonthDay ymd;
 
	ConvertDateToYMD(_date, &ymd);
 

	
 
	/* check if we entered a new month? */
 
	bool new_month = ymd.month != _cur_month;
 

	
 
	/* check if we entered a new year? */
 
	bool new_year = ymd.year != _cur_year;
 

	
 
	/* update internal variables before calling the daily/monthly/yearly loops */
 
	_cur_month = ymd.month;
 
	_cur_year  = ymd.year;
 

	
 
	/* yes, call various daily loops */
 
	OnNewDay();
 

	
 
	/* yes, call various monthly loops */
 
	if (new_month) OnNewMonth();
 

	
 
	/* yes, call various yearly loops */
 
	if (new_year) OnNewYear();
 
}
 
});
src/openttd.cpp
Show inline comments
 
@@ -59,39 +59,40 @@
 
#include "misc/getoptdata.h"
 
#include "game/game.hpp"
 
#include "game/game_config.hpp"
 
#include "town.h"
 
#include "subsidy_func.h"
 
#include "gfx_layout.h"
 
#include "viewport_func.h"
 
#include "viewport_sprite_sorter.h"
 
#include "framerate_type.h"
 
#include "industry.h"
 
#include "network/network_gui.h"
 
#include "misc_cmd.h"
 
#include "timer/timer.h"
 
#include "timer/timer_game_calendar.h"
 

	
 
#include "linkgraph/linkgraphschedule.h"
 

	
 
#include <stdarg.h>
 
#include <system_error>
 

	
 
#include "safeguards.h"
 

	
 
#ifdef __EMSCRIPTEN__
 
#	include <emscripten.h>
 
#	include <emscripten/html5.h>
 
#endif
 

	
 
void CallLandscapeTick();
 
void IncreaseDate();
 
void DoPaletteAnimations();
 
void MusicLoop();
 
void ResetMusic();
 
void CallWindowGameTickEvent();
 
bool HandleBootstrap();
 

	
 
extern void AfterLoadCompanyStats();
 
extern Company *DoStartupNewCompany(bool is_ai, CompanyID company = INVALID_COMPANY);
 
extern void OSOpenBrowser(const char *url);
 
extern void RebuildTownCaches();
 
extern void ShowOSErrorBox(const char *buf, bool system);
 
extern std::string _config_file;
 
@@ -1399,25 +1400,25 @@ void StateGameLoop()
 
			seprintf(name, lastof(name), "dmp_cmds_%08x_%08x.sav", _settings_game.game_creation.generation_seed, _date);
 
			SaveOrLoad(name, SLO_SAVE, DFT_GAME_FILE, AUTOSAVE_DIR, false);
 
		}
 

	
 
		CheckCaches();
 

	
 
		/* All these actions has to be done from OWNER_NONE
 
		 *  for multiplayer compatibility */
 
		Backup<CompanyID> cur_company(_current_company, OWNER_NONE, FILE_LINE);
 

	
 
		BasePersistentStorageArray::SwitchMode(PSM_ENTER_GAMELOOP);
 
		AnimateAnimatedTiles();
 
		IncreaseDate();
 
		TimerManager<TimerGameCalendar>::Elapsed(1);
 
		RunTileLoop();
 
		CallVehicleTicks();
 
		CallLandscapeTick();
 
		BasePersistentStorageArray::SwitchMode(PSM_LEAVE_GAMELOOP);
 

	
 
#ifndef DEBUG_DUMP_COMMANDS
 
		{
 
			PerformanceMeasurer script_framerate(PFE_ALLSCRIPTS);
 
			AI::GameLoop();
 
			Game::GameLoop();
 
		}
 
#endif
src/stdafx.h
Show inline comments
 
@@ -146,24 +146,31 @@
 
#		endif
 
#	else
 
#		define FALLTHROUGH
 
#	endif
 
#endif /* __GNUC__ || __clang__ */
 

	
 
#if __GNUC__ > 11 || (__GNUC__ == 11 && __GNUC_MINOR__ >= 1)
 
#      define NOACCESS(args) __attribute__ ((access (none, args)))
 
#else
 
#      define NOACCESS(args)
 
#endif
 

	
 
/* [[nodiscard]] on constructors doesn't work in GCC older than 10.1. */
 
#if __GNUC__ < 10 || (__GNUC__ == 10 && __GNUC_MINOR__ < 1)
 
#      define NODISCARD
 
#else
 
#      define NODISCARD [[nodiscard]]
 
#endif
 

	
 
#if defined(__WATCOMC__)
 
#	define NORETURN
 
#	define CDECL
 
#	define WARN_FORMAT(string, args)
 
#	define WARN_TIME_FORMAT(string)
 
#	define FINAL
 
#	define FALLTHROUGH
 
#	include <malloc.h>
 
#endif /* __WATCOMC__ */
 

	
 
#if defined(__MINGW32__)
 
#	include <malloc.h> // alloca()
src/timer/CMakeLists.txt
Show inline comments
 
new file 100644
 
add_files(
 
    timer_game_calendar.cpp
 
    timer_game_calendar.h
 
    timer_window.cpp
 
    timer_window.h
 
    timer_manager.h
 
    timer.h
 
)
src/timer/timer.h
Show inline comments
 
new file 100644
 
/*
 
 * 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 <http://www.gnu.org/licenses/>.
 
 */
 

	
 
/** @file timer.h Definition of Interval and OneShot timers */
 

	
 
#ifndef TIMER_H
 
#define TIMER_H
 

	
 
#include "timer_manager.h"
 

	
 
#include <functional>
 

	
 
/**
 
 * The base where every other type of timer is derived from.
 
 *
 
 * Never use this class directly yourself.
 
 */
 
template <typename TTimerType>
 
class BaseTimer {
 
public:
 
	using TPeriod = typename TTimerType::TPeriod;
 
	using TElapsed = typename TTimerType::TElapsed;
 
	using TStorage = typename TTimerType::TStorage;
 

	
 
	/**
 
	 * Create a new timer.
 
	 *
 
	 * @param period The period of the timer.
 
	 */
 
	NODISCARD BaseTimer(const TPeriod period) :
 
		period(period)
 
	{
 
		TimerManager<TTimerType>::RegisterTimer(*this);
 
	}
 

	
 
	/**
 
	 * Delete the timer.
 
	 */
 
	virtual ~BaseTimer()
 
	{
 
		TimerManager<TTimerType>::UnregisterTimer(*this);
 
	}
 

	
 
	/* Although these variables are public, they are only public to make saveload easier; not for common use. */
 

	
 
	TPeriod period; ///< The period of the timer.
 
	TStorage storage = {}; ///< The storage of the timer.
 

	
 
protected:
 
	/**
 
	 * Called by the timer manager to notify the timer that the given amount of time has elapsed.
 
	 *
 
	 * @param delta Depending on the time type, this is either in milliseconds or in ticks.
 
	 */
 
	virtual void Elapsed(TElapsed delta) = 0;
 

	
 
	/* To ensure only TimerManager can access Elapsed. */
 
	friend class TimerManager<TTimerType>;
 
};
 

	
 
/**
 
 * An interval timer will fire every interval, and will continue to fire until it is deleted.
 
 *
 
 * The callback receives how many times the timer has fired since the last time it fired.
 
 * It will always try to fire every interval, but in times of severe stress it might be late.
 
 *
 
 * Each Timer-type needs to implement the Elapsed() method, and call the callback if needed.
 
 *
 
 * Setting the period to zero disables the interval. It can be reenabled at any time by
 
 * calling SetInterval() with a non-zero period.
 
 */
 
template <typename TTimerType>
 
class IntervalTimer : public BaseTimer<TTimerType> {
 
public:
 
	using TPeriod = typename TTimerType::TPeriod;
 
	using TElapsed = typename TTimerType::TElapsed;
 

	
 
	/**
 
	 * Create a new interval timer.
 
	 *
 
	 * @param interval The interval between each callback.
 
	 * @param callback The callback to call when the interval has passed.
 
	 */
 
	NODISCARD IntervalTimer(const TPeriod interval, std::function<void(uint)> callback) :
 
		BaseTimer<TTimerType>(interval),
 
		callback(callback)
 
	{
 
	}
 

	
 
	/**
 
	 * Set a new interval for the timer.
 
	 *
 
	 * @param interval The interval between each callback.
 
	 * @param reset Whether to reset the timer to zero.
 
	 */
 
	void SetInterval(const TPeriod interval, bool reset = true)
 
	{
 
		this->period = interval;
 
		if (reset) this->storage = {};
 
	}
 

	
 
private:
 
	std::function<void(uint)> callback;
 

	
 
	void Elapsed(TElapsed count) override;
 
};
 

	
 
/**
 
 * A timeout timer will fire once after the interval. You can reset it to fire again.
 
 * The timer will never fire before the interval has passed, but in times of severe stress it might be late.
 
 */
 
template <typename TTimerType>
 
class TimeoutTimer : public BaseTimer<TTimerType> {
 
public:
 
	using TPeriod = typename TTimerType::TPeriod;
 
	using TElapsed = typename TTimerType::TElapsed;
 

	
 
	/**
 
	 * Create a new timeout timer.
 
	 *
 
	 * By default the timeout starts aborted; you will have to call Reset() before it starts.
 
	 *
 
	 * @param timeout The timeout after which the timer will fire.
 
	 * @param callback The callback to call when the timeout has passed.
 
	 * @param start Whether to start the timer immediately. If false, you can call Reset() to start it.
 
	 */
 
	NODISCARD TimeoutTimer(const TPeriod timeout, std::function<void()> callback, bool start = false) :
 
		BaseTimer<TTimerType>(timeout),
 
		fired(!start),
 
		callback(callback)
 
	{
 
	}
 

	
 
	/**
 
	 * Reset the timer, so it will fire again after the timeout.
 
	 */
 
	void Reset()
 
	{
 
		this->fired = false;
 
		this->storage = {};
 
	}
 

	
 
	/**
 
	 * Reset the timer, so it will fire again after the timeout.
 
	 *
 
	 * @param timeout Set a new timeout for the next trigger.
 
	 */
 
	void Reset(const TPeriod timeout)
 
	{
 
		this->period = timeout;
 
		this->fired = false;
 
		this->storage = {};
 
	}
 

	
 
	/**
 
	 * Abort the timer so it doesn't fire if it hasn't yet.
 
	 */
 
	void Abort()
 
	{
 
		this->fired = true;
 
	}
 

	
 
	/**
 
	 * Check whether the timeout occurred.
 
	 *
 
	 * @return True iff the timeout occurred.
 
	 */
 
	bool HasFired() const
 
	{
 
		return this->fired;
 
	}
 

	
 
	/* Although these variables are public, they are only public to make saveload easier; not for common use. */
 

	
 
	bool fired; ///< Whether the timeout has occurred.
 

	
 
private:
 
	std::function<void()> callback;
 

	
 
	void Elapsed(TElapsed count) override;
 
};
 

	
 
#endif /* TIMER_H */
src/timer/timer_game_calendar.cpp
Show inline comments
 
new file 100644
 
/*
 
 * 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 <http://www.gnu.org/licenses/>.
 
 */
 

	
 
/**
 
 * @file timer_game_calendar.cpp
 
 * This file implements the timer logic for the game-calendar-timer.
 
 */
 

	
 
#include "stdafx.h"
 
#include "date_func.h"
 
#include "openttd.h"
 
#include "timer.h"
 
#include "timer_game_calendar.h"
 
#include "vehicle_base.h"
 
#include "linkgraph/linkgraph.h"
 

	
 
#include "safeguards.h"
 

	
 
template<>
 
void IntervalTimer<TimerGameCalendar>::Elapsed(TimerGameCalendar::TElapsed trigger)
 
{
 
	if (trigger == this->period.trigger) {
 
		this->callback(1);
 
	}
 
}
 

	
 
template<>
 
void TimeoutTimer<TimerGameCalendar>::Elapsed(TimerGameCalendar::TElapsed trigger)
 
{
 
	if (this->fired) return;
 

	
 
	if (trigger == this->period.trigger) {
 
		this->callback();
 
		this->fired = true;
 
	}
 
}
 

	
 
template<>
 
void TimerManager<TimerGameCalendar>::Elapsed(TimerGameCalendar::TElapsed delta)
 
{
 
	assert(delta == 1);
 

	
 
	_tick_counter++;
 

	
 
	if (_game_mode == GM_MENU) return;
 

	
 
	_date_fract++;
 
	if (_date_fract < DAY_TICKS) return;
 
	_date_fract = 0;
 

	
 
	/* increase day counter */
 
	_date++;
 

	
 
	YearMonthDay ymd;
 
	ConvertDateToYMD(_date, &ymd);
 

	
 
	/* check if we entered a new month? */
 
	bool new_month = ymd.month != _cur_month;
 

	
 
	/* check if we entered a new year? */
 
	bool new_year = ymd.year != _cur_year;
 

	
 
	/* update internal variables before calling the daily/monthly/yearly loops */
 
	_cur_month = ymd.month;
 
	_cur_year  = ymd.year;
 

	
 
	/* Make a temporary copy of the timers, as a timer's callback might add/remove other timers. */
 
	auto timers = TimerManager<TimerGameCalendar>::GetTimers();
 

	
 
	for (auto timer : timers) {
 
		timer->Elapsed(TimerGameCalendar::DAY);
 
	}
 

	
 
	if (new_month) {
 
		for (auto timer : timers) {
 
			timer->Elapsed(TimerGameCalendar::MONTH);
 
		}
 
	}
 

	
 
	if (new_year) {
 
		for (auto timer : timers) {
 
			timer->Elapsed(TimerGameCalendar::YEAR);
 
		}
 
	}
 
}
src/timer/timer_game_calendar.h
Show inline comments
 
new file 100644
 
/*
 
 * 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 <http://www.gnu.org/licenses/>.
 
 */
 

	
 
/** @file timer_game_calendar.h Definition of the game-calendar-timer */
 

	
 
#ifndef TIMER_GAME_CALENDAR_H
 
#define TIMER_GAME_CALENDAR_H
 

	
 
/**
 
 * Timer that is increased every 27ms, and counts towards ticks / days / months / years.
 
 *
 
 * The amount of days in a month depends on the month and year (leap-years).
 
 * There are always 74 ticks in a day (and with 27ms, this makes 1 day 1.998 seconds).
 
 *
 
 * IntervalTimer and TimeoutTimer based on this Timer are a bit unusual, as their count is always one.
 
 * You create those timers based on a transition: a new day, a new month or a new year.
 
 *
 
 * Additionally, you need to set a priority. To ensure deterministic behaviour, events are executed
 
 * in priority. It is important that if you assign NONE, you do not use Random() in your callback.
 
 * Other than that, make sure you only set one callback per priority.
 
 *
 
 * For example:
 
 *   IntervalTimer<TimerGameCalendar>({TimerGameCalendar::DAY, TimerGameCalendar::Priority::NONE}, [](uint count){});
 
 *
 
 */
 
class TimerGameCalendar {
 
public:
 
	enum Trigger {
 
		DAY,
 
		MONTH,
 
		YEAR,
 
	};
 
	enum Priority {
 
		NONE, ///< These timers can be executed in any order; there is no Random() in them, so order is not relevant.
 

	
 
		/* 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,
 
		INDUSTRY,
 
		STATION,
 
		SUBSIDY,
 
		TOWN,
 
		VEHICLE,
 
	};
 

	
 
	struct TPeriod {
 
		Trigger trigger;
 
		Priority priority;
 

	
 
		TPeriod(Trigger trigger, Priority priority) : trigger(trigger), priority(priority) {}
 

	
 
		bool operator < (const TPeriod &other) const
 
		{
 
			if (this->trigger != other.trigger) return this->trigger < other.trigger;
 
			return this->priority < other.priority;
 
		}
 

	
 
		bool operator == (const TPeriod &other) const
 
		{
 
			return this->trigger == other.trigger && this->priority == other.priority;
 
		}
 
	};
 

	
 
	using TElapsed = uint;
 
	struct TStorage {
 
	};
 
};
 

	
 
#endif /* TIMER_GAME_CALENDAR_H */
src/timer/timer_manager.h
Show inline comments
 
new file 100644
 
/*
 
 * 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 <http://www.gnu.org/licenses/>.
 
 */
 

	
 
/** @file timer_manager.h Definition of the TimerManager */
 
/** @note don't include this file; include "timer.h". */
 

	
 
#ifndef TIMER_MANAGER_H
 
#define TIMER_MANAGER_H
 

	
 
#include "stdafx.h"
 

	
 
#include <set>
 

	
 
template <typename TTimerType>
 
class BaseTimer;
 

	
 
/**
 
 * The TimerManager manages a single Timer-type.
 
 *
 
 * It allows for automatic registration and unregistration of timers like Interval and OneShot.
 
 *
 
 * Each Timer-type needs to implement the Elapsed() method, and distribute that to the timers if needed.
 
 */
 
template <typename TTimerType>
 
class TimerManager {
 
public:
 
	using TElapsed = typename TTimerType::TElapsed;
 

	
 
	/* Avoid copying this object; it is a singleton object. */
 
	TimerManager(TimerManager const &) = delete;
 
	TimerManager &operator=(TimerManager const &) = delete;
 

	
 
	/**
 
	 * Register a timer.
 
	 *
 
	 * @param timer The timer to register.
 
	 */
 
	static void RegisterTimer(BaseTimer<TTimerType> &timer) {
 
		GetTimers().insert(&timer);
 
	}
 

	
 
	/**
 
	 * Unregister a timer.
 
	 *
 
	 * @param timer The timer to unregister.
 
	 */
 
	static void UnregisterTimer(BaseTimer<TTimerType> &timer) {
 
		GetTimers().erase(&timer);
 
	}
 

	
 
	/**
 
	 * Called when time for this timer elapsed.
 
	 *
 
	 * The implementation per type is different, but they all share a similar goal:
 
	 *   Call the Elapsed() method of all active timers.
 
	 *
 
	 * @param value The amount of time that has elapsed.
 
	 */
 
	static void Elapsed(TElapsed value);
 

	
 
private:
 
	/**
 
	 * Sorter for timers.
 
	 *
 
	 * It will sort based on the period, smaller first. If the period is the
 
	 * same, it will sort based on the pointer value.
 
	 */
 
	struct base_timer_sorter {
 
		bool operator() (BaseTimer<TTimerType> *a, BaseTimer<TTimerType> *b) const {
 
			if (a->period == b->period) return a < b;
 
			return a->period < b->period;
 
		}
 
	};
 

	
 
	/** Singleton list, to store all the active timers. */
 
	static std::set<BaseTimer<TTimerType> *, base_timer_sorter> &GetTimers() {
 
		static std::set<BaseTimer<TTimerType> *, base_timer_sorter> timers;
 
		return timers;
 
	}
 
};
 

	
 
#endif /* TIMER_MANAGER_H */
src/timer/timer_window.cpp
Show inline comments
 
new file 100644
 
/*
 
 * 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 <http://www.gnu.org/licenses/>.
 
 */
 

	
 
/**
 
 * @file timer_window.cpp
 
 * This file implements the timer logic for the Window system.
 
 */
 

	
 
#include "stdafx.h"
 
#include "timer.h"
 
#include "timer_window.h"
 

	
 
#include "safeguards.h"
 

	
 
template<>
 
void IntervalTimer<TimerWindow>::Elapsed(TimerWindow::TElapsed delta)
 
{
 
	if (this->period == std::chrono::milliseconds::zero()) return;
 

	
 
	this->storage.elapsed += delta;
 

	
 
	uint count = 0;
 
	while (this->storage.elapsed >= this->period) {
 
		this->storage.elapsed -= this->period;
 
		count++;
 
	}
 

	
 
	if (count > 0) {
 
		this->callback(count);
 
	}
 
}
 

	
 
template<>
 
void TimeoutTimer<TimerWindow>::Elapsed(TimerWindow::TElapsed delta)
 
{
 
	if (this->fired) return;
 
	if (this->period == std::chrono::milliseconds::zero()) return;
 

	
 
	this->storage.elapsed += delta;
 

	
 
	if (this->storage.elapsed >= this->period) {
 
		this->callback();
 
		this->fired = true;
 
	}
 
}
 

	
 
template<>
 
void TimerManager<TimerWindow>::Elapsed(TimerWindow::TElapsed delta)
 
{
 
	/* Make a temporary copy of the timers, as a timer's callback might add/remove other timers. */
 
	auto timers = TimerManager<TimerWindow>::GetTimers();
 

	
 
	for (auto timer : timers) {
 
		timer->Elapsed(delta);
 
	}
 
}
src/timer/timer_window.h
Show inline comments
 
new file 100644
 
/*
 
 * 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 <http://www.gnu.org/licenses/>.
 
 */
 

	
 
/** @file timer_window.h Definition of the Window system */
 

	
 
#ifndef TIMER_WINDOW_H
 
#define TIMER_WINDOW_H
 

	
 
#include <chrono>
 

	
 
/**
 
 * Timer that represents the real time, usable for the Window system.
 
 *
 
 * This can be used to create intervals based on milliseconds, seconds, etc.
 
 * Mostly used for animation, scrolling, etc.
 
 *
 
 * Please be mindful that the order in which timers are called is not guaranteed.
 
 *
 
 * @note The lowest possible interval is 1ms.
 
 * @note These timers can only be used in the Window system.
 
 */
 
class TimerWindow {
 
public:
 
	using TPeriod = std::chrono::milliseconds;
 
	using TElapsed = std::chrono::milliseconds;
 
	struct TStorage {
 
		std::chrono::milliseconds elapsed;
 
	};
 
};
 

	
 
#endif /* TIMER_WINDOW_H */
src/window.cpp
Show inline comments
 
@@ -30,24 +30,26 @@
 
#include "ini_type.h"
 
#include "newgrf_debug.h"
 
#include "hotkeys.h"
 
#include "toolbar_gui.h"
 
#include "statusbar_gui.h"
 
#include "error.h"
 
#include "game/game.hpp"
 
#include "video/video_driver.hpp"
 
#include "framerate_type.h"
 
#include "network/network_func.h"
 
#include "guitimer_func.h"
 
#include "news_func.h"
 
#include "timer/timer.h"
 
#include "timer/timer_window.h"
 

	
 
#include "safeguards.h"
 

	
 
/** Values for _settings_client.gui.auto_scrolling */
 
enum ViewportAutoscrolling {
 
	VA_DISABLED,                  //!< Do not autoscroll when mouse is at edge of viewport.
 
	VA_MAIN_VIEWPORT_FULLSCREEN,  //!< Scroll main viewport at edge when using fullscreen.
 
	VA_MAIN_VIEWPORT,             //!< Scroll main viewport at edge.
 
	VA_EVERY_VIEWPORT,            //!< Scroll all viewports at their edges.
 
};
 

	
 
static Point _drag_delta; ///< delta between mouse cursor and upper left corner of dragged window
 
@@ -3067,91 +3069,84 @@ void InputLoop()
 
}
 

	
 
/**
 
 * Dispatch OnRealtimeTick event over all windows
 
 */
 
void CallWindowRealtimeTickEvent(uint delta_ms)
 
{
 
	for (Window *w : Window::Iterate()) {
 
		w->OnRealtimeTick(delta_ms);
 
	}
 
}
 

	
 
/** Update various of window-related information on a regular interval. */
 
static IntervalTimer<TimerWindow> window_interval(std::chrono::milliseconds(30), [](auto) {
 
	extern int _caret_timer;
 
	_caret_timer += 3;
 
	CursorTick();
 

	
 
	HandleKeyScrolling();
 
	HandleAutoscroll();
 
	DecreaseWindowCounters();
 
});
 

	
 
/** Blink the window highlight colour constantly. */
 
static IntervalTimer<TimerWindow> highlight_interval(std::chrono::milliseconds(450), [](auto) {
 
	_window_highlight_colour = !_window_highlight_colour;
 
});
 

	
 
/** Blink all windows marked with a white border. */
 
static IntervalTimer<TimerWindow> white_border_interval(std::chrono::milliseconds(30), [](auto) {
 
	if (_network_dedicated) return;
 

	
 
	for (Window *w : Window::Iterate()) {
 
		if ((w->flags & WF_WHITE_BORDER) && --w->white_border_timer == 0) {
 
			CLRBITS(w->flags, WF_WHITE_BORDER);
 
			w->SetDirty();
 
		}
 
	}
 
});
 

	
 
/**
 
 * Update the continuously changing contents of the windows, such as the viewports
 
 */
 
void UpdateWindows()
 
{
 
	static std::chrono::steady_clock::time_point last_time = std::chrono::steady_clock::now();
 
	uint delta_ms = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - last_time).count();
 

	
 
	if (delta_ms == 0) return;
 

	
 
	last_time = std::chrono::steady_clock::now();
 

	
 
	PerformanceMeasurer framerate(PFE_DRAWING);
 
	PerformanceAccumulator::Reset(PFE_DRAWWORLD);
 

	
 
	ProcessPendingPerformanceMeasurements();
 

	
 
	TimerManager<TimerWindow>::Elapsed(std::chrono::milliseconds(delta_ms));
 
	CallWindowRealtimeTickEvent(delta_ms);
 

	
 
	static GUITimer network_message_timer = GUITimer(1);
 
	if (network_message_timer.Elapsed(delta_ms)) {
 
		network_message_timer.SetInterval(1000);
 
		NetworkChatMessageLoop();
 
	}
 

	
 
	/* Process invalidations before anything else. */
 
	for (Window *w : Window::Iterate()) {
 
		w->ProcessScheduledInvalidations();
 
		w->ProcessHighlightedInvalidations();
 
	}
 

	
 
	static GUITimer window_timer = GUITimer(1);
 
	if (window_timer.Elapsed(delta_ms)) {
 
		if (_network_dedicated) window_timer.SetInterval(MILLISECONDS_PER_TICK);
 

	
 
		extern int _caret_timer;
 
		_caret_timer += 3;
 
		CursorTick();
 

	
 
		HandleKeyScrolling();
 
		HandleAutoscroll();
 
		DecreaseWindowCounters();
 
	}
 

	
 
	static GUITimer highlight_timer = GUITimer(1);
 
	if (highlight_timer.Elapsed(delta_ms)) {
 
		highlight_timer.SetInterval(450);
 
		_window_highlight_colour = !_window_highlight_colour;
 
	}
 

	
 
	if (!_pause_mode || _game_mode == GM_EDITOR || _settings_game.construction.command_pause_level > CMDPL_NO_CONSTRUCTION) MoveAllTextEffects(delta_ms);
 

	
 
	/* Skip the actual drawing on dedicated servers without screen.
 
	 * But still empty the invalidation queues above. */
 
	if (_network_dedicated) return;
 

	
 
	if (window_timer.HasElapsed()) {
 
		window_timer.SetInterval(MILLISECONDS_PER_TICK);
 

	
 
		for (Window *w : Window::Iterate()) {
 
			if ((w->flags & WF_WHITE_BORDER) && --w->white_border_timer == 0) {
 
				CLRBITS(w->flags, WF_WHITE_BORDER);
 
				w->SetDirty();
 
			}
 
		}
 
	}
 

	
 
	DrawDirtyBlocks();
 

	
 
	for (Window *w : Window::Iterate()) {
 
		/* Update viewport only if window is not shaded. */
 
		if (w->viewport != nullptr && !w->IsShaded()) UpdateViewportPosition(w);
 
	}
 
	NetworkDrawChatMessage();
 
	/* Redraw mouse cursor in case it was hidden */
 
	DrawMouseCursor();
 
}
 

	
 
/**
0 comments (0 inline, 0 general)