Codechange: Replace path cache queues with vectors.

Ship and RoadVehicle path caches use a std::deque, which is quite memory hungry, especially for RoadVehicle which has two.
std::deque was used to be able to push/pop from either end.

Change to use a single std::vector each, which is now push/popped from the back.
This commit is contained in:
Peter Nelson 2024-04-22 18:50:15 +01:00
parent c82a2575d7
commit b4839cc7da
No known key found for this signature in database
GPG Key ID: 8EF8F0A467DF75ED
8 changed files with 96 additions and 49 deletions

View File

@ -393,8 +393,7 @@ public:
while (pNode->m_parent != nullptr) { while (pNode->m_parent != nullptr) {
steps--; steps--;
if (pNode->GetIsChoice() && steps < YAPF_ROADVEH_PATH_CACHE_SEGMENTS) { if (pNode->GetIsChoice() && steps < YAPF_ROADVEH_PATH_CACHE_SEGMENTS) {
path_cache.td.push_front(pNode->GetTrackdir()); path_cache.emplace_back(pNode->GetTrackdir(), pNode->GetTile());
path_cache.tile.push_front(pNode->GetTile());
} }
pNode = pNode->m_parent; pNode = pNode->m_parent;
} }
@ -402,13 +401,9 @@ public:
Node &best_next_node = *pNode; Node &best_next_node = *pNode;
assert(best_next_node.GetTile() == tile); assert(best_next_node.GetTile() == tile);
next_trackdir = best_next_node.GetTrackdir(); next_trackdir = best_next_node.GetTrackdir();
/* remove last element for the special case when tile == dest_tile */
if (path_found && !path_cache.empty() && tile == v->dest_tile) {
path_cache.td.pop_back();
path_cache.tile.pop_back();
}
/* Check if target is a station, and cached path ends within 8 tiles of the dest tile */ /* Check if target is a station, and cached path leads to within YAPF_ROADVEH_PATH_CACHE_DESTINATION_LIMIT
* tiles of the dest tile */
const Station *st = Yapf().GetDestinationStation(); const Station *st = Yapf().GetDestinationStation();
if (st) { if (st) {
const RoadStop *stop = st->GetPrimaryRoadStop(v); const RoadStop *stop = st->GetPrimaryRoadStop(v);
@ -417,10 +412,10 @@ public:
* 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 */
TileArea non_cached_area = v->IsBus() ? st->bus_station : st->truck_station; TileArea non_cached_area = v->IsBus() ? st->bus_station : st->truck_station;
non_cached_area.Expand(YAPF_ROADVEH_PATH_CACHE_DESTINATION_LIMIT); non_cached_area.Expand(YAPF_ROADVEH_PATH_CACHE_DESTINATION_LIMIT);
while (!path_cache.empty() && non_cached_area.Contains(path_cache.tile.back())) {
path_cache.td.pop_back(); /* Find the first tile not contained by the non-cachable area, and remove from the cache. */
path_cache.tile.pop_back(); auto it = std::find_if(std::begin(path_cache), std::end(path_cache), [&non_cached_area](const auto &pc) { return !non_cached_area.Contains(pc.tile); });
} path_cache.erase(std::begin(path_cache), it);
} }
} }
} }

View File

@ -205,8 +205,11 @@ public:
if (path_cache.empty()) return INVALID_TRACKDIR; if (path_cache.empty()) return INVALID_TRACKDIR;
const Trackdir result = path_cache.front(); /* Reverse the path so we can take from the end. */
path_cache.pop_front(); std::reverse(std::begin(path_cache), std::end(path_cache));
const Trackdir result = path_cache.back();
path_cache.pop_back();
return result; return result;
} }
@ -259,7 +262,7 @@ public:
/* The cached path must always lead to a region patch that's on the high level path. /* The cached path must always lead to a region patch that's on the high level path.
* This is what can happen when that's not the case https://github.com/OpenTTD/OpenTTD/issues/12176. */ * This is what can happen when that's not the case https://github.com/OpenTTD/OpenTTD/issues/12176. */
if (add_full_path || !node_water_patch_on_high_level_path || node_water_patch == start_water_patch) { if (add_full_path || !node_water_patch_on_high_level_path || node_water_patch == start_water_patch) {
path_cache.push_front(node->GetTrackdir()); path_cache.push_back(node->GetTrackdir());
} else { } else {
path_cache.clear(); path_cache.clear();
} }
@ -279,8 +282,8 @@ public:
if (path_cache.empty()) return CreateRandomPath(v, path_cache, 1); if (path_cache.empty()) return CreateRandomPath(v, path_cache, 1);
/* Take out the last trackdir as the result. */ /* Take out the last trackdir as the result. */
const Trackdir result = path_cache.front(); const Trackdir result = path_cache.back();
path_cache.pop_front(); path_cache.pop_back();
/* Clear path cache when in final water region patch. This is to allow ships to spread over different docking tiles dynamically. */ /* Clear path cache when in final water region patch. This is to allow ships to spread over different docking tiles dynamically. */
if (start_water_patch == end_water_patch) path_cache.clear(); if (start_water_patch == end_water_patch) path_cache.clear();

View File

@ -81,25 +81,15 @@ static const uint8_t RV_OVERTAKE_TIMEOUT = 35;
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 RoadVehPath {
std::deque<Trackdir> td; Trackdir dir;
std::deque<TileIndex> tile; TileIndex tile;
inline bool empty() const { return this->td.empty(); } constexpr RoadVehPath(Trackdir dir, TileIndex tile) : dir(dir), tile(tile) { }
inline size_t size() const
{
assert(this->td.size() == this->tile.size());
return this->td.size();
}
inline void clear()
{
this->td.clear();
this->tile.clear();
}
}; };
using RoadVehPathCache = std::vector<RoadVehPath>;
/** /**
* Buses, trucks and trams belong to this class. * Buses, trucks and trams belong to this class.
*/ */

View File

@ -963,7 +963,7 @@ 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->path.empty() && v->path.back().tile == tile) {
/* Vehicle expected a choice here, invalidate its path. */ /* Vehicle expected a choice here, invalidate its path. */
v->path.clear(); v->path.clear();
} }
@ -972,15 +972,14 @@ static Trackdir RoadFindPathToDest(RoadVehicle *v, TileIndex tile, DiagDirection
/* Attempt to follow cached path. */ /* Attempt to follow cached path. */
if (!v->path.empty()) { if (!v->path.empty()) {
if (v->path.tile.front() != tile) { if (v->path.back().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->path.clear();
} else { } else {
Trackdir trackdir = v->path.td.front(); Trackdir trackdir = v->path.back().dir;
if (HasBit(trackdirs, trackdir)) { if (HasBit(trackdirs, trackdir)) {
v->path.td.pop_front(); v->path.pop_back();
v->path.tile.pop_front();
return_track(trackdir); return_track(trackdir);
} }
@ -1291,8 +1290,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->path.emplace_back(dir, tile);
v->path.td.push_front(dir);
return false; return false;
} }
} }
@ -1407,8 +1405,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->path.emplace_back(dir, v->tile);
v->path.td.push_front(dir);
return false; return false;
} }
} }

View File

@ -379,6 +379,8 @@ enum SaveLoadVersion : uint16_t {
SLV_SCRIPT_RANDOMIZER, ///< 333 PR#12063 v14.0-RC1 Save script randomizers. SLV_SCRIPT_RANDOMIZER, ///< 333 PR#12063 v14.0-RC1 Save script randomizers.
SLV_VEHICLE_ECONOMY_AGE, ///< 334 PR#12141 v14.0 Add vehicle age in economy year, for profit stats minimum age SLV_VEHICLE_ECONOMY_AGE, ///< 334 PR#12141 v14.0 Add vehicle age in economy year, for profit stats minimum age
SLV_PATH_CACHE_FORMAT, ///< 335 PR#12345 Vehicle path cache format changed.
SL_MAX_VERSION, ///< Highest possible saveload version SL_MAX_VERSION, ///< Highest possible saveload version
}; };
@ -934,6 +936,16 @@ inline constexpr bool SlCheckVarSize(SaveLoadType cmd, VarType type, size_t leng
*/ */
#define SLE_CONDREFLIST(base, variable, type, from, to) SLE_GENERAL(SL_REFLIST, base, variable, type, 0, from, to, 0) #define SLE_CONDREFLIST(base, variable, type, from, to) SLE_GENERAL(SL_REFLIST, base, variable, type, 0, from, to, 0)
/**
* Storage of a vector of #SL_VAR elements in some savegame versions.
* @param base Name of the class or struct containing the list.
* @param variable Name of the variable in the class or struct referenced by \a base.
* @param type Storage of the data in memory and in the savegame.
* @param from First savegame version that has the list.
* @param to Last savegame version that has the list.
*/
#define SLE_CONDVECTOR(base, variable, type, from, to) SLE_GENERAL(SL_VECTOR, base, variable, type, 0, from, to, 0)
/** /**
* Storage of a deque of #SL_VAR elements in some savegame versions. * Storage of a deque of #SL_VAR elements in some savegame versions.
* @param base Name of the class or struct containing the list. * @param base Name of the class or struct containing the list.

View File

@ -12,6 +12,7 @@
#include "saveload.h" #include "saveload.h"
#include "compat/vehicle_sl_compat.h" #include "compat/vehicle_sl_compat.h"
#include "../debug.h"
#include "../vehicle_func.h" #include "../vehicle_func.h"
#include "../train.h" #include "../train.h"
#include "../roadveh.h" #include "../roadveh.h"
@ -819,6 +820,10 @@ public:
class SlVehicleRoadVeh : public DefaultSaveLoadHandler<SlVehicleRoadVeh, Vehicle> { class SlVehicleRoadVeh : public DefaultSaveLoadHandler<SlVehicleRoadVeh, Vehicle> {
public: public:
/* RoadVehicle path is stored in std::pair which cannot be directly saved. */
static inline std::vector<Trackdir> rv_path_td;
static inline std::vector<TileIndex> rv_path_tile;
inline static const SaveLoad description[] = { inline static const SaveLoad description[] = {
SLEG_STRUCT("common", SlVehicleCommon), SLEG_STRUCT("common", SlVehicleCommon),
SLE_VAR(RoadVehicle, state, SLE_UINT8), SLE_VAR(RoadVehicle, state, SLE_UINT8),
@ -828,22 +833,61 @@ 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", rv_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_CONDVECTOR("path.tile", rv_path_tile, SLE_UINT32, SLV_ROADVEH_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;
static void ClearPathCache()
{
rv_path_td.clear();
rv_path_tile.clear();
}
static void SplitPathCache(RoadVehicle &rv)
{
for (const auto &pair : rv.path) {
rv_path_td.push_back(pair.dir);
rv_path_tile.push_back(pair.tile);
}
}
static void MergePathCache(RoadVehicle &rv)
{
/* The two vectors should be the same size, but if not we can just ignore the cache and not cause more issues. */
if (rv_path_td.size() != rv_path_tile.size()) {
Debug(sl, 1, "Found RoadVehicle {} with invalid path cache, ignoring.", rv.index);
return;
}
size_t n = std::min(rv_path_td.size(), rv_path_tile.size());
if (n == 0) return;
rv.path.reserve(n);
for (size_t c = 0; c < n; ++c) {
rv.path.emplace_back(rv_path_td[c], rv_path_tile[c]);
}
if (IsSavegameVersionBefore(SLV_PATH_CACHE_FORMAT)) {
/* Path cache is now taken from back instead of front, so needs reversing. */
std::reverse(std::begin(rv.path), std::end(rv.path));
}
}
void Save(Vehicle *v) const override void Save(Vehicle *v) const override
{ {
if (v->type != VEH_ROAD) return; if (v->type != VEH_ROAD) return;
ClearPathCache();
SplitPathCache(*static_cast<RoadVehicle *>(v));
SlObject(v, this->GetDescription()); SlObject(v, this->GetDescription());
} }
void Load(Vehicle *v) const override void Load(Vehicle *v) const override
{ {
if (v->type != VEH_ROAD) return; if (v->type != VEH_ROAD) return;
ClearPathCache();
SlObject(v, this->GetLoadDescription()); SlObject(v, this->GetLoadDescription());
MergePathCache(*static_cast<RoadVehicle *>(v));
} }
void FixPointers(Vehicle *v) const override void FixPointers(Vehicle *v) const override
@ -858,7 +902,7 @@ public:
inline static const SaveLoad description[] = { inline static const SaveLoad description[] = {
SLEG_STRUCT("common", SlVehicleCommon), SLEG_STRUCT("common", SlVehicleCommon),
SLE_VAR(Ship, state, SLE_UINT8), SLE_VAR(Ship, state, SLE_UINT8),
SLE_CONDDEQUE(Ship, path, SLE_UINT8, SLV_SHIP_PATH_CACHE, SL_MAX_VERSION), SLE_CONDVECTOR(Ship, path, SLE_UINT8, SLV_SHIP_PATH_CACHE, SL_MAX_VERSION),
SLE_CONDVAR(Ship, rotation, SLE_UINT8, SLV_SHIP_ROTATION, SL_MAX_VERSION), SLE_CONDVAR(Ship, rotation, SLE_UINT8, SLV_SHIP_ROTATION, SL_MAX_VERSION),
}; };
inline const static SaveLoadCompatTable compat_description = _vehicle_ship_sl_compat; inline const static SaveLoadCompatTable compat_description = _vehicle_ship_sl_compat;
@ -873,6 +917,12 @@ public:
{ {
if (v->type != VEH_SHIP) return; if (v->type != VEH_SHIP) return;
SlObject(v, this->GetLoadDescription()); SlObject(v, this->GetLoadDescription());
if (IsSavegameVersionBefore(SLV_PATH_CACHE_FORMAT)) {
/* Path cache is now taken from back instead of front, so needs reversing. */
Ship *s = static_cast<Ship *>(v);
std::reverse(std::begin(s->path), std::end(s->path));
}
} }
void FixPointers(Vehicle *v) const override void FixPointers(Vehicle *v) const override

View File

@ -16,7 +16,7 @@
void GetShipSpriteSize(EngineID engine, uint &width, uint &height, int &xoffs, int &yoffs, EngineImageType image_type); void GetShipSpriteSize(EngineID engine, uint &width, uint &height, int &xoffs, int &yoffs, EngineImageType image_type);
WaterClass GetEffectiveWaterClass(TileIndex tile); WaterClass GetEffectiveWaterClass(TileIndex tile);
typedef std::deque<Trackdir> ShipPathCache; using ShipPathCache = std::vector<Trackdir>;
/** /**
* All ships have this type. * All ships have this type.

View File

@ -518,10 +518,10 @@ static Track ChooseShipTrack(Ship *v, TileIndex tile, TrackBits tracks)
} else { } else {
/* Attempt to follow cached path. */ /* Attempt to follow cached path. */
if (!v->path.empty()) { if (!v->path.empty()) {
track = TrackdirToTrack(v->path.front()); track = TrackdirToTrack(v->path.back());
if (HasBit(tracks, track)) { if (HasBit(tracks, track)) {
v->path.pop_front(); v->path.pop_back();
/* HandlePathfindResult() is not called here because this is not a new pathfinder result. */ /* HandlePathfindResult() is not called here because this is not a new pathfinder result. */
return track; return track;
} }
@ -870,7 +870,7 @@ static void ShipController(Ship *v)
/* Ship is back on the bridge head, we need to consume its path /* Ship is back on the bridge head, we need to consume its path
* cache entry here as we didn't have to choose a ship track. */ * cache entry here as we didn't have to choose a ship track. */
if (!v->path.empty()) v->path.pop_front(); if (!v->path.empty()) v->path.pop_back();
} }
/* update image of ship, as well as delta XY */ /* update image of ship, as well as delta XY */