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_base.hpp Base classes for YAPF. */
15 #include "../../debug.h"
16 #include "../../settings_type.h"
18 extern int _total_pf_time_us
;
21 * CYapfBaseT - A-star type path finder base class.
22 * Derive your own pathfinder from it. You must provide the following template argument:
23 * Types - used as collection of local types used in pathfinder
25 * Requirements for the Types struct:
26 * ----------------------------------
27 * The following types must be defined in the 'Types' argument:
28 * - Types::Tpf - your pathfinder derived from CYapfBaseT
29 * - Types::NodeList - open/closed node list (look at CNodeList_HashTableT)
30 * NodeList needs to have defined local type Titem - defines the pathfinder node type.
31 * Node needs to define local type Key - the node key in the collection ()
33 * For node list you can use template class CNodeList_HashTableT, for which
34 * you need to declare only your node type. Look at test_yapf.h for an example.
37 * Requirements to your pathfinder class derived from CYapfBaseT:
38 * --------------------------------------------------------------
39 * Your pathfinder derived class needs to implement following methods:
40 * inline void PfSetStartupNodes()
41 * inline void PfFollowNode(Node &org)
42 * inline bool PfCalcCost(Node &n)
43 * inline bool PfCalcEstimate(Node &n)
44 * inline bool PfDetectDestination(Node &n)
46 * For more details about those methods, look at the end of CYapfBaseT
47 * declaration. There are some examples. For another example look at
48 * test_yapf.h (part or unittest project).
50 template <class Types
>
53 typedef typename
Types::Tpf Tpf
; ///< the pathfinder class (derived from THIS class)
54 typedef typename
Types::TrackFollower TrackFollower
;
55 typedef typename
Types::NodeList NodeList
; ///< our node list
56 typedef typename
Types::VehicleType VehicleType
; ///< the type of vehicle
57 typedef typename
NodeList::Titem Node
; ///< this will be our node type
58 typedef typename
Node::Key Key
; ///< key to hash tables
61 NodeList m_nodes
; ///< node list multi-container
63 Node
*m_pBestDestNode
; ///< pointer to the destination node found at last round
64 Node
*m_pBestIntermediateNode
; ///< here should be node closest to the destination if path not found
65 const YAPFSettings
*m_settings
; ///< current settings (_settings_game.yapf)
66 int m_max_search_nodes
; ///< maximum number of nodes we are allowed to visit before we give up
67 const VehicleType
*m_veh
; ///< vehicle that we are trying to drive
69 int m_stats_cost_calcs
; ///< stats - how many node's costs were calculated
70 int m_stats_cache_hits
; ///< stats - how many node's costs were reused from cache
73 CPerformanceTimer m_perf_cost
; ///< stats - total CPU time of this run
74 CPerformanceTimer m_perf_slope_cost
; ///< stats - slope calculation CPU time
75 CPerformanceTimer m_perf_ts_cost
; ///< stats - GetTrackStatus() CPU time
76 CPerformanceTimer m_perf_other_cost
; ///< stats - other CPU time
79 int m_num_steps
; ///< this is there for debugging purposes (hope it doesn't hurt)
82 /** default constructor */
84 : m_pBestDestNode(NULL
)
85 , m_pBestIntermediateNode(NULL
)
86 , m_settings(&_settings_game
.pf
.yapf
)
87 , m_max_search_nodes(PfGetSettings().max_search_nodes
)
89 , m_stats_cost_calcs(0)
90 , m_stats_cache_hits(0)
95 /** default destructor */
99 /** to access inherited path finder */
102 return *static_cast<Tpf
*>(this);
106 /** return current settings (can be custom - company based - but later) */
107 inline const YAPFSettings
& PfGetSettings() const
113 * Main pathfinder routine:
114 * - set startup node(s)
115 * - main loop that stops if:
116 * - the destination was found
117 * - or the open list is empty (no route to destination).
118 * - or the maximum amount of loops reached - m_max_search_nodes (default = 10000)
119 * @return true if the path was found
121 inline bool FindPath(const VehicleType
*v
)
125 #ifndef NO_DEBUG_MESSAGES
126 CPerformanceTimer perf
;
128 #endif /* !NO_DEBUG_MESSAGES */
130 Yapf().PfSetStartupNodes();
131 bool bDestFound
= true;
135 Node
*n
= m_nodes
.GetBestOpenNode();
140 /* if the best open node was worse than the best path found, we can finish */
141 if (m_pBestDestNode
!= NULL
&& m_pBestDestNode
->GetCost() < n
->GetCostEstimate()) {
145 Yapf().PfFollowNode(*n
);
146 if (m_max_search_nodes
== 0 || m_nodes
.ClosedCount() < m_max_search_nodes
) {
147 m_nodes
.PopOpenNode(n
->GetKey());
148 m_nodes
.InsertClosedNode(*n
);
155 bDestFound
&= (m_pBestDestNode
!= NULL
);
157 #ifndef NO_DEBUG_MESSAGES
159 if (_debug_yapf_level
>= 2) {
160 int t
= perf
.Get(1000000);
161 _total_pf_time_us
+= t
;
163 if (_debug_yapf_level
>= 3) {
164 UnitID veh_idx
= (m_veh
!= NULL
) ? m_veh
->unitnumber
: 0;
165 char ttc
= Yapf().TransportTypeChar();
166 float cache_hit_ratio
= (m_stats_cache_hits
== 0) ? 0.0f
: ((float)m_stats_cache_hits
/ (float)(m_stats_cache_hits
+ m_stats_cost_calcs
) * 100.0f
);
167 int cost
= bDestFound
? m_pBestDestNode
->m_cost
: -1;
168 int dist
= bDestFound
? m_pBestDestNode
->m_estimate
- m_pBestDestNode
->m_cost
: -1;
170 DEBUG(yapf
, 3, "[YAPF%c]%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) -- ",
171 ttc
, bDestFound
? '-' : '!', veh_idx
, t
, m_num_steps
, m_nodes
.OpenCount(), m_nodes
.ClosedCount(),
172 cache_hit_ratio
, cost
, dist
, m_perf_cost
.Get(1000000), m_perf_slope_cost
.Get(1000000),
173 m_perf_ts_cost
.Get(1000000), m_perf_other_cost
.Get(1000000)
177 #endif /* !NO_DEBUG_MESSAGES */
182 * If path was found return the best node that has reached the destination. Otherwise
183 * return the best visited node (which was nearest to the destination).
185 inline Node
*GetBestNode()
187 return (m_pBestDestNode
!= NULL
) ? m_pBestDestNode
: m_pBestIntermediateNode
;
191 * Calls NodeList::CreateNewNode() - allocates new node that can be filled and used
192 * as argument for AddStartupNode() or AddNewNode()
194 inline Node
& CreateNewNode()
196 Node
&node
= *m_nodes
.CreateNewNode();
200 /** Add new node (created by CreateNewNode and filled with data) into open list */
201 inline void AddStartupNode(Node
&n
)
203 Yapf().PfNodeCacheFetch(n
);
204 /* insert the new node only if it is not there */
205 if (m_nodes
.FindOpenNode(n
.m_key
) == NULL
) {
206 m_nodes
.InsertOpenNode(n
);
208 /* if we are here, it means that node is already there - how it is possible?
209 * probably the train is in the position that both its ends point to the same tile/exit-dir
210 * very unlikely, but it happened */
214 /** add multiple nodes - direct children of the given node */
215 inline void AddMultipleNodes(Node
*parent
, const TrackFollower
&tf
)
217 bool is_choice
= (KillFirstBit(tf
.m_new_td_bits
) != TRACKDIR_BIT_NONE
);
218 for (TrackdirBits rtds
= tf
.m_new_td_bits
; rtds
!= TRACKDIR_BIT_NONE
; rtds
= KillFirstBit(rtds
)) {
219 Trackdir td
= (Trackdir
)FindFirstBit2x64(rtds
);
220 Node
&n
= Yapf().CreateNewNode();
221 n
.Set(parent
, tf
.m_new_tile
, td
, is_choice
);
222 Yapf().AddNewNode(n
, tf
);
227 * In some cases an intermediate node branch should be pruned.
228 * The most prominent case is when a red EOL signal is encountered, but
229 * there was a segment change (e.g. a rail type change) before that. If
230 * the branch would not be pruned, the rail type change location would
231 * remain the best intermediate node, and thus the vehicle would still
232 * go towards the red EOL signal.
234 void PruneIntermediateNodeBranch()
236 while (Yapf().m_pBestIntermediateNode
!= NULL
&& (Yapf().m_pBestIntermediateNode
->m_segment
->m_end_segment_reason
& ESRB_CHOICE_FOLLOWS
) == 0) {
237 Yapf().m_pBestIntermediateNode
= Yapf().m_pBestIntermediateNode
->m_parent
;
242 * AddNewNode() - called by Tderived::PfFollowNode() for each child node.
243 * Nodes are evaluated here and added into open list
245 void AddNewNode(Node
&n
, const TrackFollower
&tf
)
247 /* evaluate the node */
248 bool bCached
= Yapf().PfNodeCacheFetch(n
);
250 m_stats_cost_calcs
++;
252 m_stats_cache_hits
++;
255 bool bValid
= Yapf().PfCalcCost(n
, &tf
);
258 Yapf().PfNodeCacheFlush(n
);
261 if (bValid
) bValid
= Yapf().PfCalcEstimate(n
);
263 /* have the cost or estimate callbacks marked this node as invalid? */
266 /* detect the destination */
267 bool bDestination
= Yapf().PfDetectDestination(n
);
269 if (m_pBestDestNode
== NULL
|| n
< *m_pBestDestNode
) {
270 m_pBestDestNode
= &n
;
272 m_nodes
.FoundBestNode(n
);
276 if (m_max_search_nodes
> 0 && (m_pBestIntermediateNode
== NULL
|| (m_pBestIntermediateNode
->GetCostEstimate() - m_pBestIntermediateNode
->GetCost()) > (n
.GetCostEstimate() - n
.GetCost()))) {
277 m_pBestIntermediateNode
= &n
;
280 /* check new node against open list */
281 Node
*openNode
= m_nodes
.FindOpenNode(n
.GetKey());
282 if (openNode
!= NULL
) {
283 /* another node exists with the same key in the open list
284 * is it better than new one? */
285 if (n
.GetCostEstimate() < openNode
->GetCostEstimate()) {
286 /* update the old node by value from new one */
287 m_nodes
.PopOpenNode(n
.GetKey());
289 /* add the updated old node back to open list */
290 m_nodes
.InsertOpenNode(*openNode
);
295 /* check new node against closed list */
296 Node
*closedNode
= m_nodes
.FindClosedNode(n
.GetKey());
297 if (closedNode
!= NULL
) {
298 /* another node exists with the same key in the closed list
299 * is it better than new one? */
300 int node_est
= n
.GetCostEstimate();
301 int closed_est
= closedNode
->GetCostEstimate();
302 if (node_est
< closed_est
) {
303 /* If this assert occurs, you have probably problem in
304 * your Tderived::PfCalcCost() or Tderived::PfCalcEstimate().
305 * The problem could be:
306 * - PfCalcEstimate() gives too large numbers
307 * - PfCalcCost() gives too small numbers
308 * - You have used negative cost penalty in some cases (cost bonus) */
313 /* the new node is really new
314 * add it to the open list */
315 m_nodes
.InsertOpenNode(n
);
318 const VehicleType
* GetVehicle() const
323 void DumpBase(DumpTarget
&dmp
) const
325 dmp
.WriteStructT("m_nodes", &m_nodes
);
326 dmp
.WriteLine("m_num_steps = %d", m_num_steps
);
329 /* methods that should be implemented at derived class Types::Tpf (derived from CYapfBaseT) */
332 /** Example: PfSetStartupNodes() - set source (origin) nodes */
333 inline void PfSetStartupNodes()
336 Node
&n1
= *base::m_nodes
.CreateNewNode();
338 . // setup node members here
340 base::m_nodes
.InsertOpenNode(n1
);
343 /** Example: PfFollowNode() - set following (child) nodes of the given node */
344 inline void PfFollowNode(Node
&org
)
346 for (each follower of node org
) {
347 Node
&n
= *base::m_nodes
.CreateNewNode();
349 . // setup node members here
351 n
.m_parent
= &org
; // set node's parent to allow back tracking
356 /** Example: PfCalcCost() - set path cost from origin to the given node */
357 inline bool PfCalcCost(Node
&n
)
359 /* evaluate last step cost */
361 /* set the node cost as sum of parent's cost and last step cost */
362 n
.m_cost
= n
.m_parent
->m_cost
+ cost
;
363 return true; // true if node is valid follower (i.e. no obstacle was found)
366 /** Example: PfCalcEstimate() - set path cost estimate from origin to the target through given node */
367 inline bool PfCalcEstimate(Node
&n
)
369 /* evaluate the distance to our destination */
371 /* set estimate as sum of cost from origin + distance to the target */
372 n
.m_estimate
= n
.m_cost
+ distance
;
373 return true; // true if node is valid (i.e. not too far away :)
376 /** Example: PfDetectDestination() - return true if the given node is our destination */
377 inline bool PfDetectDestination(Node
&n
)
379 bool bDest
= (n
.m_key
.m_x
== m_x2
) && (n
.m_key
.m_y
== m_y2
);
385 #endif /* YAPF_BASE_HPP */