diff --git a/src/misc/dbg_helpers.h b/src/misc/dbg_helpers.h index 30e01da8e0..87505ad4cd 100644 --- a/src/misc/dbg_helpers.h +++ b/src/misc/dbg_helpers.h @@ -86,6 +86,32 @@ inline std::string ComposeNameT(E value, T &t, const char *t_unk, E val_inv, con return out; } +/** + * Helper template function that returns compound bitfield name that is + * concatenation of names of each set bit in the given value + * or unknown_name when index is out of bounds. + */ +template +inline std::string ComposeNameT(E value, std::span names, std::string_view unknown_name) +{ + std::string out; + if (value.base() == 0) { + out = ""; + } else { + for (size_t i = 0; i < std::size(names); ++i) { + if (!value.Test(static_cast(i))) continue; + out += (!out.empty() ? "+" : ""); + out += names[i]; + value.Reset(static_cast(i)); + } + if (value.base() != 0) { + out += (!out.empty() ? "+" : ""); + out += unknown_name; + } + } + return out; +} + std::string ValueStr(Trackdir td); std::string ValueStr(TrackdirBits td_bits); std::string ValueStr(DiagDirection dd); diff --git a/src/pathfinder/yapf/yapf_base.hpp b/src/pathfinder/yapf/yapf_base.hpp index ab0d043726..f8888e1364 100644 --- a/src/pathfinder/yapf/yapf_base.hpp +++ b/src/pathfinder/yapf/yapf_base.hpp @@ -196,7 +196,7 @@ public: void PruneIntermediateNodeBranch(Node *n) { bool intermediate_on_branch = false; - while (n != nullptr && (n->segment->end_segment_reason & ESRB_CHOICE_FOLLOWS) == 0) { + while (n != nullptr && !n->segment->end_segment_reason.Test(EndSegmentReason::ChoiceFollows)) { if (n == Yapf().best_intermediate_node) intermediate_on_branch = true; n = n->parent; } diff --git a/src/pathfinder/yapf/yapf_costrail.hpp b/src/pathfinder/yapf/yapf_costrail.hpp index 7c1fa7acb8..4c24ff066d 100644 --- a/src/pathfinder/yapf/yapf_costrail.hpp +++ b/src/pathfinder/yapf/yapf_costrail.hpp @@ -180,7 +180,7 @@ public: bool has_signal_along = HasSignalOnTrackdir(tile, trackdir); if (has_signal_against && !has_signal_along && IsOnewaySignal(tile, TrackdirToTrack(trackdir))) { /* one-way signal in opposite direction */ - n.segment->end_segment_reason |= ESRB_DEAD_END; + n.segment->end_segment_reason.Set(EndSegmentReason::DeadEnd); } else { if (has_signal_along) { SignalState sig_state = GetSignalStateByTrackdir(tile, trackdir); @@ -204,7 +204,7 @@ public: if (!IsPbsSignal(sig_type) && Yapf().TreatFirstRedTwoWaySignalAsEOL() && n.flags_u.flags_s.choice_seen && has_signal_against && n.num_signals_passed == 0) { /* yes, the first signal is two-way red signal => DEAD END. Prune this branch... */ Yapf().PruneIntermediateNodeBranch(&n); - n.segment->end_segment_reason |= ESRB_DEAD_END; + n.segment->end_segment_reason.Set(EndSegmentReason::DeadEnd); Yapf().stopped_on_first_two_way_signal = true; return -1; } @@ -324,7 +324,7 @@ public: /* the previous tile will be needed for transition cost calculations */ TILE prev = !has_parent ? TILE() : TILE(n.parent->GetLastTile(), n.parent->GetLastTrackdir()); - EndSegmentReasonBits end_segment_reason = ESRB_NONE; + EndSegmentReasons end_segment_reason{}; TrackFollower tf_local(v, Yapf().GetCompatibleRailTypes()); @@ -399,7 +399,7 @@ no_entry_cost: // jump here at the beginning if the node has no parent (it is th } else if (IsRailDepotTile(cur.tile)) { /* We will end in this pass (depot is possible target) */ - end_segment_reason |= ESRB_DEPOT; + end_segment_reason.Set(EndSegmentReason::Depot); } else if (cur.tile_type == MP_STATION && IsRailWaypoint(cur.tile)) { if (v->current_order.IsType(OT_GOTO_WAYPOINT) && @@ -443,7 +443,7 @@ no_entry_cost: // jump here at the beginning if the node has no parent (it is th } } /* Waypoint is also a good reason to finish. */ - end_segment_reason |= ESRB_WAYPOINT; + end_segment_reason.Set(EndSegmentReason::Waypoint); } else if (tf->is_station) { /* Station penalties. */ @@ -452,12 +452,12 @@ no_entry_cost: // jump here at the beginning if the node has no parent (it is th * if it is pass-through station (not our destination). */ segment_cost += Yapf().PfGetSettings().rail_station_penalty * platform_length; /* We will end in this pass (station is possible target) */ - end_segment_reason |= ESRB_STATION; + end_segment_reason.Set(EndSegmentReason::Station); } else if (TrackFollower::DoTrackMasking() && cur.tile_type == MP_RAILWAY) { /* Searching for a safe tile? */ if (HasSignalOnTrackdir(cur.tile, cur.td) && !IsPbsSignal(GetSignalType(cur.tile, TrackdirToTrack(cur.td)))) { - end_segment_reason |= ESRB_SAFE_TILE; + end_segment_reason.Set(EndSegmentReason::SafeTile); } } @@ -479,7 +479,7 @@ no_entry_cost: // jump here at the beginning if the node has no parent (it is th /* Finish if we already exceeded the maximum path cost (i.e. when * searching for the nearest depot). */ if (this->max_cost > 0 && (parent_cost + segment_entry_cost + segment_cost) > this->max_cost) { - end_segment_reason |= ESRB_PATH_TOO_LONG; + end_segment_reason.Set(EndSegmentReason::PathTooLong); } /* Move to the next tile/trackdir. */ @@ -490,13 +490,13 @@ no_entry_cost: // jump here at the beginning if the node has no parent (it is th assert(tf_local.err != TrackFollower::EC_NONE); /* Can't move to the next tile (EOL?). */ if (tf_local.err == TrackFollower::EC_RAIL_ROAD_TYPE) { - end_segment_reason |= ESRB_RAIL_TYPE; + end_segment_reason.Set(EndSegmentReason::RailType); } else { - end_segment_reason |= ESRB_DEAD_END; + end_segment_reason.Set(EndSegmentReason::DeadEnd); } if (TrackFollower::DoTrackMasking() && !HasOnewaySignalBlockingTrackdir(cur.tile, cur.td)) { - end_segment_reason |= ESRB_SAFE_TILE; + end_segment_reason.Set(EndSegmentReason::SafeTile); } break; } @@ -504,7 +504,7 @@ no_entry_cost: // jump here at the beginning if the node has no parent (it is th /* Check if the next tile is not a choice. */ if (KillFirstBit(tf_local.new_td_bits) != TRACKDIR_BIT_NONE) { /* More than one segment will follow. Close this one. */ - end_segment_reason |= ESRB_CHOICE_FOLLOWS; + end_segment_reason.Set(EndSegmentReason::ChoiceFollows); break; } @@ -514,10 +514,11 @@ no_entry_cost: // jump here at the beginning if the node has no parent (it is th if (TrackFollower::DoTrackMasking() && IsTileType(next.tile, MP_RAILWAY)) { if (HasSignalOnTrackdir(next.tile, next.td) && IsPbsSignal(GetSignalType(next.tile, TrackdirToTrack(next.td)))) { /* Possible safe tile. */ - end_segment_reason |= ESRB_SAFE_TILE; + end_segment_reason.Set(EndSegmentReason::SafeTile); } else if (HasSignalOnTrackdir(next.tile, ReverseTrackdir(next.td)) && GetSignalType(next.tile, TrackdirToTrack(next.td)) == SIGTYPE_PBS_ONEWAY) { /* Possible safe tile, but not so good as it's the back of a signal... */ - end_segment_reason |= ESRB_SAFE_TILE | ESRB_DEAD_END; + end_segment_reason.Set(EndSegmentReason::SafeTile); + end_segment_reason.Set(EndSegmentReason::DeadEnd); extra_cost += Yapf().PfGetSettings().rail_lastred_exit_penalty; } } @@ -525,13 +526,13 @@ no_entry_cost: // jump here at the beginning if the node has no parent (it is th /* Check the next tile for the rail type. */ if (next.rail_type != cur.rail_type) { /* Segment must consist from the same rail_type tiles. */ - end_segment_reason |= ESRB_RAIL_TYPE; + end_segment_reason.Set(EndSegmentReason::RailType); break; } /* Avoid infinite looping. */ if (next.tile == n.key.tile && next.td == n.key.td) { - end_segment_reason |= ESRB_INFINITE_LOOP; + end_segment_reason.Set(EndSegmentReason::InfiniteLoop); break; } @@ -539,13 +540,13 @@ no_entry_cost: // jump here at the beginning if the node has no parent (it is th /* Potentially in the infinite loop (or only very long segment?). We should * not force it to finish prematurely unless we are on a regular tile. */ if (IsTileType(tf->new_tile, MP_RAILWAY)) { - end_segment_reason |= ESRB_SEGMENT_TOO_LONG; + end_segment_reason.Set(EndSegmentReason::SegmentTooLong); break; } } /* Any other reason bit set? */ - if (end_segment_reason != ESRB_NONE) { + if (end_segment_reason != EndSegmentReasons{}) { break; } @@ -556,10 +557,10 @@ no_entry_cost: // jump here at the beginning if the node has no parent (it is th } // for (;;) /* Don't consider path any further it if exceeded max_cost. */ - if (end_segment_reason & ESRB_PATH_TOO_LONG) return false; + if (end_segment_reason.Test(EndSegmentReason::PathTooLong)) return false; bool target_seen = false; - if ((end_segment_reason & ESRB_POSSIBLE_TARGET) != ESRB_NONE) { + if (end_segment_reason.Any(ESRF_POSSIBLE_TARGET)) { /* Depot, station or waypoint. */ if (Yapf().PfDetectDestination(cur.tile, cur.td)) { /* Destination found. */ @@ -571,13 +572,13 @@ no_entry_cost: // jump here at the beginning if the node has no parent (it is th if (!is_cached_segment) { /* Write back the segment information so it can be reused the next time. */ segment.cost = segment_cost; - segment.end_segment_reason = end_segment_reason & ESRB_CACHED_MASK; + segment.end_segment_reason = end_segment_reason & ESRF_CACHED_MASK; /* Save end of segment back to the node. */ n.SetLastTileTrackdir(cur.tile, cur.td); } /* Do we have an excuse why not to continue pathfinding in this direction? */ - if (!target_seen && (end_segment_reason & ESRB_ABORT_PF_MASK) != ESRB_NONE) { + if (!target_seen && end_segment_reason.Any(ESRF_ABORT_PF_MASK)) { /* Reason to not continue. Stop this PF branch. */ return false; } @@ -597,7 +598,7 @@ no_entry_cost: // jump here at the beginning if the node has no parent (it is th } /* Station platform-length penalty. */ - if ((end_segment_reason & ESRB_STATION) != ESRB_NONE) { + if (end_segment_reason.Test(EndSegmentReason::Station)) { const BaseStation *st = BaseStation::GetByTile(n.GetLastTile()); assert(st != nullptr); uint platform_length = st->GetPlatformLength(n.GetLastTile(), ReverseDiagDir(TrackdirToExitdir(n.GetLastTrackdir()))); diff --git a/src/pathfinder/yapf/yapf_node_rail.hpp b/src/pathfinder/yapf/yapf_node_rail.hpp index bb3ed4e45b..e6131589b4 100644 --- a/src/pathfinder/yapf/yapf_node_rail.hpp +++ b/src/pathfinder/yapf/yapf_node_rail.hpp @@ -74,7 +74,7 @@ struct CYapfRailSegment int cost = -1; TileIndex last_signal_tile = INVALID_TILE; Trackdir last_signal_td = INVALID_TRACKDIR; - EndSegmentReasonBits end_segment_reason = ESRB_NONE; + EndSegmentReasons end_segment_reason{}; CYapfRailSegment *hash_next = nullptr; inline CYapfRailSegment(const CYapfRailSegmentKey &key) : key(key) {} diff --git a/src/pathfinder/yapf/yapf_type.hpp b/src/pathfinder/yapf/yapf_type.hpp index fee2bbfed5..fe2666b264 100644 --- a/src/pathfinder/yapf/yapf_type.hpp +++ b/src/pathfinder/yapf/yapf_type.hpp @@ -17,72 +17,67 @@ #include "../../misc/dbg_helpers.h" /* Enum used in PfCalcCost() to see why was the segment closed. */ -enum EndSegmentReason : uint8_t { +enum class EndSegmentReason : uint8_t { /* The following reasons can be saved into cached segment */ - ESR_DEAD_END = 0, ///< track ends here - ESR_RAIL_TYPE, ///< the next tile has a different rail type than our tiles - ESR_INFINITE_LOOP, ///< infinite loop detected - ESR_SEGMENT_TOO_LONG, ///< the segment is too long (possible infinite loop) - ESR_CHOICE_FOLLOWS, ///< the next tile contains a choice (the track splits to more than one segments) - ESR_DEPOT, ///< stop in the depot (could be a target next time) - ESR_WAYPOINT, ///< waypoint encountered (could be a target next time) - ESR_STATION, ///< station encountered (could be a target next time) - ESR_SAFE_TILE, ///< safe waiting position found (could be a target) + DeadEnd, ///< track ends here + RailType, ///< the next tile has a different rail type than our tiles + InfiniteLoop, ///< infinite loop detected + SegmentTooLong, ///< the segment is too long (possible infinite loop) + ChoiceFollows, ///< the next tile contains a choice (the track splits to more than one segments) + Depot, ///< stop in the depot (could be a target next time) + Waypoint, ///< waypoint encountered (could be a target next time) + Station, ///< station encountered (could be a target next time) + SafeTile, ///< safe waiting position found (could be a target) /* The following reasons are used only internally by PfCalcCost(). * They should not be found in the cached segment. */ - ESR_PATH_TOO_LONG, ///< the path is too long (searching for the nearest depot in the given radius) - ESR_FIRST_TWO_WAY_RED, ///< first signal was 2-way and it was red - ESR_LOOK_AHEAD_END, ///< we have just passed the last look-ahead signal - ESR_TARGET_REACHED, ///< we have just reached the destination + PathTooLong, ///< the path is too long (searching for the nearest depot in the given radius) + FirstTwoWayRed, ///< first signal was 2-way and it was red + LookAheadEnd, ///< we have just passed the last look-ahead signal + TargetReached, ///< we have just reached the destination +}; +using EndSegmentReasons = EnumBitSet; - /* Special values */ - ESR_NONE = 0xFF, ///< no reason to end the segment here +/* What reasons mean that the target can be found and needs to be detected. */ +static constexpr EndSegmentReasons ESRF_POSSIBLE_TARGET = { + EndSegmentReason::Depot, + EndSegmentReason::Waypoint, + EndSegmentReason::Station, + EndSegmentReason::SafeTile, }; -enum EndSegmentReasonBits : uint16_t { - ESRB_NONE = 0, - - ESRB_DEAD_END = 1 << ESR_DEAD_END, - ESRB_RAIL_TYPE = 1 << ESR_RAIL_TYPE, - ESRB_INFINITE_LOOP = 1 << ESR_INFINITE_LOOP, - ESRB_SEGMENT_TOO_LONG = 1 << ESR_SEGMENT_TOO_LONG, - ESRB_CHOICE_FOLLOWS = 1 << ESR_CHOICE_FOLLOWS, - ESRB_DEPOT = 1 << ESR_DEPOT, - ESRB_WAYPOINT = 1 << ESR_WAYPOINT, - ESRB_STATION = 1 << ESR_STATION, - ESRB_SAFE_TILE = 1 << ESR_SAFE_TILE, - - ESRB_PATH_TOO_LONG = 1 << ESR_PATH_TOO_LONG, - ESRB_FIRST_TWO_WAY_RED = 1 << ESR_FIRST_TWO_WAY_RED, - ESRB_LOOK_AHEAD_END = 1 << ESR_LOOK_AHEAD_END, - ESRB_TARGET_REACHED = 1 << ESR_TARGET_REACHED, - - /* Additional (composite) values. */ - - /* What reasons mean that the target can be found and needs to be detected. */ - ESRB_POSSIBLE_TARGET = ESRB_DEPOT | ESRB_WAYPOINT | ESRB_STATION | ESRB_SAFE_TILE, - - /* What reasons can be stored back into cached segment. */ - ESRB_CACHED_MASK = ESRB_DEAD_END | ESRB_RAIL_TYPE | ESRB_INFINITE_LOOP | ESRB_SEGMENT_TOO_LONG | ESRB_CHOICE_FOLLOWS | ESRB_DEPOT | ESRB_WAYPOINT | ESRB_STATION | ESRB_SAFE_TILE, - - /* Reasons to abort pathfinding in this direction. */ - ESRB_ABORT_PF_MASK = ESRB_DEAD_END | ESRB_PATH_TOO_LONG | ESRB_INFINITE_LOOP | ESRB_FIRST_TWO_WAY_RED, +/* What reasons can be stored back into cached segment. */ +static constexpr EndSegmentReasons ESRF_CACHED_MASK = { + EndSegmentReason::DeadEnd, + EndSegmentReason::RailType, + EndSegmentReason::InfiniteLoop, + EndSegmentReason::SegmentTooLong, + EndSegmentReason::ChoiceFollows, + EndSegmentReason::Depot, + EndSegmentReason::Waypoint, + EndSegmentReason::Station, + EndSegmentReason::SafeTile, }; -DECLARE_ENUM_AS_BIT_SET(EndSegmentReasonBits) +/* Reasons to abort pathfinding in this direction. */ +static constexpr EndSegmentReasons ESRF_ABORT_PF_MASK = { + EndSegmentReason::DeadEnd, + EndSegmentReason::PathTooLong, + EndSegmentReason::InfiniteLoop, + EndSegmentReason::FirstTwoWayRed, +}; -inline std::string ValueStr(EndSegmentReasonBits bits) +inline std::string ValueStr(EndSegmentReasons flags) { - static const char * const end_segment_reason_names[] = { + static const std::initializer_list end_segment_reason_names = { "DEAD_END", "RAIL_TYPE", "INFINITE_LOOP", "SEGMENT_TOO_LONG", "CHOICE_FOLLOWS", "DEPOT", "WAYPOINT", "STATION", "SAFE_TILE", "PATH_TOO_LONG", "FIRST_TWO_WAY_RED", "LOOK_AHEAD_END", "TARGET_REACHED" }; std::stringstream ss; - ss << "0x" << std::setfill('0') << std::setw(4) << std::hex << bits; // 0x%04X - ss << " (" << ComposeNameT(bits, end_segment_reason_names, "UNK", ESRB_NONE, "NONE") << ")"; + ss << "0x" << std::setfill('0') << std::setw(4) << std::hex << flags.base(); // 0x%04X + ss << " (" << ComposeNameT(flags, end_segment_reason_names, "UNK") << ")"; return ss.str(); }