diff --git a/src/pathfinder/pathfinder_type.h b/src/pathfinder/pathfinder_type.h index 79eeb8ec73..6151dea3ee 100644 --- a/src/pathfinder/pathfinder_type.h +++ b/src/pathfinder/pathfinder_type.h @@ -41,9 +41,6 @@ static const int YAPF_INFINITE_PENALTY = 1000 * YAPF_TILE_LENGTH; /** Maximum length of ship path cache */ 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 */ static const int YAPF_ROADVEH_PATH_CACHE_DESTINATION_LIMIT = 8; diff --git a/src/pathfinder/yapf/yapf_road.cpp b/src/pathfinder/yapf/yapf_road.cpp index 3bc96521e5..2b365381ff 100644 --- a/src/pathfinder/yapf/yapf_road.cpp +++ b/src/pathfinder/yapf/yapf_road.cpp @@ -495,16 +495,11 @@ public: Trackdir next_trackdir = INVALID_TRACKDIR; Node *pNode = Yapf().GetBestNode(); 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 * walk through the path back to its origin */ while (pNode->m_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()); + if (pNode->GetIsChoice()) { + path_cache.push_front(pNode->GetTile(), pNode->GetTrackdir()); } pNode = pNode->m_parent; } @@ -514,8 +509,7 @@ public: 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(); + path_cache.pop_back(); } path_cache.layout_ctr = _road_layout_change_counter; @@ -523,9 +517,8 @@ public: if (multiple_targets) { /* 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 */ - while (!path_cache.empty() && non_cached_area.Contains(path_cache.tile.back())) { - path_cache.td.pop_back(); - path_cache.tile.pop_back(); + while (!path_cache.empty() && non_cached_area.Contains(path_cache.back_tile())) { + path_cache.pop_back(); } } } diff --git a/src/roadveh.h b/src/roadveh.h index c3bae4bf33..02b5bb339a 100644 --- a/src/roadveh.h +++ b/src/roadveh.h @@ -17,7 +17,7 @@ #include "road.h" #include "road_map.h" #include "newgrf_engine.h" -#include +#include struct RoadVehicle; @@ -79,26 +79,56 @@ static const uint RVC_DEPOT_STOP_FRAME = 11; /** The number of ticks a vehicle has for overtaking. */ 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 GetRoadVehSpriteSize(EngineID engine, uint &width, uint &height, int &xoffs, int &yoffs, EngineImageType image_type); struct RoadVehPathCache { - std::deque td; - std::deque tile; - uint32 layout_ctr; + std::array tile; + std::array td; + uint32 layout_ctr = 0; + uint8 start = 0; + uint8 count = 0; - inline bool empty() const { return this->td.empty(); } - - inline size_t size() const - { - dbg_assert(this->td.size() == this->tile.size()); - return this->td.size(); - } + inline bool empty() const { return this->count == 0; } + inline uint8 size() const { return this->count; } + inline bool full() const { return this->count >= RV_PATH_CACHE_SEGMENTS; } inline void clear() { - this->td.clear(); - this->tile.clear(); + this->start = 0; + 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. */ struct RoadVehicle FINAL : public GroundVehicle { - RoadVehPathCache path; ///< Cached path. + std::unique_ptr cached_path; ///< Cached path. byte state; ///< @see RoadVehicleStates byte frame; uint16 blocked_ctr; @@ -175,6 +205,12 @@ struct RoadVehicle FINAL : public GroundVehicle { 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. /** diff --git a/src/roadveh_cmd.cpp b/src/roadveh_cmd.cpp index 8871cb6b2d..f6933f385e 100644 --- a/src/roadveh_cmd.cpp +++ b/src/roadveh_cmd.cpp @@ -924,8 +924,8 @@ static bool CheckRoadInfraUnsuitableForOvertaking(OvertakeData *od) if (trackbits & ~TRACK_BIT_CROSS) { RoadCachedOneWayState rcows = GetRoadCachedOneWayState(od->tile); if (rcows == RCOWS_SIDE_JUNCTION) { - const RoadVehPathCache &pc = od->v->path; - if (!pc.empty() && pc.tile.front() == od->tile && !IsStraightRoadTrackdir(pc.td.front())) { + const RoadVehPathCache *pc = od->v->cached_path.get(); + 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 */ return true; } @@ -1210,7 +1210,7 @@ static Trackdir RoadFindPathToDest(RoadVehicle *v, TileIndex tile, DiagDirection trackdirs &= DiagdirReachesTrackdirs(enterdir); if (trackdirs == TRACKDIR_BIT_NONE) { /* 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 */ return_track(_road_reverse_table[enterdir]); } @@ -1241,40 +1241,39 @@ 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->cached_path != nullptr && !v->cached_path->empty() && v->cached_path->front_tile() == tile) { /* Vehicle expected a choice here, invalidate its path. */ - v->path.clear(); + v->cached_path->clear(); } return_track(FindFirstBit2x64(trackdirs)); } /* Path cache is out of date, clear it */ - if (!v->path.empty() && v->path.layout_ctr != _road_layout_change_counter) { - v->path.clear(); + if (v->cached_path != nullptr && !v->cached_path->empty() && v->cached_path->layout_ctr != _road_layout_change_counter) { + v->cached_path->clear(); } /* Attempt to follow cached path. */ - if (!v->path.empty()) { - if (v->path.tile.front() != tile) { + if (v->cached_path != nullptr && !v->cached_path->empty()) { + if (v->cached_path->front_tile() != tile) { /* Vehicle didn't expect a choice here, invalidate its path. */ - v->path.clear(); + v->cached_path->clear(); } else { - Trackdir trackdir = v->path.td.front(); + Trackdir trackdir = v->cached_path->front_td(); if (HasBit(trackdirs, trackdir)) { - v->path.td.pop_front(); - v->path.tile.pop_front(); + v->cached_path->pop_front(); return_track(trackdir); } /* Vehicle expected a choice which is no longer available. */ - v->path.clear(); + v->cached_path->clear(); } } switch (_settings_game.pf.pathfinder_for_roadvehs) { 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(); } @@ -1814,8 +1813,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->GetOrCreatePathCache().push_front(tile, dir); return false; } } @@ -1931,8 +1929,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->GetOrCreatePathCache().push_front(v->tile, dir); return false; } } @@ -2226,7 +2223,7 @@ bool RoadVehicle::Tick() void RoadVehicle::SetDestTile(TileIndex tile) { if (tile == this->dest_tile) return; - this->path.clear(); + if (this->cached_path != nullptr) this->cached_path->clear(); this->dest_tile = tile; } diff --git a/src/saveload/vehicle_sl.cpp b/src/saveload/vehicle_sl.cpp index 2bc2093149..eba8e58b0f 100644 --- a/src/saveload/vehicle_sl.cpp +++ b/src/saveload/vehicle_sl.cpp @@ -24,6 +24,7 @@ #include "../disaster_vehicle.h" #include +#include #include "../safeguards.h" @@ -32,6 +33,9 @@ bool TrainController(Train *v, Vehicle *nomove, bool reverse = true); // From tr void ReverseTrainDirection(Train *v); void ReverseTrainSwapVeh(Train *v, int l, int r); +static std::vector _path_td; +static std::vector _path_tile; + namespace upstream_sl { static uint8 _cargo_days; @@ -247,8 +251,8 @@ 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", _path_td, SLE_UINT8, SLV_SHIP_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), }; inline const static SaveLoadCompatTable compat_description = _vehicle_roadveh_sl_compat; @@ -263,6 +267,18 @@ public: { if (v->type != VEH_ROAD) return; 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 diff --git a/src/sl/vehicle_sl.cpp b/src/sl/vehicle_sl.cpp index 2b07bbd2ab..2df5d8bafa 100644 --- a/src/sl/vehicle_sl.cpp +++ b/src/sl/vehicle_sl.cpp @@ -644,6 +644,9 @@ static Money _cargo_feeder_share; static uint32 _cargo_loaded_at_xy; CargoPacketList _cpp_packets; std::map _veh_cpp_packets; +static std::vector _path_td; +static std::vector _path_tile; +static uint32 _path_layout_ctr; static uint32 _old_ahead_separation; @@ -858,9 +861,9 @@ SaveLoadTable GetVehicleDescription(VehicleType vt) 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), - SLE_CONDVAR_X(RoadVehicle, path.layout_ctr, SLE_UINT32, SL_MIN_VERSION, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_ROAD_LAYOUT_CHANGE_CTR)), + SLEG_CONDVARVEC(_path_td, SLE_UINT8, SLV_ROADVEH_PATH_CACHE, SL_MAX_VERSION), + SLEG_CONDVARVEC(_path_tile, SLE_UINT32, SLV_ROADVEH_PATH_CACHE, SL_MAX_VERSION), + 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_CONDVAR(RoadVehicle, gv_flags, SLE_UINT16, SLV_139, SL_MAX_VERSION), @@ -1025,6 +1028,22 @@ static void Save_VEHS() SetupDescs_VEHS(); /* Write the vehicles */ 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); SlObjectSaveFiltered(v, GetVehicleDescriptionFiltered(v->type)); } @@ -1042,6 +1061,10 @@ void Load_VEHS() _cpp_packets.clear(); _veh_cpp_packets.clear(); + _path_td.clear(); + _path_tile.clear(); + _path_layout_ctr = 0; + while ((index = SlIterateArray()) != -1) { Vehicle *v; VehicleType vtype = (VehicleType)SlReadByte(); @@ -1090,6 +1113,17 @@ void Load_VEHS() if (SlXvIsFeaturePresent(XSLFI_AUTO_TIMETABLE, 1, 4)) { 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; + } } } diff --git a/src/table/newgrf_debug_data.h b/src/table/newgrf_debug_data.h index 2a7da5f9f4..f12e6a2d8e 100644 --- a/src/table/newgrf_debug_data.h +++ b/src/table/newgrf_debug_data.h @@ -413,9 +413,31 @@ class NIHVehicle : public NIHelper { seprintf(buffer, lastof(buffer), " Overtaking: %u, overtaking_ctr: %u, overtaking threshold: %u", rv->overtaking, rv->overtaking_ctr, rv->GetOvertakingCounterThreshold()); output.print(buffer); - seprintf(buffer, lastof(buffer), " Speed: %u, path cache length: %u", - rv->cur_speed, (uint) rv->path.size()); + seprintf(buffer, lastof(buffer), " Speed: %u", rv->cur_speed); 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); 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);