4 * This file is part of OpenTTD.
5 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
6 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
7 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
10 /** @file yapf_rail.cpp The rail pathfinding. */
12 #include "../../stdafx.h"
15 #include "yapf_node_rail.hpp"
16 #include "../../viewport_func.h"
17 #include "../../newgrf_station.h"
18 #include "../../station_func.h"
19 #include "../../misc/blob.hpp"
20 #include "../../misc/array.hpp"
21 #include "../../misc/hashtable.hpp"
22 #include "../../map/slope.h"
23 #include "../../pbs.h"
24 #include "../../waypoint_base.h"
25 #include "../../date_func.h"
27 #define DEBUG_YAPF_CACHE 0
30 template <typename Tpf
> void DumpState(Tpf
&pf1
, Tpf
&pf2
)
32 DumpTarget dmp1
, dmp2
;
35 FILE *f1
= fopen("yapf1.txt", "wt");
36 FILE *f2
= fopen("yapf2.txt", "wt");
37 fwrite(dmp1
.m_out
.Data(), 1, dmp1
.m_out
.Size(), f1
);
38 fwrite(dmp2
.m_out
.Data(), 1, dmp2
.m_out
.Size(), f2
);
44 int _total_pf_time_us
= 0;
48 * Base class for segment cost cache providers. Contains global counter
49 * of track layout changes and static notification function called whenever
50 * the track layout changes. It is implemented as base class because it needs
51 * to be shared between all rail YAPF types (one shared counter, one notification
54 struct CSegmentCostCacheBase
56 static int s_rail_change_counter
;
58 static void NotifyTrackLayoutChange(TileIndex tile
, Track track
)
60 s_rail_change_counter
++;
64 /** if any track changes, this counter is incremented - that will invalidate segment cost cache */
65 int CSegmentCostCacheBase::s_rail_change_counter
= 0;
67 void YapfNotifyTrackLayoutChange(TileIndex tile
, Track track
)
69 CSegmentCostCacheBase::NotifyTrackLayoutChange(tile
, track
);
74 * CSegmentCostCacheT - template class providing hash-map and storage (heap)
75 * of Tsegment structures. Each rail node contains pointer to the segment
76 * that contains cached (or non-cached) segment cost information. Nodes can
77 * differ by key type, but they use the same segment type. Segment key should
78 * be always the same (TileIndex + DiagDirection) that represent the beginning
79 * of the segment (origin tile and exit-dir from this tile).
80 * Different CYapfCachedCostT types can share the same type of CSegmentCostCacheT.
81 * Look at CYapfRailSegment (yapf_node_rail.hpp) for the segment example
83 template <class Tsegment
>
84 struct CSegmentCostCacheT
85 : public CSegmentCostCacheBase
87 static const int C_HASH_BITS
= 14;
89 typedef CHashTableT
<Tsegment
, C_HASH_BITS
> HashTable
;
90 typedef SmallArray
<Tsegment
> Heap
;
91 typedef typename
Tsegment::Key Key
; ///< key to hash table
96 inline CSegmentCostCacheT() {}
98 /** flush (clear) the cache */
105 inline bool Get(const Key
& key
, Tsegment
**item
)
107 *item
= m_map
.Find(key
);
108 if (*item
!= NULL
) return true;
109 *item
= new (m_heap
.Append()) Tsegment(key
);
116 template <class TAstar
>
117 class CYapfRailBaseT
: public TAstar
120 typedef typename
TAstar::Node Node
; ///< this will be our node type
121 typedef typename
Node::Key Key
; ///< key to hash tables
122 typedef typename
Node::CachedData CachedData
;
123 typedef typename
CachedData::Key CacheKey
;
124 typedef CSegmentCostCacheT
<CachedData
> Cache
;
125 typedef SmallArray
<CachedData
> LocalCache
;
128 const YAPFSettings
*const m_settings
; ///< current settings (_settings_game.yapf)
129 const Train
*const m_veh
; ///< vehicle that we are trying to drive
130 const RailTypes m_compatible_railtypes
;
131 const bool mask_reserved_tracks
;
132 bool m_treat_first_red_two_way_signal_as_eol
; ///< in some cases (leaving station) we need to handle first two-way signal differently
134 bool m_stopped_on_first_two_way_signal
;
137 bool m_disable_cache
;
138 Cache
& m_global_cache
;
139 LocalCache m_local_cache
;
142 * @note maximum cost doesn't work with caching enabled
143 * @todo fix maximum cost failing with caching (e.g. FS#2900)
146 CBlobT
<int> m_sig_look_ahead_costs
;
148 int m_stats_cost_calcs
; ///< stats - how many node's costs were calculated
149 int m_stats_cache_hits
; ///< stats - how many node's costs were reused from cache
151 CPerformanceTimer m_perf_cost
; ///< stats - total CPU time of this run
152 CPerformanceTimer m_perf_slope_cost
; ///< stats - slope calculation CPU time
153 CPerformanceTimer m_perf_ts_cost
; ///< stats - GetTrackStatus() CPU time
154 CPerformanceTimer m_perf_other_cost
; ///< stats - other CPU time
156 CFollowTrackRail tf
; ///< track follower to be used by Follow
157 CFollowTrackRail tf_local
; ///< track follower to be used by CalcSegment
159 static const int s_max_segment_cost
= 10000;
161 inline static Cache
& stGetGlobalCache()
163 static int last_rail_change_counter
= 0;
164 static Date last_date
= 0;
167 /* some statistics */
168 if (last_date
!= _date
) {
170 DEBUG(yapf
, 2, "Pf time today: %5d ms", _total_pf_time_us
/ 1000);
171 _total_pf_time_us
= 0;
174 /* delete the cache sometimes... */
175 if (last_rail_change_counter
!= Cache::s_rail_change_counter
) {
176 last_rail_change_counter
= Cache::s_rail_change_counter
;
183 CYapfRailBaseT (const Train
*v
, bool allow_90deg
, bool override_rail_type
, bool mask_reserved_tracks
)
184 : m_settings(&_settings_game
.pf
.yapf
)
186 , m_compatible_railtypes(v
->compatible_railtypes
| (override_rail_type
? GetRailTypeInfo(v
->railtype
)->compatible_railtypes
: RAILTYPES_NONE
))
187 , mask_reserved_tracks(mask_reserved_tracks
)
188 , m_treat_first_red_two_way_signal_as_eol(true)
189 , m_stopped_on_first_two_way_signal(false)
190 , m_disable_cache(false)
191 , m_global_cache(stGetGlobalCache())
193 , m_stats_cost_calcs(0)
194 , m_stats_cache_hits(0)
195 , tf (v
, allow_90deg
, mask_reserved_tracks
? m_compatible_railtypes
: v
->compatible_railtypes
)
196 , tf_local (v
, allow_90deg
, m_compatible_railtypes
, &m_perf_ts_cost
)
198 /* pre-compute look-ahead penalties into array */
199 int p0
= m_settings
->rail_look_ahead_signal_p0
;
200 int p1
= m_settings
->rail_look_ahead_signal_p1
;
201 int p2
= m_settings
->rail_look_ahead_signal_p2
;
202 int *pen
= m_sig_look_ahead_costs
.GrowSizeNC(m_settings
->rail_look_ahead_max_signals
);
203 for (uint i
= 0; i
< m_settings
->rail_look_ahead_max_signals
; i
++) {
204 pen
[i
] = p0
+ i
* (p1
+ i
* p2
);
209 inline bool Allow90degTurns (void) const
211 return tf
.m_allow_90deg
;
214 inline bool CanUseGlobalCache(Node
& n
) const
216 return !m_disable_cache
217 && (n
.m_parent
!= NULL
)
218 && (n
.m_parent
->m_num_signals_passed
>= m_sig_look_ahead_costs
.Size());
223 * Called by YAPF to attach cached or local segment cost data to the given node.
224 * @return true if globally cached data were used or false if local data was used
226 inline bool AttachSegmentToNode(Node
*n
)
228 CacheKey
key(n
->GetKey());
229 if (!CanUseGlobalCache(*n
)) {
230 n
->m_segment
= new (m_local_cache
.Append()) CachedData(key
);
231 n
->m_segment
->m_last
= n
->GetPos();
234 bool found
= m_global_cache
.Get(key
, &n
->m_segment
);
235 if (!found
) n
->m_segment
->m_last
= n
->GetPos();
240 /** Create and add a new node */
241 inline void AddStartupNode (const RailPathPos
&pos
, int cost
= 0)
243 Node
*node
= TAstar::CreateNewNode (NULL
, pos
, false);
245 AttachSegmentToNode(node
);
246 TAstar::InsertInitialNode(node
);
250 void SetOrigin(const RailPathPos
&pos
)
252 AddStartupNode (pos
);
256 void SetOrigin(const RailPathPos
&pos
, const RailPathPos
&rev
, int reverse_penalty
, bool treat_first_red_two_way_signal_as_eol
)
258 m_treat_first_red_two_way_signal_as_eol
= treat_first_red_two_way_signal_as_eol
;
260 AddStartupNode (pos
);
261 AddStartupNode (rev
, reverse_penalty
);
264 inline void SetMaxCost (int max_cost
)
266 m_max_cost
= max_cost
;
269 inline void DisableCache(bool disable
)
271 m_disable_cache
= disable
;
275 * In some cases an intermediate node branch should be pruned.
276 * The most prominent case is when a red EOL signal is encountered, but
277 * there was a segment change (e.g. a rail type change) before that. If
278 * the branch would not be pruned, the rail type change location would
279 * remain the best intermediate node, and thus the vehicle would still
280 * go towards the red EOL signal.
282 void PruneIntermediateNodeBranch()
284 while (TAstar::best_intermediate
!= NULL
&& (TAstar::best_intermediate
->m_segment
->m_end_segment_reason
& ESRB_CHOICE_FOLLOWS
) == 0) {
285 TAstar::best_intermediate
= TAstar::best_intermediate
->m_parent
;
295 RailPathPos last_signal
;
296 EndSegmentReasonBits end_reason
;
299 /** Return the transition cost from one tile to another. */
300 inline int TransitionCost (const RailPathPos
&pos1
, const RailPathPos
&pos2
) const
302 assert(IsValidTrackdir(pos1
.td
));
303 assert(IsValidTrackdir(pos2
.td
));
305 if (pos1
.in_wormhole() || !IsRailwayTile(pos1
.tile
)) {
306 assert(IsDiagonalTrackdir(pos1
.td
));
307 if (pos2
.in_wormhole() || !IsRailwayTile(pos2
.tile
)) {
308 /* compare only the tracks, as depots cause reversing */
309 assert(TrackdirToTrack(pos1
.td
) == TrackdirToTrack(pos2
.td
));
312 return (pos1
.td
== pos2
.td
) ? 0 : m_settings
->rail_curve45_penalty
;
315 if (pos2
.in_wormhole() || !IsRailwayTile(pos2
.tile
)) {
316 assert(IsDiagonalTrackdir(pos2
.td
));
317 return (pos1
.td
== pos2
.td
) ? 0 : m_settings
->rail_curve45_penalty
;
321 /* both tiles are railway tiles */
324 if ((TrackdirToTrackdirBits(pos2
.td
) & (TrackdirBits
)TrackdirCrossesTrackdirs(pos1
.td
)) != 0) {
325 /* 90-deg curve penalty */
326 cost
+= m_settings
->rail_curve90_penalty
;
327 } else if (pos2
.td
!= NextTrackdir(pos1
.td
)) {
328 /* 45-deg curve penalty */
329 cost
+= m_settings
->rail_curve45_penalty
;
332 DiagDirection exitdir
= TrackdirToExitdir(pos1
.td
);
333 bool t1
= KillFirstBit(GetTrackBits(pos1
.tile
) & DiagdirReachesTracks(ReverseDiagDir(exitdir
))) != TRACK_BIT_NONE
;
334 bool t2
= KillFirstBit(GetTrackBits(pos2
.tile
) & DiagdirReachesTracks(exitdir
)) != TRACK_BIT_NONE
;
335 if (t1
&& t2
) cost
+= m_settings
->rail_doubleslip_penalty
;
340 /** Return one tile cost (base cost + level crossing penalty). */
341 inline int OneTileCost (const RailPathPos
&pos
) const
345 if (IsDiagonalTrackdir(pos
.td
)) {
346 cost
+= YAPF_TILE_LENGTH
;
347 if (IsLevelCrossingTile(pos
.tile
)) {
348 /* Increase the cost for level crossings */
349 cost
+= m_settings
->rail_crossing_penalty
;
352 /* non-diagonal trackdir */
353 cost
= YAPF_TILE_CORNER_LENGTH
;
358 /** Return slope cost for a tile. */
359 inline int SlopeCost (const RailPathPos
&pos
)
361 CPerfStart
perf_cost(m_perf_slope_cost
);
363 if (pos
.in_wormhole() || !IsDiagonalTrackdir(pos
.td
)) return 0;
365 /* Only rail tracks and bridgeheads can have sloped rail. */
366 if (!IsRailwayTile(pos
.tile
)) return 0;
369 if (IsTileSubtype(pos
.tile
, TT_BRIDGE
)) {
370 /* it is bridge ramp, check if we are entering the bridge */
371 DiagDirection dir
= GetTunnelBridgeDirection(pos
.tile
);
372 if (dir
!= TrackdirToExitdir(pos
.td
)) return 0; // no, we are leaving it, no penalty
373 /* we are entering the bridge */
374 Slope tile_slope
= GetTileSlope(pos
.tile
);
375 Axis axis
= DiagDirToAxis(dir
);
376 uphill
= !HasBridgeFlatRamp(tile_slope
, axis
);
378 /* not bridge ramp */
379 Slope tile_slope
= GetTileSlope(pos
.tile
);
380 uphill
= IsUphillTrackdir(tile_slope
, pos
.td
); // slopes uphill => apply penalty
383 return uphill
? m_settings
->rail_slope_penalty
: 0;
386 /** Check for a reserved station platform. */
387 static inline bool IsAnyStationTileReserved (const RailPathPos
&pos
, int skipped
)
389 TileIndexDiff diff
= TileOffsByDiagDir(TrackdirToExitdir(ReverseTrackdir(pos
.td
)));
390 for (TileIndex tile
= pos
.tile
; skipped
>= 0; skipped
--, tile
+= diff
) {
391 if (HasStationReservation(tile
)) return true;
396 /** The cost for reserved tiles, including skipped ones. */
397 inline int ReservationCost(Node
& n
, const RailPathPos
&pos
, int skipped
) const
399 if (n
.m_num_signals_passed
>= m_sig_look_ahead_costs
.Size() / 2) return 0;
400 if (!IsPbsSignal(n
.m_last_signal_type
)) return 0;
402 if (!pos
.in_wormhole() && IsRailStationTile(pos
.tile
) && IsAnyStationTileReserved(pos
, skipped
)) {
403 return m_settings
->rail_pbs_station_penalty
* (skipped
+ 1);
404 } else if (TrackOverlapsTracks(GetReservedTrackbits(pos
.tile
), TrackdirToTrack(pos
.td
))) {
405 int cost
= m_settings
->rail_pbs_cross_penalty
;
406 if (!IsDiagonalTrackdir(pos
.td
)) cost
= (cost
* YAPF_TILE_CORNER_LENGTH
) / YAPF_TILE_LENGTH
;
407 return cost
* (skipped
+ 1);
412 /** Penalty for platform shorter/longer than needed. */
413 inline int PlatformLengthPenalty (int platform_length
) const
416 const Train
*v
= m_veh
;
417 assert(v
->gcache
.cached_total_length
!= 0);
418 int missing_platform_length
= CeilDiv(v
->gcache
.cached_total_length
, TILE_SIZE
) - platform_length
;
419 if (missing_platform_length
< 0) {
420 /* apply penalty for longer platform than needed */
421 cost
+= m_settings
->rail_longer_platform_penalty
+ m_settings
->rail_longer_platform_per_tile_penalty
* -missing_platform_length
;
422 } else if (missing_platform_length
> 0) {
423 /* apply penalty for shorter platform than needed */
424 cost
+= m_settings
->rail_shorter_platform_penalty
+ m_settings
->rail_shorter_platform_per_tile_penalty
* missing_platform_length
;
429 /* Compute cost and modify node state for a signal. */
430 inline int SignalCost(Node
& n
, const RailPathPos
&pos
, NodeData
*data
);
432 /* Compute cost and modify node state for a position. */
433 inline void HandleNodeTile (Node
*n
, const CFollowTrackRail
*tf
, NodeData
*segment
, TileIndex prev
);
435 /* Check for possible reasons to end a segment at the next tile. */
436 inline bool HandleNodeNextTile (Node
*n
, CFollowTrackRail
*tf
, NodeData
*segment
, RailType rail_type
);
438 /** Compute all costs for a newly-allocated (not cached) segment. */
439 inline EndSegmentReasonBits
CalcSegment (Node
*n
, const CFollowTrackRail
*tf
);
441 /* Fill in a node from cached data. */
442 inline EndSegmentReasonBits
RestoreCachedNode (Node
*n
);
444 /* Add special extra cost when the segment reaches our target. */
445 inline void AddTargetCost (Node
*n
, bool is_station
);
447 /** Struct to store a position in a path (node and path position). */
449 RailPathPos pos
; ///< position (tile and trackdir)
450 Node
*node
; ///< node where the position is
453 /* Find the earliest safe position on a path. */
454 Node
*FindSafePositionOnPath (Node
*node
, NodePos
*res
);
456 /* Try to reserve the path up to a given position. */
457 bool TryReservePath (TileIndex origin
, const NodePos
*res
);
460 /** Compute cost and modify node state for a signal. */
461 template <class TAstar
>
462 inline int CYapfRailBaseT
<TAstar
>::SignalCost(Node
& n
, const RailPathPos
&pos
, NodeData
*data
)
465 /* if there is one-way signal in the opposite direction, then it is not our way */
466 CPerfStart
perf_cost(m_perf_other_cost
);
468 if (HasSignalAlongPos(pos
)) {
469 SignalState sig_state
= GetSignalStateByPos(pos
);
470 SignalType sig_type
= GetSignalType(pos
);
472 n
.m_last_signal_type
= sig_type
;
474 /* cache the look-ahead polynomial constant only if we didn't pass more signals than the look-ahead limit is */
475 int look_ahead_cost
= (n
.m_num_signals_passed
< m_sig_look_ahead_costs
.Size()) ? m_sig_look_ahead_costs
.Data()[n
.m_num_signals_passed
] : 0;
476 if (sig_state
!= SIGNAL_STATE_RED
) {
478 n
.flags
.reset (n
.FLAG_LAST_SIGNAL_WAS_RED
);
479 /* negative look-ahead red-signal penalties would cause problems later, so use them as positive penalties for green signal */
480 if (look_ahead_cost
< 0) {
481 /* add its negation to the cost */
482 cost
-= look_ahead_cost
;
485 /* we have a red signal in our direction
486 * was it first signal which is two-way? */
487 if (!IsPbsSignal(sig_type
)
488 && m_settings
->rail_firstred_twoway_eol
489 && m_treat_first_red_two_way_signal_as_eol
490 && n
.flags
.test(n
.FLAG_CHOICE_SEEN
)
491 && HasSignalAgainstPos(pos
)
492 && n
.m_num_signals_passed
== 0) {
493 /* yes, the first signal is two-way red signal => DEAD END. Prune this branch... */
494 PruneIntermediateNodeBranch();
495 data
->end_reason
|= ESRB_DEAD_END
;
496 m_stopped_on_first_two_way_signal
= true;
499 n
.m_last_red_signal_type
= sig_type
;
500 n
.flags
.set (n
.FLAG_LAST_SIGNAL_WAS_RED
);
502 /* look-ahead signal penalty */
503 if (!IsPbsSignal(sig_type
) && look_ahead_cost
> 0) {
504 /* add the look ahead penalty only if it is positive */
505 cost
+= look_ahead_cost
;
508 /* special signal penalties */
509 if (n
.m_num_signals_passed
== 0) {
512 case SIGTYPE_EXIT
: cost
+= m_settings
->rail_firstred_exit_penalty
; break; // first signal is red pre-signal-exit
514 case SIGTYPE_ENTRY
: cost
+= m_settings
->rail_firstred_penalty
; break;
520 n
.m_num_signals_passed
++;
521 data
->last_signal
= pos
;
522 } else if (HasSignalAgainstPos(pos
)) {
523 if (GetSignalType(pos
) != SIGTYPE_PBS
) {
524 /* one-way signal in opposite direction */
525 data
->end_reason
|= ESRB_DEAD_END
;
527 cost
+= n
.m_num_signals_passed
< m_settings
->rail_look_ahead_max_signals
? m_settings
->rail_pbs_signal_back_penalty
: 0;
534 /** Compute cost and modify node state for a position. */
535 template <class TAstar
>
536 inline void CYapfRailBaseT
<TAstar
>::HandleNodeTile (Node
*n
, const CFollowTrackRail
*tf
, NodeData
*segment
, TileIndex prev
)
538 /* All other tile costs will be calculated here. */
539 segment
->segment_cost
+= OneTileCost(segment
->pos
);
541 /* If we skipped some tunnel/bridge/station tiles, add their base cost */
542 segment
->segment_cost
+= YAPF_TILE_LENGTH
* tf
->m_tiles_skipped
;
545 segment
->segment_cost
+= SlopeCost(segment
->pos
);
547 /* Signal cost (routine can modify segment data). */
548 segment
->segment_cost
+= SignalCost(*n
, segment
->pos
, segment
);
550 /* Reserved tiles. */
551 segment
->segment_cost
+= ReservationCost(*n
, segment
->pos
, tf
->m_tiles_skipped
);
553 /* Tests for 'potential target' reasons to close the segment. */
554 if (segment
->pos
.tile
== prev
) {
555 /* Penalty for reversing in a depot. */
556 assert(!segment
->pos
.in_wormhole());
557 assert(IsRailDepotTile(segment
->pos
.tile
));
558 assert(segment
->pos
.td
== DiagDirToDiagTrackdir(GetGroundDepotDirection(segment
->pos
.tile
)));
559 segment
->segment_cost
+= m_settings
->rail_depot_reverse_penalty
;
560 /* We will end in this pass (depot is possible target) */
561 segment
->end_reason
|= ESRB_DEPOT
;
563 } else if (!segment
->pos
.in_wormhole() && IsRailWaypointTile(segment
->pos
.tile
)) {
564 const Train
*v
= m_veh
;
565 if (v
->current_order
.IsType(OT_GOTO_WAYPOINT
) &&
566 GetStationIndex(segment
->pos
.tile
) == v
->current_order
.GetDestination() &&
567 !Waypoint::Get(v
->current_order
.GetDestination())->IsSingleTile()) {
568 /* This waypoint is our destination; maybe this isn't an unreserved
569 * one, so check that and if so see that as the last signal being
570 * red. This way waypoints near stations should work better. */
571 CFollowTrackRail
ft(v
);
572 ft
.SetPos(segment
->pos
);
576 if (!ft
.FollowNext()) {
578 add_extra_cost
= !IsWaitingPositionFree(v
, ft
.m_old
, _settings_game
.pf
.forbid_90_deg
);
582 assert(ft
.m_old
.tile
!= ft
.m_new
.tile
);
583 if (!ft
.m_new
.is_single()) {
584 /* We encountered a junction; it's going to be too complex to
585 * handle this perfectly, so just bail out. There is no simple
586 * free path, so try the other possibilities. */
587 add_extra_cost
= true;
591 /* If this is a safe waiting position we're done searching for it */
592 PBSPositionState state
= CheckWaitingPosition (v
, ft
.m_new
, _settings_game
.pf
.forbid_90_deg
);
593 if (state
!= PBS_UNSAFE
) {
594 add_extra_cost
= state
== PBS_BUSY
;
599 /* In the case this platform is (possibly) occupied we add penalty so the
600 * other platforms of this waypoint are evaluated as well, i.e. we assume
601 * that there is a red signal in the waypoint when it's occupied. */
602 if (add_extra_cost
) segment
->extra_cost
+= m_settings
->rail_lastred_penalty
;
604 /* Waypoint is also a good reason to finish. */
605 segment
->end_reason
|= ESRB_WAYPOINT
;
607 } else if (tf
->m_flag
== tf
->TF_STATION
) {
608 /* Station penalties. */
609 uint platform_length
= tf
->m_tiles_skipped
+ 1;
610 /* We don't know yet if the station is our target or not. Act like
611 * if it is pass-through station (not our destination). */
612 segment
->segment_cost
+= m_settings
->rail_station_penalty
* platform_length
;
613 /* We will end in this pass (station is possible target) */
614 segment
->end_reason
|= ESRB_STATION
;
616 } else if (mask_reserved_tracks
) {
617 /* Searching for a safe tile? */
618 if (HasSignalAlongPos(segment
->pos
) && !IsPbsSignal(GetSignalType(segment
->pos
))) {
619 segment
->end_reason
|= ESRB_SAFE_TILE
;
623 /* Apply min/max speed penalties only when inside the look-ahead radius. Otherwise
624 * it would cause desync in MP. */
625 if (n
->m_num_signals_passed
< m_sig_look_ahead_costs
.Size())
628 int max_speed
= tf
->GetSpeedLimit(&min_speed
);
629 int max_veh_speed
= m_veh
->GetDisplayMaxSpeed();
630 if (max_speed
< max_veh_speed
) {
631 segment
->extra_cost
+= YAPF_TILE_LENGTH
* (max_veh_speed
- max_speed
) * (1 + tf
->m_tiles_skipped
) / max_veh_speed
;
633 if (min_speed
> max_veh_speed
) {
634 segment
->extra_cost
+= YAPF_TILE_LENGTH
* (min_speed
- max_veh_speed
);
638 /* Finish if we already exceeded the maximum path cost (i.e. when
639 * searching for the nearest depot). */
640 if (m_max_cost
> 0 && (segment
->parent_cost
+ segment
->entry_cost
+ segment
->segment_cost
) > m_max_cost
) {
641 segment
->end_reason
|= ESRB_PATH_TOO_LONG
;
645 /** Check for possible reasons to end a segment at the next tile. */
646 template <class TAstar
>
647 inline bool CYapfRailBaseT
<TAstar
>::HandleNodeNextTile (Node
*n
, CFollowTrackRail
*tf
, NodeData
*segment
, RailType rail_type
)
649 if (!tf
->Follow(segment
->pos
)) {
650 assert(tf
->m_err
!= CFollowTrackRail::EC_NONE
);
651 /* Can't move to the next tile (EOL?). */
652 if (tf
->m_err
== CFollowTrackRail::EC_RAIL_TYPE
) {
653 segment
->end_reason
|= ESRB_RAIL_TYPE
;
655 segment
->end_reason
|= ESRB_DEAD_END
;
658 if (mask_reserved_tracks
&& !HasOnewaySignalBlockingPos(segment
->pos
)) {
659 segment
->end_reason
|= ESRB_SAFE_TILE
;
664 /* Check if the next tile is not a choice. */
665 if (!tf
->m_new
.is_single()) {
666 /* More than one segment will follow. Close this one. */
667 segment
->end_reason
|= ESRB_CHOICE_FOLLOWS
;
671 /* Gather the next tile/trackdir. */
673 if (mask_reserved_tracks
) {
674 if (HasSignalAlongPos(tf
->m_new
) && IsPbsSignal(GetSignalType(tf
->m_new
))) {
675 /* Possible safe tile. */
676 segment
->end_reason
|= ESRB_SAFE_TILE
;
677 } else if (HasSignalAgainstPos(tf
->m_new
) && GetSignalType(tf
->m_new
) == SIGTYPE_PBS_ONEWAY
) {
678 /* Possible safe tile, but not so good as it's the back of a signal... */
679 segment
->end_reason
|= ESRB_SAFE_TILE
| ESRB_DEAD_END
;
680 segment
->extra_cost
+= m_settings
->rail_lastred_exit_penalty
;
684 /* Check the next tile for the rail type. */
685 if (tf
->m_new
.in_wormhole()) {
686 RailType next_rail_type
= IsRailwayTile(tf
->m_new
.wormhole
) ? GetBridgeRailType(tf
->m_new
.wormhole
) : GetRailType(tf
->m_new
.wormhole
);
687 assert(next_rail_type
== rail_type
);
688 } else if (GetRailType(tf
->m_new
.tile
, TrackdirToTrack(tf
->m_new
.td
)) != rail_type
) {
689 /* Segment must consist from the same rail_type tiles. */
690 segment
->end_reason
|= ESRB_RAIL_TYPE
;
694 /* Avoid infinite looping. */
695 if (tf
->m_new
== n
->GetPos()) {
696 segment
->end_reason
|= ESRB_INFINITE_LOOP
;
700 if (segment
->segment_cost
> s_max_segment_cost
) {
701 /* Potentially in the infinite loop (or only very long segment?). We should
702 * not force it to finish prematurely unless we are on a regular tile. */
703 if (!tf
->m_new
.in_wormhole() && IsNormalRailTile(tf
->m_new
.tile
)) {
704 segment
->end_reason
|= ESRB_SEGMENT_TOO_LONG
;
709 /* Any other reason bit set? */
710 return segment
->end_reason
== ESRB_NONE
;
713 /** Compute all costs for a newly-allocated (not cached) segment. */
714 template <class TAstar
>
715 inline EndSegmentReasonBits CYapfRailBaseT
<TAstar
>::CalcSegment (Node
*n
, const CFollowTrackRail
*tf
)
717 /* Each node cost contains 2 or 3 main components:
718 * 1. Transition cost - cost of the move from previous node (tile):
719 * - curve cost (or zero for straight move)
722 * - YAPF_TILE_LENGTH for diagonal tiles
723 * - YAPF_TILE_CORNER_LENGTH for non-diagonal tiles
725 * - tile slope penalty (upward slopes)
726 * - red signal penalty
727 * - level crossing penalty
728 * - speed-limit penalty (bridges)
729 * - station platform penalty
730 * - penalty for reversing in the depot
732 * 3. Extra cost (applies to the last node only)
733 * - last red signal penalty
734 * - penalty for too long or too short platform on the destination station
737 /* Segment: one or more tiles connected by contiguous tracks of the same type.
738 * Each segment cost includes 'Tile cost' for all its tiles (including the first
739 * and last), and the 'Transition cost' between its tiles. The first transition
740 * cost of segment entry (move from the 'parent' node) is not included!
743 /* start at n and walk to the end of segment */
745 segment
.parent_cost
= n
->m_parent
->m_cost
;
746 segment
.entry_cost
= TransitionCost (n
->m_parent
->GetLastPos(), n
->GetPos());
747 segment
.segment_cost
= 0;
748 segment
.extra_cost
= 0;
749 segment
.pos
= n
->GetPos();
750 segment
.last_signal
= RailPathPos();
751 segment
.end_reason
= ESRB_NONE
;
753 TileIndex prev
= n
->m_parent
->GetLastPos().tile
;
755 RailType rail_type
= !segment
.pos
.in_wormhole() ? GetRailType(segment
.pos
.tile
, TrackdirToTrack(segment
.pos
.td
)) :
756 IsRailwayTile(segment
.pos
.wormhole
) ? GetBridgeRailType(segment
.pos
.wormhole
) : GetRailType(segment
.pos
.wormhole
);
759 HandleNodeTile (n
, tf
, &segment
, prev
);
761 /* Move to the next tile/trackdir. */
763 assert(tf_local
.m_veh_owner
== m_veh
->owner
);
764 assert(tf_local
.m_railtypes
== m_compatible_railtypes
);
765 assert(tf_local
.m_pPerf
== &m_perf_ts_cost
);
767 if (!HandleNodeNextTile (n
, &tf_local
, &segment
, rail_type
)) break;
769 /* Transition cost (cost of the move from previous tile) */
770 segment
.segment_cost
+= TransitionCost (segment
.pos
, tf_local
.m_new
);
772 /* For the next loop set new prev and cur tile info. */
773 prev
= segment
.pos
.tile
;
774 segment
.pos
= tf_local
.m_new
;
777 /* Write back the segment information so it can be reused the next time. */
778 n
->m_segment
->m_last
= segment
.pos
;
779 n
->m_segment
->m_cost
= segment
.segment_cost
;
780 n
->m_segment
->m_last_signal
= segment
.last_signal
;
781 n
->m_segment
->m_end_segment_reason
= segment
.end_reason
& ESRB_CACHED_MASK
;
783 /* total node cost */
784 n
->m_cost
= segment
.parent_cost
+ segment
.entry_cost
+ segment
.segment_cost
+ segment
.extra_cost
;
785 return segment
.end_reason
;
788 /** Fill in a node from cached data. */
789 template <class TAstar
>
790 inline EndSegmentReasonBits CYapfRailBaseT
<TAstar
>::RestoreCachedNode (Node
*n
)
792 /* total node cost */
793 n
->m_cost
= n
->m_parent
->m_cost
+ TransitionCost (n
->m_parent
->GetLastPos(), n
->GetPos()) + n
->m_segment
->m_cost
;
794 /* We will need also some information about the last signal (if it was red). */
795 if (n
->m_segment
->m_last_signal
.is_valid()) {
796 assert(HasSignalAlongPos(n
->m_segment
->m_last_signal
));
797 SignalState sig_state
= GetSignalStateByPos(n
->m_segment
->m_last_signal
);
798 bool is_red
= (sig_state
== SIGNAL_STATE_RED
);
799 n
->flags
.set (n
->FLAG_LAST_SIGNAL_WAS_RED
, is_red
);
801 n
->m_last_red_signal_type
= GetSignalType(n
->m_segment
->m_last_signal
);
804 /* No further calculation needed. */
805 return n
->m_segment
->m_end_segment_reason
;
808 /** Add special extra cost when the segment reaches our target. */
809 template <class TAstar
>
810 inline void CYapfRailBaseT
<TAstar
>::AddTargetCost (Node
*n
, bool is_station
)
812 n
->flags
.set (n
->FLAG_TARGET_SEEN
);
814 /* Last-red and last-red-exit penalties. */
815 if (n
->flags
.test (n
->FLAG_LAST_SIGNAL_WAS_RED
)) {
816 if (n
->m_last_red_signal_type
== SIGTYPE_EXIT
) {
817 /* last signal was red pre-signal-exit */
818 n
->m_cost
+= m_settings
->rail_lastred_exit_penalty
;
819 } else if (!IsPbsSignal(n
->m_last_red_signal_type
)) {
820 /* Last signal was red, but not exit or path signal. */
821 n
->m_cost
+= m_settings
->rail_lastred_penalty
;
825 /* Station platform-length penalty. */
827 const BaseStation
*st
= BaseStation::GetByTile(n
->GetLastPos().tile
);
829 uint platform_length
= st
->GetPlatformLength(n
->GetLastPos().tile
, ReverseDiagDir(TrackdirToExitdir(n
->GetLastPos().td
)));
830 /* Reduce the extra cost caused by passing-station penalty (each station receives it in the segment cost). */
831 n
->m_cost
-= m_settings
->rail_station_penalty
* platform_length
;
832 /* Add penalty for the inappropriate platform length. */
833 n
->m_cost
+= PlatformLengthPenalty(platform_length
);
838 * Find the earliest safe position on a path.
839 * @param node Node to start searching back from (usually the best found node).
840 * @param res Where to store the safe position found.
841 * @return The first node in the path after the initial node.
843 template <class TAstar
>
844 inline typename
TAstar::Node
*CYapfRailBaseT
<TAstar
>::FindSafePositionOnPath (Node
*node
, NodePos
*res
)
846 /* We will never pass more than two signals, no need to check for a safe tile. */
847 assert (node
->m_parent
!= NULL
);
848 while (node
->m_parent
->m_num_signals_passed
>= 2) {
849 node
= node
->m_parent
;
850 /* If the parent node has passed at least 2 signals, it
851 * cannot be a root node, because root nodes are single-tile
852 * nodes and can therefore have only one signal, if any. */
853 assert (node
->m_parent
!= NULL
);
856 /* Default safe position if no other, earlier one is found. */
858 res
->pos
= node
->GetLastPos();
860 /* Walk through the path back to the origin. */
861 CFollowTrackRail
ft (m_veh
, Allow90degTurns(), m_compatible_railtypes
);
864 Node
*parent
= node
->m_parent
;
865 assert (parent
!= NULL
);
867 /* Search node for a safe position. */
868 ft
.SetPos (node
->GetPos());
870 if (IsSafeWaitingPosition (m_veh
, ft
.m_new
, !Allow90degTurns())) {
871 /* Found a safe position in this node. */
877 if (ft
.m_new
== node
->GetLastPos()) break; // no safe position found in node
879 bool follow
= ft
.FollowNext();
881 assert (ft
.m_new
.is_single());
884 /* Stop at node after initial node. */
885 if (parent
->m_parent
== NULL
) return node
;
891 * Try to reserve a single track/platform
892 * @param pos The position to reserve (last tile for platforms)
893 * @param origin If pos is a platform, consider the platform to begin right after this tile
894 * @return Whether reservation succeeded
896 static bool ReserveSingleTrack (const RailPathPos
&pos
, TileIndex origin
= INVALID_TILE
)
898 if (pos
.in_wormhole() || !IsRailStationTile(pos
.tile
)) {
899 return TryReserveRailTrack(pos
);
902 TileIndexDiff diff
= TileOffsByDiagDir(TrackdirToExitdir(ReverseTrackdir(pos
.td
)));
903 TileIndex t
= pos
.tile
;
906 if (HasStationReservation(t
)) {
907 /* Platform could not be reserved, undo. */
909 while (t
!= pos
.tile
) {
910 t
= TILE_ADD(t
, diff
);
911 SetRailStationReservation(t
, false);
916 SetRailStationReservation(t
, true);
917 MarkTileDirtyByTile(t
);
918 t
= TILE_ADD(t
, diff
);
919 } while (IsCompatibleTrainStationTile(t
, pos
.tile
) && t
!= origin
);
921 TriggerStationRandomisation(NULL
, pos
.tile
, SRT_PATH_RESERVATION
);
927 * Unreserve a single track/platform
928 * @param pos The position to reserve (last tile for platforms)
929 * @param origin If pos is a platform, consider the platform to begin right after this tile
931 static void UnreserveSingleTrack (const RailPathPos
&pos
, TileIndex origin
= INVALID_TILE
)
933 if (pos
.in_wormhole() || !IsRailStationTile(pos
.tile
)) {
934 UnreserveRailTrack(pos
);
938 TileIndexDiff diff
= TileOffsByDiagDir(TrackdirToExitdir(ReverseTrackdir(pos
.td
)));
939 TileIndex t
= pos
.tile
;
940 while (IsCompatibleTrainStationTile(t
, pos
.tile
) && t
!= origin
) {
941 assert(HasStationReservation(t
));
942 SetRailStationReservation(t
, false);
943 t
= TILE_ADD(t
, diff
);
948 * Try to reserve the path up to a given position.
949 * @param origin Starting tile for the reservation.
950 * @param res Target tile for the reservation.
951 * @return Whether reservation succeeded.
953 template <class TAstar
>
954 bool CYapfRailBaseT
<TAstar
>::TryReservePath (TileIndex origin
, const NodePos
*res
)
956 /* Don't bother if the target is reserved. */
957 if (!IsWaitingPositionFree(m_veh
, res
->pos
)) return false;
959 CFollowTrackRail
ft (m_veh
, Allow90degTurns(), m_compatible_railtypes
);
961 for (Node
*node
= res
->node
; node
->m_parent
!= NULL
; node
= node
->m_parent
) {
962 ft
.SetPos (node
->GetPos());
964 if (!ReserveSingleTrack (ft
.m_new
, origin
)) {
965 /* Reservation failed, undo. */
966 Node
*failed_node
= node
;
967 RailPathPos res_fail
= ft
.m_new
;
968 for (node
= res
->node
; node
!= failed_node
; node
= node
->m_parent
) {
969 ft
.SetPos (node
->GetPos());
971 UnreserveSingleTrack (ft
.m_new
, origin
);
972 if (ft
.m_new
== res
->pos
) break;
973 if (ft
.m_new
== node
->GetLastPos()) break;
977 ft
.SetPos (failed_node
->GetPos());
978 while (ft
.m_new
!= res_fail
) {
979 assert (ft
.m_new
!= res
->pos
);
980 assert (ft
.m_new
!= node
->GetLastPos());
981 UnreserveSingleTrack (ft
.m_new
, origin
);
987 if (ft
.m_new
== res
->pos
) break;
988 if (ft
.m_new
== node
->GetLastPos()) break;
989 bool follow
= ft
.FollowNext();
991 assert (ft
.m_new
.is_single());
995 if (CanUseGlobalCache(*res
->node
)) {
996 YapfNotifyTrackLayoutChange(INVALID_TILE
, INVALID_TRACK
);
1003 template <class Tpf
, class TAstar
, bool Tmask_reserved_tracks
>
1004 class CYapfRailT
: public CYapfRailBaseT
<TAstar
>
1007 typedef CYapfRailBaseT
<TAstar
> Base
;
1008 typedef typename
TAstar::Node Node
; ///< this will be our node type
1009 typedef typename
Node::Key Key
; ///< key to hash tables
1010 typedef typename
Node::CachedData CachedData
;
1011 typedef typename
CachedData::Key CacheKey
;
1012 typedef CSegmentCostCacheT
<CachedData
> Cache
;
1013 typedef SmallArray
<CachedData
> LocalCache
;
1016 CYapfRailT (const Train
*v
, bool allow_90deg
, bool override_rail_type
= false)
1017 : Base (v
, allow_90deg
, override_rail_type
, Tmask_reserved_tracks
)
1021 /** to access inherited path finder */
1024 return *static_cast<Tpf
*>(this);
1028 /** Called by the A-star underlying class to find the neighbours of a node. */
1029 inline void Follow (Node
*old_node
)
1031 if (!Base::tf
.Follow(old_node
->GetLastPos())) return;
1032 if (Tmask_reserved_tracks
&& !Base::tf
.MaskReservedTracks()) return;
1034 bool is_choice
= !Base::tf
.m_new
.is_single();
1035 RailPathPos pos
= Base::tf
.m_new
;
1036 for (TrackdirBits rtds
= Base::tf
.m_new
.trackdirs
; rtds
!= TRACKDIR_BIT_NONE
; rtds
= KillFirstBit(rtds
)) {
1037 pos
.set_trackdir (FindFirstTrackdir(rtds
));
1038 Node
*n
= TAstar::CreateNewNode(old_node
, pos
, is_choice
);
1040 /* evaluate the node */
1041 bool cached
= Base::AttachSegmentToNode(n
);
1043 Yapf().m_stats_cost_calcs
++;
1045 Yapf().m_stats_cache_hits
++;
1048 CPerfStart
perf_cost(Yapf().m_perf_cost
);
1050 EndSegmentReasonBits end_reason
;
1052 end_reason
= Base::RestoreCachedNode (n
);
1054 end_reason
= Base::CalcSegment (n
, &this->tf
);
1057 assert (((end_reason
& ESRB_POSSIBLE_TARGET
) != ESRB_NONE
) || !Yapf().PfDetectDestination(n
->GetLastPos()));
1059 if (((end_reason
& ESRB_POSSIBLE_TARGET
) != ESRB_NONE
) &&
1060 Yapf().PfDetectDestination(n
->GetLastPos())) {
1061 /* Special costs for the case we have reached our target. */
1062 Base::AddTargetCost (n
, (end_reason
& ESRB_STATION
) != ESRB_NONE
);
1064 n
->m_estimate
= n
->m_cost
;
1065 this->FoundTarget(n
);
1067 } else if ((end_reason
& ESRB_ABORT_PF_MASK
) != ESRB_NONE
) {
1068 /* Reason to not continue. Stop this PF branch. */
1073 Yapf().PfCalcEstimate(*n
);
1074 this->InsertNode(n
);
1079 /** call the node follower */
1080 static inline void Follow (Tpf
*pf
, Node
*n
)
1086 * Main pathfinder routine:
1087 * - set startup node(s)
1088 * - main loop that stops if:
1089 * - the destination was found
1090 * - or the open list is empty (no route to destination).
1091 * - or the maximum amount of loops reached - m_max_search_nodes (default = 10000)
1092 * @return true if the path was found
1094 inline bool FindPath (void)
1096 #ifndef NO_DEBUG_MESSAGES
1097 CPerformanceTimer perf
;
1099 #endif /* !NO_DEBUG_MESSAGES */
1101 bool bDestFound
= TAstar::FindPath (Follow
, Base::m_settings
->max_search_nodes
);
1103 #ifndef NO_DEBUG_MESSAGES
1105 if (_debug_yapf_level
>= 2) {
1106 int t
= perf
.Get(1000000);
1107 _total_pf_time_us
+= t
;
1109 if (_debug_yapf_level
>= 3) {
1110 UnitID veh_idx
= (Base::m_veh
!= NULL
) ? Base::m_veh
->unitnumber
: 0;
1111 float cache_hit_ratio
= (Base::m_stats_cache_hits
== 0) ? 0.0f
: ((float)Base::m_stats_cache_hits
/ (float)(Base::m_stats_cache_hits
+ Base::m_stats_cost_calcs
) * 100.0f
);
1112 int cost
= bDestFound
? TAstar::best
->m_cost
: -1;
1113 int dist
= bDestFound
? TAstar::best
->m_estimate
- TAstar::best
->m_cost
: -1;
1115 DEBUG(yapf
, 3, "[YAPFt]%c%4d- %d us - %d rounds - %d open - %d closed - CHR %4.1f%% - C %d D %d - c%d(sc%d, ts%d, o%d) -- ",
1116 bDestFound
? '-' : '!', veh_idx
, t
, TAstar::num_steps
, TAstar::OpenCount(), TAstar::ClosedCount(),
1117 cache_hit_ratio
, cost
, dist
, Base::m_perf_cost
.Get(1000000), Base::m_perf_slope_cost
.Get(1000000),
1118 Base::m_perf_ts_cost
.Get(1000000), Base::m_perf_other_cost
.Get(1000000)
1122 #endif /* !NO_DEBUG_MESSAGES */
1129 : CYapfRailT
<CYapfRail
, AstarRailTrackDir
, false>
1132 typedef CYapfRailT
<CYapfRail
, AstarRailTrackDir
, false> Base
;
1133 typedef AstarRailTrackDir TAstar
;
1134 typedef TAstar::Node Node
;
1137 TileIndex m_dest_tile
;
1138 StationID m_dest_station_id
;
1141 CYapfRail (const Train
*v
, bool allow_90deg
)
1142 : Base (v
, allow_90deg
)
1144 switch (v
->current_order
.GetType()) {
1145 case OT_GOTO_WAYPOINT
:
1146 if (!Waypoint::Get(v
->current_order
.GetDestination())->IsSingleTile()) {
1147 /* In case of 'complex' waypoints we need to do a look
1148 * ahead. This look ahead messes a bit about, which
1149 * means that it 'corrupts' the cache. To prevent this
1150 * we disable caching when we're looking for a complex
1152 Base::DisableCache(true);
1155 case OT_GOTO_STATION
:
1156 m_dest_station_id
= v
->current_order
.GetDestination();
1157 m_dest_tile
= BaseStation::Get(m_dest_station_id
)->GetClosestTile(v
->tile
, v
->current_order
.IsType(OT_GOTO_STATION
) ? STATION_RAIL
: STATION_WAYPOINT
);
1161 m_dest_station_id
= INVALID_STATION
;
1162 m_dest_tile
= v
->dest_tile
;
1167 /** Called by YAPF to detect if node ends in the desired destination */
1168 inline bool PfDetectDestination (const RailPathPos
&pos
) const
1170 if (m_dest_station_id
!= INVALID_STATION
) {
1171 return !pos
.in_wormhole() && HasStationTileRail(pos
.tile
)
1172 && (GetStationIndex(pos
.tile
) == m_dest_station_id
)
1173 && (GetRailStationTrack(pos
.tile
) == TrackdirToTrack(pos
.td
));
1175 return !pos
.in_wormhole() && (pos
.tile
== m_dest_tile
);
1180 * Called by YAPF to calculate cost estimate. Calculates distance to the destination
1181 * adds it to the actual cost from origin and stores the sum to the Node::m_estimate
1183 inline void PfCalcEstimate (Node
&n
) const
1185 static const int dg_dir_to_x_offs
[] = {-1, 0, 1, 0};
1186 static const int dg_dir_to_y_offs
[] = {0, 1, 0, -1};
1188 TileIndex tile
= n
.GetLastPos().tile
;
1189 DiagDirection exitdir
= TrackdirToExitdir(n
.GetLastPos().td
);
1190 int x1
= 2 * TileX(tile
) + dg_dir_to_x_offs
[(int)exitdir
];
1191 int y1
= 2 * TileY(tile
) + dg_dir_to_y_offs
[(int)exitdir
];
1192 int x2
= 2 * TileX(m_dest_tile
);
1193 int y2
= 2 * TileY(m_dest_tile
);
1194 int dx
= abs(x1
- x2
);
1195 int dy
= abs(y1
- y2
);
1196 int dmin
= min(dx
, dy
);
1197 int dxy
= abs(dx
- dy
);
1198 int d
= dmin
* YAPF_TILE_CORNER_LENGTH
+ (dxy
- 1) * (YAPF_TILE_LENGTH
/ 2);
1199 n
.m_estimate
= n
.m_cost
+ d
;
1200 assert(n
.m_estimate
>= n
.m_parent
->m_estimate
);
1203 inline Trackdir
ChooseRailTrack(const RailPathPos
&origin
, bool reserve_track
, PFResult
*target
)
1205 if (target
!= NULL
) target
->pos
.tile
= INVALID_TILE
;
1207 /* set origin and destination nodes */
1208 Base::SetOrigin(origin
);
1210 /* find the best path */
1211 bool path_found
= Base::FindPath();
1213 /* if path not found - return INVALID_TRACKDIR */
1214 Trackdir next_trackdir
= INVALID_TRACKDIR
;
1215 Node
*pNode
= Base::GetBestNode();
1216 if (pNode
!= NULL
) {
1217 if (reserve_track
&& path_found
) {
1218 typename
Base::NodePos res
;
1219 Node
*best_next_node
= Base::FindSafePositionOnPath (pNode
, &res
);
1220 if (target
!= NULL
) target
->pos
= res
.pos
;
1221 /* return trackdir from the best origin node (one of start nodes) */
1222 next_trackdir
= best_next_node
->GetPos().td
;
1224 assert (best_next_node
->m_parent
->GetPos() == origin
);
1225 assert (best_next_node
->m_parent
->GetLastPos() == origin
);
1226 bool okay
= Base::TryReservePath (origin
.tile
, &res
);
1227 if (target
!= NULL
) target
->okay
= okay
;
1229 while (pNode
->m_parent
->m_parent
!= NULL
) {
1230 pNode
= pNode
->m_parent
;
1232 assert (pNode
->m_parent
->GetPos() == origin
);
1233 assert (pNode
->m_parent
->GetLastPos() == origin
);
1234 /* return trackdir from the best origin node (one of start nodes) */
1235 next_trackdir
= pNode
->GetPos().td
;
1239 /* Treat the path as found if stopped on the first two way signal(s). */
1240 if (target
!= NULL
) target
->found
= path_found
| Base::m_stopped_on_first_two_way_signal
;
1241 return next_trackdir
;
1244 inline bool CheckReverseTrain(const RailPathPos
&pos1
, const RailPathPos
&pos2
, int reverse_penalty
)
1246 /* create pathfinder instance
1247 * set origin and destination nodes */
1248 Base::SetOrigin(pos1
, pos2
, reverse_penalty
, false);
1250 /* find the best path */
1251 bool bFound
= Base::FindPath();
1253 if (!bFound
) return false;
1256 * walk through the path back to the origin */
1257 Node
*pNode
= Base::GetBestNode();
1258 while (pNode
->m_parent
!= NULL
) {
1259 pNode
= pNode
->m_parent
;
1262 /* check if it was reversed origin */
1263 Node
& best_org_node
= *pNode
;
1264 bool reversed
= (best_org_node
.m_cost
!= 0);
1269 Trackdir
YapfTrainChooseTrack(const Train
*v
, const RailPathPos
&origin
, bool reserve_track
, PFResult
*target
)
1271 /* create pathfinder instance */
1272 CYapfRail
pf1 (v
, !_settings_game
.pf
.forbid_90_deg
);
1273 #if !DEBUG_YAPF_CACHE
1274 Trackdir result1
= pf1
.ChooseRailTrack(origin
, reserve_track
, target
);
1277 Trackdir result1
= pf1
.ChooseRailTrack(origin
, false, NULL
);
1278 CYapfRail
pf2 (v
, !_settings_game
.pf
.forbid_90_deg
);
1279 pf2
.DisableCache(true);
1280 Trackdir result2
= pf2
.ChooseRailTrack(origin
, reserve_track
, target
);
1281 if (result1
!= result2
) {
1282 DEBUG(yapf
, 0, "CACHE ERROR: ChooseRailTrack() = [%d, %d]", result1
, result2
);
1283 DumpState(pf1
, pf2
);
1290 bool YapfTrainCheckReverse(const Train
*v
)
1292 const Train
*last_veh
= v
->Last();
1294 /* tiles where front and back are */
1295 RailPathPos pos
= v
->GetPos();
1296 RailPathPos rev
= last_veh
->GetReversePos();
1298 int reverse_penalty
= 0;
1300 if (pos
.in_wormhole()) {
1301 /* front in tunnel / on bridge */
1302 assert(TrackdirToExitdir(pos
.td
) == ReverseDiagDir(GetTunnelBridgeDirection(pos
.wormhole
)));
1304 /* Current position of the train in the wormhole */
1305 TileIndex cur_tile
= TileVirtXY(v
->x_pos
, v
->y_pos
);
1307 /* Add distance to drive in the wormhole as penalty for the forward path, i.e. bonus for the reverse path
1308 * Note: Negative penalties are ok for the start tile. */
1309 reverse_penalty
-= DistanceManhattan(cur_tile
, pos
.tile
) * YAPF_TILE_LENGTH
;
1312 if (rev
.in_wormhole()) {
1313 /* back in tunnel / on bridge */
1314 assert(TrackdirToExitdir(rev
.td
) == ReverseDiagDir(GetTunnelBridgeDirection(rev
.wormhole
)));
1316 /* Current position of the last wagon in the wormhole */
1317 TileIndex cur_tile
= TileVirtXY(last_veh
->x_pos
, last_veh
->y_pos
);
1319 /* Add distance to drive in the wormhole as penalty for the revere path. */
1320 reverse_penalty
+= DistanceManhattan(cur_tile
, rev
.tile
) * YAPF_TILE_LENGTH
;
1323 /* slightly hackish: If the pathfinders finds a path, the cost of the first node is tested to distinguish between forward- and reverse-path. */
1324 if (reverse_penalty
== 0) reverse_penalty
= 1;
1326 CYapfRail
pf1 (v
, !_settings_game
.pf
.forbid_90_deg
);
1327 bool result1
= pf1
.CheckReverseTrain(pos
, rev
, reverse_penalty
);
1329 #if DEBUG_YAPF_CACHE
1330 CYapfRail
pf2 (v
, !_settings_game
.pf
.forbid_90_deg
);
1331 pf2
.DisableCache(true);
1332 bool result2
= pf2
.CheckReverseTrain(pos
, rev
, reverse_penalty
);
1333 if (result1
!= result2
) {
1334 DEBUG(yapf
, 0, "CACHE ERROR: CheckReverseTrain() = [%s, %s]", result1
? "T" : "F", result2
? "T" : "F");
1335 DumpState(pf1
, pf2
);
1343 struct CYapfAnyDepotRail
1344 : CYapfRailT
<CYapfAnyDepotRail
, AstarRailTrackDir
, false>
1347 typedef CYapfRailT
<CYapfAnyDepotRail
, AstarRailTrackDir
, false> Base
;
1348 typedef AstarRailTrackDir TAstar
;
1349 typedef TAstar::Node Node
;
1351 CYapfAnyDepotRail (const Train
*v
, bool allow_90deg
)
1352 : Base (v
, allow_90deg
)
1356 /** Called by YAPF to detect if node ends in the desired destination */
1357 inline bool PfDetectDestination (const RailPathPos
&pos
) const
1359 return !pos
.in_wormhole() && IsRailDepotTile(pos
.tile
);
1362 /** Called by YAPF to calculate cost estimate. */
1363 inline void PfCalcEstimate (Node
&n
) const
1365 n
.m_estimate
= n
.m_cost
;
1368 inline bool FindNearestDepotTwoWay(const RailPathPos
&pos1
, const RailPathPos
&pos2
, int max_penalty
, int reverse_penalty
, TileIndex
*depot_tile
, bool *reversed
)
1370 /* set origin and destination nodes */
1371 Base::SetOrigin (pos1
, pos2
, reverse_penalty
, true);
1372 Base::SetMaxCost(max_penalty
);
1374 /* find the best path */
1375 bool bFound
= Base::FindPath();
1376 if (!bFound
) return false;
1379 * get found depot tile */
1380 Node
*n
= Base::GetBestNode();
1381 *depot_tile
= n
->GetLastPos().tile
;
1383 /* walk through the path back to the origin */
1384 while (n
->m_parent
!= NULL
) {
1388 /* if the origin node is our front vehicle tile/Trackdir then we didn't reverse
1389 * but we can also look at the cost (== 0 -> not reversed, == reverse_penalty -> reversed) */
1390 *reversed
= (n
->m_cost
!= 0);
1396 bool YapfTrainFindNearestDepot(const Train
*v
, uint max_penalty
, FindDepotData
*res
)
1399 FollowTrainReservation(v
, &origin
);
1400 RailPathPos rev
= v
->Last()->GetReversePos();
1402 CYapfAnyDepotRail
pf1 (v
, !_settings_game
.pf
.forbid_90_deg
);
1404 * With caching enabled it simply cannot get a reliable result when you
1405 * have limited the distance a train may travel. This means that the
1406 * cached result does not match uncached result in all cases and that
1407 * causes desyncs. So disable caching when finding for a depot that is
1408 * nearby. This only happens with automatic servicing of vehicles,
1409 * so it will only impact performance when you do not manually set
1410 * depot orders and you do not disable automatic servicing.
1412 if (max_penalty
!= 0) pf1
.DisableCache(true);
1413 bool result1
= pf1
.FindNearestDepotTwoWay (origin
, rev
, max_penalty
, YAPF_INFINITE_PENALTY
, &res
->tile
, &res
->reverse
);
1415 #if DEBUG_YAPF_CACHE
1416 CYapfAnyDepotRail
pf2 (v
, !_settings_game
.pf
.forbid_90_deg
);
1417 TileIndex depot_tile2
= INVALID_TILE
;
1418 bool reversed2
= false;
1419 pf2
.DisableCache(true);
1420 bool result2
= pf2
.FindNearestDepotTwoWay (origin
, rev
, max_penalty
, YAPF_INFINITE_PENALTY
, &depot_tile2
, &reversed2
);
1421 if (result1
!= result2
|| (result1
&& (res
->tile
!= depot_tile2
|| res
->reverse
!= reversed2
))) {
1422 DEBUG(yapf
, 0, "CACHE ERROR: FindNearestDepotTwoWay() = [%s, %s]", result1
? "T" : "F", result2
? "T" : "F");
1423 DumpState(pf1
, pf2
);
1431 struct CYapfAnySafeTileRail
1432 : CYapfRailT
<CYapfAnySafeTileRail
, AstarRailTrackDir
, true>
1435 typedef CYapfRailT
<CYapfAnySafeTileRail
, AstarRailTrackDir
, true> Base
;
1436 typedef AstarRailTrackDir TAstar
;
1437 typedef TAstar::Node Node
;
1439 CYapfAnySafeTileRail (const Train
*v
, bool allow_90deg
, bool override_railtype
)
1440 : Base (v
, allow_90deg
, override_railtype
)
1444 /** Called by YAPF to detect if node ends in the desired destination */
1445 inline bool PfDetectDestination (const RailPathPos
&pos
) const
1447 return IsFreeSafeWaitingPosition(Base::m_veh
, pos
, Base::Allow90degTurns());
1450 /** Called by YAPF to calculate cost estimate. */
1451 inline void PfCalcEstimate (Node
&n
) const
1453 n
.m_estimate
= n
.m_cost
;
1456 bool FindNearestSafeTile(const RailPathPos
&pos
, bool override_railtype
, bool dont_reserve
)
1458 /* Set origin and destination. */
1459 Base::SetOrigin(pos
);
1461 bool bFound
= Base::FindPath();
1462 if (!bFound
) return false;
1464 if (dont_reserve
) return true;
1466 /* Found a destination, search for a reservation target. */
1467 Node
*pNode
= Base::GetBestNode();
1468 typename
Base::NodePos res
;
1469 pNode
= Base::FindSafePositionOnPath(pNode
, &res
)->m_parent
;
1470 assert (pNode
->GetPos() == pos
);
1471 assert (pNode
->GetLastPos() == pos
);
1473 return Base::TryReservePath (pos
.tile
, &res
);
1477 bool YapfTrainFindNearestSafeTile(const Train
*v
, const RailPathPos
&pos
, bool override_railtype
)
1479 /* Create pathfinder instance */
1480 CYapfAnySafeTileRail
pf1 (v
, !_settings_game
.pf
.forbid_90_deg
, override_railtype
);
1481 #if !DEBUG_YAPF_CACHE
1482 bool result1
= pf1
.FindNearestSafeTile(pos
, override_railtype
, false);
1485 bool result2
= pf1
.FindNearestSafeTile(pos
, override_railtype
, true);
1486 CYapfAnySafeTileRail
pf2 (v
, !_settings_game
.pf
.forbid_90_deg
, override_railtype
);
1487 pf2
.DisableCache(true);
1488 bool result1
= pf2
.FindNearestSafeTile(pos
, override_railtype
, false);
1489 if (result1
!= result2
) {
1490 DEBUG(yapf
, 0, "CACHE ERROR: FindSafeTile() = [%s, %s]", result2
? "T" : "F", result1
? "T" : "F");
1491 DumpState(pf1
, pf2
);