mirror of
https://github.com/OpenTTD/OpenTTD.git
synced 2025-03-06 06:15:04 +00:00
Feature: NewGRF callback profiling (#7868)
Adds a console command newgrf_profile to collect some profiling data about NewGRF action 2 callbacks and produce a CSV file.
This commit is contained in:
parent
f88ac83408
commit
c8779fb311
@ -99,3 +99,43 @@ The following is an explanation of the different statistics:
|
||||
If the frame rate window is shaded, the title bar will instead show just the
|
||||
current simulation rate and the game speed factor.
|
||||
|
||||
## 3.0) NewGRF callback profiling
|
||||
|
||||
NewGRF developers can profile callback chains via the `newgrf_profile`
|
||||
console command. The command controls a profiling mode where every sprite
|
||||
request is measured and logged, and written to a CSV file in the end.
|
||||
|
||||
The NewGRF developer tools need to be enabled for the command to function.
|
||||
|
||||
View the syntax for the command in-game with the console command
|
||||
`help newgrf_profile`.
|
||||
|
||||
Profiling only works during game or in the editor, it's not possible to
|
||||
profile across the main menu, world generation, or loading savegames.
|
||||
|
||||
The CSV files contain one line per sprite request during the profiling.
|
||||
They can get very large, especially on large games with many objects from
|
||||
the GRF. Start profiling short periods such as 3 or 7 days, and watch the
|
||||
file sizes.
|
||||
|
||||
The produced CSV file contains the following fields:
|
||||
|
||||
- *Tick* - Game tick counter, this may wrap to zero during recording.
|
||||
Mainly useful to distinguish events from separate ticks.
|
||||
- *Sprite* - Index of the root Action 2 sprite in the GRF file. This is
|
||||
the sprite group being resolved.
|
||||
- *Feature* - NewGRF feature number the sprite group is being resolved for.
|
||||
This will be 0xFF for AI purchase selection and ambient sound callbacks.
|
||||
- *Item* - The id of the item within the GRF. For cargotypes, railtypes,
|
||||
roadtypes, and tramtypes, this is the integer representation of the label.
|
||||
- *CallbackID* - The type of callback being resolved. ID 0 is regular graphics
|
||||
lookup. See the `newgrf_callbacks.h` file in the OpenTTD source code for the
|
||||
full list of callback IDs.
|
||||
- *Microseconds* - Total time spent to resolve the Action 2, in microseconds.
|
||||
- *Depth* - Number of recursive Action 2 lookups were made during resolution.
|
||||
Value zero means the sprite group resolved directly.
|
||||
- *Result* - Result of the callback resolution. For lookups that result in
|
||||
a sprite, this is the index of the base action 2 in the GRF file. For
|
||||
callbacks that give a numeric result, this is the callback result value.
|
||||
For lookups that result in an industry production or tilelayout, this
|
||||
is the sprite index of the action 2 defining the production/tilelayout.
|
||||
|
@ -583,6 +583,7 @@
|
||||
<ClInclude Include="..\src\newgrf_industries.h" />
|
||||
<ClInclude Include="..\src\newgrf_industrytiles.h" />
|
||||
<ClInclude Include="..\src\newgrf_object.h" />
|
||||
<ClInclude Include="..\src\newgrf_profiling.h" />
|
||||
<ClInclude Include="..\src\newgrf_properties.h" />
|
||||
<ClInclude Include="..\src\newgrf_railtype.h" />
|
||||
<ClInclude Include="..\src\newgrf_roadtype.h" />
|
||||
@ -1236,6 +1237,7 @@
|
||||
<ClCompile Include="..\src\newgrf_industries.cpp" />
|
||||
<ClCompile Include="..\src\newgrf_industrytiles.cpp" />
|
||||
<ClCompile Include="..\src\newgrf_object.cpp" />
|
||||
<ClCompile Include="..\src\newgrf_profiling.cpp" />
|
||||
<ClCompile Include="..\src\newgrf_railtype.cpp" />
|
||||
<ClCompile Include="..\src\newgrf_roadtype.cpp" />
|
||||
<ClCompile Include="..\src\newgrf_sound.cpp" />
|
||||
|
@ -837,6 +837,9 @@
|
||||
<ClInclude Include="..\src\newgrf_object.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\src\newgrf_profiling.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\src\newgrf_properties.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
@ -2796,6 +2799,9 @@
|
||||
<ClCompile Include="..\src\newgrf_object.cpp">
|
||||
<Filter>NewGRF</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\src\newgrf_profiling.cpp">
|
||||
<Filter>NewGRF</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\src\newgrf_railtype.cpp">
|
||||
<Filter>NewGRF</Filter>
|
||||
</ClCompile>
|
||||
|
@ -583,6 +583,7 @@
|
||||
<ClInclude Include="..\src\newgrf_industries.h" />
|
||||
<ClInclude Include="..\src\newgrf_industrytiles.h" />
|
||||
<ClInclude Include="..\src\newgrf_object.h" />
|
||||
<ClInclude Include="..\src\newgrf_profiling.h" />
|
||||
<ClInclude Include="..\src\newgrf_properties.h" />
|
||||
<ClInclude Include="..\src\newgrf_railtype.h" />
|
||||
<ClInclude Include="..\src\newgrf_roadtype.h" />
|
||||
@ -1236,6 +1237,7 @@
|
||||
<ClCompile Include="..\src\newgrf_industries.cpp" />
|
||||
<ClCompile Include="..\src\newgrf_industrytiles.cpp" />
|
||||
<ClCompile Include="..\src\newgrf_object.cpp" />
|
||||
<ClCompile Include="..\src\newgrf_profiling.cpp" />
|
||||
<ClCompile Include="..\src\newgrf_railtype.cpp" />
|
||||
<ClCompile Include="..\src\newgrf_roadtype.cpp" />
|
||||
<ClCompile Include="..\src\newgrf_sound.cpp" />
|
||||
|
@ -837,6 +837,9 @@
|
||||
<ClInclude Include="..\src\newgrf_object.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\src\newgrf_profiling.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\src\newgrf_properties.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
@ -2796,6 +2799,9 @@
|
||||
<ClCompile Include="..\src\newgrf_object.cpp">
|
||||
<Filter>NewGRF</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\src\newgrf_profiling.cpp">
|
||||
<Filter>NewGRF</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\src\newgrf_railtype.cpp">
|
||||
<Filter>NewGRF</Filter>
|
||||
</ClCompile>
|
||||
|
@ -583,6 +583,7 @@
|
||||
<ClInclude Include="..\src\newgrf_industries.h" />
|
||||
<ClInclude Include="..\src\newgrf_industrytiles.h" />
|
||||
<ClInclude Include="..\src\newgrf_object.h" />
|
||||
<ClInclude Include="..\src\newgrf_profiling.h" />
|
||||
<ClInclude Include="..\src\newgrf_properties.h" />
|
||||
<ClInclude Include="..\src\newgrf_railtype.h" />
|
||||
<ClInclude Include="..\src\newgrf_roadtype.h" />
|
||||
@ -1236,6 +1237,7 @@
|
||||
<ClCompile Include="..\src\newgrf_industries.cpp" />
|
||||
<ClCompile Include="..\src\newgrf_industrytiles.cpp" />
|
||||
<ClCompile Include="..\src\newgrf_object.cpp" />
|
||||
<ClCompile Include="..\src\newgrf_profiling.cpp" />
|
||||
<ClCompile Include="..\src\newgrf_railtype.cpp" />
|
||||
<ClCompile Include="..\src\newgrf_roadtype.cpp" />
|
||||
<ClCompile Include="..\src\newgrf_sound.cpp" />
|
||||
|
@ -837,6 +837,9 @@
|
||||
<ClInclude Include="..\src\newgrf_object.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\src\newgrf_profiling.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\src\newgrf_properties.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
@ -2796,6 +2799,9 @@
|
||||
<ClCompile Include="..\src\newgrf_object.cpp">
|
||||
<Filter>NewGRF</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\src\newgrf_profiling.cpp">
|
||||
<Filter>NewGRF</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\src\newgrf_railtype.cpp">
|
||||
<Filter>NewGRF</Filter>
|
||||
</ClCompile>
|
||||
|
@ -270,6 +270,7 @@ newgrf_house.h
|
||||
newgrf_industries.h
|
||||
newgrf_industrytiles.h
|
||||
newgrf_object.h
|
||||
newgrf_profiling.h
|
||||
newgrf_properties.h
|
||||
newgrf_railtype.h
|
||||
newgrf_roadtype.h
|
||||
@ -986,6 +987,7 @@ newgrf_house.cpp
|
||||
newgrf_industries.cpp
|
||||
newgrf_industrytiles.cpp
|
||||
newgrf_object.cpp
|
||||
newgrf_profiling.cpp
|
||||
newgrf_railtype.cpp
|
||||
newgrf_roadtype.cpp
|
||||
newgrf_sound.cpp
|
||||
|
@ -33,6 +33,7 @@
|
||||
#include "ai/ai.hpp"
|
||||
#include "ai/ai_config.hpp"
|
||||
#include "newgrf.h"
|
||||
#include "newgrf_profiling.h"
|
||||
#include "console_func.h"
|
||||
#include "engine_base.h"
|
||||
#include "game/game.hpp"
|
||||
@ -1877,6 +1878,135 @@ DEF_CONSOLE_CMD(ConNewGRFReload)
|
||||
return true;
|
||||
}
|
||||
|
||||
DEF_CONSOLE_CMD(ConNewGRFProfile)
|
||||
{
|
||||
if (argc == 0) {
|
||||
IConsoleHelp("Collect performance data about NewGRF sprite requests and callbacks. Sub-commands can be abbreviated.");
|
||||
IConsoleHelp("Usage: newgrf_profile [list]");
|
||||
IConsoleHelp(" List all NewGRFs that can be profiled, and their status.");
|
||||
IConsoleHelp("Usage: newgrf_profile select <grf-num>...");
|
||||
IConsoleHelp(" Select one or more GRFs for profiling.");
|
||||
IConsoleHelp("Usage: newgrf_profile unselect <grf-num>...");
|
||||
IConsoleHelp(" Unselect one or more GRFs from profiling. Use the keyword \"all\" instead of a GRF number to unselect all. Removing an active profiler aborts data collection.");
|
||||
IConsoleHelp("Usage: newgrf_profile start [<num-days>]");
|
||||
IConsoleHelp(" Begin profiling all selected GRFs. If a number of days is provided, profiling stops after that many in-game days.");
|
||||
IConsoleHelp("Usage: newgrf_profile stop");
|
||||
IConsoleHelp(" End profiling and write the collected data to CSV files.");
|
||||
IConsoleHelp("Usage: newgrf_profile abort");
|
||||
IConsoleHelp(" End profiling and discard all collected data.");
|
||||
return true;
|
||||
}
|
||||
|
||||
extern const std::vector<GRFFile *> &GetAllGRFFiles();
|
||||
const std::vector<GRFFile *> &files = GetAllGRFFiles();
|
||||
|
||||
/* "list" sub-command */
|
||||
if (argc == 1 || strncasecmp(argv[1], "lis", 3) == 0) {
|
||||
IConsolePrint(CC_INFO, "Loaded GRF files:");
|
||||
int i = 1;
|
||||
for (GRFFile *grf : files) {
|
||||
auto profiler = std::find_if(_newgrf_profilers.begin(), _newgrf_profilers.end(), [&](NewGRFProfiler &pr) { return pr.grffile == grf; });
|
||||
bool selected = profiler != _newgrf_profilers.end();
|
||||
bool active = selected && profiler->active;
|
||||
TextColour tc = active ? TC_LIGHT_BLUE : selected ? TC_GREEN : CC_INFO;
|
||||
const char *statustext = active ? " (active)" : selected ? " (selected)" : "";
|
||||
IConsolePrintF(tc, "%d: [%08X] %s%s", i, BSWAP32(grf->grfid), grf->filename, statustext);
|
||||
i++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* "select" sub-command */
|
||||
if (strncasecmp(argv[1], "sel", 3) == 0 && argc >= 3) {
|
||||
for (size_t argnum = 2; argnum < argc; ++argnum) {
|
||||
int grfnum = atoi(argv[argnum]);
|
||||
if (grfnum < 1 || grfnum > (int)files.size()) { // safe cast, files.size() should not be larger than a few hundred in the most extreme cases
|
||||
IConsolePrintF(CC_WARNING, "GRF number %d out of range, not added.", grfnum);
|
||||
continue;
|
||||
}
|
||||
GRFFile *grf = files[grfnum - 1];
|
||||
if (std::any_of(_newgrf_profilers.begin(), _newgrf_profilers.end(), [&](NewGRFProfiler &pr) { return pr.grffile == grf; })) {
|
||||
IConsolePrintF(CC_WARNING, "GRF number %d [%08X] is already selected for profiling.", grfnum, BSWAP32(grf->grfid));
|
||||
continue;
|
||||
}
|
||||
_newgrf_profilers.emplace_back(grf);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* "unselect" sub-command */
|
||||
if (strncasecmp(argv[1], "uns", 3) == 0 && argc >= 3) {
|
||||
for (size_t argnum = 2; argnum < argc; ++argnum) {
|
||||
if (strcasecmp(argv[argnum], "all") == 0) {
|
||||
_newgrf_profilers.clear();
|
||||
break;
|
||||
}
|
||||
int grfnum = atoi(argv[argnum]);
|
||||
if (grfnum < 1 || grfnum > (int)files.size()) {
|
||||
IConsolePrintF(CC_WARNING, "GRF number %d out of range, not removing.", grfnum);
|
||||
continue;
|
||||
}
|
||||
GRFFile *grf = files[grfnum - 1];
|
||||
auto pos = std::find_if(_newgrf_profilers.begin(), _newgrf_profilers.end(), [&](NewGRFProfiler &pr) { return pr.grffile == grf; });
|
||||
if (pos != _newgrf_profilers.end()) _newgrf_profilers.erase(pos);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* "start" sub-command */
|
||||
if (strncasecmp(argv[1], "sta", 3) == 0) {
|
||||
std::string grfids;
|
||||
size_t started = 0;
|
||||
for (NewGRFProfiler &pr : _newgrf_profilers) {
|
||||
if (!pr.active) {
|
||||
pr.Start();
|
||||
started++;
|
||||
|
||||
if (!grfids.empty()) grfids += ", ";
|
||||
char grfidstr[12]{ 0 };
|
||||
seprintf(grfidstr, lastof(grfidstr), "[%08X]", BSWAP32(pr.grffile->grfid));
|
||||
grfids += grfidstr;
|
||||
}
|
||||
}
|
||||
if (started > 0) {
|
||||
IConsolePrintF(CC_DEBUG, "Started profiling for GRFID%s %s", (started > 1) ? "s" : "", grfids.c_str());
|
||||
if (argc >= 3) {
|
||||
int days = max(atoi(argv[2]), 1);
|
||||
_newgrf_profile_end_date = _date + days;
|
||||
|
||||
char datestrbuf[32]{ 0 };
|
||||
SetDParam(0, _newgrf_profile_end_date);
|
||||
GetString(datestrbuf, STR_JUST_DATE_ISO, lastof(datestrbuf));
|
||||
IConsolePrintF(CC_DEBUG, "Profiling will automatically stop on game date %s", datestrbuf);
|
||||
} else {
|
||||
_newgrf_profile_end_date = MAX_DAY;
|
||||
}
|
||||
} else if (_newgrf_profilers.empty()) {
|
||||
IConsolePrintF(CC_WARNING, "No GRFs selected for profiling, did not start.");
|
||||
} else {
|
||||
IConsolePrintF(CC_WARNING, "Did not start profiling for any GRFs, all selected GRFs are already profiling.");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* "stop" sub-command */
|
||||
if (strncasecmp(argv[1], "sto", 3) == 0) {
|
||||
NewGRFProfiler::FinishAll();
|
||||
return true;
|
||||
}
|
||||
|
||||
/* "abort" sub-command */
|
||||
if (strncasecmp(argv[1], "abo", 3) == 0) {
|
||||
for (NewGRFProfiler &pr : _newgrf_profilers) {
|
||||
pr.Abort();
|
||||
}
|
||||
_newgrf_profile_end_date = MAX_DAY;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef _DEBUG
|
||||
/******************
|
||||
* debug commands
|
||||
@ -2056,4 +2186,5 @@ void IConsoleStdLibRegister()
|
||||
|
||||
/* NewGRF development stuff */
|
||||
IConsoleCmdRegister("reload_newgrfs", ConNewGRFReload, ConHookNewGRFDeveloperTool);
|
||||
IConsoleCmdRegister("newgrf_profile", ConNewGRFProfile, ConHookNewGRFDeveloperTool);
|
||||
}
|
||||
|
@ -18,6 +18,7 @@
|
||||
#include "rail_gui.h"
|
||||
#include "linkgraph/linkgraph.h"
|
||||
#include "saveload/saveload.h"
|
||||
#include "newgrf_profiling.h"
|
||||
|
||||
#include "safeguards.h"
|
||||
|
||||
@ -245,6 +246,10 @@ static void OnNewMonth()
|
||||
*/
|
||||
static void OnNewDay()
|
||||
{
|
||||
if (!_newgrf_profilers.empty() && _newgrf_profile_end_date <= _date) {
|
||||
NewGRFProfiler::FinishAll();
|
||||
}
|
||||
|
||||
if (_network_server) NetworkServerDailyLoop();
|
||||
|
||||
DisasterDailyLoop();
|
||||
|
@ -29,6 +29,7 @@
|
||||
#include "station_kdtree.h"
|
||||
#include "town_kdtree.h"
|
||||
#include "viewport_kdtree.h"
|
||||
#include "newgrf_profiling.h"
|
||||
|
||||
#include "safeguards.h"
|
||||
|
||||
@ -69,6 +70,8 @@ void InitializeGame(uint size_x, uint size_y, bool reset_date, bool reset_settin
|
||||
_thd.redsq = INVALID_TILE;
|
||||
if (reset_settings) MakeNewgameSettingsLive();
|
||||
|
||||
_newgrf_profilers.clear();
|
||||
|
||||
if (reset_date) {
|
||||
SetDate(ConvertYMDToDate(_settings_game.game_creation.starting_year, 0, 1), 0);
|
||||
InitializeOldNames();
|
||||
|
@ -66,6 +66,11 @@
|
||||
/** List of all loaded GRF files */
|
||||
static std::vector<GRFFile *> _grf_files;
|
||||
|
||||
const std::vector<GRFFile *> &GetAllGRFFiles()
|
||||
{
|
||||
return _grf_files;
|
||||
}
|
||||
|
||||
/** Miscellaneous GRF features, set by Action 0x0D, parameter 0x9E */
|
||||
byte _misc_grf_features = 0;
|
||||
|
||||
@ -5000,6 +5005,7 @@ static void NewSpriteGroup(ByteReader *buf)
|
||||
|
||||
assert(DeterministicSpriteGroup::CanAllocateItem());
|
||||
DeterministicSpriteGroup *group = new DeterministicSpriteGroup();
|
||||
group->nfo_line = _cur.nfo_line;
|
||||
act_group = group;
|
||||
group->var_scope = HasBit(type, 1) ? VSG_SCOPE_PARENT : VSG_SCOPE_SELF;
|
||||
|
||||
@ -5116,6 +5122,7 @@ static void NewSpriteGroup(ByteReader *buf)
|
||||
{
|
||||
assert(RandomizedSpriteGroup::CanAllocateItem());
|
||||
RandomizedSpriteGroup *group = new RandomizedSpriteGroup();
|
||||
group->nfo_line = _cur.nfo_line;
|
||||
act_group = group;
|
||||
group->var_scope = HasBit(type, 1) ? VSG_SCOPE_PARENT : VSG_SCOPE_SELF;
|
||||
|
||||
@ -5164,6 +5171,7 @@ static void NewSpriteGroup(ByteReader *buf)
|
||||
|
||||
assert(RealSpriteGroup::CanAllocateItem());
|
||||
RealSpriteGroup *group = new RealSpriteGroup();
|
||||
group->nfo_line = _cur.nfo_line;
|
||||
act_group = group;
|
||||
|
||||
group->num_loaded = num_loaded;
|
||||
@ -5197,6 +5205,7 @@ static void NewSpriteGroup(ByteReader *buf)
|
||||
|
||||
assert(TileLayoutSpriteGroup::CanAllocateItem());
|
||||
TileLayoutSpriteGroup *group = new TileLayoutSpriteGroup();
|
||||
group->nfo_line = _cur.nfo_line;
|
||||
act_group = group;
|
||||
|
||||
/* On error, bail out immediately. Temporary GRF data was already freed */
|
||||
@ -5212,6 +5221,7 @@ static void NewSpriteGroup(ByteReader *buf)
|
||||
|
||||
assert(IndustryProductionSpriteGroup::CanAllocateItem());
|
||||
IndustryProductionSpriteGroup *group = new IndustryProductionSpriteGroup();
|
||||
group->nfo_line = _cur.nfo_line;
|
||||
act_group = group;
|
||||
group->version = type;
|
||||
if (type == 0) {
|
||||
|
@ -58,6 +58,9 @@ struct AirportResolverObject : public ResolverObject {
|
||||
}
|
||||
|
||||
const SpriteGroup *ResolveReal(const RealSpriteGroup *group) const override;
|
||||
|
||||
GrfSpecFeature GetFeature() const override;
|
||||
uint32 GetDebugID() const override;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -226,6 +229,16 @@ void AirportOverrideManager::SetEntitySpec(AirportSpec *as)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
GrfSpecFeature AirportResolverObject::GetFeature() const
|
||||
{
|
||||
return GSF_AIRPORTS;
|
||||
}
|
||||
|
||||
uint32 AirportResolverObject::GetDebugID() const
|
||||
{
|
||||
return AirportSpec::Get(this->airport_scope.airport_id)->grf_prop.local_id;
|
||||
}
|
||||
|
||||
/* virtual */ uint32 AirportScopeResolver::GetRandomBits() const
|
||||
{
|
||||
return this->st == nullptr ? 0 : this->st->random_bits;
|
||||
|
@ -220,6 +220,16 @@ AirportTileResolverObject::AirportTileResolverObject(const AirportTileSpec *ats,
|
||||
this->root_spritegroup = ats->grf_prop.spritegroup[0];
|
||||
}
|
||||
|
||||
GrfSpecFeature AirportTileResolverObject::GetFeature() const
|
||||
{
|
||||
return GSF_AIRPORTTILES;
|
||||
}
|
||||
|
||||
uint32 AirportTileResolverObject::GetDebugID() const
|
||||
{
|
||||
return this->tiles_scope.ats->grf_prop.local_id;
|
||||
}
|
||||
|
||||
uint16 GetAirportTileCallback(CallbackID callback, uint32 param1, uint32 param2, const AirportTileSpec *ats, Station *st, TileIndex tile, int extra_data = 0)
|
||||
{
|
||||
AirportTileResolverObject object(ats, tile, st, callback, param1, param2);
|
||||
|
@ -22,6 +22,7 @@ struct AirportTileScopeResolver : public ScopeResolver {
|
||||
struct Station *st; ///< %Station of the airport for which the callback is run, or \c nullptr for build gui.
|
||||
byte airport_id; ///< Type of airport for which the callback is run.
|
||||
TileIndex tile; ///< Tile for the callback, only valid for airporttile callbacks.
|
||||
const AirportTileSpec *ats;
|
||||
|
||||
/**
|
||||
* Constructor of the scope resolver specific for airport tiles.
|
||||
@ -30,7 +31,7 @@ struct AirportTileScopeResolver : public ScopeResolver {
|
||||
* @param st Station of the airport for which the callback is run, or \c nullptr for build gui.
|
||||
*/
|
||||
AirportTileScopeResolver(ResolverObject &ro, const AirportTileSpec *ats, TileIndex tile, Station *st)
|
||||
: ScopeResolver(ro), st(st), tile(tile)
|
||||
: ScopeResolver(ro), st(st), tile(tile), ats(ats)
|
||||
{
|
||||
assert(st != nullptr);
|
||||
this->airport_id = st->airport.type;
|
||||
@ -54,6 +55,9 @@ struct AirportTileResolverObject : public ResolverObject {
|
||||
default: return ResolverObject::GetScope(scope, relative);
|
||||
}
|
||||
}
|
||||
|
||||
GrfSpecFeature GetFeature() const override;
|
||||
uint32 GetDebugID() const override;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -13,6 +13,7 @@
|
||||
#include "newgrf_canal.h"
|
||||
#include "water.h"
|
||||
#include "water_map.h"
|
||||
#include "spritecache.h"
|
||||
|
||||
#include "safeguards.h"
|
||||
|
||||
@ -35,6 +36,7 @@ struct CanalScopeResolver : public ScopeResolver {
|
||||
/** Resolver object for canals. */
|
||||
struct CanalResolverObject : public ResolverObject {
|
||||
CanalScopeResolver canal_scope;
|
||||
CanalFeature feature;
|
||||
|
||||
CanalResolverObject(CanalFeature feature, TileIndex tile,
|
||||
CallbackID callback = CBID_NO_CALLBACK, uint32 callback_param1 = 0, uint32 callback_param2 = 0);
|
||||
@ -48,6 +50,9 @@ struct CanalResolverObject : public ResolverObject {
|
||||
}
|
||||
|
||||
const SpriteGroup *ResolveReal(const RealSpriteGroup *group) const override;
|
||||
|
||||
GrfSpecFeature GetFeature() const override;
|
||||
uint32 GetDebugID() const override;
|
||||
};
|
||||
|
||||
/* virtual */ uint32 CanalScopeResolver::GetRandomBits() const
|
||||
@ -111,6 +116,16 @@ struct CanalResolverObject : public ResolverObject {
|
||||
return group->loaded[0];
|
||||
}
|
||||
|
||||
GrfSpecFeature CanalResolverObject::GetFeature() const
|
||||
{
|
||||
return GSF_CANALS;
|
||||
}
|
||||
|
||||
uint32 CanalResolverObject::GetDebugID() const
|
||||
{
|
||||
return this->feature;
|
||||
}
|
||||
|
||||
/**
|
||||
* Canal resolver constructor.
|
||||
* @param feature Which canal feature we want.
|
||||
@ -121,7 +136,7 @@ struct CanalResolverObject : public ResolverObject {
|
||||
*/
|
||||
CanalResolverObject::CanalResolverObject(CanalFeature feature, TileIndex tile,
|
||||
CallbackID callback, uint32 callback_param1, uint32 callback_param2)
|
||||
: ResolverObject(_water_feature[feature].grffile, callback, callback_param1, callback_param2), canal_scope(*this, tile)
|
||||
: ResolverObject(_water_feature[feature].grffile, callback, callback_param1, callback_param2), canal_scope(*this, tile), feature(feature)
|
||||
{
|
||||
this->root_spritegroup = _water_feature[feature].group;
|
||||
}
|
||||
|
@ -15,9 +15,14 @@
|
||||
|
||||
/** Resolver of cargo. */
|
||||
struct CargoResolverObject : public ResolverObject {
|
||||
const CargoSpec *cargospec;
|
||||
|
||||
CargoResolverObject(const CargoSpec *cs, CallbackID callback = CBID_NO_CALLBACK, uint32 callback_param1 = 0, uint32 callback_param2 = 0);
|
||||
|
||||
const SpriteGroup *ResolveReal(const RealSpriteGroup *group) const override;
|
||||
|
||||
GrfSpecFeature GetFeature() const override;
|
||||
uint32 GetDebugID() const override;
|
||||
};
|
||||
|
||||
/* virtual */ const SpriteGroup *CargoResolverObject::ResolveReal(const RealSpriteGroup *group) const
|
||||
@ -30,6 +35,16 @@ struct CargoResolverObject : public ResolverObject {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
GrfSpecFeature CargoResolverObject::GetFeature() const
|
||||
{
|
||||
return GSF_CARGOES;
|
||||
}
|
||||
|
||||
uint32 CargoResolverObject::GetDebugID() const
|
||||
{
|
||||
return this->cargospec->label;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor of the cargo resolver.
|
||||
* @param cs Cargo being resolved.
|
||||
@ -38,7 +53,7 @@ struct CargoResolverObject : public ResolverObject {
|
||||
* @param callback_param2 Second parameter (var 18) of the callback.
|
||||
*/
|
||||
CargoResolverObject::CargoResolverObject(const CargoSpec *cs, CallbackID callback, uint32 callback_param1, uint32 callback_param2)
|
||||
: ResolverObject(cs->grffile, callback, callback_param1, callback_param2)
|
||||
: ResolverObject(cs->grffile, callback, callback_param1, callback_param2), cargospec(cs)
|
||||
{
|
||||
this->root_spritegroup = cs->group;
|
||||
}
|
||||
|
@ -946,6 +946,22 @@ static uint32 VehicleGetVariable(Vehicle *v, const VehicleScopeResolver *object,
|
||||
return in_motion ? group->loaded[set] : group->loading[set];
|
||||
}
|
||||
|
||||
GrfSpecFeature VehicleResolverObject::GetFeature() const
|
||||
{
|
||||
switch (Engine::Get(this->self_scope.self_type)->type) {
|
||||
case VEH_TRAIN: return GSF_TRAINS;
|
||||
case VEH_ROAD: return GSF_ROADVEHICLES;
|
||||
case VEH_SHIP: return GSF_SHIPS;
|
||||
case VEH_AIRCRAFT: return GSF_AIRCRAFT;
|
||||
default: return GSF_INVALID;
|
||||
}
|
||||
}
|
||||
|
||||
uint32 VehicleResolverObject::GetDebugID() const
|
||||
{
|
||||
return Engine::Get(this->self_scope.self_type)->grf_prop.local_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the grf file associated with an engine type.
|
||||
* @param engine_type Engine to query.
|
||||
|
@ -65,6 +65,9 @@ struct VehicleResolverObject : public ResolverObject {
|
||||
ScopeResolver *GetScope(VarSpriteGroupScope scope = VSG_SCOPE_SELF, byte relative = 0) override;
|
||||
|
||||
const SpriteGroup *ResolveReal(const RealSpriteGroup *group) const override;
|
||||
|
||||
GrfSpecFeature GetFeature() const override;
|
||||
uint32 GetDebugID() const override;
|
||||
};
|
||||
|
||||
static const uint TRAININFO_DEFAULT_VEHICLE_WIDTH = 29;
|
||||
|
@ -29,6 +29,8 @@ struct GenericScopeResolver : public ScopeResolver {
|
||||
uint8 count;
|
||||
uint8 station_size;
|
||||
|
||||
uint8 feature;
|
||||
|
||||
/**
|
||||
* Generic scope resolver.
|
||||
* @param ro Surrounding resolver.
|
||||
@ -36,7 +38,7 @@ struct GenericScopeResolver : public ScopeResolver {
|
||||
*/
|
||||
GenericScopeResolver(ResolverObject &ro, bool ai_callback)
|
||||
: ScopeResolver(ro), cargo_type(0), default_selection(0), src_industry(0), dst_industry(0), distance(0),
|
||||
event(), count(0), station_size(0), ai_callback(ai_callback)
|
||||
event(), count(0), station_size(0), feature(GSF_INVALID), ai_callback(ai_callback)
|
||||
{
|
||||
}
|
||||
|
||||
@ -62,6 +64,16 @@ struct GenericResolverObject : public ResolverObject {
|
||||
}
|
||||
|
||||
const SpriteGroup *ResolveReal(const RealSpriteGroup *group) const override;
|
||||
|
||||
GrfSpecFeature GetFeature() const override
|
||||
{
|
||||
return (GrfSpecFeature)this->generic_scope.feature;
|
||||
}
|
||||
|
||||
uint32 GetDebugID() const override
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
struct GenericCallback {
|
||||
@ -226,6 +238,7 @@ uint16 GetAiPurchaseCallbackResult(uint8 feature, CargoID cargo_type, uint8 defa
|
||||
object.generic_scope.event = event;
|
||||
object.generic_scope.count = count;
|
||||
object.generic_scope.station_size = station_size;
|
||||
object.generic_scope.feature = feature;
|
||||
|
||||
uint16 callback = GetGenericCallbackResult(feature, object, 0, 0, file);
|
||||
if (callback != CALLBACK_FAILED) callback = GB(callback, 0, 8);
|
||||
@ -247,6 +260,7 @@ void AmbientSoundEffectCallback(TileIndex tile)
|
||||
|
||||
/* Prepare resolver object. */
|
||||
GenericResolverObject object(false, CBID_SOUNDS_AMBIENT_EFFECT);
|
||||
object.generic_scope.feature = GSF_SOUNDFX;
|
||||
|
||||
uint32 param1_v7 = GetTileType(tile) << 28 | Clamp(TileHeight(tile), 0, 15) << 24 | GB(r, 16, 8) << 16 | GetTerrainType(tile);
|
||||
uint32 param1_v8 = GetTileType(tile) << 24 | GetTileZ(tile) << 16 | GB(r, 16, 8) << 8 | (HasTileWaterClass(tile) ? GetWaterClass(tile) : 0) << 3 | GetTerrainType(tile);
|
||||
|
@ -62,6 +62,16 @@ HouseResolverObject::HouseResolverObject(HouseID house_id, TileIndex tile, Town
|
||||
this->root_spritegroup = HouseSpec::Get(house_id)->grf_prop.spritegroup[0];
|
||||
}
|
||||
|
||||
GrfSpecFeature HouseResolverObject::GetFeature() const
|
||||
{
|
||||
return GSF_HOUSES;
|
||||
}
|
||||
|
||||
uint32 HouseResolverObject::GetDebugID() const
|
||||
{
|
||||
return HouseSpec::Get(this->house_scope.house_id)->grf_prop.local_id;
|
||||
}
|
||||
|
||||
HouseClassID AllocateHouseClassID(byte grf_class_id, uint32 grfid)
|
||||
{
|
||||
/* Start from 1 because 0 means that no class has been assigned. */
|
||||
|
@ -64,6 +64,9 @@ struct HouseResolverObject : public ResolverObject {
|
||||
default: return ResolverObject::GetScope(scope, relative);
|
||||
}
|
||||
}
|
||||
|
||||
GrfSpecFeature GetFeature() const override;
|
||||
uint32 GetDebugID() const override;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -489,6 +489,16 @@ TownScopeResolver *IndustriesResolverObject::GetTown()
|
||||
return this->town_scope;
|
||||
}
|
||||
|
||||
GrfSpecFeature IndustriesResolverObject::GetFeature() const
|
||||
{
|
||||
return GSF_INDUSTRIES;
|
||||
}
|
||||
|
||||
uint32 IndustriesResolverObject::GetDebugID() const
|
||||
{
|
||||
return GetIndustrySpec(this->industries_scope.type)->grf_prop.local_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform an industry callback.
|
||||
* @param callback The callback to perform.
|
||||
|
@ -63,6 +63,9 @@ struct IndustriesResolverObject : public ResolverObject {
|
||||
return ResolverObject::GetScope(scope, relative);
|
||||
}
|
||||
}
|
||||
|
||||
GrfSpecFeature GetFeature() const override;
|
||||
uint32 GetDebugID() const override;
|
||||
};
|
||||
|
||||
/** When should the industry(tile) be triggered for random bits? */
|
||||
|
@ -139,11 +139,22 @@ IndustryTileResolverObject::IndustryTileResolverObject(IndustryGfx gfx, TileInde
|
||||
CallbackID callback, uint32 callback_param1, uint32 callback_param2)
|
||||
: ResolverObject(GetIndTileGrffile(gfx), callback, callback_param1, callback_param2),
|
||||
indtile_scope(*this, indus, tile),
|
||||
ind_scope(*this, tile, indus, indus->type)
|
||||
ind_scope(*this, tile, indus, indus->type),
|
||||
gfx(gfx)
|
||||
{
|
||||
this->root_spritegroup = GetIndustryTileSpec(gfx)->grf_prop.spritegroup[0];
|
||||
}
|
||||
|
||||
GrfSpecFeature IndustryTileResolverObject::GetFeature() const
|
||||
{
|
||||
return GSF_INDUSTRYTILES;
|
||||
}
|
||||
|
||||
uint32 IndustryTileResolverObject::GetDebugID() const
|
||||
{
|
||||
return GetIndustryTileSpec(gfx)->grf_prop.local_id;
|
||||
}
|
||||
|
||||
static void IndustryDrawTileLayout(const TileInfo *ti, const TileLayoutSpriteGroup *group, byte rnd_colour, byte stage, IndustryGfx gfx)
|
||||
{
|
||||
const DrawTileSprites *dts = group->ProcessRegisters(&stage);
|
||||
|
@ -39,6 +39,7 @@ struct IndustryTileScopeResolver : public ScopeResolver {
|
||||
struct IndustryTileResolverObject : public ResolverObject {
|
||||
IndustryTileScopeResolver indtile_scope; ///< Scope resolver for the industry tile.
|
||||
IndustriesScopeResolver ind_scope; ///< Scope resolver for the industry owning the tile.
|
||||
IndustryGfx gfx;
|
||||
|
||||
IndustryTileResolverObject(IndustryGfx gfx, TileIndex tile, Industry *indus,
|
||||
CallbackID callback = CBID_NO_CALLBACK, uint32 callback_param1 = 0, uint32 callback_param2 = 0);
|
||||
@ -51,6 +52,9 @@ struct IndustryTileResolverObject : public ResolverObject {
|
||||
default: return ResolverObject::GetScope(scope, relative);
|
||||
}
|
||||
}
|
||||
|
||||
GrfSpecFeature GetFeature() const override;
|
||||
uint32 GetDebugID() const override;
|
||||
};
|
||||
|
||||
bool DrawNewIndustryTile(TileInfo *ti, Industry *i, IndustryGfx gfx, const IndustryTileSpec *inds);
|
||||
|
@ -352,7 +352,7 @@ unhandled:
|
||||
*/
|
||||
ObjectResolverObject::ObjectResolverObject(const ObjectSpec *spec, Object *obj, TileIndex tile, uint8 view,
|
||||
CallbackID callback, uint32 param1, uint32 param2)
|
||||
: ResolverObject(spec->grf_prop.grffile, callback, param1, param2), object_scope(*this, obj, tile, view)
|
||||
: ResolverObject(spec->grf_prop.grffile, callback, param1, param2), object_scope(*this, obj, spec, tile, view)
|
||||
{
|
||||
this->town_scope = nullptr;
|
||||
this->root_spritegroup = (obj == nullptr && spec->grf_prop.spritegroup[CT_PURCHASE_OBJECT] != nullptr) ?
|
||||
@ -384,6 +384,16 @@ TownScopeResolver *ObjectResolverObject::GetTown()
|
||||
return this->town_scope;
|
||||
}
|
||||
|
||||
GrfSpecFeature ObjectResolverObject::GetFeature() const
|
||||
{
|
||||
return GSF_OBJECTS;
|
||||
}
|
||||
|
||||
uint32 ObjectResolverObject::GetDebugID() const
|
||||
{
|
||||
return this->object_scope.spec->grf_prop.local_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a callback for an object.
|
||||
* @param callback The callback to perform.
|
||||
|
@ -98,9 +98,10 @@ struct ObjectSpec {
|
||||
|
||||
/** Object scope resolver. */
|
||||
struct ObjectScopeResolver : public ScopeResolver {
|
||||
struct Object *obj; ///< The object the callback is ran for.
|
||||
TileIndex tile; ///< The tile related to the object.
|
||||
uint8 view; ///< The view of the object.
|
||||
struct Object *obj; ///< The object the callback is ran for.
|
||||
const ObjectSpec *spec; ///< Specification of the object type.
|
||||
TileIndex tile; ///< The tile related to the object.
|
||||
uint8 view; ///< The view of the object.
|
||||
|
||||
/**
|
||||
* Constructor of an object scope resolver.
|
||||
@ -109,8 +110,8 @@ struct ObjectScopeResolver : public ScopeResolver {
|
||||
* @param tile %Tile of the object.
|
||||
* @param view View of the object.
|
||||
*/
|
||||
ObjectScopeResolver(ResolverObject &ro, Object *obj, TileIndex tile, uint8 view = 0)
|
||||
: ScopeResolver(ro), obj(obj), tile(tile), view(view)
|
||||
ObjectScopeResolver(ResolverObject &ro, Object *obj, const ObjectSpec *spec, TileIndex tile, uint8 view = 0)
|
||||
: ScopeResolver(ro), obj(obj), spec(spec), tile(tile), view(view)
|
||||
{
|
||||
}
|
||||
|
||||
@ -144,6 +145,9 @@ struct ObjectResolverObject : public ResolverObject {
|
||||
}
|
||||
}
|
||||
|
||||
GrfSpecFeature GetFeature() const override;
|
||||
uint32 GetDebugID() const override;
|
||||
|
||||
private:
|
||||
TownScopeResolver *GetTown();
|
||||
};
|
||||
|
162
src/newgrf_profiling.cpp
Normal file
162
src/newgrf_profiling.cpp
Normal file
@ -0,0 +1,162 @@
|
||||
/*
|
||||
* 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 newgrf_profiling.cpp Profiling of NewGRF action 2 handling. */
|
||||
|
||||
#include "newgrf_profiling.h"
|
||||
#include "date_func.h"
|
||||
#include "fileio_func.h"
|
||||
#include "string_func.h"
|
||||
#include "console_func.h"
|
||||
#include "spritecache.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <time.h>
|
||||
|
||||
|
||||
std::vector<NewGRFProfiler> _newgrf_profilers;
|
||||
Date _newgrf_profile_end_date;
|
||||
|
||||
|
||||
/**
|
||||
* Create profiler object and begin profiling session.
|
||||
* @param grffile The GRF file to collect profiling data on
|
||||
* @param end_date Game date to end profiling on
|
||||
*/
|
||||
NewGRFProfiler::NewGRFProfiler(const GRFFile *grffile) : grffile{ grffile }, active{ false }, cur_call{}
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete profiling session and write data to file
|
||||
*/
|
||||
NewGRFProfiler::~NewGRFProfiler()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Capture the start of a sprite group resolution.
|
||||
* @param resolver Data about sprite group being resolved
|
||||
*/
|
||||
void NewGRFProfiler::BeginResolve(const ResolverObject &resolver)
|
||||
{
|
||||
using namespace std::chrono;
|
||||
this->cur_call.root_sprite = resolver.root_spritegroup->nfo_line;
|
||||
this->cur_call.subs = 0;
|
||||
this->cur_call.time = (uint32)time_point_cast<microseconds>(high_resolution_clock::now()).time_since_epoch().count();
|
||||
this->cur_call.tick = _tick_counter;
|
||||
this->cur_call.cb = resolver.callback;
|
||||
this->cur_call.feat = resolver.GetFeature();
|
||||
this->cur_call.item = resolver.GetDebugID();
|
||||
}
|
||||
|
||||
/**
|
||||
* Capture the completion of a sprite group resolution.
|
||||
*/
|
||||
void NewGRFProfiler::EndResolve(const SpriteGroup *result)
|
||||
{
|
||||
using namespace std::chrono;
|
||||
this->cur_call.time = (uint32)time_point_cast<microseconds>(high_resolution_clock::now()).time_since_epoch().count() - this->cur_call.time;
|
||||
|
||||
if (result == nullptr) {
|
||||
this->cur_call.result = 0;
|
||||
} else if (result->type == SGT_CALLBACK) {
|
||||
this->cur_call.result = static_cast<const CallbackResultSpriteGroup *>(result)->result;
|
||||
} else if (result->type == SGT_RESULT) {
|
||||
this->cur_call.result = GetSpriteLocalID(static_cast<const ResultSpriteGroup *>(result)->sprite);
|
||||
} else {
|
||||
this->cur_call.result = result->nfo_line;
|
||||
}
|
||||
|
||||
this->calls.push_back(this->cur_call);
|
||||
}
|
||||
|
||||
/**
|
||||
* Capture a recursive sprite group resolution.
|
||||
*/
|
||||
void NewGRFProfiler::RecursiveResolve()
|
||||
{
|
||||
this->cur_call.subs += 1;
|
||||
}
|
||||
|
||||
void NewGRFProfiler::Start()
|
||||
{
|
||||
this->Abort();
|
||||
this->active = true;
|
||||
this->start_tick = _tick_counter;
|
||||
}
|
||||
|
||||
uint32 NewGRFProfiler::Finish()
|
||||
{
|
||||
if (!this->active) return 0;
|
||||
|
||||
if (this->calls.empty()) {
|
||||
IConsolePrintF(CC_DEBUG, "Finished profile of NewGRF [%08X], no events collected, not writing a file", BSWAP32(this->grffile->grfid));
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string filename = this->GetOutputFilename();
|
||||
IConsolePrintF(CC_DEBUG, "Finished profile of NewGRF [%08X], writing %u events to %s", BSWAP32(this->grffile->grfid), (uint)this->calls.size(), filename.c_str());
|
||||
|
||||
FILE *f = FioFOpenFile(filename.c_str(), "wt", Subdirectory::NO_DIRECTORY);
|
||||
FileCloser fcloser(f);
|
||||
|
||||
uint32 total_microseconds = 0;
|
||||
|
||||
fputs("Tick,Sprite,Feature,Item,CallbackID,Microseconds,Depth,Result\n", f);
|
||||
for (const Call &c : this->calls) {
|
||||
fprintf(f, "%u,%u,0x%X,%d,0x%X,%u,%u,%u\n", c.tick, c.root_sprite, c.feat, c.item, (uint)c.cb, c.time, c.subs, c.result);
|
||||
total_microseconds += c.time;
|
||||
}
|
||||
|
||||
this->Abort();
|
||||
|
||||
return total_microseconds;
|
||||
}
|
||||
|
||||
void NewGRFProfiler::Abort()
|
||||
{
|
||||
this->active = false;
|
||||
this->calls.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get name of the file that will be written.
|
||||
* @return File name of profiling output file.
|
||||
*/
|
||||
std::string NewGRFProfiler::GetOutputFilename() const
|
||||
{
|
||||
time_t write_time = time(nullptr);
|
||||
|
||||
char timestamp[16] = {};
|
||||
strftime(timestamp, lengthof(timestamp), "%Y%m%d-%H%M", localtime(&write_time));
|
||||
|
||||
char filepath[MAX_PATH] = {};
|
||||
seprintf(filepath, lastof(filepath), "%sgrfprofile-%s-%08X.csv", FiosGetScreenshotDir(), timestamp, BSWAP32(this->grffile->grfid));
|
||||
|
||||
return std::string(filepath);
|
||||
}
|
||||
|
||||
uint32 NewGRFProfiler::FinishAll()
|
||||
{
|
||||
int max_ticks = 0;
|
||||
uint32 total_microseconds = 0;
|
||||
for (NewGRFProfiler &pr : _newgrf_profilers) {
|
||||
if (pr.active) {
|
||||
total_microseconds += pr.Finish();
|
||||
max_ticks = max(max_ticks, _tick_counter - pr.start_tick);
|
||||
}
|
||||
}
|
||||
|
||||
if (total_microseconds > 0 && max_ticks > 0) {
|
||||
IConsolePrintF(CC_DEBUG, "Total NewGRF callback processing: %u microseconds over %d ticks", total_microseconds, max_ticks);
|
||||
}
|
||||
|
||||
_newgrf_profile_end_date = MAX_DAY;
|
||||
|
||||
return total_microseconds;
|
||||
}
|
63
src/newgrf_profiling.h
Normal file
63
src/newgrf_profiling.h
Normal file
@ -0,0 +1,63 @@
|
||||
/*
|
||||
* 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 newgrf_profiling.h Profiling of NewGRF action 2 handling. */
|
||||
|
||||
#ifndef NEWGRF_PROFILING_H
|
||||
#define NEWGRF_PROFILING_H
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "date_type.h"
|
||||
#include "newgrf.h"
|
||||
#include "newgrf_callbacks.h"
|
||||
#include "newgrf_spritegroup.h"
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
|
||||
/**
|
||||
* Callback profiler for NewGRF development
|
||||
*/
|
||||
struct NewGRFProfiler {
|
||||
NewGRFProfiler(const GRFFile *grffile);
|
||||
~NewGRFProfiler();
|
||||
|
||||
void BeginResolve(const ResolverObject &resolver);
|
||||
void EndResolve(const SpriteGroup *result);
|
||||
void RecursiveResolve();
|
||||
|
||||
void Start();
|
||||
uint32 Finish();
|
||||
void Abort();
|
||||
std::string GetOutputFilename() const;
|
||||
|
||||
static uint32 FinishAll();
|
||||
|
||||
/** Measurement of a single sprite group resolution */
|
||||
struct Call {
|
||||
uint32 root_sprite; ///< Pseudo-sprite index in GRF file
|
||||
uint32 item; ///< Local ID of item being resolved for
|
||||
uint32 result; ///< Result of callback
|
||||
uint32 subs; ///< Sub-calls to other sprite groups
|
||||
uint32 time; ///< Time taken for resolution (microseconds)
|
||||
uint16 tick; ///< Game tick
|
||||
CallbackID cb; ///< Callback ID
|
||||
GrfSpecFeature feat; ///< GRF feature being resolved for
|
||||
};
|
||||
|
||||
const GRFFile *grffile; ///< Which GRF is being profiled
|
||||
bool active; ///< Is this profiler collecting data
|
||||
uint16 start_tick; ///< Tick number this profiler was started on
|
||||
Call cur_call; ///< Data for current call in progress
|
||||
std::vector<Call> calls; ///< All calls collected so far
|
||||
};
|
||||
|
||||
extern std::vector<NewGRFProfiler> _newgrf_profilers;
|
||||
extern Date _newgrf_profile_end_date;
|
||||
|
||||
#endif /* NEWGRF_PROFILING_H */
|
@ -65,6 +65,16 @@
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
GrfSpecFeature RailTypeResolverObject::GetFeature() const
|
||||
{
|
||||
return GSF_RAILTYPES;
|
||||
}
|
||||
|
||||
uint32 RailTypeResolverObject::GetDebugID() const
|
||||
{
|
||||
return this->railtype_scope.rti->label;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolver object for rail types.
|
||||
* @param rti Railtype. nullptr in NewGRF Inspect window.
|
||||
@ -75,7 +85,7 @@
|
||||
* @param param2 Extra parameter (second parameter of the callback, except railtypes do not have callbacks).
|
||||
*/
|
||||
RailTypeResolverObject::RailTypeResolverObject(const RailtypeInfo *rti, TileIndex tile, TileContext context, RailTypeSpriteGroup rtsg, uint32 param1, uint32 param2)
|
||||
: ResolverObject(rti != nullptr ? rti->grffile[rtsg] : nullptr, CBID_NO_CALLBACK, param1, param2), railtype_scope(*this, tile, context)
|
||||
: ResolverObject(rti != nullptr ? rti->grffile[rtsg] : nullptr, CBID_NO_CALLBACK, param1, param2), railtype_scope(*this, rti, tile, context)
|
||||
{
|
||||
this->root_spritegroup = rti != nullptr ? rti->group[rtsg] : nullptr;
|
||||
}
|
||||
|
@ -18,6 +18,7 @@
|
||||
struct RailTypeScopeResolver : public ScopeResolver {
|
||||
TileIndex tile; ///< Tracktile. For track on a bridge this is the southern bridgehead.
|
||||
TileContext context; ///< Are we resolving sprites for the upper halftile, or on a bridge?
|
||||
const RailtypeInfo *rti;
|
||||
|
||||
/**
|
||||
* Constructor of the railtype scope resolvers.
|
||||
@ -25,8 +26,8 @@ struct RailTypeScopeResolver : public ScopeResolver {
|
||||
* @param tile %Tile containing the track. For track on a bridge this is the southern bridgehead.
|
||||
* @param context Are we resolving sprites for the upper halftile, or on a bridge?
|
||||
*/
|
||||
RailTypeScopeResolver(ResolverObject &ro, TileIndex tile, TileContext context)
|
||||
: ScopeResolver(ro), tile(tile), context(context)
|
||||
RailTypeScopeResolver(ResolverObject &ro, const RailtypeInfo *rti, TileIndex tile, TileContext context)
|
||||
: ScopeResolver(ro), tile(tile), context(context), rti(rti)
|
||||
{
|
||||
}
|
||||
|
||||
@ -49,6 +50,9 @@ struct RailTypeResolverObject : public ResolverObject {
|
||||
}
|
||||
|
||||
const SpriteGroup *ResolveReal(const RealSpriteGroup *group) const override;
|
||||
|
||||
GrfSpecFeature GetFeature() const override;
|
||||
uint32 GetDebugID() const override;
|
||||
};
|
||||
|
||||
SpriteID GetCustomRailSprite(const RailtypeInfo *rti, TileIndex tile, RailTypeSpriteGroup rtsg, TileContext context = TCX_NORMAL, uint *num_results = nullptr);
|
||||
|
@ -65,16 +65,32 @@
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
GrfSpecFeature RoadTypeResolverObject::GetFeature() const
|
||||
{
|
||||
RoadType rt = GetRoadTypeByLabel(this->roadtype_scope.rti->label, false);
|
||||
switch (GetRoadTramType(rt)) {
|
||||
case RTT_ROAD: return GSF_ROADTYPES;
|
||||
case RTT_TRAM: return GSF_TRAMTYPES;
|
||||
default: return GSF_INVALID;
|
||||
}
|
||||
}
|
||||
|
||||
uint32 RoadTypeResolverObject::GetDebugID() const
|
||||
{
|
||||
return this->roadtype_scope.rti->label;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor of the roadtype scope resolvers.
|
||||
* @param ro Surrounding resolver.
|
||||
* @param tile %Tile containing the track. For track on a bridge this is the southern bridgehead.
|
||||
* @param context Are we resolving sprites for the upper halftile, or on a bridge?
|
||||
*/
|
||||
RoadTypeScopeResolver::RoadTypeScopeResolver(ResolverObject &ro, TileIndex tile, TileContext context) : ScopeResolver(ro)
|
||||
RoadTypeScopeResolver::RoadTypeScopeResolver(ResolverObject &ro, const RoadTypeInfo *rti, TileIndex tile, TileContext context) : ScopeResolver(ro)
|
||||
{
|
||||
this->tile = tile;
|
||||
this->context = context;
|
||||
this->rti = rti;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -87,7 +103,7 @@ RoadTypeScopeResolver::RoadTypeScopeResolver(ResolverObject &ro, TileIndex tile,
|
||||
* @param param2 Extra parameter (second parameter of the callback, except roadtypes do not have callbacks).
|
||||
*/
|
||||
RoadTypeResolverObject::RoadTypeResolverObject(const RoadTypeInfo *rti, TileIndex tile, TileContext context, RoadTypeSpriteGroup rtsg, uint32 param1, uint32 param2)
|
||||
: ResolverObject(rti != nullptr ? rti->grffile[rtsg] : nullptr, CBID_NO_CALLBACK, param1, param2), roadtype_scope(*this, tile, context)
|
||||
: ResolverObject(rti != nullptr ? rti->grffile[rtsg] : nullptr, CBID_NO_CALLBACK, param1, param2), roadtype_scope(*this, rti, tile, context)
|
||||
{
|
||||
this->root_spritegroup = rti != nullptr ? rti->group[rtsg] : nullptr;
|
||||
}
|
||||
|
@ -18,8 +18,9 @@
|
||||
struct RoadTypeScopeResolver : public ScopeResolver {
|
||||
TileIndex tile; ///< Tracktile. For track on a bridge this is the southern bridgehead.
|
||||
TileContext context; ///< Are we resolving sprites for the upper halftile, or on a bridge?
|
||||
const RoadTypeInfo *rti;
|
||||
|
||||
RoadTypeScopeResolver(ResolverObject &ro, TileIndex tile, TileContext context);
|
||||
RoadTypeScopeResolver(ResolverObject &ro, const RoadTypeInfo *rti, TileIndex tile, TileContext context);
|
||||
|
||||
/* virtual */ uint32 GetRandomBits() const;
|
||||
/* virtual */ uint32 GetVariable(byte variable, uint32 parameter, bool *available) const;
|
||||
@ -40,6 +41,9 @@ struct RoadTypeResolverObject : public ResolverObject {
|
||||
}
|
||||
|
||||
/* virtual */ const SpriteGroup *ResolveReal(const RealSpriteGroup *group) const;
|
||||
|
||||
GrfSpecFeature GetFeature() const override;
|
||||
uint32 GetDebugID() const override;
|
||||
};
|
||||
|
||||
SpriteID GetCustomRoadSprite(const RoadTypeInfo *rti, TileIndex tile, RoadTypeSpriteGroup rtsg, TileContext context = TCX_NORMAL, uint *num_results = nullptr);
|
||||
|
@ -11,6 +11,7 @@
|
||||
#include <algorithm>
|
||||
#include "debug.h"
|
||||
#include "newgrf_spritegroup.h"
|
||||
#include "newgrf_profiling.h"
|
||||
#include "core/pool_func.hpp"
|
||||
|
||||
#include "safeguards.h"
|
||||
@ -34,10 +35,23 @@ TemporaryStorageArray<int32, 0x110> _temp_store;
|
||||
/* static */ const SpriteGroup *SpriteGroup::Resolve(const SpriteGroup *group, ResolverObject &object, bool top_level)
|
||||
{
|
||||
if (group == nullptr) return nullptr;
|
||||
if (top_level) {
|
||||
|
||||
const GRFFile *grf = object.grffile;
|
||||
auto profiler = std::find_if(_newgrf_profilers.begin(), _newgrf_profilers.end(), [&](const NewGRFProfiler &pr) { return pr.grffile == grf; });
|
||||
|
||||
if (profiler == _newgrf_profilers.end() || !profiler->active) {
|
||||
if (top_level) _temp_store.ClearChanges();
|
||||
return group->Resolve(object);
|
||||
} else if (top_level) {
|
||||
profiler->BeginResolve(object);
|
||||
_temp_store.ClearChanges();
|
||||
const SpriteGroup *result = group->Resolve(object);
|
||||
profiler->EndResolve(result);
|
||||
return result;
|
||||
} else {
|
||||
profiler->RecursiveResolve();
|
||||
return group->Resolve(object);
|
||||
}
|
||||
return group->Resolve(object);
|
||||
}
|
||||
|
||||
RealSpriteGroup::~RealSpriteGroup()
|
||||
|
@ -56,13 +56,14 @@ extern SpriteGroupPool _spritegroup_pool;
|
||||
/* Common wrapper for all the different sprite group types */
|
||||
struct SpriteGroup : SpriteGroupPool::PoolItem<&_spritegroup_pool> {
|
||||
protected:
|
||||
SpriteGroup(SpriteGroupType type) : type(type) {}
|
||||
SpriteGroup(SpriteGroupType type) : nfo_line(0), type(type) {}
|
||||
/** Base sprite group resolver */
|
||||
virtual const SpriteGroup *Resolve(ResolverObject &object) const { return this; };
|
||||
|
||||
public:
|
||||
virtual ~SpriteGroup() {}
|
||||
|
||||
uint32 nfo_line;
|
||||
SpriteGroupType type;
|
||||
|
||||
virtual SpriteID GetResult() const { return 0; }
|
||||
@ -398,6 +399,18 @@ struct ResolverObject {
|
||||
this->used_triggers = 0;
|
||||
memset(this->reseed, 0, sizeof(this->reseed));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the feature number being resolved for.
|
||||
* This function is mainly intended for the callback profiling feature.
|
||||
*/
|
||||
virtual GrfSpecFeature GetFeature() const { return GSF_INVALID; }
|
||||
/**
|
||||
* Get an identifier for the item being resolved.
|
||||
* This function is mainly intended for the callback profiling feature,
|
||||
* and should return an identifier recognisable by the NewGRF developer.
|
||||
*/
|
||||
virtual uint32 GetDebugID() const { return 0; }
|
||||
};
|
||||
|
||||
#endif /* NEWGRF_SPRITEGROUP_H */
|
||||
|
@ -527,6 +527,16 @@ uint32 Waypoint::GetNewGRFVariable(const ResolverObject &object, byte variable,
|
||||
return group->loading[0];
|
||||
}
|
||||
|
||||
GrfSpecFeature StationResolverObject::GetFeature() const
|
||||
{
|
||||
return GSF_STATIONS;
|
||||
}
|
||||
|
||||
uint32 StationResolverObject::GetDebugID() const
|
||||
{
|
||||
return this->station_scope.statspec->grf_prop.local_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolver for stations.
|
||||
* @param statspec Station (type) specification.
|
||||
|
@ -75,6 +75,9 @@ struct StationResolverObject : public ResolverObject {
|
||||
}
|
||||
|
||||
const SpriteGroup *ResolveReal(const RealSpriteGroup *group) const override;
|
||||
|
||||
GrfSpecFeature GetFeature() const override;
|
||||
uint32 GetDebugID() const override;
|
||||
};
|
||||
|
||||
enum StationClassID : byte {
|
||||
|
@ -145,6 +145,17 @@ uint GetOriginFileSlot(SpriteID sprite)
|
||||
return GetSpriteCache(sprite)->file_slot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the GRF-local sprite id of a given sprite.
|
||||
* @param sprite The sprite to look at.
|
||||
* @return The GRF-local sprite id.
|
||||
*/
|
||||
uint32 GetSpriteLocalID(SpriteID sprite)
|
||||
{
|
||||
if (!SpriteExists(sprite)) return 0;
|
||||
return GetSpriteCache(sprite)->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Count the sprites which originate from a specific file slot in a range of SpriteIDs.
|
||||
* @param file_slot FIOS file slot.
|
||||
|
@ -30,6 +30,7 @@ bool SpriteExists(SpriteID sprite);
|
||||
|
||||
SpriteType GetSpriteType(SpriteID sprite);
|
||||
uint GetOriginFileSlot(SpriteID sprite);
|
||||
uint32 GetSpriteLocalID(SpriteID sprite);
|
||||
uint GetSpriteCountForSlot(uint file_slot, SpriteID begin, SpriteID end);
|
||||
uint GetMaxSpriteID();
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user