Change road vehicle path cache to be optional and use ring buffers

Show path cache in debug window
This commit is contained in:
Jonathan G Rennison
2023-08-16 13:03:37 +01:00
parent 1183476182
commit 2ae4e5bdc1
7 changed files with 151 additions and 56 deletions

View File

@@ -41,9 +41,6 @@ static const int YAPF_INFINITE_PENALTY = 1000 * YAPF_TILE_LENGTH;
/** Maximum length of ship path cache */ /** Maximum length of ship path cache */
static const int YAPF_SHIP_PATH_CACHE_LENGTH = 32; static const int YAPF_SHIP_PATH_CACHE_LENGTH = 32;
/** Maximum segments of road vehicle path cache */
static const int YAPF_ROADVEH_PATH_CACHE_SEGMENTS = 16;
/** Distance from destination road stops to not cache any further */ /** Distance from destination road stops to not cache any further */
static const int YAPF_ROADVEH_PATH_CACHE_DESTINATION_LIMIT = 8; static const int YAPF_ROADVEH_PATH_CACHE_DESTINATION_LIMIT = 8;

View File

@@ -495,16 +495,11 @@ public:
Trackdir next_trackdir = INVALID_TRACKDIR; Trackdir next_trackdir = INVALID_TRACKDIR;
Node *pNode = Yapf().GetBestNode(); Node *pNode = Yapf().GetBestNode();
if (pNode != nullptr) { if (pNode != nullptr) {
uint steps = 0;
for (Node *n = pNode; n->m_parent != nullptr; n = n->m_parent) steps++;
/* path was found or at least suggested /* path was found or at least suggested
* walk through the path back to its origin */ * walk through the path back to its origin */
while (pNode->m_parent != nullptr) { while (pNode->m_parent != nullptr) {
steps--; if (pNode->GetIsChoice()) {
if (pNode->GetIsChoice() && steps < YAPF_ROADVEH_PATH_CACHE_SEGMENTS) { path_cache.push_front(pNode->GetTile(), pNode->GetTrackdir());
path_cache.td.push_front(pNode->GetTrackdir());
path_cache.tile.push_front(pNode->GetTile());
} }
pNode = pNode->m_parent; pNode = pNode->m_parent;
} }
@@ -514,8 +509,7 @@ public:
next_trackdir = best_next_node.GetTrackdir(); next_trackdir = best_next_node.GetTrackdir();
/* remove last element for the special case when tile == dest_tile */ /* remove last element for the special case when tile == dest_tile */
if (path_found && !path_cache.empty() && tile == v->dest_tile) { if (path_found && !path_cache.empty() && tile == v->dest_tile) {
path_cache.td.pop_back(); path_cache.pop_back();
path_cache.tile.pop_back();
} }
path_cache.layout_ctr = _road_layout_change_counter; path_cache.layout_ctr = _road_layout_change_counter;
@@ -523,9 +517,8 @@ public:
if (multiple_targets) { if (multiple_targets) {
/* Destination station has at least 2 usable road stops, or first is a drive-through stop, /* Destination station has at least 2 usable road stops, or first is a drive-through stop,
* trim end of path cache within a number of tiles of road stop tile area */ * trim end of path cache within a number of tiles of road stop tile area */
while (!path_cache.empty() && non_cached_area.Contains(path_cache.tile.back())) { while (!path_cache.empty() && non_cached_area.Contains(path_cache.back_tile())) {
path_cache.td.pop_back(); path_cache.pop_back();
path_cache.tile.pop_back();
} }
} }
} }

View File

@@ -17,7 +17,7 @@
#include "road.h" #include "road.h"
#include "road_map.h" #include "road_map.h"
#include "newgrf_engine.h" #include "newgrf_engine.h"
#include <deque> #include <array>
struct RoadVehicle; struct RoadVehicle;
@@ -79,26 +79,56 @@ static const uint RVC_DEPOT_STOP_FRAME = 11;
/** The number of ticks a vehicle has for overtaking. */ /** The number of ticks a vehicle has for overtaking. */
static const byte RV_OVERTAKE_TIMEOUT = 35; static const byte RV_OVERTAKE_TIMEOUT = 35;
/** Maximum segments of road vehicle path cache */
static const uint8 RV_PATH_CACHE_SEGMENTS = 16;
static const uint8 RV_PATH_CACHE_SEGMENT_MASK = (RV_PATH_CACHE_SEGMENTS - 1);
static_assert((RV_PATH_CACHE_SEGMENTS & RV_PATH_CACHE_SEGMENT_MASK) == 0, ""); // Must be a power of 2
void RoadVehUpdateCache(RoadVehicle *v, bool same_length = false); void RoadVehUpdateCache(RoadVehicle *v, bool same_length = false);
void GetRoadVehSpriteSize(EngineID engine, uint &width, uint &height, int &xoffs, int &yoffs, EngineImageType image_type); void GetRoadVehSpriteSize(EngineID engine, uint &width, uint &height, int &xoffs, int &yoffs, EngineImageType image_type);
struct RoadVehPathCache { struct RoadVehPathCache {
std::deque<Trackdir> td; std::array<TileIndex, RV_PATH_CACHE_SEGMENTS> tile;
std::deque<TileIndex> tile; std::array<Trackdir, RV_PATH_CACHE_SEGMENTS> td;
uint32 layout_ctr; uint32 layout_ctr = 0;
uint8 start = 0;
uint8 count = 0;
inline bool empty() const { return this->td.empty(); } inline bool empty() const { return this->count == 0; }
inline uint8 size() const { return this->count; }
inline size_t size() const inline bool full() const { return this->count >= RV_PATH_CACHE_SEGMENTS; }
{
dbg_assert(this->td.size() == this->tile.size());
return this->td.size();
}
inline void clear() inline void clear()
{ {
this->td.clear(); this->start = 0;
this->tile.clear(); this->count = 0;
}
inline TileIndex front_tile() const { return this->tile[this->start]; }
inline Trackdir front_td() const { return this->td[this->start]; }
inline uint8 back_index() const { return (this->start + this->count - 1) & RV_PATH_CACHE_SEGMENT_MASK; }
inline TileIndex back_tile() const { return this->tile[this->back_index()]; }
inline Trackdir back_td() const { return this->td[this->back_index()]; }
/* push an item to the front of the ring, if the ring is already full, the back item is overwritten */
inline void push_front(TileIndex tile, Trackdir td)
{
this->start = (this->start - 1) & RV_PATH_CACHE_SEGMENT_MASK;
if (!this->full()) this->count++;
this->tile[this->start] = tile;
this->td[this->start] = td;
}
inline void pop_front()
{
this->start = (this->start + 1) & RV_PATH_CACHE_SEGMENT_MASK;
this->count--;
}
inline void pop_back()
{
this->count--;
} }
}; };
@@ -106,7 +136,7 @@ struct RoadVehPathCache {
* Buses, trucks and trams belong to this class. * Buses, trucks and trams belong to this class.
*/ */
struct RoadVehicle FINAL : public GroundVehicle<RoadVehicle, VEH_ROAD> { struct RoadVehicle FINAL : public GroundVehicle<RoadVehicle, VEH_ROAD> {
RoadVehPathCache path; ///< Cached path. std::unique_ptr<RoadVehPathCache> cached_path; ///< Cached path.
byte state; ///< @see RoadVehicleStates byte state; ///< @see RoadVehicleStates
byte frame; byte frame;
uint16 blocked_ctr; uint16 blocked_ctr;
@@ -175,6 +205,12 @@ struct RoadVehicle FINAL : public GroundVehicle<RoadVehicle, VEH_ROAD> {
void SetRoadVehicleOvertaking(byte overtaking); void SetRoadVehicleOvertaking(byte overtaking);
inline RoadVehPathCache &GetOrCreatePathCache()
{
if (!this->cached_path) this->cached_path.reset(new RoadVehPathCache());
return *this->cached_path;
}
protected: // These functions should not be called outside acceleration code. protected: // These functions should not be called outside acceleration code.
/** /**

View File

@@ -924,8 +924,8 @@ static bool CheckRoadInfraUnsuitableForOvertaking(OvertakeData *od)
if (trackbits & ~TRACK_BIT_CROSS) { if (trackbits & ~TRACK_BIT_CROSS) {
RoadCachedOneWayState rcows = GetRoadCachedOneWayState(od->tile); RoadCachedOneWayState rcows = GetRoadCachedOneWayState(od->tile);
if (rcows == RCOWS_SIDE_JUNCTION) { if (rcows == RCOWS_SIDE_JUNCTION) {
const RoadVehPathCache &pc = od->v->path; const RoadVehPathCache *pc = od->v->cached_path.get();
if (!pc.empty() && pc.tile.front() == od->tile && !IsStraightRoadTrackdir(pc.td.front())) { if (pc != nullptr && !pc->empty() && pc->front_tile() == od->tile && !IsStraightRoadTrackdir(pc->front_td())) {
/* cached path indicates that we are turning here, do not overtake */ /* cached path indicates that we are turning here, do not overtake */
return true; return true;
} }
@@ -1210,7 +1210,7 @@ static Trackdir RoadFindPathToDest(RoadVehicle *v, TileIndex tile, DiagDirection
trackdirs &= DiagdirReachesTrackdirs(enterdir); trackdirs &= DiagdirReachesTrackdirs(enterdir);
if (trackdirs == TRACKDIR_BIT_NONE) { if (trackdirs == TRACKDIR_BIT_NONE) {
/* If vehicle expected a path, it no longer exists, so invalidate it. */ /* If vehicle expected a path, it no longer exists, so invalidate it. */
if (!v->path.empty()) v->path.clear(); if (v->cached_path != nullptr) v->cached_path->clear();
/* No reachable tracks, so we'll reverse */ /* No reachable tracks, so we'll reverse */
return_track(_road_reverse_table[enterdir]); return_track(_road_reverse_table[enterdir]);
} }
@@ -1241,40 +1241,39 @@ static Trackdir RoadFindPathToDest(RoadVehicle *v, TileIndex tile, DiagDirection
/* Only one track to choose between? */ /* Only one track to choose between? */
if (KillFirstBit(trackdirs) == TRACKDIR_BIT_NONE) { if (KillFirstBit(trackdirs) == TRACKDIR_BIT_NONE) {
if (!v->path.empty() && v->path.tile.front() == tile) { if (v->cached_path != nullptr && !v->cached_path->empty() && v->cached_path->front_tile() == tile) {
/* Vehicle expected a choice here, invalidate its path. */ /* Vehicle expected a choice here, invalidate its path. */
v->path.clear(); v->cached_path->clear();
} }
return_track(FindFirstBit2x64(trackdirs)); return_track(FindFirstBit2x64(trackdirs));
} }
/* Path cache is out of date, clear it */ /* Path cache is out of date, clear it */
if (!v->path.empty() && v->path.layout_ctr != _road_layout_change_counter) { if (v->cached_path != nullptr && !v->cached_path->empty() && v->cached_path->layout_ctr != _road_layout_change_counter) {
v->path.clear(); v->cached_path->clear();
} }
/* Attempt to follow cached path. */ /* Attempt to follow cached path. */
if (!v->path.empty()) { if (v->cached_path != nullptr && !v->cached_path->empty()) {
if (v->path.tile.front() != tile) { if (v->cached_path->front_tile() != tile) {
/* Vehicle didn't expect a choice here, invalidate its path. */ /* Vehicle didn't expect a choice here, invalidate its path. */
v->path.clear(); v->cached_path->clear();
} else { } else {
Trackdir trackdir = v->path.td.front(); Trackdir trackdir = v->cached_path->front_td();
if (HasBit(trackdirs, trackdir)) { if (HasBit(trackdirs, trackdir)) {
v->path.td.pop_front(); v->cached_path->pop_front();
v->path.tile.pop_front();
return_track(trackdir); return_track(trackdir);
} }
/* Vehicle expected a choice which is no longer available. */ /* Vehicle expected a choice which is no longer available. */
v->path.clear(); v->cached_path->clear();
} }
} }
switch (_settings_game.pf.pathfinder_for_roadvehs) { switch (_settings_game.pf.pathfinder_for_roadvehs) {
case VPF_NPF: best_track = NPFRoadVehicleChooseTrack(v, tile, enterdir, path_found); break; case VPF_NPF: best_track = NPFRoadVehicleChooseTrack(v, tile, enterdir, path_found); break;
case VPF_YAPF: best_track = YapfRoadVehicleChooseTrack(v, tile, enterdir, trackdirs, path_found, v->path); break; case VPF_YAPF: best_track = YapfRoadVehicleChooseTrack(v, tile, enterdir, trackdirs, path_found, v->GetOrCreatePathCache()); break;
default: NOT_REACHED(); default: NOT_REACHED();
} }
@@ -1814,8 +1813,7 @@ again:
if (u != nullptr) { if (u != nullptr) {
v->cur_speed = u->First()->cur_speed; v->cur_speed = u->First()->cur_speed;
/* We might be blocked, prevent pathfinding rerun as we already know where we are heading to. */ /* We might be blocked, prevent pathfinding rerun as we already know where we are heading to. */
v->path.tile.push_front(tile); v->GetOrCreatePathCache().push_front(tile, dir);
v->path.td.push_front(dir);
return false; return false;
} }
} }
@@ -1931,8 +1929,7 @@ again:
if (u != nullptr) { if (u != nullptr) {
v->cur_speed = u->First()->cur_speed; v->cur_speed = u->First()->cur_speed;
/* We might be blocked, prevent pathfinding rerun as we already know where we are heading to. */ /* We might be blocked, prevent pathfinding rerun as we already know where we are heading to. */
v->path.tile.push_front(v->tile); v->GetOrCreatePathCache().push_front(v->tile, dir);
v->path.td.push_front(dir);
return false; return false;
} }
} }
@@ -2226,7 +2223,7 @@ bool RoadVehicle::Tick()
void RoadVehicle::SetDestTile(TileIndex tile) void RoadVehicle::SetDestTile(TileIndex tile)
{ {
if (tile == this->dest_tile) return; if (tile == this->dest_tile) return;
this->path.clear(); if (this->cached_path != nullptr) this->cached_path->clear();
this->dest_tile = tile; this->dest_tile = tile;
} }

View File

@@ -24,6 +24,7 @@
#include "../disaster_vehicle.h" #include "../disaster_vehicle.h"
#include <map> #include <map>
#include <vector>
#include "../safeguards.h" #include "../safeguards.h"
@@ -32,6 +33,9 @@ bool TrainController(Train *v, Vehicle *nomove, bool reverse = true); // From tr
void ReverseTrainDirection(Train *v); void ReverseTrainDirection(Train *v);
void ReverseTrainSwapVeh(Train *v, int l, int r); void ReverseTrainSwapVeh(Train *v, int l, int r);
static std::vector<Trackdir> _path_td;
static std::vector<TileIndex> _path_tile;
namespace upstream_sl { namespace upstream_sl {
static uint8 _cargo_days; static uint8 _cargo_days;
@@ -247,8 +251,8 @@ public:
SLE_VAR(RoadVehicle, overtaking_ctr, SLE_UINT8), SLE_VAR(RoadVehicle, overtaking_ctr, SLE_UINT8),
SLE_VAR(RoadVehicle, crashed_ctr, SLE_UINT16), SLE_VAR(RoadVehicle, crashed_ctr, SLE_UINT16),
SLE_VAR(RoadVehicle, reverse_ctr, SLE_UINT8), SLE_VAR(RoadVehicle, reverse_ctr, SLE_UINT8),
SLE_CONDDEQUE(RoadVehicle, path.td, SLE_UINT8, SLV_ROADVEH_PATH_CACHE, SL_MAX_VERSION), SLEG_CONDVECTOR("path.td", _path_td, SLE_UINT8, SLV_SHIP_PATH_CACHE, SL_MAX_VERSION),
SLE_CONDDEQUE(RoadVehicle, path.tile, SLE_UINT32, SLV_ROADVEH_PATH_CACHE, SL_MAX_VERSION), SLEG_CONDVECTOR("path.tile", _path_tile, SLE_UINT32, SLV_SHIP_PATH_CACHE, SL_MAX_VERSION),
SLE_CONDVAR(RoadVehicle, gv_flags, SLE_UINT16, SLV_139, SL_MAX_VERSION), SLE_CONDVAR(RoadVehicle, gv_flags, SLE_UINT16, SLV_139, SL_MAX_VERSION),
}; };
inline const static SaveLoadCompatTable compat_description = _vehicle_roadveh_sl_compat; inline const static SaveLoadCompatTable compat_description = _vehicle_roadveh_sl_compat;
@@ -263,6 +267,18 @@ public:
{ {
if (v->type != VEH_ROAD) return; if (v->type != VEH_ROAD) return;
SlObject(v, this->GetLoadDescription()); SlObject(v, this->GetLoadDescription());
if (!_path_td.empty() && _path_td.size() <= RV_PATH_CACHE_SEGMENTS && _path_td.size() == _path_tile.size()) {
RoadVehicle *rv = RoadVehicle::From(v);
rv->cached_path.reset(new RoadVehPathCache());
rv->cached_path->count = _path_td.size();
for (size_t i = 0; i < _path_td.size(); i++) {
rv->cached_path->td[i] = _path_td[i];
rv->cached_path->tile[i] = _path_tile[i];
}
}
_path_td.clear();
_path_tile.clear();
} }
void FixPointers(Vehicle *v) const override void FixPointers(Vehicle *v) const override

View File

@@ -644,6 +644,9 @@ static Money _cargo_feeder_share;
static uint32 _cargo_loaded_at_xy; static uint32 _cargo_loaded_at_xy;
CargoPacketList _cpp_packets; CargoPacketList _cpp_packets;
std::map<VehicleID, CargoPacketList> _veh_cpp_packets; std::map<VehicleID, CargoPacketList> _veh_cpp_packets;
static std::vector<Trackdir> _path_td;
static std::vector<TileIndex> _path_tile;
static uint32 _path_layout_ctr;
static uint32 _old_ahead_separation; static uint32 _old_ahead_separation;
@@ -858,9 +861,9 @@ SaveLoadTable GetVehicleDescription(VehicleType vt)
SLE_VAR(RoadVehicle, overtaking_ctr, SLE_UINT8), SLE_VAR(RoadVehicle, overtaking_ctr, SLE_UINT8),
SLE_VAR(RoadVehicle, crashed_ctr, SLE_UINT16), SLE_VAR(RoadVehicle, crashed_ctr, SLE_UINT16),
SLE_VAR(RoadVehicle, reverse_ctr, SLE_UINT8), SLE_VAR(RoadVehicle, reverse_ctr, SLE_UINT8),
SLE_CONDDEQUE(RoadVehicle, path.td, SLE_UINT8, SLV_ROADVEH_PATH_CACHE, SL_MAX_VERSION), SLEG_CONDVARVEC(_path_td, SLE_UINT8, SLV_ROADVEH_PATH_CACHE, SL_MAX_VERSION),
SLE_CONDDEQUE(RoadVehicle, path.tile, SLE_UINT32, SLV_ROADVEH_PATH_CACHE, SL_MAX_VERSION), SLEG_CONDVARVEC(_path_tile, SLE_UINT32, SLV_ROADVEH_PATH_CACHE, SL_MAX_VERSION),
SLE_CONDVAR_X(RoadVehicle, path.layout_ctr, SLE_UINT32, SL_MIN_VERSION, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_ROAD_LAYOUT_CHANGE_CTR)), SLEG_CONDVAR_X(_path_layout_ctr, SLE_UINT32, SL_MIN_VERSION, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_ROAD_LAYOUT_CHANGE_CTR)),
SLE_CONDNULL(2, SLV_6, SLV_69), SLE_CONDNULL(2, SLV_6, SLV_69),
SLE_CONDVAR(RoadVehicle, gv_flags, SLE_UINT16, SLV_139, SL_MAX_VERSION), SLE_CONDVAR(RoadVehicle, gv_flags, SLE_UINT16, SLV_139, SL_MAX_VERSION),
@@ -1025,6 +1028,22 @@ static void Save_VEHS()
SetupDescs_VEHS(); SetupDescs_VEHS();
/* Write the vehicles */ /* Write the vehicles */
for (Vehicle *v : Vehicle::Iterate()) { for (Vehicle *v : Vehicle::Iterate()) {
if (v->type == VEH_ROAD) {
_path_td.clear();
_path_tile.clear();
_path_layout_ctr = 0;
RoadVehicle *rv = RoadVehicle::From(v);
if (rv->cached_path != nullptr && !rv->cached_path->empty()) {
uint idx = rv->cached_path->start;
for (uint i = 0; i < rv->cached_path->size(); i++) {
_path_td.push_back(rv->cached_path->td[idx]);
_path_tile.push_back(rv->cached_path->tile[idx]);
idx = (idx + 1) & RV_PATH_CACHE_SEGMENT_MASK;
}
_path_layout_ctr = rv->cached_path->layout_ctr;
}
}
SlSetArrayIndex(v->index); SlSetArrayIndex(v->index);
SlObjectSaveFiltered(v, GetVehicleDescriptionFiltered(v->type)); SlObjectSaveFiltered(v, GetVehicleDescriptionFiltered(v->type));
} }
@@ -1042,6 +1061,10 @@ void Load_VEHS()
_cpp_packets.clear(); _cpp_packets.clear();
_veh_cpp_packets.clear(); _veh_cpp_packets.clear();
_path_td.clear();
_path_tile.clear();
_path_layout_ctr = 0;
while ((index = SlIterateArray()) != -1) { while ((index = SlIterateArray()) != -1) {
Vehicle *v; Vehicle *v;
VehicleType vtype = (VehicleType)SlReadByte(); VehicleType vtype = (VehicleType)SlReadByte();
@@ -1090,6 +1113,17 @@ void Load_VEHS()
if (SlXvIsFeaturePresent(XSLFI_AUTO_TIMETABLE, 1, 4)) { if (SlXvIsFeaturePresent(XSLFI_AUTO_TIMETABLE, 1, 4)) {
SB(v->vehicle_flags, VF_SEPARATION_ACTIVE, 1, _old_ahead_separation ? 1 : 0); SB(v->vehicle_flags, VF_SEPARATION_ACTIVE, 1, _old_ahead_separation ? 1 : 0);
} }
if (vtype == VEH_ROAD && !_path_td.empty() && _path_td.size() <= RV_PATH_CACHE_SEGMENTS && _path_td.size() == _path_tile.size()) {
RoadVehicle *rv = RoadVehicle::From(v);
rv->cached_path.reset(new RoadVehPathCache());
rv->cached_path->count = _path_td.size();
for (size_t i = 0; i < _path_td.size(); i++) {
rv->cached_path->td[i] = _path_td[i];
rv->cached_path->tile[i] = _path_tile[i];
}
rv->cached_path->layout_ctr = _path_layout_ctr;
}
} }
} }

View File

@@ -413,9 +413,31 @@ class NIHVehicle : public NIHelper {
seprintf(buffer, lastof(buffer), " Overtaking: %u, overtaking_ctr: %u, overtaking threshold: %u", seprintf(buffer, lastof(buffer), " Overtaking: %u, overtaking_ctr: %u, overtaking threshold: %u",
rv->overtaking, rv->overtaking_ctr, rv->GetOvertakingCounterThreshold()); rv->overtaking, rv->overtaking_ctr, rv->GetOvertakingCounterThreshold());
output.print(buffer); output.print(buffer);
seprintf(buffer, lastof(buffer), " Speed: %u, path cache length: %u", seprintf(buffer, lastof(buffer), " Speed: %u", rv->cur_speed);
rv->cur_speed, (uint) rv->path.size());
output.print(buffer); output.print(buffer);
b = buffer + seprintf(buffer, lastof(buffer), " Path cache: ");
if (rv->cached_path != nullptr) {
b += seprintf(b, lastof(buffer), "length: %u, layout ctr: %X (current: %X)", (uint)rv->cached_path->size(), rv->cached_path->layout_ctr, _road_layout_change_counter);
output.print(buffer);
b = buffer;
uint idx = rv->cached_path->start;
for (uint i = 0; i < rv->cached_path->size(); i++) {
if ((i & 3) == 0) {
if (b > buffer + 4) output.print(buffer);
b = buffer + seprintf(buffer, lastof(buffer), " ");
} else {
b += seprintf(b, lastof(buffer), ", ");
}
b += seprintf(b, lastof(buffer), "(%ux%u, %X)", TileX(rv->cached_path->tile[idx]), TileY(rv->cached_path->tile[idx]), rv->cached_path->td[idx]);
idx = (idx + 1) & RV_PATH_CACHE_SEGMENT_MASK;
}
if (b > buffer + 4) output.print(buffer);
} else {
b += seprintf(b, lastof(buffer), "none");
output.print(buffer);
}
output.register_next_line_click_flag_toggle(8 << flag_shift); output.register_next_line_click_flag_toggle(8 << flag_shift);
seprintf(buffer, lastof(buffer), " [%c] Roadtype: %u (%s), Compatible: 0x" OTTD_PRINTFHEX64, seprintf(buffer, lastof(buffer), " [%c] Roadtype: %u (%s), Compatible: 0x" OTTD_PRINTFHEX64,
(output.flags & (8 << flag_shift)) ? '-' : '+', rv->roadtype, dumper().RoadTypeLabel(rv->roadtype), rv->compatible_roadtypes); (output.flags & (8 << flag_shift)) ? '-' : '+', rv->roadtype, dumper().RoadTypeLabel(rv->roadtype), rv->compatible_roadtypes);