diff --git a/src/pathfinder/yapf/yapf_road.cpp b/src/pathfinder/yapf/yapf_road.cpp index 267332653c..94db5b00c9 100644 --- a/src/pathfinder/yapf/yapf_road.cpp +++ b/src/pathfinder/yapf/yapf_road.cpp @@ -401,8 +401,7 @@ public: while (pNode->parent != nullptr) { steps--; if (pNode->GetIsChoice() && steps < YAPF_ROADVEH_PATH_CACHE_SEGMENTS) { - path_cache.td.push_front(pNode->GetTrackdir()); - path_cache.tile.push_front(pNode->GetTile()); + path_cache.emplace_back(pNode->GetTrackdir(), pNode->GetTile()); } pNode = pNode->parent; } @@ -410,13 +409,9 @@ public: Node &best_next_node = *pNode; assert(best_next_node.GetTile() == tile); 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(); if (st) { const RoadStop *stop = st->GetPrimaryRoadStop(v); @@ -425,10 +420,10 @@ public: * 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; 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(); - path_cache.tile.pop_back(); - } + + /* Find the first tile not contained by the non-cachable area, and remove from the cache. */ + 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); } } } diff --git a/src/pathfinder/yapf/yapf_ship.cpp b/src/pathfinder/yapf/yapf_ship.cpp index 8bec19e067..c2dde7eb55 100644 --- a/src/pathfinder/yapf/yapf_ship.cpp +++ b/src/pathfinder/yapf/yapf_ship.cpp @@ -204,8 +204,11 @@ public: if (path_cache.empty()) return INVALID_TRACKDIR; - const Trackdir result = path_cache.front(); - path_cache.pop_front(); + /* Reverse the path so we can take from the end. */ + std::reverse(std::begin(path_cache), std::end(path_cache)); + + const Trackdir result = path_cache.back().trackdir; + path_cache.pop_back(); return result; } @@ -258,7 +261,7 @@ public: /* 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. */ 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 { path_cache.clear(); } @@ -278,8 +281,8 @@ public: if (path_cache.empty()) return CreateRandomPath(v, path_cache, 1); /* Take out the last trackdir as the result. */ - const Trackdir result = path_cache.front(); - path_cache.pop_front(); + const Trackdir result = path_cache.back().trackdir; + 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. */ if (start_water_patch == end_water_patch) path_cache.clear(); diff --git a/src/roadveh.h b/src/roadveh.h index 0907d2f3bd..90790b9b51 100644 --- a/src/roadveh.h +++ b/src/roadveh.h @@ -81,25 +81,17 @@ static const uint8_t RV_OVERTAKE_TIMEOUT = 35; void RoadVehUpdateCache(RoadVehicle *v, bool same_length = false); void GetRoadVehSpriteSize(EngineID engine, uint &width, uint &height, int &xoffs, int &yoffs, EngineImageType image_type); -struct RoadVehPathCache { - std::deque td; - std::deque tile; +/** Element of the RoadVehPathCache. */ +struct RoadVehPathElement { + Trackdir trackdir; ///< Trackdir for this element. + TileIndex tile; ///< Tile for this element. - inline bool empty() const { return this->td.empty(); } - - 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(); - } + constexpr RoadVehPathElement() : trackdir(INVALID_TRACKDIR), tile(INVALID_TILE) {} + constexpr RoadVehPathElement(Trackdir trackdir, TileIndex tile) : trackdir(trackdir), tile(tile) {} }; +using RoadVehPathCache = std::vector; + /** * Buses, trucks and trams belong to this class. */ diff --git a/src/roadveh_cmd.cpp b/src/roadveh_cmd.cpp index c3b977b32a..a56c92cd22 100644 --- a/src/roadveh_cmd.cpp +++ b/src/roadveh_cmd.cpp @@ -957,7 +957,7 @@ static Trackdir RoadFindPathToDest(RoadVehicle *v, TileIndex tile, DiagDirection /* Only one track to choose between? */ 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. */ v->path.clear(); } @@ -966,15 +966,14 @@ static Trackdir RoadFindPathToDest(RoadVehicle *v, TileIndex tile, DiagDirection /* Attempt to follow cached path. */ 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. */ v->path.clear(); } else { - Trackdir trackdir = v->path.td.front(); + Trackdir trackdir = v->path.back().trackdir; if (HasBit(trackdirs, trackdir)) { - v->path.td.pop_front(); - v->path.tile.pop_front(); + v->path.pop_back(); return_track(trackdir); } @@ -1281,8 +1280,7 @@ again: if (u != nullptr) { v->cur_speed = u->First()->cur_speed; /* We might be blocked, prevent pathfinding rerun as we already know where we are heading to. */ - v->path.tile.push_front(tile); - v->path.td.push_front(dir); + v->path.emplace_back(dir, tile); return false; } } @@ -1397,8 +1395,7 @@ again: if (u != nullptr) { v->cur_speed = u->First()->cur_speed; /* 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.td.push_front(dir); + v->path.emplace_back(dir, v->tile); return false; } } diff --git a/src/saveload/saveload.h b/src/saveload/saveload.h index 63ccac6236..f1150ddcf3 100644 --- a/src/saveload/saveload.h +++ b/src/saveload/saveload.h @@ -390,7 +390,9 @@ enum SaveLoadVersion : uint16_t { SLV_WATER_TILE_TYPE, ///< 342 PR#13030 Simplify water tile type. SLV_PRODUCTION_HISTORY, ///< 343 PR#10541 Industry production history. SLV_ROAD_TYPE_LABEL_MAP, ///< 344 PR#13021 Add road type label map to allow upgrade/conversion of road types. + SLV_NONFLOODING_WATER_TILES, ///< 345 PR#13013 Store water tile non-flooding state. + SLV_PATH_CACHE_FORMAT, ///< 346 PR#12345 Vehicle path cache format changed. SL_MAX_VERSION, ///< Highest possible saveload version }; @@ -946,6 +948,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) +/** + * 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. * @param base Name of the class or struct containing the list. diff --git a/src/saveload/vehicle_sl.cpp b/src/saveload/vehicle_sl.cpp index 91936f387c..169b8426d1 100644 --- a/src/saveload/vehicle_sl.cpp +++ b/src/saveload/vehicle_sl.cpp @@ -12,6 +12,7 @@ #include "saveload.h" #include "compat/vehicle_sl_compat.h" +#include "../debug.h" #include "../vehicle_func.h" #include "../train.h" #include "../roadveh.h" @@ -821,8 +822,23 @@ public: } }; +class SlVehicleRoadVehPath : public VectorSaveLoadHandler { +public: + inline static const SaveLoad description[] = { + SLE_VAR(RoadVehPathElement, trackdir, SLE_UINT8), + SLE_VAR(RoadVehPathElement, tile, SLE_UINT32), + }; + inline const static SaveLoadCompatTable compat_description = {}; + + std::vector &GetVector(RoadVehicle *rv) const override { return rv->path; } +}; + class SlVehicleRoadVeh : public DefaultSaveLoadHandler { public: + /* RoadVehicle path is stored in std::pair which cannot be directly saved. */ + static inline std::vector rv_path_td; + static inline std::vector rv_path_tile; + inline static const SaveLoad description[] = { SLEG_STRUCT("common", SlVehicleCommon), SLE_VAR(RoadVehicle, state, SLE_UINT8), @@ -832,12 +848,32 @@ public: SLE_VAR(RoadVehicle, overtaking_ctr, SLE_UINT8), SLE_VAR(RoadVehicle, crashed_ctr, SLE_UINT16), SLE_VAR(RoadVehicle, reverse_ctr, SLE_UINT8), - SLE_CONDDEQUE(RoadVehicle, 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.td", rv_path_td, SLE_UINT8, SLV_ROADVEH_PATH_CACHE, SLV_PATH_CACHE_FORMAT), + SLEG_CONDVECTOR("path.tile", rv_path_tile, SLE_UINT32, SLV_ROADVEH_PATH_CACHE, SLV_PATH_CACHE_FORMAT), + SLEG_CONDSTRUCTLIST("path", SlVehicleRoadVehPath, SLV_PATH_CACHE_FORMAT, 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; + static void ConvertPathCache(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]); + } + + /* 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 { if (v->type != VEH_ROAD) return; @@ -848,6 +884,9 @@ public: { if (v->type != VEH_ROAD) return; SlObject(v, this->GetLoadDescription()); + if (!IsSavegameVersionBefore(SLV_ROADVEH_PATH_CACHE) && IsSavegameVersionBefore(SLV_PATH_CACHE_FORMAT)) { + ConvertPathCache(*static_cast(v)); + } } void FixPointers(Vehicle *v) const override @@ -857,12 +896,25 @@ public: } }; +class SlVehicleShipPath : public VectorSaveLoadHandler { +public: + inline static const SaveLoad description[] = { + SLE_VAR(ShipPathElement, trackdir, SLE_UINT8), + }; + inline const static SaveLoadCompatTable compat_description = {}; + + std::vector &GetVector(Ship *s) const override { return s->path; } +}; + class SlVehicleShip : public DefaultSaveLoadHandler { public: + static inline std::vector ship_path_td; + inline static const SaveLoad description[] = { SLEG_STRUCT("common", SlVehicleCommon), SLE_VAR(Ship, state, SLE_UINT8), - SLE_CONDDEQUE(Ship, path, SLE_UINT8, SLV_SHIP_PATH_CACHE, SL_MAX_VERSION), + SLEG_CONDVECTOR("path", ship_path_td, SLE_UINT8, SLV_SHIP_PATH_CACHE, SLV_PATH_CACHE_FORMAT), + SLEG_CONDSTRUCTLIST("path", SlVehicleShipPath, SLV_PATH_CACHE_FORMAT, 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; @@ -877,6 +929,12 @@ public: { if (v->type != VEH_SHIP) return; 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(v); + std::transform(std::rbegin(ship_path_td), std::rend(ship_path_td), std::back_inserter(s->path), [](Trackdir trackdir) { return trackdir; }); + } } void FixPointers(Vehicle *v) const override diff --git a/src/ship.h b/src/ship.h index c47be05436..7dfca66a24 100644 --- a/src/ship.h +++ b/src/ship.h @@ -16,7 +16,15 @@ void GetShipSpriteSize(EngineID engine, uint &width, uint &height, int &xoffs, int &yoffs, EngineImageType image_type); WaterClass GetEffectiveWaterClass(TileIndex tile); -typedef std::deque ShipPathCache; +/** Element of the ShipPathCache. */ +struct ShipPathElement { + Trackdir trackdir; ///< Trackdir for this element. + + constexpr ShipPathElement() : trackdir(INVALID_TRACKDIR) {} + constexpr ShipPathElement(Trackdir trackdir) : trackdir(trackdir) {} +}; + +using ShipPathCache = std::vector; /** * All ships have this type. diff --git a/src/ship_cmd.cpp b/src/ship_cmd.cpp index 3184f3badb..9007b8b3a6 100644 --- a/src/ship_cmd.cpp +++ b/src/ship_cmd.cpp @@ -506,10 +506,10 @@ static Track ChooseShipTrack(Ship *v, TileIndex tile, TrackBits tracks) } else { /* Attempt to follow cached path. */ if (!v->path.empty()) { - track = TrackdirToTrack(v->path.front()); + track = TrackdirToTrack(v->path.back().trackdir); 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. */ return track; } @@ -854,7 +854,7 @@ static void ShipController(Ship *v) /* 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. */ - 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 */