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.
pull/132/head
Niels Martin Hansen 4 years ago committed by GitHub
parent f88ac83408
commit c8779fb311
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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();
};

@ -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;
}

@ -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…
Cancel
Save